diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index c6124fdfff38c..fd3a00d56fe0b 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -93,7 +93,7 @@ impl<'hir> LoweringContext<'_, 'hir> { match asm::InlineAsmClobberAbi::parse( asm_arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, *abi_name, ) { Ok(abi) => { diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 4d9bc09faeecb..911ec5956006d 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -662,10 +662,10 @@ impl<'hir> LoweringContext<'_, 'hir> { p.def_id.to_def_id(), ); - self.create_resolved_path(res, p.name.ident(), p.span) + self.create_resolved_qpath(res, p.name.ident(), p.span) } - pub(super) fn create_resolved_path( + pub(super) fn create_resolved_qpath( &mut self, res: Res, ident: Ident, diff --git a/compiler/rustc_ast_lowering/src/delegation/mod.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs index d0033ba0e472e..02fd6de314d3a 100644 --- a/compiler/rustc_ast_lowering/src/delegation/mod.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -439,7 +439,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let ident = Ident::new(kw::SelfUpper, span); - let path = self.create_resolved_path(res, ident, span); + let path = self.create_resolved_qpath(res, ident, span); // FIXME(fn_delegation): add default `..` for all other fields. let initializer = hir::ExprKind::Struct( @@ -454,7 +454,14 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::StructTailExpr::None, ); - self.arena.alloc(self.mk_expr(initializer, span)) + let expr = self.mk_expr(initializer, span); + + let path = self.make_lang_item_qpath(hir::LangItem::FromFn, span, None); + let path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span)); + + let call = hir::ExprKind::Call(path, self.arena.alloc_slice(&[expr])); + + self.arena.alloc(self.mk_expr(call, span)) } else { self.arena.alloc(call) }; diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs index 1d9bcef7ac5a7..dd1b9518e6d7f 100644 --- a/compiler/rustc_ast_lowering/src/delegation/resolution.rs +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -5,10 +5,10 @@ use hir::def::DefKind; use rustc_ast::{self as ast, Delegation, DelegationSource, NodeId}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_hir as hir; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_middle::{span_bug, ty}; use rustc_span::def_id::{DefId, LocalDefId}; -use rustc_span::{ErrorGuaranteed, Span, kw}; +use rustc_span::{ErrorGuaranteed, Span}; use crate::delegation::generics::GenericsGenerationResults; use crate::delegation::resolution::resolver::DelegationResolver; @@ -31,7 +31,7 @@ pub(super) struct ParamInfo { pub splatted: Option, } -#[derive(Default)] +#[derive(Default, Debug)] pub(super) struct SigMapping { pub map_return: bool, pub arguments_to_map: FxIndexSet, @@ -254,17 +254,52 @@ impl<'tcx> DelegationResolver<'_, 'tcx> { } if self.can_perform_self_mapping(delegation, parent)? { - // FIXME(fn_delegation): support heuristics for mapping of complex - // return types: `Self` -> `Box>>` - mapping.map_return = sig.output().is_param(0); + /// Finds `Self` generic param only in ADT or references, so we avoid cases like + /// `Self::Item` which will return true if `output.contains(...)` will be used. + struct SelfFinder; + + impl<'tcx> TypeVisitor> for SelfFinder { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + match t.kind() { + ty::Adt(_, args) => { + if args + .iter() + .flat_map(|arg| arg.as_type()) + .any(|type_arg| type_arg.is_self_param()) + { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + ty::Ref(_, ref_t, _) => { + if ref_t.is_self_param() { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + _ => ControlFlow::Continue(()), + } + } + } + + impl SelfFinder { + fn contains_self(t: Ty<'_>) -> bool { + t.is_self_param() || t.visit_with(&mut SelfFinder).is_break() + } + } + + mapping.map_return = SelfFinder::contains_self(sig.output()); - let self_param = Ty::new_param(self.tcx(), 0, kw::SelfUpper); let arguments_to_map = sig .inputs() .iter() .enumerate() .skip(1) // Already checked above. - .filter_map(|(idx, param)| param.contains(self_param).then_some(idx)); + .filter_map(|(idx, ¶m)| SelfFinder::contains_self(param).then_some(idx)); mapping.arguments_to_map.extend(arguments_to_map); } diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 2d4d98d812c65..3479224cc5546 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -26,8 +26,8 @@ use rustc_macros::extension; use rustc_middle::mir::RETURN_PLACE; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ - self, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, RegionExt, RegionVid, - Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, + self, BoundVariableKind, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, + List, RegionExt, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, }; use rustc_middle::{bug, span_bug}; use rustc_span::{ErrorGuaranteed, kw, sym}; @@ -134,6 +134,231 @@ pub(crate) enum DefiningTy<'tcx> { } impl<'tcx> DefiningTy<'tcx> { + #[instrument(level = "debug", skip(tcx), ret)] + fn new(tcx: TyCtxt<'tcx>, body_def_id: LocalDefId) -> DefiningTy<'tcx> { + match tcx.hir_body_owner_kind(body_def_id) { + BodyOwnerKind::Closure | BodyOwnerKind::Fn => { + let defining_ty = tcx.type_of(body_def_id).instantiate_identity().skip_norm_wip(); + match *defining_ty.kind() { + ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), + ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), + ty::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, args) + } + ty::FnDef(def_id, args) => { + DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) + } + _ => span_bug!( + tcx.def_span(body_def_id), + "expected defining type for `{body_def_id:?}`: `{defining_ty:?}`", + ), + } + } + + BodyOwnerKind::Const { inline: true } => { + // This is required for `AscribeUserType` canonical query, which will call + // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes + // into borrowck, which is ICE #78174. + // + // As a workaround, inline consts have an additional generic param (`ty` + // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // proper type with NLL infer vars. + // + // Fetch the actual type from MIR, as `type_of` returns something useless + // like ``. + let body = tcx.mir_promoted(body_def_id).0.borrow(); + let ty = body.local_decls[RETURN_PLACE].ty; + let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); + let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let args = InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }).args; + DefiningTy::InlineConst(body_def_id.to_def_id(), args) + } + + BodyOwnerKind::Const { inline: false } | BodyOwnerKind::Static(..) => { + let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); + DefiningTy::Const(body_def_id.to_def_id(), args) + } + + BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(body_def_id.to_def_id()), + } + } + + /// The bound variables for a given defining type. This differs from their usual bound vars + /// in that closures and coroutine closures have an additional `'env`, while C-variadic + /// functions have an additional region for their implicit `VaList` input. + pub(crate) fn bound_vars(self, tcx: TyCtxt<'tcx>) -> &'tcx List> { + match self { + DefiningTy::Closure(_, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + tcx.mk_bound_variable_kinds_from_iter(inputs_and_output.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::CoroutineClosure(_, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + if sig.skip_binder().c_variadic() { + // FIXME(#160495): Don't use an anonymous region here + tcx.mk_bound_variable_kinds_from_iter(sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::Anon)), + )) + } else { + sig.bound_vars() + } + } + + DefiningTy::Coroutine(..) + | DefiningTy::Const(..) + | DefiningTy::InlineConst(..) + | DefiningTy::GlobalAsm(..) => ty::List::empty(), + } + } + + #[instrument(level = "debug", skip(tcx), ret)] + fn inputs_and_output(self, tcx: TyCtxt<'tcx>) -> ty::Binder<'tcx, &'tcx ty::List>> { + match self { + DefiningTy::Closure(def_id, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + let bound_vars = self.bound_vars(tcx); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_ty = tcx.closure_env_ty( + Ty::new_closure(tcx, def_id, args), + args.as_closure().kind(), + env_region, + ); + + // The "inputs" of the closure in the + // signature appear as a tuple. The MIR side + // flattens this tuple. + let (&output, tuplized_inputs) = + inputs_and_output.skip_binder().split_last().unwrap(); + assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); + let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { + bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); + }; + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::Coroutine(def_id, args) => { + let resume_ty = args.as_coroutine().resume_ty(); + let output = args.as_coroutine().return_ty(); + let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); + let inputs_and_output = tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); + ty::Binder::dummy(inputs_and_output) + } + + // Construct the signature of the CoroutineClosure for the purposes of borrowck. + // This is pretty straightforward -- we: + // 1. first grab the `coroutine_closure_sig`, + // 2. compute the self type (`&`/`&mut`/no borrow), + // 3. flatten the tupled_input_tys, + // 4. construct the correct generator type to return with + // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. + // Then we wrap it all up into a list of inputs and output. + DefiningTy::CoroutineClosure(def_id, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + let bound_vars = self.bound_vars(tcx); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_kind = args.as_coroutine_closure().kind(); + + let closure_ty = tcx.closure_env_ty( + Ty::new_coroutine_closure(tcx, def_id, args), + closure_kind, + env_region, + ); + + let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); + let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( + tcx, + args.as_coroutine_closure().parent_args(), + tcx.coroutine_for_closure(def_id), + closure_kind, + env_region, + args.as_coroutine_closure().tupled_upvars_ty(), + args.as_coroutine_closure().coroutine_captures_by_ref_ty(), + ); + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + let inputs_and_output = sig.inputs_and_output(); + + // C-variadic fns also have a `VaList` input that's not listed in the signature + // (as it's created inside the body itself, not passed in from outside). + if tcx.fn_sig(def_id).skip_binder().c_variadic() { + let va_list_did = tcx.require_lang_item(LangItem::VaList, tcx.def_span(def_id)); + + let bound_vars = self.bound_vars(tcx); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::Anon, + }; + let region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let va_list_ty = + tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]).skip_norm_wip(); + + // The signature needs to follow the order [input_tys, va_list_ty, output_ty] + let (output_ty, input_tys) = + inputs_and_output.skip_binder().split_last().unwrap(); + return ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + input_tys.iter().copied().chain([va_list_ty, *output_ty]), + ), + bound_vars, + ); + } + + inputs_and_output + } + + DefiningTy::Const(def_id, _) => { + // For a constant body, there are no inputs, and one + // "output" (the type of the constant). + let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::InlineConst(_def_id, args) => { + let ty = args.as_inline_const().ty(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( + tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), + ), + } + } + /// Returns a list of all the upvar types for this MIR. If this is /// not a closure or coroutine, there are no upvars, and hence it /// will be an empty list. The order of types in this list will @@ -488,7 +713,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } else { // If this is a closure, coroutine, or inline-const, then the late-bound regions from the enclosing // function/closures are actually external regions to us. For example, here, 'a is not local - // to the closure c (although it is local to the fn foo): + // to the closure c (although it is local to the fn foo). We need to add them as they could be + // explicitly named in this body: + // // fn foo<'a>() { // let c = || { let x: &'a u32 = ...; } // } @@ -518,8 +745,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { // on its signature are local. // // We manually loop over `bound_inputs_and_output` instead of using - // `for_each_late_bound_region_in_item` as we may need to add the otherwise - // implicit `ClosureEnv` region. + // `for_each_late_bound_region_in_item` as both closures and function + // definitions have implicit late bound regions. Closures have a `'env` + // regions while c-variadic function definitions have a `&VaList` argument. let bound_inputs_and_output = self.compute_inputs_and_output(&indices, defining_ty); for (idx, bound_var) in bound_inputs_and_output.bound_vars().iter().enumerate() { if let ty::BoundVariableKind::Region(kind) = bound_var { @@ -581,82 +809,23 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } } - /// Returns the "defining type" of the current MIR; - /// see `DefiningTy` for details. + /// Returns the "defining type" of the current MIR; see `DefiningTy` for details. fn defining_ty(&self) -> DefiningTy<'tcx> { - let tcx = self.infcx.tcx; - - match tcx.hir_body_owner_kind(self.mir_def) { - BodyOwnerKind::Closure | BodyOwnerKind::Fn => { - let defining_ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - debug!("defining_ty (pre-replacement): {:?}", defining_ty); - - let defining_ty = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - defining_ty, - ); - - match *defining_ty.kind() { - ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), - ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), - ty::CoroutineClosure(def_id, args) => { - DefiningTy::CoroutineClosure(def_id, args) - } - ty::FnDef(def_id, args) => { - DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) - } - _ => span_bug!( - tcx.def_span(self.mir_def), - "expected defining type for `{:?}`: `{:?}`", - self.mir_def, - defining_ty - ), - } - } - - BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { - match tcx.def_kind(self.mir_def) { - DefKind::AnonConst - if tcx.anon_const_kind(self.mir_def) - == ty::AnonConstKind::NonTypeSystemInline => - { - // This is required for `AscribeUserType` canonical query, which will call - // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes - // into borrowck, which is ICE #78174. - // - // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the - // proper type with NLL infer vars. - // - // Fetch the actual type from MIR, as `type_of` returns something useless - // like ``. - let body = tcx.mir_promoted(self.mir_def).0.borrow(); - let ty = body.local_decls[RETURN_PLACE].ty; - let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.to_def_id()); - let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let args = - InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) - .args; - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - args, - ); - DefiningTy::InlineConst(self.mir_def.to_def_id(), args) - } - _ => { - let identity_args = - GenericArgs::identity_for_item(tcx, self.mir_def.to_def_id()); - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - identity_args, - ); - DefiningTy::Const(self.mir_def.to_def_id(), args) - } - } + let defining_ty = DefiningTy::new(self.infcx.tcx, self.mir_def); + let f = |args| { + let fr = NllRegionVariableOrigin::FreeRegion; + self.infcx.replace_free_regions_with_nll_infer_vars(fr, args) + }; + match defining_ty { + DefiningTy::Closure(def_id, args) => DefiningTy::Closure(def_id, f(args)), + DefiningTy::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, f(args)), + DefiningTy::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, f(args)) } - - BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(self.mir_def.to_def_id()), + DefiningTy::FnDef(def_id, args) => DefiningTy::FnDef(def_id, f(args)), + DefiningTy::Const(def_id, args) => DefiningTy::Const(def_id, f(args)), + DefiningTy::InlineConst(def_id, args) => DefiningTy::InlineConst(def_id, f(args)), + DefiningTy::GlobalAsm(def_id) => DefiningTy::GlobalAsm(def_id), } } @@ -694,163 +863,8 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { defining_ty: DefiningTy<'tcx>, ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; - - let inputs_and_output = match defining_ty { - DefiningTy::Closure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_closure().sig(); - let inputs_and_output = closure_sig.inputs_and_output(); - let bound_vars = tcx.mk_bound_variable_kinds_from_iter( - inputs_and_output.bound_vars().iter().chain(iter::once( - ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), - )), - ); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_ty = tcx.closure_env_ty( - Ty::new_closure(tcx, def_id, args), - args.as_closure().kind(), - env_region, - ); - - // The "inputs" of the closure in the - // signature appear as a tuple. The MIR side - // flattens this tuple. - let (&output, tuplized_inputs) = - inputs_and_output.skip_binder().split_last().unwrap(); - assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); - let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { - bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); - }; - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::Coroutine(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let resume_ty = args.as_coroutine().resume_ty(); - let output = args.as_coroutine().return_ty(); - let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); - let inputs_and_output = - self.infcx.tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); - ty::Binder::dummy(inputs_and_output) - } - - // Construct the signature of the CoroutineClosure for the purposes of borrowck. - // This is pretty straightforward -- we: - // 1. first grab the `coroutine_closure_sig`, - // 2. compute the self type (`&`/`&mut`/no borrow), - // 3. flatten the tupled_input_tys, - // 4. construct the correct generator type to return with - // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. - // Then we wrap it all up into a list of inputs and output. - DefiningTy::CoroutineClosure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); - let bound_vars = - tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( - iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), - )); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_kind = args.as_coroutine_closure().kind(); - - let closure_ty = tcx.closure_env_ty( - Ty::new_coroutine_closure(tcx, def_id, args), - closure_kind, - env_region, - ); - - let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); - let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( - tcx, - args.as_coroutine_closure().parent_args(), - tcx.coroutine_for_closure(def_id), - closure_kind, - env_region, - args.as_coroutine_closure().tupled_upvars_ty(), - args.as_coroutine_closure().coroutine_captures_by_ref_ty(), - ); - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::FnDef(def_id, _) => { - let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); - let sig = indices.fold_to_region_vids(tcx, sig); - let inputs_and_output = sig.inputs_and_output(); - - // C-variadic fns also have a `VaList` input that's not listed in the signature - // (as it's created inside the body itself, not passed in from outside). - if self.infcx.tcx.fn_sig(def_id).skip_binder().c_variadic() { - let va_list_did = self - .infcx - .tcx - .require_lang_item(LangItem::VaList, self.infcx.tcx.def_span(self.mir_def)); - - let reg_vid = self - .infcx - .next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { - RegionCtxt::Free(sym::c_dash_variadic) - }) - .as_var(); - - let region = ty::Region::new_var(self.infcx.tcx, reg_vid); - let va_list_ty = self - .infcx - .tcx - .type_of(va_list_did) - .instantiate(self.infcx.tcx, &[region.into()]) - .skip_norm_wip(); - - // The signature needs to follow the order [input_tys, va_list_ty, output_ty] - return inputs_and_output.map_bound(|tys| { - let (output_ty, input_tys) = tys.split_last().unwrap(); - tcx.mk_type_list_from_iter( - input_tys.iter().copied().chain([va_list_ty, *output_ty]), - ) - }); - } - - inputs_and_output - } - - DefiningTy::Const(def_id, _) => { - // For a constant body, there are no inputs, and one - // "output" (the type of the constant). - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - let ty = indices.fold_to_region_vids(tcx, ty); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::InlineConst(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = args.as_inline_const().ty(); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( - tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), - ), - }; + let inputs_and_output = defining_ty.inputs_and_output(tcx); + let inputs_and_output = indices.fold_to_region_vids(tcx, inputs_and_output); // FIXME(#129952): We probably want a more principled approach here. if let Err(e) = inputs_and_output.error_reported() { diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index 03fd11afa3f10..0b8eb75972ec0 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -404,7 +404,7 @@ impl<'tcx> InlineAssemblyGenerator<'_, 'tcx> { let abi_clobber = InlineAsmClobberAbi::parse( self.arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, sym::C, ) .unwrap() diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index 8ee0e71d82dec..8b0ca770ec067 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -39,6 +39,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{self, Configurable}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig, back}; +use rustc_data_structures::unord::UnordSet; use rustc_log::tracing::info; use rustc_middle::dep_graph::WorkProductMap; use rustc_session::config::{NATIVE_CPU, OutputFilenames}; @@ -170,8 +171,6 @@ impl CodegenBackend for CraneliftCodegenBackend { }, _ => vec![], }; - // FIXME do `unstable_target_features` properly - let unstable_target_features = target_features.clone(); // FIXME(f16_f128): `rustc_codegen_llvm` currently disables support on Windows GNU // targets due to GCC using a different ABI than LLVM. Therefore `f16` and `f128` @@ -186,8 +185,7 @@ impl CodegenBackend for CraneliftCodegenBackend { let has_reliable_f128_math = has_reliable_f16_f128 && sess.target.env == Env::Gnu; TargetConfig { - target_features, - unstable_target_features, + internal_target_features: UnordSet::from_iter(target_features), // `rustc_codegen_cranelift` polyfills functionality not yet // available in Cranelift. has_reliable_f16: has_reliable_f16_f128, diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index c7c0687c4f156..cbc7db8e9e23f 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -85,7 +85,7 @@ use rustc_codegen_ssa::back::write::{ CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryFn, ThinLtoInput, }; use rustc_codegen_ssa::base::codegen_crate; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods}; use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleCodegen, TargetConfig}; use rustc_data_structures::profiling::SelfProfilerRef; @@ -532,7 +532,7 @@ fn to_gcc_opt_level(optlevel: Option) -> OptimizationLevel { /// Returns the features that should be set in `cfg(target_feature)`. fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig { - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| to_gcc_features(sess, feature), |feature| { @@ -556,8 +556,7 @@ fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, // There are no known bugs with GCC support for f16 or f128 has_reliable_f16, has_reliable_f16_math: has_reliable_f16, diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index d2dfa9a45de8b..fba43bba737e0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -970,14 +970,14 @@ fn dummy_output_type<'ll>(cx: &CodegenCx<'ll, '_>, reg: InlineAsmRegClass) -> &' Hexagon(HexagonInlineAsmRegClass::vreg) => { // HVX vector register size depends on the HVX mode. // LLVM's "v" constraint requires the exact vector width. - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 16) // 512-bit for 64B mode } } Hexagon(HexagonInlineAsmRegClass::vreg_pair) => { - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 64) // 2048-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 64B mode diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index a2f44757c020a..06dab7f7e46fd 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -393,9 +393,9 @@ fn packed_stack_attr<'ll>( // The backchain and softfloat flags can be set via -Ctarget-features=... // or via #[target_features(enable = ...)] so we have to check both possibilities - let have_backchain = sess.unstable_target_features.contains(&sym::backchain) + let have_backchain = sess.internal_target_features.contains(&sym::backchain) || function_attributes.iter().any(|feature| feature.name == sym::backchain); - let have_softfloat = sess.unstable_target_features.contains(&sym::soft_float) + let have_softfloat = sess.internal_target_features.contains(&sym::soft_float) || function_attributes.iter().any(|feature| feature.name == sym::soft_float); // If both, backchain and packedstack, are enabled LLVM cannot generate valid function entry points diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index edf52e67b434b..5238ba3a661a2 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -213,7 +213,7 @@ pub(crate) fn target_machine_factory( let code_model = to_llvm_code_model(sess.code_model()); // This is used to set cfg_has_threads, so all logic must be in this method. - let singlethread = sess.target.singlethread(&sess.target_features); + let singlethread = sess.target.singlethread(&sess.internal_target_features); let triple = SmallCStr::new(&versioned_llvm_target(sess)); let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 6892e616e1f11..f85a15762ab49 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -7,7 +7,7 @@ use std::{ptr, slice, str}; use libc::c_int; use rustc_codegen_ssa::base::wants_wasm_eh; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::{TargetConfig, target_features}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::small_c_str::SmallCStr; @@ -314,7 +314,7 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option TargetConfig { let target_machine = create_informational_target_machine(sess, true); - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| { to_llvm_features(sess, feature) @@ -322,9 +322,9 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { .unwrap_or_default() }, |feature| { - // This closure determines whether the target CPU has the feature according to LLVM. We do - // *not* consider the `-Ctarget-feature`s here, as that will be handled later in - // `cfg_target_feature`. + // This closure determines whether the target CPU has the feature according to LLVM. We + // do *not* consider the `-Ctarget-feature`s here, as that will be handled later in + // `internal_target_features`. if let Some(feat) = to_llvm_features(sess, feature) { // All the LLVM features this expands to must be enabled. for llvm_feature in feat { @@ -344,8 +344,7 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { ); let mut cfg = TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, @@ -736,7 +735,10 @@ pub(crate) fn global_llvm_features(sess: &Session, for_cfg: bool) -> Vec target_features::flag_to_backend_features(sess, extend_backend_features); } - // We add this in the "base target" so that these show up in `sess.unstable_target_features`. + // `-C` flags that map to LLVM target features. + // We need to include them even with `only_base_features` as this is used to populate + // `sess.internal_target_features` where we very much want them to be present (e.g. the inline + // asm logic uses that to check which registers may be used). llvm_features_by_flags(sess, &mut features); // `-Zllvm-target-features`, all the way at the end to overwrite everything. diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index dbc0abdb50da8..f8cc07201d10f 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -229,7 +229,7 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport] // It is important that the order of reservation matches the order of writing. // The object crate contains many debug asserts that fire if you get this wrong. - let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features) + let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.internal_target_features) else { sess.dcx().fatal(format!( "raw-dylib is not supported for the architecture `{}`", diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 50a3e7fb7a1d1..135faa5817516 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -137,9 +137,6 @@ pub(crate) fn get_linker<'a>( // to the linker args construction. assert!(cmd.get_args().is_empty() || sess.target.cfg_abi == CfgAbi::Uwp); match flavor { - LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::L4Re => { - Box::new(L4Bender::new(cmd, sess)) as Box - } LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::Aix => { Box::new(AixLinker::new(cmd, sess)) as Box } @@ -279,7 +276,6 @@ generate_arg_methods! { MsvcLinker<'_> EmLinker<'_> WasmLd<'_> - L4Bender<'_> AixLinker<'_> LlbcLinker<'_> BpfLinker<'_> @@ -1468,128 +1464,6 @@ impl<'a> WasmLd<'a> { } } -/// Linker shepherd script for L4Re (Fiasco) -struct L4Bender<'a> { - cmd: Command, - sess: &'a Session, - hinted_static: bool, -} - -impl<'a> Linker for L4Bender<'a> { - fn cmd(&mut self) -> &mut Command { - &mut self.cmd - } - - fn set_output_kind( - &mut self, - _output_kind: LinkOutputKind, - _crate_type: CrateType, - _out_filename: &Path, - ) { - } - - fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_arg(format!("-PC{name}")); - } else { - self.link_arg("--whole-archive") - .link_or_cc_arg(format!("-l{name}")) - .link_arg("--no-whole-archive"); - } - } - - fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_or_cc_arg(path); - } else { - self.link_arg("--whole-archive").link_or_cc_arg(path).link_arg("--no-whole-archive"); - } - } - - fn full_relro(&mut self) { - self.link_args(&["-z", "relro", "-z", "now"]); - } - - fn partial_relro(&mut self) { - self.link_args(&["-z", "relro"]); - } - - fn no_relro(&mut self) { - self.link_args(&["-z", "norelro"]); - } - - fn gc_sections(&mut self, keep_metadata: bool) { - if !keep_metadata { - self.link_arg("--gc-sections"); - } - } - - fn optimize(&mut self) { - // GNU-style linkers support optimization with -O. GNU ld doesn't - // need a numeric argument, but other linkers do. - if self.sess.opts.optimize == config::OptLevel::More - || self.sess.opts.optimize == config::OptLevel::Aggressive - { - self.link_arg("-O1"); - } - } - - fn pgo_gen(&mut self) {} - - fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) { - match strip { - Strip::None => {} - Strip::Debuginfo => { - self.link_arg("--strip-debug"); - } - Strip::Symbols => { - self.link_arg("--strip-all"); - } - } - } - - fn no_default_libraries(&mut self) { - self.cc_arg("-nostdlib"); - } - - fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[SymbolExport]) { - // ToDo, not implemented, copy from GCC - self.sess.dcx().emit_warn(diagnostics::L4BenderExportingSymbolsUnimplemented); - } - - fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind) { - let subsystem = subsystem.as_str(); - self.link_arg(&format!("--subsystem {subsystem}")); - } - - fn reset_per_library_state(&mut self) { - self.hint_static(); // Reset to default before returning the composed command line. - } - - fn linker_plugin_lto(&mut self) {} - - fn control_flow_guard(&mut self) {} - - fn ehcont_guard(&mut self) {} - - fn no_crt_objects(&mut self) {} -} - -impl<'a> L4Bender<'a> { - fn new(cmd: Command, sess: &'a Session) -> L4Bender<'a> { - L4Bender { cmd, sess, hinted_static: false } - } - - fn hint_static(&mut self) { - if !self.hinted_static { - self.link_or_cc_arg("-static"); - self.hinted_static = true; - } - } -} - /// Linker for AIX. struct AixLinker<'a> { cmd: Command, diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 951a60426b5d5..a43bf72b6a27d 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -207,7 +207,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Endianness::Big, }; let Some((architecture, sub_architecture)) = - sess.target.object_architecture(&sess.unstable_target_features) + sess.target.object_architecture(&sess.internal_target_features) else { return None; }; @@ -328,12 +328,12 @@ pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 { let mut e_flags: u32 = 0x0; // Check if compression is enabled - if sess.target_features.contains(&sym::zca) { + if sess.internal_target_features.contains(&sym::zca) { e_flags |= elf::EF_RISCV_RVC; } // Check if RVTSO is enabled - if sess.target_features.contains(&sym::ztso) { + if sess.internal_target_features.contains(&sym::ztso) { e_flags |= elf::EF_RISCV_TSO; } diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index e6aab553072f2..2b77eb2cf24fb 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -97,10 +97,6 @@ pub(crate) struct Ld64UnimplementedModifier; #[diag("`as-needed` modifier not supported for current linker")] pub(crate) struct LinkerUnsupportedModifier; -#[derive(Diagnostic)] -#[diag("exporting symbols not implemented yet for L4Bender")] -pub(crate) struct L4BenderExportingSymbolsUnimplemented; - #[derive(Diagnostic)] #[diag("error enumerating natvis directory: {$error}")] pub(crate) struct NoNatvisDirectory { @@ -1100,7 +1096,7 @@ pub(crate) struct TargetFeatureSafeTrait { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}")] -pub(crate) struct ForbiddenTargetFeatureAttr<'a> { +pub(crate) struct InternalOnlyTargetFeatureAttr<'a> { #[primary_span] pub span: Span, pub feature: &'a str, @@ -1233,7 +1229,7 @@ pub(crate) struct UnstableCTargetFeature<'a> { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}")] -pub(crate) struct ForbiddenCTargetFeature<'a> { +pub(crate) struct InternalOnlyCTargetFeature<'a> { pub feature: &'a str, pub enabled: &'a str, pub reason: &'a str, diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 9a42debe1dd97..02ae2d50390cc 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use rustc_abi::Size; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; -use rustc_data_structures::unord::UnordMap; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir::CRATE_HIR_ID; use rustc_hir::attrs::{CfgEntry, NativeLibKind, WindowsSubsystemKind}; use rustc_hir::def_id::CrateNum; @@ -306,14 +306,12 @@ pub struct CrateInfo { pub exported_symbols_for_lto: Vec, } -/// Target-specific options that get set in `cfg(...)`. +/// Target-specific options that get set in `sess`/`cfg(...)`. /// /// RUSTC_SPECIFIC_FEATURES should be skipped here, those are handled outside codegen. pub struct TargetConfig { - /// Options to be set in `cfg(target_features)`. - pub target_features: Vec, - /// Options to be set in `cfg(target_features)`, but including unstable features. - pub unstable_target_features: Vec, + /// Options to be set in `sess.internal_target_features`. + pub internal_target_features: UnordSet, /// Option for `cfg(target_has_reliable_f16)`, true if `f16` basic arithmetic works. pub has_reliable_f16: bool, /// Option for `cfg(target_has_reliable_f16_math)`, true if `f16` math calls work. diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 131a345fe557d..33cc321ea6d32 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -151,7 +151,7 @@ fn prefix_and_suffix<'tcx>( let asm_binary_format = &tcx.sess.target.binary_format; let is_arm = tcx.sess.target.arch == Arch::Arm; - let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode); + let is_thumb = tcx.sess.internal_target_features.contains(&sym::thumb_mode); let function_sections = tcx.sess.opts.unstable_opts.function_sections.unwrap_or(tcx.sess.target.function_sections); diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8f459e5a218d2..69487d2039c31 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -72,7 +72,7 @@ pub(crate) fn from_target_feature_attr( // Only allow target features whose feature gates have been enabled // and which are permitted to be toggled. if let Err(reason) = stability.toggle_allowed() { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: feature_str, reason, @@ -107,7 +107,7 @@ pub(crate) fn from_target_feature_attr( diagnostics::Aarch64SoftfloatNeon, ); } else { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: name.as_str(), reason: "this feature is incompatible with the target ABI", @@ -122,7 +122,17 @@ pub(crate) fn from_target_feature_attr( } else { TargetFeatureKind::Enabled }; - target_features.push(TargetFeature { name, kind }) + target_features.push(TargetFeature { name, kind }); + + if !rust_target_features + .get(name.as_str()) + .is_some_and(|s| s.toggle_allowed().is_ok()) + { + tcx.dcx().span_delayed_bug( + feature_span, + format!("internal-only feature {name} should not be toggled by `#[target_feature]`"), + ); + } } } } @@ -131,7 +141,7 @@ pub(crate) fn from_target_feature_attr( /// Computes the set of target features used in a function for the purposes of /// inline assembly. fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet { - let mut target_features = tcx.sess.unstable_target_features.clone(); + let mut target_features = tcx.sess.internal_target_features.clone(); if tcx.def_kind(did).has_codegen_attrs() { let attrs = tcx.codegen_fn_attrs(did); target_features.extend(attrs.target_features.iter().map(|feature| feature.name)); @@ -164,20 +174,22 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, } } -/// Parse the value of the target spec `features` field or `-Ctarget-feature`, also expanding -/// implied features, and call the closure for each (expanded) Rust feature. If the list contains -/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked. +/// Parse the value of the target spec `features` field or `-Ctarget-feature`, calling the closure +/// for each entry in the list, also expanding implied features (but only for actual Rust target +/// features). If the list contains a syntactically invalid item (not starting with `+`/`-`) , the +/// error callback is invoked. fn parse_rust_feature_list<'a>( sess: &'a Session, features: &'a str, err_callback: impl Fn(&'a str), mut callback: impl FnMut( /* base_feature */ &'a str, - /* with_implied */ FxHashSet<&'a str>, + /* with_implied */ Option>, /* enable */ bool, ), ) { - // A cache for the backwards implication map. + // A cache for the forward and backwards feature maps. + let mut features_map: Option> = None; let mut inverse_implied_features: Option>> = None; for feature in features.split(',') { @@ -187,13 +199,30 @@ fn parse_rust_feature_list<'a>( continue; } - callback(base_feature, sess.target.implied_target_features(base_feature), true) + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, true); + continue; + } + + let implied_features = sess.target.implied_target_features(base_feature, &features_map); + callback(base_feature, Some(implied_features), true) } else if let Some(base_feature) = feature.strip_prefix('-') { // Skip features that are not target features, but rustc features. if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) { continue; } + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, false); + continue; + } + // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical // contraposition. So we have to find all the reverse implications of `base_feature` and // disable them, too. @@ -210,10 +239,10 @@ fn parse_rust_feature_list<'a>( // Inverse implied target features have their own inverse implied target features, so we // traverse the map until there are no more features to add. - let mut features = FxHashSet::default(); + let mut implied_features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { - if features.insert(new_feature) { + if implied_features.insert(new_feature) { if let Some(implied_features) = inverse_implied_features.get(&new_feature) { #[allow(rustc::potential_query_instability)] new_features.extend(implied_features) @@ -221,16 +250,15 @@ fn parse_rust_feature_list<'a>( } } - callback(base_feature, features, false) + callback(base_feature, Some(implied_features), false) } else if !feature.is_empty() { err_callback(feature) } } } -/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically, -/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and -/// 2nd component of the return value, respectively). +/// Utility function for a codegen backend to compute the set of all actually enabled Rust target +/// features (which will be stored in `sess.internal_target_features`). /// /// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is /// used for diagnostic purposes only. @@ -242,15 +270,15 @@ fn parse_rust_feature_list<'a>( /// to target features. /// /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere. -pub fn cfg_target_feature<'a, const N: usize>( +pub fn internal_target_features<'a, const N: usize>( sess: &Session, to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>, mut target_base_has_feature: impl FnMut(&str) -> bool, -) -> (Vec, Vec) { - let known_features = sess.target.rust_target_features(); +) -> UnordSet { + let features_map = sess.target.rust_target_features_map(); - // Compute which of the known target features are enabled in the 'base' target machine. We only - // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now. + // Compute which of the known target features are enabled in the 'base' target machine: for + // every Rust target feature, ask the backend if it is enabled. let mut features: UnordSet = sess .target .rust_target_features() @@ -263,10 +291,14 @@ pub fn cfg_target_feature<'a, const N: usize>( // // Iteration order is irrelevant because we're collecting into an `UnordSet`. #[allow(rustc::potential_query_instability)] - sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f)) + sess.target + .implied_target_features(base_feature, &features_map) + .into_iter() + .map(|f| Symbol::intern(f)) }) .collect(); + // State gathered for "tied features" check. let mut enabled_disabled_features = FxHashMap::default(); // Add enabled and remove disabled features. @@ -278,37 +310,23 @@ pub fn cfg_target_feature<'a, const N: usize>( sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { - // Iteration order is irrelevant since this only influences an `FxHashMap`. - #[allow(rustc::potential_query_instability)] - enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); - - // Iteration order is irrelevant since this only influences an `UnordSet`. - #[allow(rustc::potential_query_instability)] - if enable { - features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); - } else { - // Remove `new_features` from `features`. - for new in new_features { - features.remove(&Symbol::intern(new)); - } - } - - // Check feature validity. - let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature); - match feature_state { + match features_map.get(base_feature) { None => { - // This is definitely not a valid Rust feature name. Maybe it is a backend - // feature name? If so, give a better error message. - let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| { - let backend_features = to_backend_features(rust_feature); - if backend_features.contains(&base_feature) - && !backend_features.contains(&rust_feature) - { - Some(rust_feature) - } else { - None - } - }); + // This is definitely not a valid Rust feature name. We do not add it to + // `features`. Maybe it is a backend feature name? If so, give a better error + // message. + let rust_feature = sess.target.rust_target_features().iter().find_map( + |&(rust_feature, _, _)| { + let backend_features = to_backend_features(rust_feature); + if backend_features.contains(&base_feature) + && !backend_features.contains(&rust_feature) + { + Some(rust_feature) + } else { + None + } + }, + ); let unknown_feature = if let Some(rust_feature) = rust_feature { diagnostics::UnknownCTargetFeature { feature: base_feature, @@ -322,9 +340,27 @@ pub fn cfg_target_feature<'a, const N: usize>( }; sess.dcx().emit_warn(unknown_feature); } - Some((_, stability, _)) => { - if let Stability::Forbidden { reason, hard_error } = stability { - let diag = diagnostics::ForbiddenCTargetFeature { + Some((stability, _)) => { + let new_features = new_features.unwrap(); + // Add feature to our set -- only if it is actually a recognized feature. + // Iteration order is irrelevant since this only influences an `FxHashMap`. + #[allow(rustc::potential_query_instability)] + enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); + + // Iteration order is irrelevant since this only influences an `UnordSet`. + #[allow(rustc::potential_query_instability)] + if enable { + features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); + } else { + // Remove `new_features` from `features`. + for new in new_features { + features.remove(&Symbol::intern(new)); + } + } + + // Check feature stability. + if let Stability::InternalOnly { reason, hard_error } = stability { + let diag = diagnostics::InternalOnlyCTargetFeature { feature: base_feature, enabled: if enable { "enabled" } else { "disabled" }, reason, @@ -363,34 +399,11 @@ pub fn cfg_target_feature<'a, const N: usize>( }); } - // Filter enabled features based on feature gates. - let f = |allow_unstable| { - sess.target - .rust_target_features() - .iter() - .filter_map(|(feature, gate, _)| { - // The `allow_unstable` set is used by rustc internally to determine which target - // features are truly available, so we want to return even perma-unstable - // "forbidden" features. - if allow_unstable - || (gate.in_cfg() - && (sess.is_nightly_build() - || gate.requires_nightly(/* in_cfg */ true).is_none())) - { - Some(Symbol::intern(feature)) - } else { - None - } - }) - .filter(|feature| features.contains(&feature)) - .collect() - }; - - (f(true), f(false)) + features } /// Given a map from target_features to whether they are enabled or disabled, ensure only valid -/// combinations are allowed. +/// combinations are allowed. Returns `Some` if a violation is found. pub fn check_tied_features( sess: &Session, features: &FxHashMap<&str, bool>, @@ -416,8 +429,6 @@ pub fn target_spec_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - let mut rust_features = vec![]; - // This check handles SM versions that defaults (by LLVM) to unsupported (by Rust) PTX ISA versions. // sm_70, sm_72 and sm_75 defaults to PTX ISA versions with major version 6, while sm_80 default to 7.0 if sess.target.arch == Arch::Nvptx64 @@ -426,7 +437,7 @@ pub fn target_spec_to_backend_features<'a>( None | Some("sm_70") | Some("sm_72") | Some("sm_75") ) { - rust_features.push((true, "ptx70")); + extend_backend_features("ptx70", true); } // Compute implied features @@ -435,20 +446,18 @@ pub fn target_spec_to_backend_features<'a>( &sess.target.features, /* err_callback */ |feature| { - panic!("Target spec contains invalid feature {feature}"); + panic!("Target spec contains invalid feature {feature} (missing `+`/`-` prefix)"); }, - |_base_feature, new_features, enable| { - // FIXME emit an error for unknown features like cfg_target_feature would for -Ctarget-feature - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // FIXME emit an error for unknown features in the target spec like + // internal_target_features would for -Ctarget-feature. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Translates the `-Ctarget-feature` flag into a backend target feature list. @@ -459,26 +468,22 @@ pub fn flag_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - // Compute implied features - let mut rust_features = vec![]; parse_rust_feature_list( sess, &sess.opts.cg.target_feature, /* err_callback */ |_feature| { - // Errors are already emitted in `cfg_target_feature`; avoid duplicates. + // Errors are already emitted in `internal_target_features`; avoid duplicates. }, - |_base_feature, new_features, enable| { - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // Forward unknown features to the backend as that's what we have always done. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Computes the backend target features to be added to account for retpoline flags. @@ -533,9 +538,12 @@ pub(crate) fn provide(providers: &mut Providers) { (Stability::Stable, _) | ( Stability::Unstable { .. }, - Stability::Unstable { .. } | Stability::Forbidden { .. }, + Stability::Unstable { .. } | Stability::InternalOnly { .. }, ) - | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { + | ( + Stability::InternalOnly { .. }, + Stability::InternalOnly { .. }, + ) => { // The stability in the entry is at least as good as the new // one, just keep it. } @@ -553,13 +561,18 @@ pub(crate) fn provide(providers: &mut Providers) { .target .rust_target_features() .iter() - .map(|(a, b, _)| (a.to_string(), *b)) + .map(|(feat, stab, _)| (feat.to_string(), *stab)) .collect() } }, implied_target_features: |tcx, feature: Symbol| { + if tcx.sess.opts.actually_rustdoc { + // We can't handle implication when we are mixing all targets. + return vec![feature]; + } + let features_map = tcx.sess.target.rust_target_features_map(); let feature = feature.as_str(); - UnordSet::from(tcx.sess.target.implied_target_features(feature)) + UnordSet::from(tcx.sess.target.implied_target_features(feature, &features_map)) .into_sorted_stable_ord() .into_iter() .map(|s| Symbol::intern(s)) diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 85882af9e7cd0..36f4d858d0be5 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -44,8 +44,7 @@ pub trait CodegenBackend { /// `target_feature` and support for unstable float types. fn target_config(&self, _sess: &Session) -> TargetConfig { TargetConfig { - target_features: vec![], - unstable_target_features: vec![], + internal_target_features: Default::default(), // `true` is used as a default so backends need to acknowledge when they do not // support the float types, rather than accidentally quietly skipping all tests. has_reliable_f16: true, diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 00a2fc299efd8..9d8b0e101d374 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -457,6 +457,7 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); + FromFn, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 3074a5900773d..d2430a06a0072 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -31,6 +31,18 @@ use crate::method::TreatNotYetDefinedOpaques; use crate::method::confirm::ConfirmContext; use crate::method::probe::{IsSuggestion, Mode}; +/// Side-table info for lowering splatted function arguments. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum SplatLoweringInfo<'tcx> { + /// The DefId of the FnDef being called, used to look up the function type. + /// Also used during argument suggestion for non-splatted function calls. + FnDef(DefId), + /// The type of the FnPtr being called. + FnPtr(Ty<'tcx>), + /// Type resolution errored. + Error(ErrorGuaranteed), +} + /// Checks that it is legal to call methods of the trait corresponding /// to `trait_id` (this only cares about the trait, not the specific /// method that is called). @@ -600,13 +612,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let fn_sig = self.normalize(call_expr.span, Unnormalized::new_wip(fn_sig)); + // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly + let fn_id = match def_id { + Some(x) => SplatLoweringInfo::FnDef(x), + None => SplatLoweringInfo::FnPtr(callee_ty), + }; + self.check_argument_types_maybe_method_like( &fn_sig, call_expr, arg_exprs, expected, TupleArgumentsFlag::with_fn_sig_kind(fn_sig.fn_sig_kind, false), - def_id, + fn_id, callee_generic_args, ); @@ -643,7 +661,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, tuple_arguments_flag: TupleArgumentsFlag, - def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, ) { let do_check = || { @@ -656,7 +674,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.c_variadic(), tuple_arguments_flag, - def_id, + fn_id, callee_generic_args, ); }; @@ -1074,7 +1092,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(closure_def_id.to_def_id()), + SplatLoweringInfo::FnDef(closure_def_id.to_def_id()), None, ); @@ -1172,7 +1190,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, method.sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), None, ); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 12e7f82cadd43..2053cfc1a4634 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -39,6 +39,7 @@ use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, @@ -1488,7 +1489,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, method.sig.fn_sig_kind.c_variadic(), method_tuple_args_flag, - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), Some(method.args), ); @@ -1500,22 +1501,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let guar = self.report_method_error(expr.hir_id, rcvr_t, error, expected, false); let err_inputs = self.err_args(args.len(), guar); - let err_output = Ty::new_error(self.tcx, guar); + let err_ty = Ty::new_error(self.tcx, guar); self.check_argument_types( segment.ident.span, expr, &err_inputs, - err_output, + err_ty, NoExpectation, args, false, TupleArgumentsFlag::DontTupleArguments, - None, + SplatLoweringInfo::Error(guar), Some(GenericArgsRef::default()), ); - err_output + err_ty } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index a7651cf365edd..bdb2bbc9a2fac 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -41,7 +41,7 @@ use rustc_trait_selection::traits::{ }; use tracing::{debug, instrument}; -use crate::callee::{self, DeferredCallResolution}; +use crate::callee::{self, DeferredCallResolution, SplatLoweringInfo}; use crate::diagnostics::{self, CtorIsPrivate}; use crate::method::{self, MethodCallee}; use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy}; @@ -238,7 +238,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn write_splatted_resolution( &self, hir_id: HirId, - r: Result, + r: Result, ErrorGuaranteed>, ) { self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); } @@ -260,7 +260,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, hir_id: HirId, span: Span, - callee_def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, first_tupled_arg_index: u16, tupled_args_count: u16, @@ -268,16 +268,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME(const_trait_impl): enforce constness using enforce_context_effects() and add // _and_enforce_effects to this method's name - self.write_splatted_resolution( - hir_id, - Ok(SplattedDef { - def_id: callee_def_id, - arg_index: first_tupled_arg_index, - arg_count: tupled_args_count, - }), - ); - if let Some(callee_generic_args) = callee_generic_args { - self.write_args(hir_id, callee_generic_args); + match fn_id { + // We're splatting a FnDef based on its DefId + SplatLoweringInfo::FnDef(def_id) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::FnDef { + def_id, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + // We're splatting a FnPtr based on its type + SplatLoweringInfo::FnPtr(fn_ty) => { + // FIXME(splat): do we need to look up both these HirIds? + // They can be different (and are different in some UI tests) + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::FnPtr { + fn_ptr_type: fn_ty, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + // FIXME(splat): is this actually populated and used correctly? + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + SplatLoweringInfo::Error(guar) => { + self.write_splatted_resolution(hir_id, Err(guar)); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 8a8a2e95db70e..339a2daf1897f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -32,6 +32,7 @@ use tracing::debug; use crate::Expectation::*; use crate::TupleArgumentsFlag::*; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::SuggestPtrNullMut; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; @@ -203,8 +204,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { c_variadic: bool, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) { @@ -301,7 +302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, expected_input_tys, tuple_arguments, - fn_def_id, + fn_id, callee_generic_args, ); let TupledArgCheckOutcome { @@ -552,7 +553,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -575,8 +576,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { mut expected_input_tys: Option>>, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) -> TupledArgCheckOutcome<'tcx> { @@ -736,7 +737,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we don't check argument counts here, and there's a subtle bug in the code above, // later compilation stages can fail in unrelated places with confusing errors. if !matches!(tuple_type.kind(), ty::Tuple(_)) { - let spans = if let Some(def_id) = fn_def_id + let spans = if let SplatLoweringInfo::FnDef(def_id) = fn_id && let Some(hir_node) = self.tcx.hir_get_if_local(def_id) && let Some(fn_decl) = hir_node.fn_decl() && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index_usz) @@ -797,7 +798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_splatted_call( call_expr.hir_id, call_span, - fn_def_id, + fn_id, callee_generic_args, first_tupled_arg_index, tupled_args_count.unwrap().try_into().unwrap(), @@ -834,7 +835,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, // FIXME(splat): when the feature design is settled, improve the errors here @@ -849,7 +851,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -923,7 +925,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Call out where the function is defined fn_call_diag_ctxt.label_fn_like( &mut err, - fn_def_id, + fn_id, fn_call_diag_ctxt.callee_ty, call_expr, None, @@ -1593,7 +1595,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_fn_like( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, callee_ty: Option>, call_expr: &'tcx hir::Expr<'tcx>, expected_ty: Option>, @@ -1604,7 +1607,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { is_method: bool, tuple_arguments: TupleArgumentsFlag, ) { - let Some(mut def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(mut def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -1943,14 +1947,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_generic_mismatches( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, matched_inputs: &IndexVec>, provided_arg_tys: &IndexVec, Span)>, formal_and_expected_inputs: &IndexVec, Ty<'tcx>)>, is_method: bool, is_splat: bool, ) { - let Some(def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -2187,7 +2193,8 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -2199,7 +2206,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -2310,7 +2317,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { }; self.arg_matching_ctxt.args_ctxt.call_ctxt.fn_ctxt.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, None, @@ -2468,7 +2475,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { // Call out where the function is defined self.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, Some(expected_ty), @@ -2887,7 +2894,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { fn label_generic_mismatches(&self, err: &mut Diag<'a>) { self.fn_ctxt.label_generic_mismatches( err, - self.fn_def_id, + self.fn_id, &self.matched_inputs, &self.provided_arg_tys, &self.formal_and_expected_inputs, @@ -3082,7 +3089,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3094,7 +3102,7 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3229,7 +3237,8 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3241,7 +3250,7 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3348,7 +3357,8 @@ struct CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + /// Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3372,7 +3382,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3404,7 +3415,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3491,7 +3502,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { "()".to_string() } else if ty.is_suggestable(self.tcx, false) { with_forced_trimmed_paths!(format!("/* {ty} */")) - } else if let Some(fn_def_id) = self.fn_def_id + } else if let SplatLoweringInfo::FnDef(fn_def_id) = self.fn_id && self.tcx.def_kind(fn_def_id).is_fn_like() && let self_implicit = matches!(self.call_expr.kind, hir::ExprKind::MethodCall(..)) as usize @@ -3501,6 +3512,9 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { { format!("/* {} */", arg.name) } else { + // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? + // SplatLoweringInfo::FnPtr(Ty) and SplatLoweringInfo::Error currently fall through to + // this placeholder "/* value */".to_string() } } diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 3ce52256d618b..654a782262e22 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -11,7 +11,7 @@ use rustc_ast as ast; use rustc_attr_parsing::ShouldEmit; use rustc_codegen_ssa::back::archive::{ArArchiveBuilderBuilder, ArchiveBuilderBuilder}; use rustc_codegen_ssa::back::link::link_binary; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig}; use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN}; @@ -50,10 +50,27 @@ pub(crate) fn add_configuration( let tf = sym::target_feature; let tf_cfg = codegen_backend.target_config(sess); - sess.unstable_target_features.extend(tf_cfg.unstable_target_features.iter().copied()); - sess.target_features.extend(tf_cfg.target_features.iter().copied()); + // Add some of the target features to `cfg`. + cfg.extend( + sess.target + .rust_target_features() + .iter() + .filter_map(|(feature, gate, _)| { + if gate.in_cfg() + && (sess.is_nightly_build() + || gate.requires_nightly(/* in_cfg */ true).is_none()) + { + Some(Symbol::intern(feature)) + } else { + None + } + }) + .filter(|feature| tf_cfg.internal_target_features.contains(&feature)) + .map(|feature| (sym::target_feature, Some(feature))), + ); - cfg.extend(tf_cfg.target_features.into_iter().map(|feat| (tf, Some(feat)))); + // Store all of them in the session. + sess.internal_target_features.extend(tf_cfg.internal_target_features.into_sorted_stable_ord()); if tf_cfg.has_reliable_f16 { cfg.insert((sym::target_has_reliable_f16, None)); @@ -74,10 +91,10 @@ pub(crate) fn add_configuration( } /// Ensures that all target features required by the ABI are present. -/// Must be called after `unstable_target_features` has been populated! +/// Must be called after `internal_target_features` has been populated! pub(crate) fn check_abi_required_features(sess: &Session) { let abi_feature_constraints = sess.target.abi_required_features(); - // We check this against `unstable_target_features` as that is conveniently already + // We check this against `internal_target_features` as that is conveniently already // back-translated to rustc feature names, taking into account `-Ctarget-cpu` and `-Ctarget-feature`. // Just double-check that the features we care about are actually on our list. for feature in @@ -90,13 +107,13 @@ pub(crate) fn check_abi_required_features(sess: &Session) { } for feature in abi_feature_constraints.required { - if !sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if !sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "enabled" }); } } for feature in abi_feature_constraints.incompatible { - if sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "disabled" }); } @@ -374,7 +391,7 @@ impl CodegenBackend for DummyCodegenBackend { } let abi_required_features = sess.target.abi_required_features(); - let (target_features, unstable_target_features) = cfg_target_feature::<0>( + let internal_target_features = internal_target_features::<0>( sess, |_feature| Default::default(), |feature| { @@ -387,8 +404,7 @@ impl CodegenBackend for DummyCodegenBackend { ); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 399f20ebfe1eb..2f4e5606cd555 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -180,7 +180,7 @@ decl_derive!( decl_derive!([Lift, attributes(lift)] => lift::lift_derive); decl_derive!( [Diagnostic, attributes( - // struct attributes + // struct and field attributes diag, help, help_once, @@ -194,7 +194,9 @@ decl_derive!( suggestion, suggestion_short, suggestion_hidden, - suggestion_verbose)] => diagnostics::diagnostic_derive + suggestion_verbose)] => + #[doc = "See "] + diagnostics::diagnostic_derive ); decl_derive!( [Subdiagnostic, attributes( diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 3b6c38a17a625..0e1519f5a27f9 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1190,6 +1190,15 @@ impl<'tcx> Ty<'tcx> { matches!(self.kind(), Adt(..)) } + #[inline] + pub fn is_self_param(self) -> bool { + if let Param(param) = self.kind() { + param.index == 0 && param.name == kw::SelfUpper + } else { + false + } + } + #[inline] pub fn is_ref(self) -> bool { matches!(self.kind(), Ref(..)) diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index a0f38dcb50cb4..ff7cef3613437 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -37,7 +37,7 @@ pub struct TypeckResults<'tcx> { type_dependent_defs: ItemLocalMap>, /// Resolved definitions for splatted function calls. - splatted_defs: ItemLocalMap>, + splatted_defs: ItemLocalMap, ErrorGuaranteed>>, /// Resolved field indices for field accesses in expressions (`S { field }`, `obj.field`) /// or patterns (`S { field }`). The index is often useful by itself, but to learn more @@ -295,18 +295,20 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } - pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + pub fn splatted_defs( + &self, + ) -> LocalTableInContext<'_, Result, ErrorGuaranteed>> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } } - pub fn splatted_def(&self, id: HirId) -> Option { + pub fn splatted_def(&self, id: HirId) -> Option> { validate_hir_id_for_typeck_results(self.hir_owner, id); self.splatted_defs.get(&id.local_id).cloned().and_then(|r| r.ok()) } pub fn splatted_defs_mut( &mut self, - ) -> LocalTableInContextMut<'_, Result> { + ) -> LocalTableInContextMut<'_, Result, ErrorGuaranteed>> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } } @@ -431,7 +433,7 @@ impl<'tcx> TypeckResults<'tcx> { } pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { - matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(_))) } /// Returns the computed binding mode for a `PatKind::Binding` pattern @@ -598,14 +600,62 @@ impl<'tcx> TypeckResults<'tcx> { /// A resolved splatted function call. #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] -pub struct SplattedDef { - /// The function DefId, if available (FnPtrs don't have DefIds) - pub def_id: Option, - /// The index of the first argument in the callee's splatted tuple, and the index of the - /// splatted tuple argument in the caller. - pub arg_index: u16, - /// The number of arguments in the splatted tuple. - pub arg_count: u16, +pub enum SplattedDef<'tcx> { + /// A resolved FnDef call. + FnDef { + /// The DefId of the FnDef (used to look up its type). + def_id: DefId, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, + + /// A resolved FnPtr Call. + FnPtr { + /// The resolved type of the FnPtr. + fn_ptr_type: Ty<'tcx>, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, +} + +impl<'tcx> SplattedDef<'tcx> { + pub fn def_id(&self) -> Option { + match self { + SplattedDef::FnDef { def_id, .. } => Some(*def_id), + SplattedDef::FnPtr { .. } => None, + } + } + + pub fn fn_ptr_type(&self) -> Option> { + match self { + SplattedDef::FnDef { .. } => None, + SplattedDef::FnPtr { fn_ptr_type, .. } => Some(*fn_ptr_type), + } + } + + pub fn arg_index(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_index, .. } => *arg_index, + SplattedDef::FnPtr { arg_index, .. } => *arg_index, + } + } + + pub fn arg_count(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_count, .. } => *arg_count, + SplattedDef::FnPtr { arg_count, .. } => *arg_count, + } + } } /// Validate that the given HirId (respectively its `local_id` part) can be diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 70e9129ffee3f..69590fc351320 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -448,7 +448,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { let build_enabled = self .tcx .sess - .target_features + .internal_target_features .iter() .copied() .filter(|feature| missing.contains(feature)) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 4b067a8ca79e2..fcf2432b4d8dc 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -28,6 +28,36 @@ use tracing::{debug, info, instrument, trace}; use crate::diagnostics::*; use crate::thir::cx::ThirBuildCx; +/// The receiver of a splatted method, or the expression for a splatted function call. +#[derive(Copy, Clone, Debug)] +enum SplattedFunc<'tcx> { + /// The expression for a method receiver. Always a FnDef. + FnDefReceiver(&'tcx hir::Expr<'tcx>), + /// The expression or path for a function call. + /// This can be a FnDef or FnPtr. + FnExpression(&'tcx hir::Expr<'tcx>), +} + +impl<'tcx> SplattedFunc<'tcx> { + fn has_receiver(&self) -> bool { + matches!(self, SplattedFunc::FnDefReceiver(_)) + } + + fn receiver(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(receiver) => Some(receiver), + SplattedFunc::FnExpression(_fn_expression) => None, + } + } + + fn fn_expression(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(_receiver) => None, + SplattedFunc::FnExpression(fn_expression) => Some(fn_expression), + } + } +} + fn parsed_attrs(id: HirId, tcx: TyCtxt<'_>) -> ThinVec { HasAttrs::get_attrs(id, &tcx) .into_iter() @@ -375,7 +405,12 @@ impl<'tcx> ThirBuildCx<'tcx> { if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fn_span, args, Some(receiver)) + self.convert_splatted_callee( + expr, + fn_span, + args, + SplattedFunc::FnDefReceiver(receiver), + ) } else { // Rewrite a.b(c) into UFCS form like Trait::b(a, c) let expr = self.method_callee(expr, segment.ident.span, None); @@ -425,7 +460,12 @@ impl<'tcx> ThirBuildCx<'tcx> { } else if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `f(a, u, v)` into `f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fun.span, args, None) + self.convert_splatted_callee( + expr, + fun.span, + args, + SplattedFunc::FnExpression(fun), + ) } else { // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind @@ -1224,95 +1264,42 @@ impl<'tcx> ThirBuildCx<'tcx> { } } - fn splatted_callee( - &mut self, - expr: &hir::Expr<'_>, - span: Span, - ) -> (Expr<'tcx>, u16 /* arg_index */, u16 /* arg_count */) { - let SplattedDef { def_id, arg_index, arg_count } = - self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { - span_bug!(expr.span, "no splatted def for function or method callee") - }); - - let expr = if let Some(def_id) = def_id { - // We're calling a function via a FnDef, and its possibly generic type - let def_kind = self.tcx.def_kind(def_id); - let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); - debug!( - "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", - user_ty, def_kind, def_id, arg_index, arg_count, - ); - - Expr { - temp_scope_id: expr.hir_id.local_id, - ty: self - .tcx - .type_of(def_id) - .instantiate(self.tcx, self.typeck_results.node_args(expr.hir_id)) - .skip_norm_wip(), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - } else { - // We're calling a function via a FnPtr and its type - // FIXME(splat): populate the side-tables for FnPtrs, using liberated_fn_sigs if needed - let fn_ty = self.typeck_results.expr_ty_adjusted(expr); - let user_ty = - self.typeck_results.user_provided_types().get(expr.hir_id).copied().map(Box::new); - debug!( - "splatted_callee FnPtr: user_ty={:?} fn_ty={:?} arg_index={:?} arg_count={:?}", - user_ty, fn_ty, arg_index, arg_count, - ); - - if !fn_ty.is_fn() { - span_bug!(expr.span, "splatted FnPtr side-tables are not yet implemented") - } - - Expr { - temp_scope_id: expr.hir_id.local_id, - // Create a new FnPtr FnSig type, representing the splatted function arguments with - // user-supplied generic types applied - ty: Ty::new_fn_ptr(self.tcx, fn_ty.fn_sig(self.tcx)), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - }; - - (expr, arg_index, arg_count) - } - /// The callee has a splatted tuple argument. /// Rewrite a splatted call `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))`. /// The receiver is optional. fn convert_splatted_callee( &mut self, - expr: &hir::Expr<'_>, + call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], - receiver: Option<&'tcx hir::Expr<'tcx>>, + receiver_or_func: SplattedFunc<'tcx>, ) -> ExprKind<'tcx> { let tcx = self.tcx; - // The callee has a splatted tuple argument. - let (func, tupled_arg_index, tupled_args_count) = self.splatted_callee(expr, fn_span); - let tupled_arg_index = usize::from(tupled_arg_index); - let tupled_args_count = usize::from(tupled_args_count); + // Look up the typeck results + let splatted_def = + self.typeck_results.splatted_def(call_expr.hir_id).unwrap_or_else(|| { + span_bug!(call_expr.span, "no splatted def for function or method callee") + }); + + let tupled_arg_index = usize::from(splatted_def.arg_index()); + let tupled_args_count = usize::from(splatted_def.arg_count()); // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[rustc_splat] ())`. // In that case, the tupled arg index is one past the end of the args. if tupled_arg_index + tupled_args_count > args.len() { span_bug!( - expr.span, - "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", + call_expr.span, + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: {:?}, args {:?}", tupled_arg_index, tupled_args_count, args.len(), - receiver, + receiver_or_func, args, ); } - info!("Using splatted function span: {:?}", func.span); + debug!("Using splatted function span: {:?}", fn_span); // Split into non-tupled and tupled arguments let initial_non_tupled_args = @@ -1331,29 +1318,94 @@ impl<'tcx> ThirBuildCx<'tcx> { let tupled_arg_tys = tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); - let temp_scope_id = - if receiver.is_some() { func.temp_scope_id } else { expr.hir_id.local_id }; + // We need the tupled arguments in HIR/MIR for type checking + // FIXME(splat): de-tuple args in codegen for performance let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), - temp_scope_id, - span: expr.span, + temp_scope_id: call_expr.hir_id.local_id, + span: call_expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, }; let tupled_args = self.thir.exprs.push(tupled_args); - let mut args = - if let Some(receiver) = receiver { vec![self.mirror_expr(receiver)] } else { vec![] }; + // Handle the receiver as the first arg, if present + let mut args = Vec::with_capacity( + usize::from(receiver_or_func.has_receiver()) + + initial_non_tupled_args.len() + + 1 + + final_non_tupled_args.len(), + ); + if let Some(receiver) = receiver_or_func.receiver() { + args.push(self.mirror_expr(receiver)); + } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - // We need the tupled arguments in HIR/MIR for type checking, but codegen can - // de-tuple them for performance - let fn_span = if receiver.is_some() { func.span } else { expr.span }; + let fn_span = if receiver_or_func.has_receiver() { fn_span } else { call_expr.span }; + + let (fn_ty, fun_expr) = match (splatted_def, receiver_or_func.fn_expression()) { + // Create a FnDef shim for user-provided types + (SplattedDef::FnDef { def_id, arg_index, arg_count }, _) => { + // We're calling a function via a FnDef, and its possibly generic type + // This is effectively `self.method_callee(call_expr, fn_span, None)`, + // applied to `splatted_def` instead of `type_dependent_def`. + let def_kind = self.tcx.def_kind(def_id); + let user_ty = + self.user_args_applied_to_res(call_expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count, + ); + + // Create a new FnDef expression with user-provided type applied + let callee_expr = Expr { + temp_scope_id: call_expr.hir_id.local_id, + ty: self + .tcx + .type_of(def_id) + .instantiate(self.tcx, self.typeck_results.node_args(call_expr.hir_id)) + .skip_norm_wip(), + span: fn_span, + kind: ExprKind::ZstLiteral { user_ty }, + }; + (callee_expr.ty, self.thir.exprs.push(callee_expr)) + } + + // We're calling a function via a FnPtr and its type + // FIXME(splat): do we need to populate and apply user_provided_types() ? + (SplattedDef::FnPtr { fn_ptr_type, arg_index, arg_count }, Some(fn_expression)) => { + debug!( + "splatted_callee FnPtr: fn_ty={:?} arg_index={:?} arg_count={:?}", + fn_ptr_type, arg_index, arg_count, + ); + + if !fn_ptr_type.is_fn() { + span_bug!( + call_expr.span, + "splatted FnPtr side-tables were not populated correctly, non-fn type received: {:?}", + fn_ptr_type + ) + } + + // Pass through the FnPtr type and the mirrored function path + (fn_ptr_type, self.mirror_expr(fn_expression)) + } + // FnPtrs must have a function expression (and they never have method receivers) + (SplattedDef::FnPtr { .. }, None) => { + span_bug!( + call_expr.span, + "convert_splatted_callee: FnPtr without fn expression (or with receiver) is invalid: splatted_def={:?}, receiver_or_func={:?}", + splatted_def, + receiver_or_func, + ); + } + }; + ExprKind::Call { - ty: func.ty, - fun: self.thir.exprs.push(func), + ty: fn_ty, + fun: fun_expr, args: args.into_boxed_slice(), from_hir_call: true, fn_span, diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index be1a7e1419a29..085a978b9956d 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -203,8 +203,23 @@ fn build_adrop_for_coroutine_shim<'tcx>( let ty::Coroutine(coroutine_def_id, impl_args) = impl_ty.kind() else { bug!("build_adrop_for_coroutine_shim not for coroutine impl type: ({:?})", shim); }; + let ty::Coroutine(_, id_args) = *tcx.type_of(*coroutine_def_id).skip_binder().kind() else { + bug!() + }; let source_info = SourceInfo::outermost(span); - let body = tcx.optimized_mir(*coroutine_def_id).future_drop_poll().unwrap(); + + // If the kind tys differ, we must use the by-move body + let def_id = if id_args.as_coroutine().kind_ty() == impl_args.as_coroutine().kind_ty() { + *coroutine_def_id + } else { + assert_eq!( + impl_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(), + ty::ClosureKind::FnOnce + ); + + tcx.coroutine_by_move_body_def_id(*coroutine_def_id) + }; + let body = tcx.optimized_mir(def_id).future_drop_poll().unwrap(); let mut body: Body<'tcx> = EarlyBinder::bind(tcx, body.clone()).instantiate(tcx, impl_args).skip_norm_wip(); body.source.instance = ty::InstanceKind::Shim(shim); diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 4479ce2dba08b..173595de5c8c2 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -57,7 +57,7 @@ fn do_check_simd_vector_abi<'tcx>( ) { let codegen_attrs = tcx.codegen_fn_attrs(def_id); let have_feature = |feat: Symbol| { - let target_feats = tcx.sess.unstable_target_features.contains(&feat); + let target_feats = tcx.sess.internal_target_features.contains(&feat); let fn_feats = codegen_attrs.target_features.iter().any(|x| x.name == feat); target_feats || fn_feats }; diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index 84a26af6b54ce..e5c874503a00f 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -304,7 +304,7 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { } } - if !sess.target.singlethread(&sess.target_features) { + if !sess.target.singlethread(&sess.internal_target_features) { ins_none!(sym::target_has_threads); } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 834424b8fbd84..f30d825b470ac 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -371,11 +371,11 @@ pub struct Session { /// Architecture to use for interpreting asm!. pub asm_arch: Option, - /// Set of enabled features for the current target. - pub target_features: FxIndexSet, - - /// Set of enabled features for the current target, including unstable ones. - pub unstable_target_features: FxIndexSet, + /// Set of actually enabled features for the current target, including ones that are not + /// in `cfg(target_feature)` because they are unstable or internal-only. + /// This is used by the compiler itself when it needs to know which target features are actually + /// going to be enabled in the backend. + pub internal_target_features: FxIndexSet, /// The version of the rustc process, possibly including a commit hash and description. pub cfg_version: &'static str, @@ -1341,8 +1341,7 @@ pub fn build_session( ctfe_backtrace, miri_unleashed_features: Lock::new(Default::default()), asm_arch, - target_features: Default::default(), - unstable_target_features: Default::default(), + internal_target_features: Default::default(), cfg_version, using_internal_features, env_depinfo: Default::default(), diff --git a/compiler/rustc_target/src/spec/base/l4re.rs b/compiler/rustc_target/src/spec/base/l4re.rs index 8722c8a71e23a..cc67bd6d3a487 100644 --- a/compiler/rustc_target/src/spec/base/l4re.rs +++ b/compiler/rustc_target/src/spec/base/l4re.rs @@ -1,14 +1,59 @@ -use crate::spec::{Cc, Env, LinkerFlavor, Os, PanicStrategy, RelocModel, TargetOptions, cvs}; +use crate::spec::{ + Cc, Env, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFlavor, + Os, PanicStrategy, TargetOptions, add_link_args, crt_objects, cvs, +}; pub(crate) fn opts() -> TargetOptions { + // add ld- and cc-style args + macro_rules! prepare_args { + ($($val:expr),+) => {{ + let ld_args = &[$($val),+]; + let cc_args = &[$(concat!("-Wl,", $val)),+]; + + let mut ret = TargetOptions::link_args(LinkerFlavor::Unix(Cc::No), ld_args); + add_link_args(&mut ret, LinkerFlavor::Unix(Cc::Yes), cc_args); + ret + }}; + } + + let pre_link_args = prepare_args!("-nostdlib", "-dynamic-linker=rom/libld-l4.so"); + + let late_link_args = prepare_args!("-lc", "-lgcc_eh"); + + let pre_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crt1.o", "crti.o", "crtbeginT.o"]), + (LinkOutputKind::StaticPicExe, &["crt1.p.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crt1.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicPicExe, &["crt1.s.o", "crti.o", "crtbeginS.o"]), + (LinkOutputKind::DynamicDylib, &["crti.s.o", "crtbeginS.o"]), + (LinkOutputKind::StaticDylib, &["crti.s.o", "crtbeginS.o"]), + ]); + + let post_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crtendT.o", "crtn.o"]), + (LinkOutputKind::StaticPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicPicExe, &["crtendS.o", "crtn.o"]), + (LinkOutputKind::DynamicDylib, &["crtendS.o", "crtn.s.o"]), + (LinkOutputKind::StaticDylib, &["crtendS.o", "crtn.s.o"]), + ]); + TargetOptions { os: Os::L4Re, env: Env::Uclibc, - linker_flavor: LinkerFlavor::Unix(Cc::No), - panic_strategy: PanicStrategy::Abort, - linker: Some("l4-bender".into()), families: cvs!["unix"], - relocation_model: RelocModel::Static, + panic_strategy: PanicStrategy::Abort, + linker_flavor: LinkerFlavor::Unix(Cc::No), + dynamic_linking: true, + position_independent_executables: true, + has_thread_local: true, + pre_link_args, + late_link_args, + pre_link_objects_self_contained, + post_link_objects_self_contained, + link_self_contained: LinkSelfContainedDefault::WithComponents( + LinkSelfContainedComponents::LIBC | LinkSelfContainedComponents::CRT_OBJECTS, + ), ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 1f17173953643..97c8db0780733 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1569,6 +1569,7 @@ supported_targets! { ("avr-none", avr_none), + ("aarch64-unknown-l4re-uclibc", aarch64_unknown_l4re_uclibc), ("x86_64-unknown-l4re-uclibc", x86_64_unknown_l4re_uclibc), ("aarch64-unknown-redox", aarch64_unknown_redox), @@ -3835,7 +3836,7 @@ impl Target { pub fn object_architecture( &self, - unstable_target_features: &FxIndexSet, + internal_target_features: &FxIndexSet, ) -> Option<(object::Architecture, Option)> { use object::Architecture; Some(match self.arch { @@ -3878,7 +3879,7 @@ impl Target { Arch::RiscV32 => (Architecture::Riscv32, None), Arch::RiscV64 => (Architecture::Riscv64, None), Arch::Sparc => { - if unstable_target_features.contains(&sym::v8plus) { + if internal_target_features.contains(&sym::v8plus) { // Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode (Architecture::Sparc32Plus, None) } else { diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs new file mode 100644 index 0000000000000..bca1195ddad72 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs @@ -0,0 +1,28 @@ +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetOptions, base}; + +pub(crate) fn target() -> Target { + let mut base = base::l4re::opts(); + + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); + + Target { + llvm_target: "aarch64-unknown-l4re-uclibc".into(), + metadata: crate::spec::TargetMetadata { + description: Some("Arm64 L4Re".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32".into(), + arch: Arch::AArch64, + options: TargetOptions { + features: "+v8a".into(), + mcount: "__mcount".into(), + max_atomic_width: Some(128), + ..base + } + } +} diff --git a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs index 5ab6b094dfa06..7030a98305a0b 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs @@ -1,11 +1,13 @@ -use crate::spec::{Arch, PanicStrategy, Target, TargetMetadata, base}; +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetMetadata, base}; pub(crate) fn target() -> Target { let mut base = base::l4re::opts(); base.cpu = "x86-64".into(); base.plt_by_default = false; base.max_atomic_width = Some(64); - base.panic_strategy = PanicStrategy::Abort; + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); Target { llvm_target: "x86_64-unknown-l4re-gnu".into(), diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index ce09972396e56..f1dd2d8191985 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -28,8 +28,8 @@ //! call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as //! arguments, so letting people toggle that feature would be unsound. To this end, the //! [`Target::abi_required_features`] function computes which target features must and must not be -//! enabled for any given target, and individual features can also be marked as [`Forbidden`]. See -//! for some more context. +//! enabled for any given target, and individual features can also be marked as [`InternalOnly`]. +//! See for some more context. //! //! The one exception to features that change the ABI is features that enable larger vector //! registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store @@ -45,7 +45,8 @@ use rustc_span::{Symbol, sym}; use crate::spec::{Arch, FloatAbi, LlvmAbi, RustcAbi, Target}; -/// Features that control behaviour of rustc, rather than the codegen. +/// Features that control behaviour of rustc, rather than the codegen. Not to be included in +/// `cfg(target_feature)`, `sess.internal_target_features`, or the backend's feature list. /// These exist globally and are not in the target-specific lists below. pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"]; @@ -69,17 +70,21 @@ pub enum Stability { /// feature gate! Symbol, ), + /// This is not actually something we expose as a "target feature" to our users. + /// We just manage it internally as a target feature since that's how LLVM represents it. /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be /// set in the target spec. It is never set in `cfg(target_feature)`. Used in particular for /// features are actually ABI configuration flags (such as "soft-float" on many targets). - /// However, "forbidden" target features can still sometimes be enabled via `-Ctarget-cpu` or - /// target feature implications (on the Rust/LLVM level). To prevent that, ABI-relevant target - /// features are ideally pinned down (required or forbidden) in - /// [`Target::abi_required_features`]. - Forbidden { + /// + /// However, "internal" target features can still sometimes be enabled or disabled via + /// `-Ctarget-cpu` or Rust/LLVM target feature implications. Make sure nothing implies this + /// target feature and nothing is implied by this target feature (except for other internal-only + /// features). Ideally, ABI-relevant target features are pinned down (marked as required or + /// incompatible) in [`Target::abi_required_features`]. + InternalOnly { reason: &'static str, /// True if this is always an error, false if this can be reported as a warning when set via - /// `-Ctarget-feature`. + /// `-Ctarget-feature` (and a hard error when set via `#[target_feature]`). hard_error: bool, }, } @@ -120,7 +125,9 @@ impl Stability { } } Stability::Stable { .. } => None, - Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"), + Stability::InternalOnly { .. } => { + panic!("internal-only features should not reach this far") + } } } @@ -138,7 +145,7 @@ impl Stability { Stability::Unstable(_) | Stability::CfgStableToggleUnstable(_) | Stability::Stable { .. } => Ok(()), - Stability::Forbidden { reason, hard_error: _ } => Err(reason), + Stability::InternalOnly { reason, hard_error: _ } => Err(reason), } } } @@ -157,7 +164,8 @@ static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("aes", Unstable(sym::arm_target_feature), &["neon"]), ( "atomics-32", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -244,7 +252,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // We forbid directly toggling just `fp-armv8`; it must be toggled with `neon`. ( "fp-armv8", - Stability::Forbidden { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, + // Pinned down by [`Target::abi_required_features`] when needed. + Stability::InternalOnly { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, &[], ), // FEAT_FP8 @@ -311,7 +320,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdm", Stable, &["neon"]), ( "reserve-x18", - Forbidden { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + InternalOnly { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, &[], ), // FEAT_SB @@ -492,7 +502,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdseed", Stable, &[]), ( "retpoline-external-thunk", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline-external-thunk` compiler flag instead", hard_error: false, }, @@ -500,7 +511,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-branches", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -508,7 +520,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-calls", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -521,7 +534,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("sm4", Stable, &["avx2"]), ( "soft-float", - Stability::Forbidden { reason: "use a soft-float target instead", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + Stability::InternalOnly { reason: "use a soft-float target instead", hard_error: false }, &[], ), ("sse", Stable, &[]), @@ -585,7 +599,8 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("altivec", Unstable(sym::powerpc_target_feature), &[]), ( "hard-float", - Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[], ), ("msync", Unstable(sym::powerpc_target_feature), &[]), @@ -597,7 +612,12 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("power9-vector", Unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]), ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]), ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]), - ("spe", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + ( + "spe", + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, + &[], + ), ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]), // tidy-alphabetical-end ]; @@ -661,7 +681,8 @@ static RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("f", CfgStableToggleUnstable(sym::riscv_target_feature), &["zicsr"]), ( "forced-atomics", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -921,7 +942,8 @@ const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("miscellaneous-extensions-3", Stable, &[]), ("miscellaneous-extensions-4", Stable, &[]), ("nnp-assist", Stable, &["vector"]), - ("soft-float", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + // Pinned down by [`Target::abi_required_features`]. + ("soft-float", InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), ("transactional-execution", Unstable(sym::s390x_target_feature), &[]), ("vector", Stable, &[]), ("vector-enhancements-1", Stable, &["vector"]), @@ -975,7 +997,8 @@ static AVR_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("spmx", Unstable(sym::avr_target_feature), &[]), ( "sram", - Forbidden { reason: "devices that have no SRAM are unsupported", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "devices that have no SRAM are unsupported", hard_error: false }, &[], ), ("tinyencoding", Unstable(sym::avr_target_feature), &[]), @@ -990,7 +1013,11 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("interrupt", Unstable(sym::xtensa_target_feature), &["exception"]), ( "windowed", - Forbidden { reason: "windowed changes the Xtensa calling convention", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { + reason: "windowed changes the Xtensa calling convention", + hard_error: false, + }, &["exception"], ), ("loop", Unstable(sym::xtensa_target_feature), &[]), @@ -1017,17 +1044,17 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ /// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator! pub fn all_rust_features() -> impl Iterator { std::iter::empty() - .chain(ARM_FEATURES.iter()) - .chain(AARCH64_FEATURES.iter()) - .chain(X86_FEATURES.iter()) - .chain(HEXAGON_FEATURES.iter()) - .chain(POWERPC_FEATURES.iter()) - .chain(MIPS_FEATURES.iter()) - .chain(NVPTX_FEATURES.iter()) - .chain(RISCV_FEATURES.iter()) - .chain(WASM_FEATURES.iter()) - .chain(BPF_FEATURES.iter()) - .chain(XTENSA_FEATURES.iter()) + .chain(ARM_FEATURES) + .chain(AARCH64_FEATURES) + .chain(X86_FEATURES) + .chain(HEXAGON_FEATURES) + .chain(POWERPC_FEATURES) + .chain(MIPS_FEATURES) + .chain(NVPTX_FEATURES) + .chain(RISCV_FEATURES) + .chain(WASM_FEATURES) + .chain(BPF_FEATURES) + .chain(XTENSA_FEATURES) .chain(CSKY_FEATURES) .chain(LOONGARCH_FEATURES) .chain(IBMZ_FEATURES) @@ -1152,6 +1179,16 @@ impl Target { } } + /// Computes a map mapping each Rust target feature to the features it implies. + pub fn rust_target_features_map( + &self, + ) -> FxHashMap<&'static str, (Stability, ImpliedFeatures)> { + self.rust_target_features() + .iter() + .map(|&(f, s, i)| (f, (s, i))) + .collect::>() + } + pub fn features_for_correct_fixed_length_vector_abi(&self) -> &'static [(u64, &'static str)] { match &self.arch { Arch::X86 | Arch::X86_64 => X86_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI, @@ -1193,20 +1230,23 @@ impl Target { } } - // Note: the returned set includes `base_feature`. - pub fn implied_target_features<'a>(&self, base_feature: &'a str) -> FxHashSet<&'a str> { - let implied_features = - self.rust_target_features().iter().map(|(f, _, i)| (f, i)).collect::>(); - + /// Note: the returned set includes `base_feature`. + #[track_caller] + pub fn implied_target_features<'a>( + &self, + base_feature: &'a str, + target_features_map: &FxHashMap<&'static str, (Stability, ImpliedFeatures)>, + ) -> FxHashSet<&'a str> { // Implied target features have their own implied target features, so we traverse the // map until there are no more features to add. let mut features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { if features.insert(new_feature) { - if let Some(implied_features) = implied_features.get(&new_feature) { - new_features.extend(implied_features.iter().copied()) - } + let (_, implied_features) = target_features_map + .get(&new_feature) + .unwrap_or_else(|| panic!("encountered non-Rust target feature {new_feature}")); + new_features.extend(implied_features.iter().copied()); } } features @@ -1226,7 +1266,7 @@ impl Target { const NOTHING: FeatureConstraints = FeatureConstraints { required: &[], incompatible: &[] }; // Some architectures don't have a clean explicit ABI designation; instead, the ABI is // defined by target features. When that is the case, those target features must be - // "forbidden" in the list above to ensure that there is a consistent answer to the + // "internal-only" in the list above to ensure that there is a consistent answer to the // questions "which ABI is used". match &self.arch { Arch::X86 => { @@ -1301,9 +1341,9 @@ impl Target { // LLVM will use float registers when `fp-armv8` is available, e.g. for // calls to built-ins. The only way to ensure a consistent softfloat ABI // on aarch64 is to never enable `fp-armv8`, so we enforce that. - // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is the - // feature we have to mark as incompatible. - FeatureConstraints { required: &[], incompatible: &["neon"] } + // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is also + // marked as incompatible. + FeatureConstraints { required: &[], incompatible: &["neon", "fp-armv8"] } } None => { // Everything else is assumed to use a hardfloat ABI. neon and fp-armv8 must be enabled. diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index ae8458c199503..912623b73050e 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -591,6 +591,7 @@ pub const trait From: Sized { #[rustc_diagnostic_item = "from_fn"] #[must_use] #[stable(feature = "rust1", since = "1.0.0")] + #[lang = "from"] fn from(value: T) -> Self; } diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index 9d204a150dd45..1644a2495d97e 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -36,11 +36,6 @@ cfg_select! { #[path = "hermit.rs"] mod imp; } - target_os = "l4re" => { - // L4Re is unix family but does not yet support unwinding. - #[path = "dummy.rs"] - mod imp; - } any( all(target_family = "windows", target_env = "gnu"), target_os = "psp", diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 4c5cd0e0c9e6a..3b8499758d90d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -37,6 +37,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 1b069f2e77f6b..2a656e2f8c196 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -962,6 +962,10 @@ fn recursive_mkdir_slash() { } #[test] +#[cfg_attr( + target_os = "l4re", + ignore = "Path '.' in the file system root can not be resolved in L4Re" +)] fn recursive_mkdir_dot() { check!(fs::create_dir_all(Path::new("."))); } @@ -2117,6 +2121,7 @@ fn rename_directory() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "futimens")] fn test_file_times() { #[cfg(target_vendor = "apple")] use crate::os::darwin::fs::FileTimesExt; @@ -2145,7 +2150,8 @@ fn test_file_times() { target_os = "android", target_os = "redox", target_os = "espidf", - target_os = "horizon" + target_os = "horizon", + target_os = "l4re", )) ) )))] diff --git a/library/std/src/net/ip_addr.rs b/library/std/src/net/ip_addr.rs index 7262899b3bbbe..6bd78de910fec 100644 --- a/library/std/src/net/ip_addr.rs +++ b/library/std/src/net/ip_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "ip_addr", since = "1.7.0")] diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs index 2a8b0f8ca9aad..1b1096925dd4a 100644 --- a/library/std/src/net/mod.rs +++ b/library/std/src/net/mod.rs @@ -42,7 +42,7 @@ mod hostname; mod ip_addr; mod socket_addr; mod tcp; -#[cfg(test)] +#[cfg(all(test, not(target_os = "l4re")))] pub(crate) mod tests; mod udp; diff --git a/library/std/src/net/socket_addr.rs b/library/std/src/net/socket_addr.rs index cae14e34e73e7..2dab8c26f1f6b 100644 --- a/library/std/src/net/socket_addr.rs +++ b/library/std/src/net/socket_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index d9090320bd5a6..4ba4c4e8caa4c 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -7,6 +7,7 @@ all(target_os = "wasi", target_env = "p1"), target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs index cd925b9bdfdf8..4aa77fc9c1fe9 100644 --- a/library/std/src/net/udp.rs +++ b/library/std/src/net/udp.rs @@ -6,6 +6,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/os/fd/mod.rs b/library/std/src/os/fd/mod.rs index 473d7ae3e2ae6..735f1cf8925fb 100644 --- a/library/std/src/os/fd/mod.rs +++ b/library/std/src/os/fd/mod.rs @@ -20,6 +20,7 @@ mod net; mod stdio; #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; // Export the types and traits for the public API. diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 0d96958b6cca1..a0c96e2836fc5 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -16,7 +16,7 @@ use crate::io; use crate::os::hermit::io::OwnedFd; #[cfg(all(not(target_os = "hermit"), not(target_os = "motor")))] use crate::os::raw; -#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx"))))] +#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx", target_os = "l4re"))))] use crate::os::unix::io::AsFd; #[cfg(unix)] use crate::os::unix::io::OwnedFd; diff --git a/library/std/src/os/l4re/fs.rs b/library/std/src/os/l4re/fs.rs index 491e04a4d25cf..2dc899bcb5a6e 100644 --- a/library/std/src/os/l4re/fs.rs +++ b/library/std/src/os/l4re/fs.rs @@ -21,7 +21,7 @@ pub trait MetadataExt { /// Unix platforms. The `os::unix::fs::MetadataExt` trait contains the /// cross-Unix abstractions contained within the raw stat. /// - /// [`stat`]: struct@crate::os::linux::raw::stat + /// [`stat`]: struct@crate::os::l4re::raw::stat /// /// # Examples /// @@ -29,7 +29,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -50,7 +50,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -68,7 +68,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -86,7 +86,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -104,7 +104,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -122,7 +122,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -140,7 +140,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -158,7 +158,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -179,7 +179,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -197,7 +197,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -217,7 +217,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -235,7 +235,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -255,7 +255,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -273,7 +273,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -293,7 +293,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -311,7 +311,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -329,7 +329,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -345,7 +345,7 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } + unsafe { &*(self.as_inner().as_inner() as *const _ as *const raw::stat) } } fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 @@ -372,22 +372,22 @@ impl MetadataExt for Metadata { self.as_inner().as_inner().st_size as u64 } fn st_atime(&self) -> i64 { - self.as_inner().as_inner().st_atime as i64 + self.as_inner().as_inner().st_atim.tv_sec as i64 } fn st_atime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_atime_nsec as i64 + self.as_inner().as_inner().st_atim.tv_nsec as i64 } fn st_mtime(&self) -> i64 { - self.as_inner().as_inner().st_mtime as i64 + self.as_inner().as_inner().st_mtim.tv_sec as i64 } fn st_mtime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_mtime_nsec as i64 + self.as_inner().as_inner().st_mtim.tv_nsec as i64 } fn st_ctime(&self) -> i64 { - self.as_inner().as_inner().st_ctime as i64 + self.as_inner().as_inner().st_ctim.tv_sec as i64 } fn st_ctime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_ctime_nsec as i64 + self.as_inner().as_inner().st_ctim.tv_nsec as i64 } fn st_blksize(&self) -> u64 { self.as_inner().as_inner().st_blksize as u64 diff --git a/library/std/src/os/l4re/raw.rs b/library/std/src/os/l4re/raw.rs index 8fb6e99ecfa1e..f41fff015cab6 100644 --- a/library/std/src/os/l4re/raw.rs +++ b/library/std/src/os/l4re/raw.rs @@ -10,355 +10,14 @@ )] #![allow(deprecated)] -use crate::os::raw::c_ulong; - #[stable(feature = "raw_ext", since = "1.1.0")] -pub type dev_t = u64; +pub type dev_t = libc::dev_t; #[stable(feature = "raw_ext", since = "1.1.0")] -pub type mode_t = u32; +pub type mode_t = libc::mode_t; #[stable(feature = "pthread_t", since = "1.8.0")] -pub type pthread_t = c_ulong; +pub type pthread_t = libc::pthread_t; #[doc(inline)] #[stable(feature = "raw_ext", since = "1.1.0")] -pub use self::arch::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; - -#[cfg(any( - target_arch = "x86", - target_arch = "m68k", - target_arch = "csky", - target_arch = "powerpc", - target_arch = "sparc", - target_arch = "arm", - target_arch = "wasm32" -))] -mod arch { - use crate::os::raw::{c_long, c_short, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: c_short, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __st_ino: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_uint, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - } -} - -#[cfg(target_arch = "mips")] -mod arch { - use crate::os::raw::{c_long, c_ulong}; - - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad1: [c_long; 3], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad2: [c_long; 2], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad5: [c_long; 14], - } -} - -#[cfg(target_arch = "hexagon")] -mod arch { - use crate::os::raw::{c_int, c_long, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = c_long; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = c_uint; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad3: [c_int; 2], - } -} - -#[cfg(any( - target_arch = "mips64", - target_arch = "s390x", - target_arch = "sparc64", - target_arch = "riscv64", - target_arch = "riscv32" -))] -mod arch { - pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; -} - -#[cfg(target_arch = "aarch64")] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = i32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = c_long; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_int; 2], - } -} - -#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad0: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_long; 3], - } -} +pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index 90ad137dac178..aa604aabaffce 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -18,6 +18,7 @@ use crate::sys::{AsInner, AsInnerMut, FromInner}; use crate::{io, sys}; // Tests for this module +#[cfg(not(target_os = "l4re"))] #[cfg(test)] mod tests; diff --git a/library/std/src/os/unix/net/mod.rs b/library/std/src/os/unix/net/mod.rs index 137088dd832f7..92d2696a5ef43 100644 --- a/library/std/src/os/unix/net/mod.rs +++ b/library/std/src/os/unix/net/mod.rs @@ -10,7 +10,7 @@ mod ancillary; mod datagram; mod listener; mod stream; -#[cfg(all(test, not(target_os = "emscripten")))] +#[cfg(all(test, not(any(target_os = "emscripten", target_os = "l4re"))))] mod tests; #[cfg(any( target_os = "android", diff --git a/library/std/src/process.rs b/library/std/src/process.rs index c5ffbbc666e43..a398363cf4bf9 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -157,6 +157,7 @@ target_os = "xous", target_os = "trusty", target_os = "hermit", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 68c62a861075f..9fe14b2e468a5 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -28,7 +28,11 @@ fn shell_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn smoke() { @@ -53,7 +57,11 @@ fn smoke_failure() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn exit_reported_right() { @@ -71,7 +79,11 @@ fn exit_reported_right() { #[test] #[cfg(unix)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn signal_reported_right() { @@ -98,7 +110,11 @@ pub fn run_output(mut cmd: Command) -> String { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdout_works() { @@ -116,7 +132,11 @@ fn stdout_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn set_current_dir_works() { @@ -142,7 +162,11 @@ fn set_current_dir_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdin_works() { @@ -163,7 +187,11 @@ fn stdin_works() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn child_stdout_read_buf() { @@ -197,7 +225,11 @@ fn child_stdout_read_buf() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_status() { @@ -217,6 +249,7 @@ fn test_process_status() { } #[test] +#[cfg_attr(any(target_os = "l4re"), ignore = "no fork/exec available")] fn test_process_output_fail_to_start() { match Command::new("/no-binary-by-this-name-should-exist").output() { Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound), @@ -226,7 +259,11 @@ fn test_process_output_fail_to_start() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_output() { @@ -244,7 +281,11 @@ fn test_process_output_output() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_error() { @@ -262,7 +303,11 @@ fn test_process_output_error() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_once() { @@ -276,7 +321,11 @@ fn test_finish_once() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_twice() { @@ -291,7 +340,11 @@ fn test_finish_twice() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_wait_with_output_once() { @@ -329,7 +382,11 @@ pub fn env_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_override_env() { @@ -355,7 +412,11 @@ fn test_override_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_add_to_env() { @@ -370,7 +431,11 @@ fn test_add_to_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_capture_env_at_spawn() { @@ -654,6 +719,7 @@ fn run_canonical_bat_script() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "no shell available")] fn terminate_exited_process() { let mut cmd = if cfg!(target_os = "android") { let mut p = shell_cmd(); diff --git a/library/std/src/random.rs b/library/std/src/random.rs index ef561d1ed0c60..853756fcd32b6 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -103,7 +103,7 @@ use crate::sys::random as sys; /// Vita | `arc4random_buf` /// Hermit | `read_entropy` /// Horizon, Cygwin | `getrandom` -/// AIX, Hurd, L4Re, QNX | `/dev/urandom` +/// AIX, Hurd, QNX | `/dev/urandom` /// Redox | `/scheme/rand` /// RTEMS | [`arc4random_buf`](https://docs.rtems.org/branches/main/bsp-howto/getentropy.html) /// SGX | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3caa41e16845d..d34621083406a 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -29,19 +29,20 @@ use libc::{ }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", target_os = "android", target_os = "hurd", + target_os = "l4re", )))] use libc::{ dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, }; -#[cfg(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", - target_os = "hurd" -))] +#[cfg(target_os = "l4re")] +use libc::{ + dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, + off_t as off64_t, open as open64, stat as stat64, +}; +#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; use crate::ffi::{CStr, OsStr, OsString}; @@ -272,6 +273,7 @@ cfg_select! { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", ) => { pub use crate::sys::fs::common::Dir; } @@ -560,7 +562,8 @@ impl FileAttr { target_os = "nto", target_os = "qnx", target_os = "aix", - target_os = "wasi" + target_os = "wasi", + target_os = "l4re" )))] impl FileAttr { #[cfg(not(any( @@ -686,7 +689,7 @@ impl FileAttr { } } -#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi"))] +#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi", target_os = "l4re"))] impl FileAttr { pub fn modified(&self) -> io::Result { SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec.into()) @@ -1066,6 +1069,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", ))] pub fn file_type(&self) -> io::Result { self.metadata().map(|m| m.file_type()) @@ -1080,6 +1084,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", )))] pub fn file_type(&self) -> io::Result { match self.entry.d_type { @@ -1289,6 +1294,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", ))] unsafe fn os_datasync(fd: c_int) -> c_int { libc::fdatasync(fd) @@ -1304,6 +1310,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", target_vendor = "apple", )))] unsafe fn os_datasync(fd: c_int) -> c_int { @@ -1550,7 +1557,7 @@ impl File { pub fn set_times(&self, times: FileTimes) -> io::Result<()> { cfg_select! { - any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re") => { // Redox doesn't appear to support `UTIME_OMIT`. // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore // the same as for Redox. @@ -1940,6 +1947,7 @@ pub fn link(original: &CStr, link: &CStr) -> io::Result<()> { // Other misc platforms target_os = "horizon", target_os = "vita", + target_os = "l4re", target_env = "nto70", ) => { cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; @@ -2308,6 +2316,7 @@ pub use remove_dir_impl::remove_dir_all; target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri ))] mod remove_dir_impl { @@ -2323,6 +2332,7 @@ mod remove_dir_impl { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri )))] mod remove_dir_impl { diff --git a/library/std/src/sys/io/error/unix.rs b/library/std/src/sys/io/error/unix.rs index 89647ff27ca8e..5c51c5705a7aa 100644 --- a/library/std/src/sys/io/error/unix.rs +++ b/library/std/src/sys/io/error/unix.rs @@ -201,7 +201,8 @@ pub fn error_string(errno: i32) -> String { target_os = "linux", target_os = "hurd", target_env = "newlib", - target_os = "cygwin" + target_os = "cygwin", + target_env = "uclibc", ), not(target_env = "ohos") ), diff --git a/library/std/src/sys/net/connection/mod.rs b/library/std/src/sys/net/connection/mod.rs index 84b53fd375c93..49a0f47c959d2 100644 --- a/library/std/src/sys/net/connection/mod.rs +++ b/library/std/src/sys/net/connection/mod.rs @@ -1,6 +1,6 @@ cfg_select! { any( - all(target_family = "unix", not(target_os = "l4re")), + target_family = "unix", target_os = "windows", target_os = "hermit", all(target_os = "wasi", any(target_env = "p2", target_env = "p3")), diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 769dc66af8ed1..b7840d2c054c3 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -1,4 +1,5 @@ #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; use crate::ffi::{c_int, c_void}; diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 8fca169d93119..f58c7f5cb95bc 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -145,6 +145,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { target_os = "horizon", target_os = "vxworks", target_os = "vita", + target_os = "l4re", // Unikraft's `signal` implementation is currently broken: // https://github.com/unikraft/lib-musl/issues/57 target_vendor = "unikraft", @@ -363,15 +364,24 @@ cfg_select! { _ => {} } -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] -pub mod unsupported { - use crate::io; - - pub fn unsupported() -> io::Result { - Err(unsupported_err()) - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported() -> crate::io::Result { + Err(unsupported_err()) +} - pub fn unsupported_err() -> io::Error { - io::Error::UNSUPPORTED_PLATFORM - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported_err() -> crate::io::Error { + io::Error::UNSUPPORTED_PLATFORM } diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index 3b363aa2d024c..daa53703994b0 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -30,7 +30,7 @@ cfg_select! { target_os = "psp", target_os = "xous", target_os = "solid_asp3", - all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re"), not(target_os = "nuttx")), + all(target_family = "unix", not(target_os = "espidf"), not(target_os = "nuttx")), all(target_vendor = "fortanix", target_env = "sgx"), ) => { mod gcc; diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index ee61175a278b0..f46870e0c4042 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -45,7 +45,8 @@ pub use imp::{ target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", @@ -83,7 +84,8 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index 8215b196127ac..2e32770e90e77 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -12,7 +12,7 @@ use crate::path::Path; use crate::process::StdioPipes; use crate::sys::fd::FileDesc; use crate::sys::fs::File; -#[cfg(not(target_os = "fuchsia"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] use crate::sys::fs::OpenOptions; use crate::sys::pipe::pipe; use crate::sys::process::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; @@ -24,6 +24,9 @@ mod cstring_array; cfg_select! { target_os = "fuchsia" => { // fuchsia doesn't have /dev/null + }, + target_os = "l4re" => { + // l4re doesn't have /dev/null } target_os = "vxworks" => { const DEV_NULL: &CStr = c"/null"; @@ -119,9 +122,9 @@ pub enum ChildStdio { Explicit(c_int), Owned(FileDesc), - // On Fuchsia, null stdio is the default, so we simply don't specify - // any actions at the time of spawning. - #[cfg(target_os = "fuchsia")] + // On Fuchsia and L4Re, null stdio is the default, so we simply don't + // specify any actions at the time of spawning. + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Null, } @@ -427,7 +430,7 @@ impl Stdio { Ok((ChildStdio::Owned(theirs), Some(ours))) } - #[cfg(not(target_os = "fuchsia"))] + #[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] Stdio::Null => { let mut opts = OpenOptions::new(); opts.read(readable); @@ -436,7 +439,7 @@ impl Stdio { Ok((ChildStdio::Owned(fd.into_inner()), None)) } - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Stdio::Null => Ok((ChildStdio::Null, None)), } } @@ -483,7 +486,7 @@ impl ChildStdio { ChildStdio::Explicit(fd) => Some(fd), ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()), - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] ChildStdio::Null => None, } } diff --git a/library/std/src/sys/process/unix/common/tests.rs b/library/std/src/sys/process/unix/common/tests.rs index bc1d158b74861..eacb4d2d43122 100644 --- a/library/std/src/sys/process/unix/common/tests.rs +++ b/library/std/src/sys/process/unix/common/tests.rs @@ -19,6 +19,8 @@ macro_rules! t { // newly spawned process may just be raced in the macOS, so to prevent this // test from being flaky we ignore it on macOS. target_os = "macos", + // cat not available + target_os = "l4re", // When run under our current QEMU emulation test suite this test fails, // although the reason isn't very clear as to why. For now this test is // ignored there. @@ -84,6 +86,8 @@ fn test_process_mask() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -116,6 +120,8 @@ fn test_process_group_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -154,6 +160,8 @@ fn test_process_group_no_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -192,6 +200,8 @@ fn test_setsid_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", diff --git a/library/std/src/sys/process/unix/mod.rs b/library/std/src/sys/process/unix/mod.rs index 837761431e990..47baf5a1e92ed 100644 --- a/library/std/src/sys/process/unix/mod.rs +++ b/library/std/src/sys/process/unix/mod.rs @@ -1,4 +1,7 @@ -#[cfg_attr(any(target_os = "espidf", target_os = "horizon", target_os = "nuttx"), allow(unused))] +#[cfg_attr( + any(target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re"), + allow(unused) +)] mod common; cfg_select! { @@ -10,7 +13,7 @@ cfg_select! { mod vxworks; use vxworks as imp; } - any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx") => { + any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx", target_os = "l4re") => { mod unsupported; use unsupported as imp; pub use unsupported::output; diff --git a/library/std/src/sys/process/unix/unix/tests.rs b/library/std/src/sys/process/unix/unix/tests.rs index 663ba61f966c9..9a029f16a3a20 100644 --- a/library/std/src/sys/process/unix/unix/tests.rs +++ b/library/std/src/sys/process/unix/unix/tests.rs @@ -51,7 +51,10 @@ fn exitstatus_display_tests() { #[test] #[cfg_attr(target_os = "emscripten", ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn test_command_fork_no_unwind() { let got = catch_unwind(|| { let mut c = Command::new("echo"); diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index 17421d1e2e35d..2235ec1f1c3b6 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -4,7 +4,7 @@ use super::common::*; use crate::io; use crate::num::NonZero; use crate::process::StdioPipes; -use crate::sys::pal::unsupported::*; +use crate::sys::pal::{unsupported, unsupported_err}; //////////////////////////////////////////////////////////////////////////////// // Command diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index e5a66dc463c6b..5b0d19cc63eca 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -52,7 +52,6 @@ cfg_select! { any( target_os = "aix", target_os = "hurd", - target_os = "l4re", target_os = "nto", target_os = "qnx", ) => { @@ -107,6 +106,7 @@ cfg_select! { all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", + target_os = "l4re", ) => { // FIXME: finally remove std support for wasm32-unknown-unknown // FIXME: add random data generation to xous @@ -123,6 +123,7 @@ cfg_select! { all(target_os = "wasi", not(target_env = "p1")), target_os = "xous", target_os = "vexos", + target_os = "l4re", )))] pub fn hashmap_random_keys() -> (u64, u64) { let mut buf = [0; 16]; diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index 21e7a2b2ed087..355a00c2a95ad 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -681,13 +681,10 @@ pub fn park_timeout(dur: Duration) { /// # Examples /// /// ``` -/// # #![allow(dead_code)] -/// use std::{io, thread}; +/// use std::thread; /// -/// fn main() -> io::Result<()> { -/// let count = thread::available_parallelism()?.get(); -/// assert!(count >= 1_usize); -/// Ok(()) +/// if let Ok(count) = thread::available_parallelism() { +/// assert!(count.get() >= 1_usize); /// } /// ``` #[doc(alias = "available_concurrency")] // Alias for a previous name we gave this API on unstable. diff --git a/library/std/tests/env.rs b/library/std/tests/env.rs index 9d624d5592ce7..758d0a069a831 100644 --- a/library/std/tests/env.rs +++ b/library/std/tests/env.rs @@ -4,7 +4,10 @@ use std::path::Path; mod common; #[test] -#[cfg_attr(any(target_os = "emscripten", target_os = "wasi", target_env = "sgx"), ignore)] +#[cfg_attr( + any(target_os = "emscripten", target_os = "wasi", target_env = "sgx", target_os = "l4re"), + ignore +)] fn test_self_exe_path() { let path = current_exe(); assert!(path.is_ok()); diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs index 9643c3b7bdad8..c14db690224db 100644 --- a/library/std/tests/pipe_subprocess.rs +++ b/library/std/tests/pipe_subprocess.rs @@ -1,6 +1,10 @@ fn main() { - // No `Command` on Miri and emscripten - #[cfg(all(not(miri), any(unix, windows), not(target_os = "emscripten")))] + // No `Command` on Miri, emscripten or L4Re + #[cfg(all( + not(miri), + any(unix, windows), + not(any(target_os = "emscripten", target_os = "l4re")) + ))] { use std::io::{Read, pipe}; use std::{env, process}; diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs index 80e712a2388a1..b7a9a1077696b 100644 --- a/library/std/tests/process_spawning.rs +++ b/library/std/tests/process_spawning.rs @@ -7,7 +7,10 @@ mod common; #[test] // Process spawning not supported by Miri, Emscripten and wasi #[cfg_attr(any(miri, target_os = "emscripten", target_os = "wasi"), ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn issue_15149() { // If we're the parent, copy our own binary to a new directory. let my_path = env::current_exe().unwrap(); diff --git a/library/std/tests/time.rs b/library/std/tests/time.rs index d6736e25ace18..6d8b4cbfd094f 100644 --- a/library/std/tests/time.rs +++ b/library/std/tests/time.rs @@ -181,6 +181,7 @@ fn system_time_elapsed() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "No wallclock time support in L4Re")] fn since_epoch() { let ts = SystemTime::now(); let a = ts.duration_since(UNIX_EPOCH + Duration::SECOND).unwrap(); diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index 3725375a713dc..eba08aec4d109 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -19,7 +19,6 @@ cfg_select! { // Windows MSVC no extra unwinder support needed } any( - target_os = "l4re", target_os = "none", target_os = "espidf", target_os = "nuttx", @@ -31,6 +30,7 @@ cfg_select! { windows, target_os = "psp", target_os = "solid_asp3", + target_os = "l4re", all(target_vendor = "fortanix", target_env = "sgx"), all(target_os = "wasi", panic = "unwind"), target_os = "xous", diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 3c4815993b786..f3bc9840f8e45 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -20,6 +20,23 @@ use crate::core::config::TargetSelection; use crate::utils::build_stamp::{self, BuildStamp}; use crate::{CodegenBackendKind, Compiler, Mode, Subcommand, t}; +/// Allows individual check-step instances to keep track of whether they +/// represent `cargo check` or `cargo fix`, independently of [`Builder::kind`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum CheckKind { + Check, + Fix, +} + +impl CheckKind { + fn to_kind(self) -> Kind { + match self { + CheckKind::Check => Kind::Check, + CheckKind::Fix => Kind::Fix, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Std { /// Compiler that will check this std. @@ -225,11 +242,7 @@ impl Step for PrepareRustcRmetaSysroot { fn run(self, builder: &Builder<'_>) -> Self::Output { // Check rustc - let stamp = builder.ensure(Rustc::from_build_compiler( - self.build_compiler.clone(), - self.target, - vec![], - )); + let stamp = Rustc::check_rustc_for_preparing_sysroot(builder, &self); let build_compiler = self.build_compiler.build_compiler(); @@ -284,9 +297,12 @@ impl Step for PrepareStdRmetaSysroot { /// Checks rustc using `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { + check_kind: CheckKind, + /// Compiler that will check this rustc. - pub build_compiler: CompilerForCheck, - pub target: TargetSelection, + build_compiler: CompilerForCheck, + target: TargetSelection, + /// Whether to build only a subset of crates. /// /// This shouldn't be used from other steps; see the comment on [`compile::Rustc`]. @@ -296,17 +312,17 @@ pub struct Rustc { } impl Rustc { - pub fn new(builder: &Builder<'_>, target: TargetSelection, crates: Vec) -> Self { - let build_compiler = prepare_compiler_for_check(builder, target, Mode::Rustc); - Self::from_build_compiler(build_compiler, target, crates) - } - - fn from_build_compiler( - build_compiler: CompilerForCheck, - target: TargetSelection, - crates: Vec, - ) -> Self { - Self { build_compiler, target, crates } + fn check_rustc_for_preparing_sysroot( + builder: &Builder<'_>, + prepare: &PrepareRustcRmetaSysroot, + ) -> BuildStamp { + builder.ensure(Rustc { + // We specifically want `cargo check`, not the current bootstrap subcommand. + check_kind: CheckKind::Check, + build_compiler: prepare.build_compiler.clone(), + target: prepare.target, + crates: vec![], + }) } } @@ -323,8 +339,17 @@ impl CommandLineStep for Rustc { } fn make_run(run: RunConfig<'_>) { + let check_kind = match run.builder.kind { + Kind::Check => CheckKind::Check, + Kind::Fix => CheckKind::Fix, + kind => panic!("unexpected kind for `check::Rustc`: {kind:?}"), + }; + + let target = run.target; + let build_compiler = prepare_compiler_for_check(run.builder, target, Mode::Rustc); let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc::new(run.builder, run.target, crates)); + + run.builder.ensure(Rustc { check_kind, build_compiler, target, crates }); } /// Check the compiler. @@ -344,7 +369,7 @@ impl CommandLineStep for Rustc { Mode::Rustc, SourceType::InTree, target, - Kind::Check, + self.check_kind.to_kind(), ); rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); @@ -358,7 +383,7 @@ impl CommandLineStep for Rustc { } let _guard = builder.msg( - Kind::Check, + self.check_kind.to_kind(), format_args!("compiler artifacts{}", crate_description(&self.crates)), Mode::Rustc, self.build_compiler.build_compiler(), @@ -381,13 +406,11 @@ impl CommandLineStep for Rustc { } fn metadata(&self) -> Option { - let metadata = StepMetadata::check("rustc", self.target) + let mut metadata = StepMetadata::new("rustc", self.target, self.check_kind.to_kind()) .built_by(self.build_compiler.build_compiler()); - let metadata = if self.crates.is_empty() { - metadata - } else { - metadata.with_metadata(format!("({} crates)", self.crates.len())) - }; + if !self.crates.is_empty() { + metadata = metadata.with_metadata(format!("({} crates)", self.crates.len())); + } Some(metadata) } } diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 603ef65854cf6..ccb6efb8bd723 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -229,7 +229,7 @@ impl StepMetadata { Self::new(name, target, Kind::Run) } - fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { + pub fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { Self { name: name.to_string(), kind, target, built_by: None, stage: None, metadata: None } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index dddb70b3fd468..57f50d981d1c4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -3086,6 +3086,15 @@ mod snapshot { [run] rustc 0 -> miri 1 "); } + + #[test] + fn fix_compiler() { + let ctx = TestCtx::new(); + insta::assert_snapshot!(ctx.config("fix").path("compiler").render_steps(), @r" + [build] llvm + [fix] rustc 0 -> rustc 1 (74 crates) + "); + } } struct ExecutedSteps { diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 400d0715a4738..e4942a8ce669f 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -34,6 +34,7 @@ pub struct Finder { /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "aarch64-unknown-l4re-uclibc", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index f4a9b5704a434..8cddd822c806e 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -227,7 +227,8 @@ pub fn use_host_linker(target: TargetSelection) -> bool { || target.contains("fortanix") || target.contains("fuchsia") || target.contains("bpf") - || target.contains("switch")) + || target.contains("switch") + || target.contains("l4re")) } pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool { diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index ca5890840581c..bedfa65ac894d 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -84,6 +84,7 @@ - [avr-none](platform-support/avr-none.md) - [\*-espidf](platform-support/esp-idf.md) - [\*-unknown-fuchsia](platform-support/fuchsia.md) + - [\*-unknown-l4re](platform-support/l4re.md) - [\*-unknown-trusty](platform-support/trusty.md) - [\*-kmc-solid_\*](platform-support/kmc-solid.md) - [csky-unknown-linux-gnuabiv2\*](platform-support/csky-unknown-linux-gnuabiv2.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 81e843263487c..c8ae02b091034 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -273,6 +273,7 @@ target | std | host | notes [`aarch64-unknown-helenos`](platform-support/helenos.md) | ✓ | | ARM64 HelenOS [`aarch64-unknown-hermit`](platform-support/hermit.md) | ✓ | | ARM64 Hermit [`aarch64-unknown-illumos`](platform-support/illumos.md) | ✓ | ✓ | ARM64 illumos +[`aarch64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | ARM64 L4Re with uclibc `aarch64-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (ILP32 ABI) [`aarch64-unknown-linux-pauthtest`](platform-support/aarch64-unknown-linux-pauthtest.md) | ✓ | ✓ | ARM64 PAC ELF ABI [`aarch64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | ARM64 Managarm @@ -459,7 +460,7 @@ target | std | host | notes [`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit [`x86_64-unknown-helenos`](platform-support/helenos.md) | ✓ | | x86_64 (amd64) HelenOS [`x86_64-unknown-hurd-gnu`](platform-support/hurd.md) | ✓ | ✓ | 64-bit GNU/Hurd -`x86_64-unknown-l4re-uclibc` | ? | | +[`x86_64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | x86_64 L4Re with uclibc [`x86_64-unknown-linux-none`](platform-support/x86_64-unknown-linux-none.md) | * | | 64-bit Linux with no libc [`x86_64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | x86_64 Managarm [`x86_64-unknown-motor`](platform-support/motor.md) | ✓ | | x86_64 Motor OS diff --git a/src/doc/rustc/src/platform-support/l4re.md b/src/doc/rustc/src/platform-support/l4re.md new file mode 100644 index 0000000000000..56044319dc77a --- /dev/null +++ b/src/doc/rustc/src/platform-support/l4re.md @@ -0,0 +1,63 @@ +# `*-l4re-uclibc` + +**Tier: 3** + +[L4Re] is an open source, microkernel-based operating system and hypervisor. + +Target triplets available so far: + +- x86_64-unknown-l4re-uclibc +- aarch64-unknown-l4re-uclibc + +## Target maintainers + +- Marius Melzer ([@farao](https://github.com/farao)) + +## Requirements + +The L4Re targets are cross-compiled from a host environment, commonly Linux. +See [Getting Started] for options to set up L4Re. + +The L4Re sources can be found in the [Github Repos]. + +## Building an L4Re Rust Toolchain + +Configure one or several of the above L4Re targets and also add the host triple +in config.toml and build Rust as documented. Start off the toolchain by copying +`build/host/stage2/` to a self-chosen location. + +For each target, build an L4Re sysroot directory by running `make sysroot` in +the L4Re build directory. Copy the content of `sysroot/usr/lib/` into the +`self-contained` directory of the respective target in the Rust Toolchain +directory tree. + +Use rustup to install the L4Re Rust Toolchain locally: + +```sh +rustup toolchain link l4re +``` + +Now use the toolchain via a cargo (or directly a rustc) installed via `rustup`: + +```sh +cargo +l4re build --target +``` + +or + +```sh +rustc +l4re --target +``` + +## Run Rust Programs on L4Re + +You can run an L4Re application written in Rust just like any other externally +built (meaning not build with the L4Re build system) L4Re binary. A good option +is to build an L4Re image and add the application binary to the image and run it +via the ned script. The image can then be put on hardware or run on Qemu. + +See [l4re.org](https://l4re.org) for more information. + +[L4Re]: https://l4re.org +[Getting Started]: https://l4re.org/getting_started +[Github Repos]: https://github.com/L4Re diff --git a/src/doc/rustc/src/platform-support/netbsd.md b/src/doc/rustc/src/platform-support/netbsd.md index f7b57fff8a1f9..0d060f1de4cf5 100644 --- a/src/doc/rustc/src/platform-support/netbsd.md +++ b/src/doc/rustc/src/platform-support/netbsd.md @@ -24,10 +24,11 @@ are currently defined running NetBSD: | 3 | `sparc64-unknown-netbsd` | [Sun UltraSPARC systems](https://wiki.netbsd.org/ports/sparc64/) | All use the "native" `stdc++` library which goes along with the natively -supplied GNU C++ compiler for the given OS version. Many of the bootstraps -are built for NetBSD 9.x, although some exceptions exist (some -are built for NetBSD 8.x but also work on newer OS versions). -`x86_64-unknown-netbsd` is built for NetBSD 10.x to access a newer gcc. +supplied GNU C++ compiler for the given OS version. Most of the bootstraps +are built for NetBSD 9.x, although some exceptions exist (some are +built for newer NetBSD versions, due to target becoming usable first +with newer versions). `x86_64-unknown-netbsd` is built for NetBSD +10.x to access a newer gcc. ## Target Maintainers @@ -37,7 +38,7 @@ are built for NetBSD 8.x but also work on newer OS versions). Further contacts: -- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust188/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. +- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust197/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. - [NetBSD's pkgsrc lang/rust](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust) for the "proper" package in pkgsrc. - [NetBSD's pkgsrc lang/rust-bin](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust-bin) which re-uses the bootstrap kit as a binary distribution and therefore avoids the rather protracted native build time of rust itself diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index eb382f368905f..b19ed533d4953 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1295,7 +1295,7 @@ fn format_integer_type(it: rustc_abi::IntegerType) -> String { pub(super) fn target(sess: &rustc_session::Session) -> Target { // Build a set of which features are enabled on this target let globally_enabled_features: FxHashSet<&str> = - sess.unstable_target_features.iter().map(|name| name.as_str()).collect(); + sess.internal_target_features.iter().map(|name| name.as_str()).collect(); // Build a map of target feature stability by feature name use rustc_target::target_features::Stability; diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 5ede5327b625e..8b7935fb994a2 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -171,6 +171,9 @@ jobs: - name: build Priroda working-directory: priroda run: cargo build --locked + - name: clippy Priroda + working-directory: priroda + run: cargo clippy --all-targets --locked -- -D warnings - name: test Priroda working-directory: priroda run: | diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock index 7d46f75d2fab7..48ba54ef8eebf 100644 --- a/src/tools/miri/priroda/Cargo.lock +++ b/src/tools/miri/priroda/Cargo.lock @@ -351,6 +351,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -891,6 +902,7 @@ dependencies = [ name = "priroda" version = "0.1.0" dependencies = [ + "emmy_dap_types", "miri", "regex", "ui_test", diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml index 88e65653449c8..ff299bae2acbf 100644 --- a/src/tools/miri/priroda/Cargo.toml +++ b/src/tools/miri/priroda/Cargo.toml @@ -18,6 +18,7 @@ name = "cli" harness = false [dependencies] +emmy_dap_types = "0.2.0" miri = { path = ".." } [package.metadata.rust-analyzer] diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index a8c25bf279868..6283bc28bb7c2 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -38,6 +38,18 @@ from `miri/priroda/`: cargo run -- ../tests/pass/empty_main.rs ``` +## DAP Prototype + +Priroda's `--dap` mode speaks a bounded Debug Adapter Protocol prototype over +stdio. It currently supports the startup handshake, stops at the first +user-relevant source location after `configurationDone`, reports one current +stack frame, exposes one flat Locals scope, and maps `list_locals()` into DAP +variables with no child expansion. + +The `next` and `stepIn` requests are wired to Priroda's existing source-line +step so VS Code can drive one visible step. They are not true DAP step-over or +step-in semantics yet. + ## Test Priroda's CLI tests also need `MIRI_SYSROOT`. Run them from `miri/priroda/`: diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs new file mode 100644 index 0000000000000..b2b7c8779709a --- /dev/null +++ b/src/tools/miri/priroda/src/debugger.rs @@ -0,0 +1,856 @@ +use std::collections::{HashMap, HashSet}; +use std::ops::Range; +use std::path::PathBuf; + +use miri::Immediate::Uninit; +use miri::*; +use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; +use rustc_hir::def::CtorKind; +use rustc_middle::mir::interpret::AllocId; +use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; +use rustc_middle::ty::{self, TyKind}; +use rustc_span::source_map::SourceMap; +use rustc_span::{Span, Symbol}; + +/// Structured source information for frontends. +pub(super) struct SourceLocation { + // Keep the span so each frontend can resolve paths with its own rendering + // rules instead of forcing every caller to use one path representation. + pub(super) span: Span, + pub(super) line: usize, + pub(super) column: usize, +} + +impl SourceLocation { + fn local_path(&self, source_map: &SourceMap) -> Option { + let loc = source_map.lookup_char_pos(self.span.lo()); + loc.file.name.clone().into_local_path().map(normalize_path) + } +} + +/// Source-level breakpoints indexed by normalized path, then line. +type BreakpointTable = HashMap>; + +/// Owns one interpreter session and its debugger state. +/// +/// Frontend rendering should eventually live outside this type. +pub(super) struct PrirodaContext<'tcx> { + pub(super) ecx: MiriInterpCx<'tcx>, + breakpoints: BreakpointTable, + pub(super) current_location: Option, + last_location: Option, +} + +pub(super) enum StorageProj { + Field(usize), + Deref, + Downcast(Symbol), + Variant(usize), + Unsupported(String), +} + +impl StorageProj { + pub(super) fn render(&self) -> String { + match self { + StorageProj::Field(field_idx) => format!(".{field_idx}"), + StorageProj::Deref => ".*".to_string(), + StorageProj::Downcast(name) => format!(" as {name}"), + StorageProj::Variant(variant_idx) => format!(" as variant#{variant_idx}"), + StorageProj::Unsupported(unsop) => format!("."), + } + } +} + +pub(super) struct LocalDesc { + /// Source variable name from `VarDebugInfo`, if this row has one. + pub(super) source_name: Option, + + /// Source-side projection from `VarDebugInfo::composite`, e.g. `.field` in source fragment `x.field`. + pub(super) source_projection: Option>, + + /// MIR storage local that backs this description, if any. + pub(super) local: Option, + + /// rendered/debug MIR place projection for now + pub(super) storage_projection: Vec, + + /// Display-rendered type for this description. + pub(super) ty: String, + + /// Run-time state for now; will be expanded later + pub(super) value: String, +} + +impl LocalDesc { + pub(super) fn source_projection_str(&self) -> String { + self.source_projection + .as_ref() + .map(|fields| fields.iter().map(|field| field.to_string()).collect::()) + .unwrap_or_default() + } + + pub(super) fn storage_projection_str(&self) -> String { + self.storage_projection.iter().map(StorageProj::render).collect::() + } +} + +/// Controls when execution returns to the frontend. +enum ResumeMode { + /// Stop at the next visible MIR instruction. + MirInstruction, + /// Stop at the next source line. + /// + /// `None` means the current interpreter position has no source location, so + /// the first mapped source location is good enough to report. + SourceLine(Option<(PathBuf, usize)>), + /// Stop at the first mapped source location from a user-relevant frame. + /// + /// This is the DAP entry-stop primitive: it skips over interpreter startup + /// and Miri-internal frames until there is a location an editor can show. + FirstUserSourceLocation, + /// Continue until reaching a breakpoint. + Continue, +} + +/// Describes whether the current MIR instruction should be shown to the user. +enum InstructionVisibility { + NoInstruction, + Hidden, + Visible, +} + +/// Describes why execution stopped and returned control to the frontend. +pub(super) enum StepResult { + Step, + Breakpoint, +} + +fn normalize_path(path: PathBuf) -> PathBuf { + path.canonicalize().unwrap_or(path) +} + +impl<'tcx> PrirodaContext<'tcx> { + pub(super) fn new(ecx: MiriInterpCx<'tcx>) -> Self { + Self { ecx, breakpoints: HashMap::new(), current_location: None, last_location: None } + } + + pub(super) fn local_path(&self, location: &SourceLocation) -> Option { + let source_map = self.ecx.tcx.sess.source_map(); + location.local_path(source_map) + } + + fn current_source_position(&self) -> Option<(PathBuf, usize)> { + let location = self.current_location.as_ref()?; + Some((self.local_path(location)?, location.line)) + } + + // Used to treat `continue` like a source-level step for breakpoint checks: + // several MIR locations can point at one source line, but they should only + // report that source breakpoint once. + fn last_source_position(&self) -> Option<(PathBuf, usize)> { + let location = self.last_location.as_ref()?; + Some((self.local_path(location)?, location.line)) + } + + /// Step to the next visible MIR instruction. + fn stepi(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::MirInstruction) + } + /// Step until the displayed source file or line changes. + pub(super) fn step(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::SourceLine(self.current_source_position())) + } + + /// Run until the initial editor-visible stop point. + pub(super) fn stop_at_first_user_location(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::FirstUserSourceLocation) + } + + /// Return the active frame name while DAP still reports only one frame. + pub(super) fn current_frame_name(&self) -> Option { + let frame = self.ecx.active_thread_stack().last()?; + Some(frame.instance().to_string()) + } + + /// Continue execution until reaching a breakpoint or propagating termination. + pub(super) fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::Continue) + } + + pub(super) fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { + // FIXME: validate breakpoints here so every frontend gets the same behavior. + // Reject empty paths, missing files, directories, and line 0. Decide whether + // out-of-range lines should be rejected or kept as pending breakpoints. + // Report duplicate registrations separately. + + let path = normalize_path(path); + match self.breakpoints.entry(path.clone()).or_default().insert(line) { + true => BreakpointSetResult::Added(path, line), + false => BreakpointSetResult::Duplicate, + } + } + + /// Advance execution until the selected resume mode reaches a stopping point. + fn resume(&mut self, mode: ResumeMode) -> InterpResult<'tcx, StepResult> { + loop { + self.advance()?; + + // An explicit breakpoint should stop execution even when the current + // MIR instruction would normally be hidden during manual stepping. + if self.is_at_breakpoint() { + return interp_ok(StepResult::Breakpoint); + } + + match mode { + ResumeMode::MirInstruction + if matches!( + self.current_instruction_visibility(), + InstructionVisibility::Visible + ) => + { + return interp_ok(StepResult::Step); + } + + ResumeMode::SourceLine(ref prev_location) => { + match (prev_location, &self.current_location) { + // We started from an unmapped location; stop once there + // is a source position the frontend can display. + (None, Some(_)) => return interp_ok(StepResult::Step), + + (Some((prev_path, prev_line)), Some(current_location)) => { + if let Some(current_path) = self.local_path(current_location) { + // A source step stops when the displayed source + // position changes to a different file or line. + if *prev_path != current_path || *prev_line != current_location.line + { + return interp_ok(StepResult::Step); + } + } + } + + _ => {} + } + } + + ResumeMode::FirstUserSourceLocation + if self.current_location.is_some() && self.has_user_relevant_frame() => + { + return interp_ok(StepResult::Step); + } + + ResumeMode::MirInstruction + | ResumeMode::FirstUserSourceLocation + | ResumeMode::Continue => {} + } + } + } + + fn has_user_relevant_frame(&self) -> bool { + // Walk the whole stack, not just the top frame: during interpreter + // startup the user's `main` can sit under Miri-internal frames that + // have no source span, so checking only `last()` would miss it. + self.ecx.active_thread_stack().iter().any(|frame| frame.extra.user_relevance == u8::MAX) + } + + /// Advance Miri by one interpreter-loop transition. + fn advance(&mut self) -> InterpResult<'tcx> { + // FIXME: use a Miri-owned scheduler-aware debugger step API before + // claiming support for multi-threaded interpreted programs. + + // State inspection should happen only after a successful step. + self.ecx.step_current_thread()?; + self.last_location = self.current_location.take(); + self.current_location = self.resolve_current_location(); + interp_ok(()) + } + + fn current_instruction_visibility(&self) -> InstructionVisibility { + // If the active thread has no stack frame, there is no MIR instruction to show. + let Some(frame) = self.ecx.active_thread_stack().last() else { + return InstructionVisibility::NoInstruction; + }; + + // `Right(span)` means the frame has source context but no precise MIR program-counter location. + let Either::Left(location) = frame.current_loc() else { + return InstructionVisibility::NoInstruction; + }; + + let basic_block = &frame.body().basic_blocks[location.block]; + + // `statement_index == statements.len()` points at the block terminator. + // Terminators affect control flow, so they are always visible. + let Some(statement) = basic_block.statements.get(location.statement_index) else { + return InstructionVisibility::Visible; + }; + + // Hide bookkeeping-only MIR statements during manual stepping. + match statement.kind { + mir::StatementKind::StorageLive(_) + | mir::StatementKind::StorageDead(_) + | mir::StatementKind::Nop => InstructionVisibility::Hidden, + _ => InstructionVisibility::Visible, + } + } + + fn is_at_breakpoint(&self) -> bool { + let Some(bp) = self.current_breakpoint() else { + return false; + }; + + // If the previous interpreter step had the same source position, this + // is another MIR location for the breakpoint we just reported. + self.last_source_position().as_ref() != Some(&bp) + } + + fn current_breakpoint(&self) -> Option<(PathBuf, usize)> { + let (path, line) = self.current_source_position()?; + let lines = self.breakpoints.get(&path)?; + if lines.contains(&line) { Some((path, line)) } else { None } + } + + fn resolve_current_location(&self) -> Option { + let span = self.ecx.machine.current_user_relevant_span(); + if span.is_dummy() { + return None; + } + + let span = span.source_callsite(); + let source_map = self.ecx.tcx.sess.source_map(); + let loc = source_map.lookup_char_pos(span.lo()); + + Some(SourceLocation { span, line: loc.line, column: loc.col_display + 1 }) + } + + pub(super) fn run_command( + &mut self, + command: DebuggerCommand, + ) -> InterpResult<'tcx, CommandResult> { + match command { + DebuggerCommand::StepI => self.stepi().map(CommandResult::ExecutionStopped), + DebuggerCommand::Step => self.step().map(CommandResult::ExecutionStopped), + DebuggerCommand::Continue => + self.continue_execution().map(CommandResult::ExecutionStopped), + DebuggerCommand::Breakpoint(path, line) => + interp_ok(CommandResult::BreakpointResult(self.set_breakpoint(path, line))), + DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())), + DebuggerCommand::Print(local) => + interp_ok(CommandResult::SingleLocal(self.get_local(local))), + DebuggerCommand::Follow(alloc_id, offset) => + self.follow_alloc(alloc_id, offset).map(CommandResult::Memory), + DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), + } + } + + fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> { + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + if offset > alloc.len() { + return Err(miri::err_unsup_format!( + "allocation offset {offset} is outside {alloc_id}" + )) + .into(); + } + + let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?; + interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}")) + } + + fn get_local(&self, local: usize) -> Option { + let frame = self.ecx.active_thread_stack().last()?; + + self.make_mir_local_desc(frame, local) + } + + /// Returns structured descriptions for locals in the innermost stack frame. + /// + /// Starts from all MIR locals, then enriches them with source names from + /// `var_debug_info` when a debug entry maps directly to a whole local. + pub(super) fn list_locals(&self) -> Vec { + let Some(frame) = self.ecx.active_thread_stack().last() else { + return Vec::new(); + }; + + self.build_local_descs(frame) + } + + /// Renders the current byte range of an indirect MIR value. + /// + /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`, + /// and complete pointer-sized provenance as pointer markers. + fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { + let size = match self.ecx.size_and_align_of_val(mplace)? { + Some((size, _)) => size, + None => { + // Extern types cannot currently be executed as by-value locals, + // so this path cannot yet be covered by a Priroda UI fixture. + // FIXME: Add coverage once Priroda supports printing dereferenced places. + return interp_ok("".to_string()); + } + }; + + let size = size.bytes_usize(); + if size == 0 { + return interp_ok("[]".to_string()); + } + + let (alloc_id, offset, _) = + self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; + let offset = offset.bytes_usize(); + let range = offset..offset.strict_add(size); + + self.render_alloc_bytes(alloc_id, range) + } + + /// Render a raw allocation range without requiring a typed memory place. + /// + /// This is also used by the future-facing `follow` command, where we have a + /// pointer target but do not yet know the target's type or size. + fn render_alloc_bytes( + &self, + alloc_id: AllocId, + range: Range, + ) -> InterpResult<'tcx, String> { + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + + let mut rendered = Vec::with_capacity(range.len()); + + let ptr_size = self.ecx.tcx.data_layout.pointer_size(); + + for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { + let chunk_range = chunk.range(); + let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); + + if chunk.is_init() { + let ptr_size = ptr_size.bytes_usize(); + let mut cursor = chunk_range.start; + + while cursor < chunk_range.end { + // Full pointer provenance is rendered as a pointer marker. Bytewise + // provenance fragments are intentionally left as raw bytes here: they do + // not represent a complete pointer-sized value. + if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor)) + && cursor + ptr_size <= chunk_range.end + { + let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( + cursor..cursor + ptr_size, + ); + let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes) + .map_err(|err| { + miri::err_unsup_format!("invalid pointer representation: {err}") + })?; + + let offset = Size::from_bytes(offset); + rendered.push(format!("{:?}", Pointer::new(Some(prov), offset))); + + cursor += ptr_size; + } else { + let byte = alloc + .inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0]; + + rendered.push(format!("{byte:02x}")); + cursor += 1; + } + } + } else { + rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); + } + } + + interp_ok(format!("[{}]", rendered.join(" "))) + } + + /// Render an evaluated operand using Rust-source-shaped containers with raw leaves. + /// + /// The operand is produced from live interpreter state, usually via `local_to_op` + /// for a whole MIR local or `eval_place_to_op` for a projected debug-info place. + /// + /// This intentionally does not call user `Debug` / `Display`, and it does not + /// try to make every scalar leaf pretty yet. Unsupported cases and leaf values + /// fall back to `render_op`, preserving the old raw byte/provenance renderer. + /// + /// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers, + /// chars, raw pointers/references) once the source-shaped container output is + /// stable enough to stop depending on byte dumps for every field. + /// + /// FIXME: decide how much dereferencing belongs in this renderer. References + /// currently stay as raw pointer leaves; following them may belong in the + /// existing `follow` command instead of automatic local rendering. + fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String { + self.render_source_shaped_op_inner(op, 0) + } + + /// Recursive worker for `render_source_shaped_op`. + /// + /// The depth limit keeps cyclic/reference-heavy values from making debugger + /// output explode once more container kinds are added. At the limit, the raw + /// renderer remains the ground truth. + /// + /// FIXME: replace this fixed recursion limit with a value-size/output-budget + /// policy so large acyclic values and deeply nested values degrade more + /// predictably. + fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String { + const MAX_SOURCE_SHAPE_DEPTH: usize = 8; + + if depth >= MAX_SOURCE_SHAPE_DEPTH { + return self.render_op(op); + } + + match op.layout.ty.kind() { + // Empty enums have no active variant to format. Unions do not record + // which field is currently active, so choosing one would be misleading. + // + // FIXME: support unions only with an explicit user-selected field or + // another source of active-field information. Guessing from layout + // bytes would make debugger output look more certain than it is. + ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op), + + ty::Adt(def, _) => { + // Enums need their runtime discriminant and a downcasted layout + // view before fields can be projected. Structs use their sole + // variant directly. Keep the display name tied to the same choice. + let (variant_idx, down, name) = if def.is_enum() { + let variant_idx = match self.ecx.read_discriminant(&op).discard_err() { + Some(variant_idx) => variant_idx, + // FIXME: expose this as an explicit render error when + // Priroda grows structured value states. Falling back to + // bytes keeps today's UI usable but hides why the enum + // could not be source-shaped. + None => return self.render_op(op), + }; + let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() { + Some(down) => down, + // FIXME: distinguish invalid/uninitialized discriminants + // from projection bugs in the rendered output once locals + // can carry structured diagnostics. + None => return self.render_op(op), + }; + let variant_def = &def.variants()[variant_idx]; + ( + variant_idx, + down, + format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name), + ) + } else { + let variant_idx = FIRST_VARIANT; + let variant_def = &def.variants()[variant_idx]; + (variant_idx, op.clone(), variant_def.name.to_string()) + }; + + let variant_def = &def.variants()[variant_idx]; + + let mut fields = Vec::with_capacity(variant_def.fields.len()); + for i in 0..variant_def.fields.len() { + let field_idx = FieldIdx::from_usize(i); + // `project_field` avoids manual offset math and works for both + // immediate and memory-backed operands through `Projectable`. + let field_op = match self.ecx.project_field(&down, field_idx).discard_err() { + Some(field_op) => field_op, + // FIXME: preserve the successfully rendered fields and + // mark only this field as unavailable once the value model + // can represent partial render failures. + None => return self.render_op(op), + }; + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); + } + + // Match Rust constructor spelling: + // - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit` + // - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()` + // - `None`: braced structs/variants, including the empty `{}` case + match variant_def.ctor_kind() { + Some(CtorKind::Const) => name, + Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")), + None if fields.is_empty() => format!("{name} {{}}"), + None => { + let fields = variant_def + .fields + .iter() + .zip(fields) + .map(|(field_def, value)| format!("{}: {value}", field_def.name)) + .collect::>() + .join(", "); + format!("{name} {{ {fields} }}") + } + } + } + + ty::Tuple(args) => { + let mut fields = Vec::with_capacity(args.len()); + for i in 0..args.len() { + // Tuples have no field names in source, so preserve their + // source field order and render children positionally. + let field_op = + match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() { + Some(field_op) => field_op, + // FIXME: render tuple fields independently so one + // projection failure does not throw away the whole + // source-shaped tuple. + None => return self.render_op(op), + }; + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); + } + + if fields.len() == 1 { + format!("({},)", fields[0]) + } else { + format!("({})", fields.join(", ")) + } + } + + ty::Array(_, _) | ty::Slice(_) => { + // `project_array_fields` uses the dynamic length for slices. That + // avoids the classic mistake of treating slice layout as a fixed + // zero-length array. + let mut iter = match self.ecx.project_array_fields(&op).discard_err() { + Some(iter) => iter, + // FIXME: when slice metadata is invalid, show that as a slice + // length problem instead of silently falling back to raw bytes. + None => return self.render_op(op), + }; + + let mut fields = Vec::new(); + // FIXME: add an output budget/truncation policy before rendering + // very large arrays or slices in full. + loop { + match iter.next(&self.ecx).discard_err() { + Some(Some((_idx, field_op))) => + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)), + Some(None) => break, + // FIXME: keep already-rendered elements and mark the + // failed index once partial render errors are supported. + None => return self.render_op(op), + } + } + + format!("[{}]", fields.join(", ")) + } + + // FIXME: consider source-shaped special cases for strings, closures, + // generators/coroutines, trait objects, and SIMD/vector-like types. + // Until then these stay on the raw renderer path. + _ => self.render_op(op), + } + } + + /// Render an evaluated operand using the same raw representation for + /// whole locals and projected MIR places. + fn render_op(&self, op: OpTy<'tcx>) -> String { + match op.as_mplace_or_imm() { + Either::Right(imm) => format!("{imm}"), + + Either::Left(mplace) => + match self.render_mplace_bytes(&mplace).report_err() { + Ok(bytes) => bytes, + Err(err) => format!("", err.to_string()), + }, + } + } + + /// Render the source-side path from composite debug info, such as `.field`. + fn render_source_projection( + fragment: Option<&VarDebugInfoFragment<'tcx>>, + ) -> Option> { + let VarDebugInfoFragment { ty, projection } = fragment?; + + // Walk the source-side projection from the original + // composite variable type. Each `Field` element stores the + // resulting field type, so resolve the field name from the + // current base type before advancing to `field_ty`. + let mut projection_ty = ty; + + Some( + projection + .iter() + .map(|elem| { + match elem { + ProjectionElem::Field(field_idx, field_ty) => { + let rendered = match projection_ty.kind() { + TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { + let variant = adt_def.non_enum_variant(); + let field = &variant.fields[*field_idx]; + Symbol::intern(&format!(".{}", field.name)) + } + + TyKind::Tuple(_) => + Symbol::intern(&format!(".{}", field_idx.index())), + + _ => Symbol::intern("."), + }; + + projection_ty = field_ty; + + rendered + } + // `VarDebugInfoFragment::projection` is expected to be + // field-only. If that ever changes, keep the unexpected + // segment visible instead of silently rendering a + // misleading source path. + other => Symbol::intern(&format!(".")), + } + }) + .collect(), + ) + } + + /// Render the MIR storage-side path that backs a debug-info local. + fn render_storage_projection(projection: &[mir::PlaceElem<'tcx>]) -> Vec { + projection + .iter() + .map(|projection_elem| { + match projection_elem { + ProjectionElem::Field(field_idx, _) => StorageProj::Field(field_idx.index()), + ProjectionElem::Deref => StorageProj::Deref, + ProjectionElem::Downcast(Some(name), _) => StorageProj::Downcast(*name), + ProjectionElem::Downcast(None, variant_idx) => + StorageProj::Variant(variant_idx.index()), + other => StorageProj::Unsupported(format!("{other:?}")), + } + }) + .collect() + } + + /// Builds the baseline debugger row for one MIR local without scanning debug info. + fn make_mir_local_desc( + &self, + frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, + local: usize, + ) -> Option { + let local = mir::Local::from_usize(local); + let local_decl = frame.body().local_decls.get(local)?; + + // Create LocalDesc for MIR local before processing debug info. + // Debug-info enrichment is layered on by build_local_descs. + let mut local_desc = LocalDesc { + source_name: None, + source_projection: None, + local: Some(local), + storage_projection: Vec::new(), + ty: local_decl.ty.to_string(), + value: "".to_string(), + }; + + match &frame.locals[local].as_mplace_or_imm() { + None => { + local_desc.value = "".to_string(); + } + Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), + + Some(Either::Left(_) | Either::Right(_)) => { + let op = self + .ecx + .local_to_op(local, None) + .expect("this error can only occur in CTFE on generic code"); + local_desc.value = self.render_source_shaped_op(op); + } + }; + + Some(local_desc) + } + + fn build_local_descs( + &self, + frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, + ) -> Vec { + let local_decls = &frame.body().local_decls; + + let mut local_descs: Vec = Vec::with_capacity(local_decls.len()); + + // Start with one baseline row for every MIR local, then layer debug info on top. + for (local_idx, _) in local_decls.iter_enumerated() { + local_descs.push(self.make_mir_local_desc(frame, local_idx.index()).unwrap()); + } + + // FIXME: Finish classifying `var_debug_info` by keeping the source path + // and MIR storage path separate: + // + // - source side: `var_debug_info.name` plus + // `var_debug_info.composite.projection` + // - storage side: `VarDebugInfoContents::Place(place).local` plus + // `place.projection` + // + // Already handled by the `place.as_local()` path below: + // - whole source variable -> whole MIR local: + // `composite = None`, `Place(_N)` with empty projection. + // - source fragment -> whole MIR local: + // `composite = Some(source_proj)`, `Place(_N)` with empty projection. + // + // Remaining cases to represent or explicitly defer: + // - whole source variable -> projected MIR storage: + // `composite = None`, `Place(_N.proj)`. + // - source fragment -> projected MIR storage: + // `composite = Some(source_proj)`, `Place(_N.storage_proj)`. + // - source variable/fragment -> constant: + // `Const(...)`, with no MIR local id. + // - optimized-out/debug-only/unsupported shapes: + // explicit deferred state, not silent discard. + // + // Final output should be produced by walking `Vec`, + // then append explicit deferred/debug-info-only rows where needed. + // Related: SROA can split a source local like `_slice: ExtraSlice` into + // field locals whose debug paths should be printed as `_slice._slice` + // and `_slice._extra`, not as two separate locals both named `_slice`. + + // Whole-place debug entries enrich the direct storage-local description. + // Projected places are evaluated from their original MIR Place and use + // the same raw renderer as ordinary locals. + for var_debug_info in &frame.body().var_debug_info { + if let VarDebugInfoContents::Place(place) = &var_debug_info.value { + if let Some(local_idx) = place.as_local() + && local_descs[local_idx.index()].source_name.is_none() + { + let local_idx = local_idx.index(); + local_descs[local_idx].source_projection = + Self::render_source_projection(var_debug_info.composite.as_deref()); + local_descs[local_idx].source_name = Some(var_debug_info.name); + } else if !place.projection.is_empty() { + let storage_projection = Self::render_storage_projection(place.projection); + let source_projection = + Self::render_source_projection(var_debug_info.composite.as_deref()); + let value = self + .ecx + .eval_place_to_op(*place, None) + .map(|op| self.render_source_shaped_op(op)) + .unwrap_or_else(|err| format!("", err.to_string())); + + local_descs.push(LocalDesc { + source_name: Some(var_debug_info.name), + source_projection, + local: Some(place.local), + storage_projection, + ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(), + value, + }); + } + } + } + + local_descs + } +} + +pub(super) enum DebuggerCommand { + StepI, + Step, + TerminateSession, + Continue, + Breakpoint(PathBuf, usize), + ListLocals, + Print(usize), + Follow(AllocId, usize), +} + +pub(super) enum BreakpointSetResult { + Added(PathBuf, usize), + Duplicate, + // FIXME: add pending breakpoint support later if needed. +} + +pub(super) enum CommandResult { + ExecutionStopped(StepResult), + BreakpointResult(BreakpointSetResult), + Locals(Vec), + SingleLocal(Option), + Memory(String), + // FIXME: distinguish terminating the debugger session from disconnecting a + // frontend and terminating the interpreted program once multiple frontends exist. + TerminateSession, +} diff --git a/src/tools/miri/priroda/src/frontend/cli.rs b/src/tools/miri/priroda/src/frontend/cli.rs new file mode 100644 index 0000000000000..e4e92351a92f6 --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/cli.rs @@ -0,0 +1,176 @@ +use std::io::{self, Write}; +use std::num::NonZeroU64; +use std::path::PathBuf; + +use miri::{InterpResult, interp_ok}; +use rustc_middle::mir::interpret::AllocId; + +use crate::debugger::{ + BreakpointSetResult, CommandResult, DebuggerCommand, PrirodaContext, StepResult, +}; + +pub(crate) struct Cli; + +impl Cli { + pub(crate) fn run_cli_loop<'tcx>( + &self, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx> { + loop { + print!("(priroda) "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + let bytes_read = io::stdin().read_line(&mut input).unwrap(); + + if bytes_read == 0 { + println!("stdin closed, stopping"); + return interp_ok(()); + } + + if let Some(command) = self.parse_command(&input) { + let command_res = session.run_command(command)?; + if !Self::print_command_result(command_res, session)? { + return interp_ok(()); + }; + } else { + println!("no command"); + } + + io::stdout().flush().unwrap(); + } + } + + fn print_command_result<'tcx>( + command_res: CommandResult, + session: &PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, bool> { + match command_res { + CommandResult::ExecutionStopped(result) => { + if matches!(result, StepResult::Breakpoint) { + println!("Hit breakpoint"); + } + Self::print_location(session); + } + CommandResult::BreakpointResult(res) => + match res { + BreakpointSetResult::Added(path, line) => { + println!("breakpoint added: {}:{}", path.display(), line) + } + + BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), + }, + CommandResult::Locals(locals_desc) => + if locals_desc.is_empty() { + println!("no locals"); + } else { + for local_desc in &locals_desc { + let source_projection = local_desc.source_projection_str(); + + let name = local_desc + .source_name + .map_or_else(|| "".to_string(), |name| name.to_string()); + + let display_name = format!("{name}{source_projection}"); + + let local_id = local_desc.local.map_or_else( + || "".to_string(), + |local_idx| format!("_{}", local_idx.index()), + ); + + let display_local_id = + format!("{}{}", local_id, local_desc.storage_projection_str()); + println!( + "Name: {}, Id: {}, Ty: {}, Value: {}", + display_name, display_local_id, local_desc.ty, local_desc.value + ); + } + }, + CommandResult::SingleLocal(local_desc) => + match local_desc { + Some(local_desc) => { + println!( + "Id: _{}, Ty: {}, Value: {}", + local_desc.local.unwrap().index(), + local_desc.ty, + local_desc.value + ); + } + None => println!("no local for this id"), + }, + CommandResult::Memory(memory) => println!("{memory}"), + CommandResult::TerminateSession => { + println!("quitting"); + return interp_ok(false); + } + } + interp_ok(true) + } + + fn parse_command(&self, input: &str) -> Option { + // TODO: look at the Spanned crate for how to easily produce errors in + // rustc's style while manually parsing text input. + // FIXME: we need to distinguish malformed input from the unknown commands by returning useful + // command error that describes if it malformed or non exist command + let input = input.trim(); + let mut parts = input.splitn(2, char::is_whitespace); + let command = parts.next().unwrap_or(""); + let args = parts.next().unwrap_or("").trim(); + + match command { + // FIXME: empty line should repats last command user typed not exeute specific command. + "" | "si" | "stepi" => Some(DebuggerCommand::StepI), + "s" | "step" => Some(DebuggerCommand::Step), + "q" | "quit" => Some(DebuggerCommand::TerminateSession), + "c" | "continue" => Some(DebuggerCommand::Continue), + "b" | "break" => self.parse_breakpoint(args), + "l" | "locals" => Some(DebuggerCommand::ListLocals), + "p" | "print" => self.parse_print_local(args), + "f" | "follow" => self.parse_follow(args), + _ => None, + } + } + + fn print_location<'tcx>(session: &PrirodaContext<'tcx>) { + match &session.current_location { + Some(location) => + if let Some(path) = session.local_path(location) { + println!("{}:{}", path.display(), location.line); + } else { + let source_map = session.ecx.tcx.sess.source_map(); + println!("{}", source_map.span_to_diagnostic_string(location.span)); + }, + None => println!("no-location"), + } + io::stdout().flush().unwrap(); + } + + fn parse_breakpoint(&self, input: &str) -> Option { + // FIXME: return a typed CommandError so malformed breakpoint input is + // distinguishable from an unknown command. Semantic validation belongs + // in PrirodaContext::set_breakpoint so non-CLI frontends cannot bypass it. + let (path, line) = input.rsplit_once(':')?; + let line = line.parse().ok()?; + + Some(DebuggerCommand::Breakpoint(PathBuf::from(path), line)) + } + + fn parse_print_local(&self, input: &str) -> Option { + let local = input.parse().ok()?; + Some(DebuggerCommand::Print(local)) + } + + fn parse_follow(&self, input: &str) -> Option { + let mut parts = input.split_whitespace(); + let alloc_id = parts.next()?; + let offset = parts.next()?; + if parts.next().is_some() { + return None; + } + + let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?; + let alloc_id = AllocId(NonZeroU64::new(alloc_id)?); + let offset = offset.parse().ok()?; + Some(DebuggerCommand::Follow(alloc_id, offset)) + } +} diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs new file mode 100644 index 0000000000000..6e48510cadc5c --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -0,0 +1,708 @@ +use std::io::{self, BufReader, BufWriter}; + +use emmy_dap_types::errors::ServerError; +use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; +use emmy_dap_types::prelude::requests::SetBreakpointsArguments; +use emmy_dap_types::prelude::responses::{ + ContinueResponse, ScopesResponse, SetBreakpointsResponse, StackTraceResponse, ThreadsResponse, + VariablesResponse, +}; +use emmy_dap_types::prelude::types::{ + Breakpoint as DapBreakpoint, Capabilities, Scope, ScopePresentationhint, Source, StackFrame, + StoppedEventReason, Thread, Variable, +}; +use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; +use miri::{InterpErrorInfo, InterpErrorKind, InterpResult, TerminationInfo, bug, interp_ok}; + +use crate::debugger::{LocalDesc, PrirodaContext, StepResult}; + +// Priroda still exposes one interpreted thread and one selected frame to DAP. +// Keep the ids stable so editor follow-up requests can address the stopped state. +const THREAD_ID: i64 = 1; +const STACK_FRAME_ID: i64 = 1; +const LOCALS_VARIABLES_REFERENCE: i64 = 1; + +enum HandlerResponse { + Success(ResponseBody), + Error(String), +} + +struct HandlerSuccess { + response: HandlerResponse, + state: Option, + events: Vec, + outcome: HandlerOutcome, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum HandlerOutcome { + Continue, + Exit, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum DapState { + Fresh, + Initialized, + Launched, + Stopped, + Terminated, +} + +enum ExecutionOutcome { + Stopped(StepResult), + Terminated { code: i32 }, + Failed(String), +} + +/// Debug Adapter Protocol frontend. +pub(crate) struct Dap; + +impl Dap { + /// Serve DAP requests on stdin/stdout. + pub(crate) fn run_dap_loop<'tcx>( + &self, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx> { + if let Err(err) = DapSession::stdio().run_requests(session) { + eprintln!("priroda dap error: {err:?}"); + } + + interp_ok(()) + } +} + +type DapServer = Server, io::StdoutLock<'static>>; + +/// Owns the DAP stdio transport and dispatches requests into Priroda handlers. +struct DapSession { + server: DapServer, + state: DapState, +} + +impl DapSession { + fn stdio() -> Self { + Self { + server: Server::new( + BufReader::new(io::stdin().lock()), + BufWriter::new(io::stdout().lock()), + ), + state: DapState::Fresh, + } + } + + fn run_requests<'tcx>( + &mut self, + session: &mut PrirodaContext<'tcx>, + ) -> Result<(), ServerError> { + loop { + let request = match self.server.poll_request() { + Ok(Some(request)) => request, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }; + + match self.dispatch_request(&request, session) { + Ok(s) => { + let response = match s.response { + HandlerResponse::Success(body) => request.success(body), + HandlerResponse::Error(message) => request.error(&message), + }; + self.server.respond(response)?; + if let Some(st) = s.state { + self.state = st; + } + for ev in s.events { + self.server.send_event(ev)?; + } + if s.outcome == HandlerOutcome::Exit { + return Ok(()); + } + } + Err(msg) => { + self.server.respond(request.error(msg))?; + } + } + } + } + + fn dispatch_request<'tcx>( + &self, + request: &Request, + session: &mut PrirodaContext<'tcx>, + ) -> Result { + if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { + return Err("initialize must be sent first"); + } + + match &request.command { + Command::Initialize(_) => self.handle_initialize(), + Command::Launch(_) => self.handle_launch(), + Command::ConfigurationDone => self.handle_configuration_done(session), + Command::Threads => self.handle_threads(), + Command::StackTrace(args) => self.handle_stack_trace(args.thread_id, session), + Command::Scopes(args) => self.handle_scopes(args.frame_id, session), + Command::Variables(args) => self.handle_variables(args.variables_reference, session), + Command::Continue(args) => self.handle_continue(args.thread_id, session), + Command::SetBreakpoints(args) => self.handle_set_breakpoints(args, session), + Command::Next(args) => self.handle_step(ResponseBody::Next, args.thread_id, session), + Command::StepIn(args) => + self.handle_step(ResponseBody::StepIn, args.thread_id, session), + Command::Disconnect(_) => self.handle_disconnect(), + Command::Attach(_) + | Command::BreakpointLocations(_) + | Command::Cancel(_) + | Command::Completions(_) + | Command::DataBreakpointInfo(_) + | Command::Disassemble(_) + | Command::Evaluate(_) + | Command::ExceptionInfo(_) + | Command::Goto(_) + | Command::GotoTargets(_) + | Command::LoadedSources + | Command::Modules(_) + | Command::Pause(_) + | Command::ReadMemory(_) + | Command::Restart(_) + | Command::RestartFrame(_) + | Command::ReverseContinue(_) + | Command::SetDataBreakpoints(_) + | Command::SetExceptionBreakpoints(_) + | Command::SetExpression(_) + | Command::SetFunctionBreakpoints(_) + | Command::SetInstructionBreakpoints(_) + | Command::SetVariable(_) + | Command::Source(_) + | Command::StepBack(_) + | Command::StepInTargets(_) + | Command::StepOut(_) + | Command::Terminate(_) + | Command::TerminateThreads(_) + | Command::WriteMemory(_) => self.handle_unsupported_request(&request.command), + } + } + + /// FIXME: connect launch arguments to Priroda's session model. + fn handle_launch(&self) -> Result { + self.require_state(DapState::Initialized)?; + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Launch), + state: Some(DapState::Launched), + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + fn handle_scopes<'tcx>( + &self, + frame_id: i64, + session: &PrirodaContext<'tcx>, + ) -> Result { + self.require_stopped()?; + Self::require_frame_id(frame_id)?; + + let (source, line, column) = match &session.current_location { + Some(location) => { + let source = session.local_path(location).as_ref().map(|path| { + Source { + name: path.file_name().map(|name| name.to_string_lossy().into_owned()), + path: Some(path.display().to_string()), + source_reference: Some(0), + presentation_hint: None, + origin: None, + sources: None, + checksums: None, + } + }); + let line = + location.line.try_into().unwrap_or_else(|_| bug!("source line exceeds i64")); + let column = location + .column + .try_into() + .unwrap_or_else(|_| bug!("source column exceeds i64")); + (source, Some(line), Some(column)) + } + None => (None, None, None), + }; + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Scopes(ScopesResponse { + scopes: vec![Scope { + name: "Locals".to_string(), + presentation_hint: Some(ScopePresentationhint::Locals), + variables_reference: LOCALS_VARIABLES_REFERENCE, + named_variables: None, + indexed_variables: Some(0), + expensive: false, + source, + line, + column, + end_line: None, + end_column: None, + }], + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + fn handle_variables<'tcx>( + &self, + variables_reference: i64, + session: &PrirodaContext<'tcx>, + ) -> Result { + self.require_stopped()?; + Self::require_variables_reference(variables_reference)?; + + let variables = if variables_reference == LOCALS_VARIABLES_REFERENCE { + session.list_locals().into_iter().map(Self::local_to_variable).collect() + } else { + Vec::new() + }; + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Variables(VariablesResponse { + variables, + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + fn handle_configuration_done<'tcx>( + &self, + session: &mut PrirodaContext<'tcx>, + ) -> Result { + self.require_state(DapState::Launched)?; + + match Self::execution_outcome(session.stop_at_first_user_location()) { + ExecutionOutcome::Stopped(_) => + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::ConfigurationDone), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body( + StoppedEventReason::Entry, + ))], + outcome: HandlerOutcome::Continue, + }), + ExecutionOutcome::Terminated { code } => + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::ConfigurationDone), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), + ExecutionOutcome::Failed(message) => + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), + } + } + + /// FIXME: replace this with Miri thread state once Priroda exposes a + /// frontend-facing thread model. + fn handle_threads(&self) -> Result { + self.reject_after_termination()?; + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + /// FIXME: report all frames once Priroda exposes a frontend-facing stack model. + fn handle_stack_trace<'tcx>( + &self, + thread_id: i64, + session: &PrirodaContext<'tcx>, + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; + + let stack_frames = match &session.current_location { + Some(location) => { + let path = session.local_path(location); + vec![StackFrame { + id: STACK_FRAME_ID, + name: session.current_frame_name().unwrap_or_else(|| "".to_string()), + source: path.as_ref().map(|path| { + Source { + name: path.file_name().map(|name| name.to_string_lossy().into_owned()), + path: Some(path.display().to_string()), + source_reference: Some(0), + presentation_hint: None, + origin: None, + sources: None, + checksums: None, + } + }), + line: location + .line + .try_into() + .unwrap_or_else(|_| bug!("source line exceeds i64")), + column: location + .column + .try_into() + .unwrap_or_else(|_| bug!("source column exceeds i64")), + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + }] + } + None => Vec::new(), + }; + let total_frames: i64 = + stack_frames.len().try_into().unwrap_or_else(|_| bug!("frame count exceeds i64")); + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames, + total_frames: Some(total_frames), + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + /// FIXME: grow capabilities as Priroda adds DAP features. + fn handle_initialize(&self) -> Result { + if self.state != DapState::Fresh { + return Err("initialize may only be sent once"); + } + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Initialize(Capabilities { + supports_configuration_done_request: Some(true), + supports_single_thread_execution_requests: Some(true), + ..Capabilities::default() + })), + state: Some(DapState::Initialized), + events: vec![Event::Initialized], + outcome: HandlerOutcome::Continue, + }) + } + + /// FIXME: distinguish step-over from step-in once Priroda has call-aware stepping. + fn handle_step<'tcx>( + &self, + body: ResponseBody, + thread_id: i64, + session: &mut PrirodaContext<'tcx>, + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; + + match Self::execution_outcome(session.step()) { + ExecutionOutcome::Stopped(result) => + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body(Self::stopped_reason( + result, + )))], + outcome: HandlerOutcome::Continue, + }), + ExecutionOutcome::Terminated { code } => + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), + ExecutionOutcome::Failed(message) => + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), + } + } + + fn handle_continue<'tcx>( + &self, + thread_id: i64, + session: &mut PrirodaContext<'tcx>, + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; + + let body = ResponseBody::Continue(ContinueResponse { all_threads_continued: Some(true) }); + + match Self::execution_outcome(session.continue_execution()) { + ExecutionOutcome::Stopped(result) => + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body(Self::stopped_reason( + result, + )))], + outcome: HandlerOutcome::Continue, + }), + ExecutionOutcome::Terminated { code } => + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), + ExecutionOutcome::Failed(message) => + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), + } + } + + fn handle_set_breakpoints<'tcx>( + &self, + args: &SetBreakpointsArguments, + session: &mut PrirodaContext<'tcx>, + ) -> Result { + self.reject_after_termination()?; + + let Some(ref path_str) = args.source.path else { + return Err( + "setBreakpoints requires a source.path; sourceReference loads are not supported", + ); + }; + + let path = std::path::PathBuf::from(path_str); + let mut breakpoints = Vec::new(); + if let Some(ref req_bps) = args.breakpoints { + for req_bp in req_bps { + let line = req_bp.line as usize; + session.set_breakpoint(path.clone(), line); + breakpoints.push(DapBreakpoint { + verified: true, + message: None, + source: Some(args.source.clone()), + line: Some(req_bp.line), + column: req_bp.column, + end_line: None, + end_column: None, + id: None, + instruction_reference: None, + offset: None, + }); + } + } + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::SetBreakpoints( + SetBreakpointsResponse { breakpoints }, + )), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + fn handle_disconnect(&self) -> Result { + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Disconnect), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }) + } + + fn handle_unsupported_request( + &self, + command: &Command, + ) -> Result { + Ok(HandlerSuccess { + response: HandlerResponse::Error(format!( + "unsupported request in Priroda DAP demo mode: {}", + Self::display_command(command) + )), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) + } + + fn reject_after_termination(&self) -> Result<(), &'static str> { + if self.state == DapState::Terminated { + return Err("request received after termination"); + } + Ok(()) + } + + fn require_state(&self, expected: DapState) -> Result<(), &'static str> { + if self.state != expected { + return Err(match expected { + DapState::Initialized => "launch requires initialize", + DapState::Launched => "configurationDone requires launch", + _ => "invalid session state for request", + }); + } + Ok(()) + } + + fn require_stopped(&self) -> Result<(), &'static str> { + if self.state != DapState::Stopped { + return Err("request requires a stopped frame"); + } + Ok(()) + } + + fn require_thread_id(thread_id: i64) -> Result<(), &'static str> { + if thread_id != THREAD_ID { + return Err("unknown threadId"); + } + Ok(()) + } + + fn require_frame_id(frame_id: i64) -> Result<(), &'static str> { + if frame_id != STACK_FRAME_ID { + return Err("unknown frameId"); + } + Ok(()) + } + + fn require_variables_reference(variables_reference: i64) -> Result<(), &'static str> { + if variables_reference != LOCALS_VARIABLES_REFERENCE { + return Err("unknown variablesReference"); + } + Ok(()) + } + + fn execution_outcome<'tcx>(result: InterpResult<'tcx, StepResult>) -> ExecutionOutcome { + match result.report_err() { + Ok(step) => ExecutionOutcome::Stopped(step), + Err(err) => Self::interp_error_outcome(err), + } + } + + fn interp_error_outcome<'tcx>(err: InterpErrorInfo<'tcx>) -> ExecutionOutcome { + let kind = err.into_kind(); + if let InterpErrorKind::MachineStop(info) = &kind + && let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + { + return ExecutionOutcome::Terminated { code: *code }; + } + + ExecutionOutcome::Failed(kind.to_string()) + } + + fn stopped_event_body(reason: StoppedEventReason) -> StoppedEventBody { + StoppedEventBody { + reason, + description: None, + thread_id: Some(THREAD_ID), + preserve_focus_hint: None, + text: None, + all_threads_stopped: Some(true), + hit_breakpoint_ids: None, + } + } + + fn stopped_reason(result: StepResult) -> StoppedEventReason { + match result { + StepResult::Step => StoppedEventReason::Step, + StepResult::Breakpoint => StoppedEventReason::Breakpoint, + } + } + + fn display_command(command: &Command) -> &'static str { + match command { + Command::Initialize(_) => "initialize", + Command::Launch(_) => "launch", + Command::ConfigurationDone => "configurationDone", + Command::Threads => "threads", + Command::StackTrace(_) => "stackTrace", + Command::Scopes(_) => "scopes", + Command::Variables(_) => "variables", + Command::Next(_) => "next", + Command::StepIn(_) => "stepIn", + Command::Disconnect(_) => "disconnect", + Command::Attach(_) => "attach", + Command::BreakpointLocations(_) => "breakpointLocations", + Command::Cancel(_) => "cancel", + Command::Completions(_) => "completions", + Command::Continue(_) => "continue", + Command::DataBreakpointInfo(_) => "dataBreakpointInfo", + Command::Disassemble(_) => "disassemble", + Command::Evaluate(_) => "evaluate", + Command::ExceptionInfo(_) => "exceptionInfo", + Command::Goto(_) => "goto", + Command::GotoTargets(_) => "gotoTargets", + Command::LoadedSources => "loadedSources", + Command::Modules(_) => "modules", + Command::Pause(_) => "pause", + Command::ReadMemory(_) => "readMemory", + Command::Restart(_) => "restart", + Command::RestartFrame(_) => "restartFrame", + Command::ReverseContinue(_) => "reverseContinue", + Command::SetBreakpoints(_) => "setBreakpoints", + Command::SetDataBreakpoints(_) => "setDataBreakpoints", + Command::SetExceptionBreakpoints(_) => "setExceptionBreakpoints", + Command::SetExpression(_) => "setExpression", + Command::SetFunctionBreakpoints(_) => "setFunctionBreakpoints", + Command::SetInstructionBreakpoints(_) => "setInstructionBreakpoints", + Command::SetVariable(_) => "setVariable", + Command::Source(_) => "source", + Command::StepBack(_) => "stepBack", + Command::StepInTargets(_) => "stepInTargets", + Command::StepOut(_) => "stepOut", + Command::Terminate(_) => "terminate", + Command::TerminateThreads(_) => "terminateThreads", + Command::WriteMemory(_) => "writeMemory", + } + } + + fn local_to_variable(local: LocalDesc) -> Variable { + Variable { + name: Self::local_name(&local), + value: local.value, + type_field: Some(local.ty), + presentation_hint: None, + evaluate_name: None, + // FIXME: add child handles once Priroda can identify places across requests. + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + } + } + + fn local_name(local: &LocalDesc) -> String { + let source_projection = local.source_projection_str(); + + // Prefer source names when debug info gives us one. If a local only has + // MIR storage identity, keep that visible so the DAP Variables view + // still has a stable row for every backing local. + if let Some(source_name) = local.source_name { + return format!("{source_name}{source_projection}"); + } + + let local_id = local + .local + .map_or_else(|| "".to_string(), |local_idx| format!("_{}", local_idx.index())); + format!("{local_id}{}", local.storage_projection_str()) + } +} diff --git a/src/tools/miri/priroda/src/frontend/mod.rs b/src/tools/miri/priroda/src/frontend/mod.rs new file mode 100644 index 0000000000000..8d2f57fb674f8 --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/mod.rs @@ -0,0 +1,5 @@ +mod cli; +mod dap; + +pub(super) use cli::Cli; +pub(super) use dap::Dap; diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index bc0dacc589a79..9b0efdf9fadb8 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -15,26 +15,17 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_type_ir; -use std::collections::{HashMap, HashSet}; -use std::io::{self, Write}; -use std::num::NonZeroU64; -use std::ops::Range; -use std::path::PathBuf; +mod debugger; +mod frontend; -use miri::Immediate::Uninit; -use miri::{interpret, *}; -use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; +use debugger::PrirodaContext; +use miri::*; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; -use rustc_hir::def::CtorKind; use rustc_interface::interface; -use rustc_middle::mir::interpret::AllocId; -use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; -use rustc_middle::ty::{self, TyCtxt, TyKind}; +use rustc_middle::ty::TyCtxt; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; -use rustc_span::source_map::SourceMap; -use rustc_span::{Span, Symbol}; fn find_sysroot() -> String { std::env::var("MIRI_SYSROOT") @@ -46,6 +37,7 @@ fn main() { rustc_driver::init_rustc_env_logger(&early_dcx); let mut args: Vec = std::env::args().collect(); + let frontend = Frontend::parse_from_args(&mut args); args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); @@ -55,15 +47,48 @@ fn main() { args.push(find_sysroot()); } // FIXME: handle the same `-Z` flags that Miri accepts. - rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new()); + rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new(frontend)); } -struct PrirodaCompilerCalls; +/// Frontend selected by Priroda-specific CLI flags. +#[derive(Clone, Copy)] +enum Frontend { + Cli, + Dap, +} + +impl Frontend { + /// Remove Priroda-only flags before forwarding the remaining arguments to rustc. + fn parse_from_args(args: &mut Vec) -> Self { + let mut frontend = Frontend::Cli; + let mut rustc_args = Vec::with_capacity(args.len()); + let mut parsing_priroda_args = true; + + for (idx, arg) in args.drain(..).enumerate() { + if idx != 0 && parsing_priroda_args && arg == "--dap" { + frontend = Frontend::Dap; + continue; + } + + if arg == "--" { + parsing_priroda_args = false; + } + + rustc_args.push(arg); + } + + *args = rustc_args; + frontend + } +} + +struct PrirodaCompilerCalls { + frontend: Frontend, +} impl PrirodaCompilerCalls { - // FIXME: remove this constructor if PrirodaCompilerCalls remains a unit struct. - fn new() -> Self { - Self + fn new(frontend: Frontend) -> Self { + Self { frontend } } } @@ -80,8 +105,10 @@ impl rustc_driver::Callbacks for PrirodaCompilerCalls { let ecx = create_ecx(tcx); let mut session = PrirodaContext::new(ecx); - let cli = Cli {}; - let result = cli.run_cli_loop(&mut session); + let result = match self.frontend { + Frontend::Cli => frontend::Cli {}.run_cli_loop(&mut session), + Frontend::Dap => frontend::Dap {}.run_dap_loop(&mut session), + }; match result.report_err() { Ok(()) => {} @@ -110,962 +137,3 @@ fn create_ecx<'tcx>(tcx: TyCtxt<'tcx>) -> MiriInterpCx<'tcx> { // FIXME: report interpreter initialization failures instead of panicking. miri::create_ecx(tcx, entry_id, entry_type, &config, None).unwrap() } - -/// Structured source information for frontends. -struct SourceLocation { - // storing `span` to use it lazily to compute path. - span: Span, - line: usize, -} - -impl SourceLocation { - fn local_path(&self, source_map: &SourceMap) -> Option { - let loc = source_map.lookup_char_pos(self.span.lo()); - loc.file.name.clone().into_local_path().map(normalize_path) - } -} - -/// Source-level breakpoints indexed by normalized path, then line. -type BreakpointTable = HashMap>; - -/// Owns one interpreter session and its debugger state. -/// -/// Frontend rendering should eventually live outside this type. -struct PrirodaContext<'tcx> { - ecx: MiriInterpCx<'tcx>, - breakpoints: BreakpointTable, - current_location: Option, - last_location: Option, -} - -enum StorageProj { - Field(usize), - Deref, - Downcast(Symbol), - Variant(usize), - Unsupported(String), -} - -impl StorageProj { - fn render(&self) -> String { - match self { - StorageProj::Field(field_idx) => format!(".{field_idx}"), - StorageProj::Deref => format!(".*"), - StorageProj::Downcast(name) => format!(" as {name}"), - StorageProj::Variant(variant_idx) => format!(" as variant#{variant_idx}"), - StorageProj::Unsupported(unsop) => format!("."), - } - } -} - -struct LocalDesc { - /// Source variable name from `VarDebugInfo`, if this row has one. - source_name: Option, - - /// Source-side projection from `VarDebugInfo::composite`, e.g. `.field` in source fragment `x.field`. - source_projection: Option>, - - /// MIR storage local that backs this description, if any. - local: Option, - - /// rendered/debug MIR place projection for now - storage_projection: Vec, - - /// Display-rendered type for this description. - ty: String, - - /// Run-time state for now; will be expanded later - value: String, -} - -/// Controls when execution returns to the frontend. -enum ResumeMode { - /// Stop at the next visible MIR instruction. - MirInstruction, - /// Stop at the next source line - /// - /// Take `Option` because some cases current state has no mapped to source code location - SourceLine(Option<(PathBuf, usize)>), - /// Continue until reaching a breakpoint. - Continue, -} - -/// Describes whether the current MIR instruction should be shown to the user. -enum InstructionVisibility { - NoInstruction, - Hidden, - Visible, -} - -/// Describes why execution stopped and returned control to the frontend. -enum StepResult { - Step, - Breakpoint, -} - -fn normalize_path(path: PathBuf) -> PathBuf { - path.canonicalize().unwrap_or(path) -} - -impl<'tcx> PrirodaContext<'tcx> { - fn new(ecx: MiriInterpCx<'tcx>) -> Self { - Self { ecx, breakpoints: HashMap::new(), current_location: None, last_location: None } - } - - fn local_path(&self, location: &SourceLocation) -> Option { - let source_map = self.ecx.tcx.sess.source_map(); - location.local_path(source_map) - } - - fn current_source_position(&self) -> Option<(PathBuf, usize)> { - let location = self.current_location.as_ref()?; - Some((self.local_path(location)?, location.line)) - } - - // Used to treat `continue` like a source-level step for breakpoint checks: - // several MIR locations can point at one source line, but they should only - // report that source breakpoint once. - fn last_source_position(&self) -> Option<(PathBuf, usize)> { - let location = self.last_location.as_ref()?; - Some((self.local_path(location)?, location.line)) - } - - /// Step to the next visible MIR instruction. - fn stepi(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::MirInstruction) - } - fn step(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::SourceLine(self.current_source_position())) - } - - /// Continue execution until reaching a breakpoint or propagating termination. - fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::Continue) - } - - fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { - // FIXME: validate breakpoints here so every frontend gets the same behavior. - // Reject empty paths, missing files, directories, and line 0. Decide whether - // out-of-range lines should be rejected or kept as pending breakpoints. - // Report duplicate registrations separately. - - let path = normalize_path(path); - match self.breakpoints.entry(path.clone()).or_default().insert(line) { - true => BreakpointSetResult::Added(path, line), - false => BreakpointSetResult::Duplicate, - } - } - - /// Advance execution until the selected resume mode reaches a stopping point. - fn resume(&mut self, mode: ResumeMode) -> InterpResult<'tcx, StepResult> { - loop { - self.advance()?; - - // An explicit breakpoint should stop execution even when the current - // MIR instruction would normally be hidden during manual stepping. - if self.is_at_breakpoint() { - return interp_ok(StepResult::Breakpoint); - } - - match mode { - ResumeMode::MirInstruction - if matches!( - self.current_instruction_visibility(), - InstructionVisibility::Visible - ) => - { - return interp_ok(StepResult::Step); - } - - ResumeMode::SourceLine(ref prev_location) => { - match (prev_location, &self.current_location) { - // We started from an unmapped source location. Stop at the first mapped source location we can show to the user. - (None, Some(_)) => return interp_ok(StepResult::Step), - - (Some((prev_path, prev_line)), Some(current_location)) => { - if let Some(current_path) = self.local_path(current_location) { - // A source step stops when the visible source position changes to a different file or line. - if *prev_path != current_path || *prev_line != current_location.line - { - return interp_ok(StepResult::Step); - } - } - } - - _ => {} - } - } - - ResumeMode::MirInstruction | ResumeMode::Continue => {} - } - } - } - - /// Advance Miri by one interpreter-loop transition. - fn advance(&mut self) -> InterpResult<'tcx> { - // FIXME: use a Miri-owned scheduler-aware debugger step API before - // claiming support for multi-threaded interpreted programs. - - // State inspection should happen only after a successful step. - self.ecx.step_current_thread()?; - self.last_location = self.current_location.take(); - self.current_location = self.resolve_current_location(); - interp_ok(()) - } - - fn current_instruction_visibility(&self) -> InstructionVisibility { - // If the active thread has no stack frame, there is no MIR instruction to show. - let Some(frame) = self.ecx.active_thread_stack().last() else { - return InstructionVisibility::NoInstruction; - }; - - // `Right(span)` means the frame has source context but no precise MIR program-counter location. - let Either::Left(location) = frame.current_loc() else { - return InstructionVisibility::NoInstruction; - }; - - let basic_block = &frame.body().basic_blocks[location.block]; - - // `statement_index == statements.len()` points at the block terminator. - // Terminators affect control flow, so they are always visible. - let Some(statement) = basic_block.statements.get(location.statement_index) else { - return InstructionVisibility::Visible; - }; - - // Hide bookkeeping-only MIR statements during manual stepping. - match statement.kind { - mir::StatementKind::StorageLive(_) - | mir::StatementKind::StorageDead(_) - | mir::StatementKind::Nop => InstructionVisibility::Hidden, - _ => InstructionVisibility::Visible, - } - } - - fn is_at_breakpoint(&self) -> bool { - let Some(bp) = self.current_breakpoint() else { - return false; - }; - - // If the previous interpreter step had the same source position, this - // is another MIR location for the breakpoint we just reported. - self.last_source_position().as_ref() != Some(&bp) - } - - fn current_breakpoint(&self) -> Option<(PathBuf, usize)> { - let (path, line) = self.current_source_position()?; - let lines = self.breakpoints.get(&path)?; - - if lines.contains(&line) { Some((path, line)) } else { None } - } - - fn resolve_current_location(&self) -> Option { - // FIXME: resolve macro-backed lines such as `println!` and `assert_eq!` - // through `span.source_callsite()` before matching breakpoints. - let span = self.ecx.machine.current_user_relevant_span(); - if span.is_dummy() { - return None; - } - - let source_map = self.ecx.tcx.sess.source_map(); - let loc = source_map.lookup_char_pos(span.lo()); - - Some(SourceLocation { span, line: loc.line }) - } - - fn run_command(&mut self, command: DebuggerCommand) -> InterpResult<'tcx, CommandResult> { - match command { - DebuggerCommand::StepI => self.stepi().map(CommandResult::ExecutionStopped), - DebuggerCommand::Step => self.step().map(CommandResult::ExecutionStopped), - DebuggerCommand::Continue => - self.continue_execution().map(CommandResult::ExecutionStopped), - DebuggerCommand::Breakpoint(path, line) => - interp_ok(CommandResult::BreakpointResult(self.set_breakpoint(path, line))), - DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())), - DebuggerCommand::Print(local) => - interp_ok(CommandResult::SingleLocal(self.get_local(local))), - DebuggerCommand::Follow(alloc_id, offset) => - self.follow_alloc(alloc_id, offset).map(CommandResult::Memory), - DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), - } - } - - fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> { - let alloc = self.ecx.get_alloc_raw(alloc_id)?; - if offset > alloc.len() { - return Err(miri::err_unsup_format!( - "allocation offset {offset} is outside {alloc_id}" - )) - .into(); - } - - let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?; - interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}")) - } - - fn get_local(&self, local: usize) -> Option { - let frame = self.ecx.active_thread_stack().last()?; - - self.make_mir_local_desc(frame, local) - } - - /// Returns structured descriptions for locals in the innermost stack frame. - /// - /// Starts from all MIR locals, then enriches them with source names from - /// `var_debug_info` when a debug entry maps directly to a whole local. - fn list_locals(&self) -> Vec { - let Some(frame) = self.ecx.active_thread_stack().last() else { - return Vec::new(); - }; - - self.build_local_descs(frame) - } - - /// Renders the current byte range of an indirect MIR value. - /// - /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`, - /// and complete pointer-sized provenance as pointer markers. - fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { - let size = match self.ecx.size_and_align_of_val(mplace)? { - Some((size, _)) => size, - None => { - // Extern types cannot currently be executed as by-value locals, - // so this path cannot yet be covered by a Priroda UI fixture. - // FIXME: Add coverage once Priroda supports printing dereferenced places. - return interp_ok("".to_string()); - } - }; - - let size = size.bytes_usize(); - if size == 0 { - return interp_ok("[]".to_string()); - } - - let (alloc_id, offset, _) = - self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; - let offset = offset.bytes_usize(); - let range = offset..offset.strict_add(size); - - self.render_alloc_bytes(alloc_id, range) - } - - /// Render a raw allocation range without requiring a typed memory place. - /// - /// This is also used by the future-facing `follow` command, where we have a - /// pointer target but do not yet know the target's type or size. - fn render_alloc_bytes( - &self, - alloc_id: AllocId, - range: Range, - ) -> InterpResult<'tcx, String> { - let alloc = self.ecx.get_alloc_raw(alloc_id)?; - - let mut rendered = Vec::with_capacity(range.len()); - - let ptr_size = self.ecx.tcx.data_layout.pointer_size(); - - for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { - let chunk_range = chunk.range(); - let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); - - if chunk.is_init() { - let ptr_size = ptr_size.bytes_usize(); - let mut cursor = chunk_range.start; - - while cursor < chunk_range.end { - // Full pointer provenance is rendered as a pointer marker. Bytewise - // provenance fragments are intentionally left as raw bytes here: they do - // not represent a complete pointer-sized value. - if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor)) - && cursor + ptr_size <= chunk_range.end - { - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( - cursor..cursor + ptr_size, - ); - let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes) - .map_err(|err| { - miri::err_unsup_format!("invalid pointer representation: {err}") - })?; - - let offset = Size::from_bytes(offset); - rendered.push(format!("{:?}", Pointer::new(Some(prov), offset))); - - cursor += ptr_size; - } else { - let byte = alloc - .inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0]; - - rendered.push(format!("{byte:02x}")); - cursor += 1; - } - } - } else { - rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); - } - } - - interp_ok(format!("[{}]", rendered.join(" "))) - } - - /// Render an evaluated operand using Rust-source-shaped containers with raw leaves. - /// - /// The operand is produced from live interpreter state, usually via `local_to_op` - /// for a whole MIR local or `eval_place_to_op` for a projected debug-info place. - /// - /// This intentionally does not call user `Debug` / `Display`, and it does not - /// try to make every scalar leaf pretty yet. Unsupported cases and leaf values - /// fall back to `render_op`, preserving the old raw byte/provenance renderer. - /// - /// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers, - /// chars, raw pointers/references) once the source-shaped container output is - /// stable enough to stop depending on byte dumps for every field. - /// - /// FIXME: decide how much dereferencing belongs in this renderer. References - /// currently stay as raw pointer leaves; following them may belong in the - /// existing `follow` command instead of automatic local rendering. - fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String { - self.render_source_shaped_op_inner(op, 0) - } - - /// Recursive worker for `render_source_shaped_op`. - /// - /// The depth limit keeps cyclic/reference-heavy values from making debugger - /// output explode once more container kinds are added. At the limit, the raw - /// renderer remains the ground truth. - /// - /// FIXME: replace this fixed recursion limit with a value-size/output-budget - /// policy so large acyclic values and deeply nested values degrade more - /// predictably. - fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String { - const MAX_SOURCE_SHAPE_DEPTH: usize = 8; - - if depth >= MAX_SOURCE_SHAPE_DEPTH { - return self.render_op(op); - } - - match op.layout.ty.kind() { - // Empty enums have no active variant to format. Unions do not record - // which field is currently active, so choosing one would be misleading. - // - // FIXME: support unions only with an explicit user-selected field or - // another source of active-field information. Guessing from layout - // bytes would make debugger output look more certain than it is. - ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op), - - ty::Adt(def, _) => { - // Enums need their runtime discriminant and a downcasted layout - // view before fields can be projected. Structs use their sole - // variant directly. Keep the display name tied to the same choice. - let (variant_idx, down, name) = if def.is_enum() { - let variant_idx = match self.ecx.read_discriminant(&op).discard_err() { - Some(variant_idx) => variant_idx, - // FIXME: expose this as an explicit render error when - // Priroda grows structured value states. Falling back to - // bytes keeps today's UI usable but hides why the enum - // could not be source-shaped. - None => return self.render_op(op), - }; - let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() { - Some(down) => down, - // FIXME: distinguish invalid/uninitialized discriminants - // from projection bugs in the rendered output once locals - // can carry structured diagnostics. - None => return self.render_op(op), - }; - let variant_def = &def.variants()[variant_idx]; - ( - variant_idx, - down, - format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name), - ) - } else { - let variant_idx = FIRST_VARIANT; - let variant_def = &def.variants()[variant_idx]; - (variant_idx, op.clone(), variant_def.name.to_string()) - }; - - let variant_def = &def.variants()[variant_idx]; - - let mut fields = Vec::with_capacity(variant_def.fields.len()); - for i in 0..variant_def.fields.len() { - let field_idx = FieldIdx::from_usize(i); - // `project_field` avoids manual offset math and works for both - // immediate and memory-backed operands through `Projectable`. - let field_op = match self.ecx.project_field(&down, field_idx).discard_err() { - Some(field_op) => field_op, - // FIXME: preserve the successfully rendered fields and - // mark only this field as unavailable once the value model - // can represent partial render failures. - None => return self.render_op(op), - }; - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); - } - - // Match Rust constructor spelling: - // - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit` - // - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()` - // - `None`: braced structs/variants, including the empty `{}` case - match variant_def.ctor_kind() { - Some(CtorKind::Const) => name, - Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")), - None if fields.is_empty() => format!("{name} {{}}"), - None => { - let fields = variant_def - .fields - .iter() - .zip(fields) - .map(|(field_def, value)| format!("{}: {value}", field_def.name)) - .collect::>() - .join(", "); - format!("{name} {{ {fields} }}") - } - } - } - - ty::Tuple(args) => { - let mut fields = Vec::with_capacity(args.len()); - for i in 0..args.len() { - // Tuples have no field names in source, so preserve their - // source field order and render children positionally. - let field_op = - match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() { - Some(field_op) => field_op, - // FIXME: render tuple fields independently so one - // projection failure does not throw away the whole - // source-shaped tuple. - None => return self.render_op(op), - }; - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); - } - - if fields.len() == 1 { - format!("({},)", fields[0]) - } else { - format!("({})", fields.join(", ")) - } - } - - ty::Array(_, _) | ty::Slice(_) => { - // `project_array_fields` uses the dynamic length for slices. That - // avoids the classic mistake of treating slice layout as a fixed - // zero-length array. - let mut iter = match self.ecx.project_array_fields(&op).discard_err() { - Some(iter) => iter, - // FIXME: when slice metadata is invalid, show that as a slice - // length problem instead of silently falling back to raw bytes. - None => return self.render_op(op), - }; - - let mut fields = Vec::new(); - // FIXME: add an output budget/truncation policy before rendering - // very large arrays or slices in full. - loop { - match iter.next(&self.ecx).discard_err() { - Some(Some((_idx, field_op))) => - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)), - Some(None) => break, - // FIXME: keep already-rendered elements and mark the - // failed index once partial render errors are supported. - None => return self.render_op(op), - } - } - - format!("[{}]", fields.join(", ")) - } - - // FIXME: consider source-shaped special cases for strings, closures, - // generators/coroutines, trait objects, and SIMD/vector-like types. - // Until then these stay on the raw renderer path. - _ => self.render_op(op), - } - } - - /// Render an evaluated operand using the same raw representation for - /// whole locals and projected MIR places. - fn render_op(&self, op: OpTy<'tcx>) -> String { - match op.as_mplace_or_imm() { - Either::Right(imm) => format!("{imm}"), - - Either::Left(mplace) => - match self.render_mplace_bytes(&mplace).report_err() { - Ok(bytes) => bytes, - Err(err) => format!("", interpret::format_interp_error(err)), - }, - } - } - - /// Render the source-side path from composite debug info, such as `.field`. - fn render_source_projection( - fragment: Option<&VarDebugInfoFragment<'tcx>>, - ) -> Option> { - let VarDebugInfoFragment { ty, projection } = fragment?; - - // Walk the source-side projection from the original - // composite variable type. Each `Field` element stores the - // resulting field type, so resolve the field name from the - // current base type before advancing to `field_ty`. - let mut projection_ty = ty; - - Some( - projection - .iter() - .map(|elem| { - match elem { - ProjectionElem::Field(field_idx, field_ty) => { - let rendered = match projection_ty.kind() { - TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { - let variant = adt_def.non_enum_variant(); - let field = &variant.fields[*field_idx]; - Symbol::intern(&format!(".{}", field.name)) - } - - TyKind::Tuple(_) => - Symbol::intern(&format!(".{}", field_idx.index())), - - _ => Symbol::intern("."), - }; - - projection_ty = field_ty; - - rendered - } - // `VarDebugInfoFragment::projection` is expected to be - // field-only. If that ever changes, keep the unexpected - // segment visible instead of silently rendering a - // misleading source path. - other => Symbol::intern(&format!(".")), - } - }) - .collect(), - ) - } - - /// Render the MIR storage-side path that backs a debug-info local. - fn render_storage_projection(projection: &[mir::PlaceElem<'tcx>]) -> Vec { - projection - .iter() - .map(|projection_elem| { - match projection_elem { - ProjectionElem::Field(field_idx, _) => StorageProj::Field(field_idx.index()), - ProjectionElem::Deref => StorageProj::Deref, - ProjectionElem::Downcast(Some(name), _) => StorageProj::Downcast(*name), - ProjectionElem::Downcast(None, variant_idx) => - StorageProj::Variant(variant_idx.index()), - other => StorageProj::Unsupported(format!("{other:?}")), - } - }) - .collect() - } - - /// Builds the baseline debugger row for one MIR local without scanning debug info. - fn make_mir_local_desc( - &self, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - local: usize, - ) -> Option { - let local = mir::Local::from_usize(local); - let local_decl = frame.body().local_decls.get(local)?; - - // Create LocalDesc for MIR local before processing debug info. - // Debug-info enrichment is layered on by build_local_descs. - let mut local_desc = LocalDesc { - source_name: None, - source_projection: None, - local: Some(local), - storage_projection: Vec::new(), - ty: local_decl.ty.to_string(), - value: "".to_string(), - }; - - match &frame.locals[local].as_mplace_or_imm() { - None => { - local_desc.value = "".to_string(); - } - Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), - - Some(Either::Left(_) | Either::Right(_)) => { - let op = self - .ecx - .local_to_op(local, None) - .expect("this error can only occur in CTFE on generic code"); - local_desc.value = self.render_source_shaped_op(op); - } - }; - - Some(local_desc) - } - - fn build_local_descs( - &self, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - ) -> Vec { - let local_decls = &frame.body().local_decls; - - let mut local_descs: Vec = Vec::with_capacity(local_decls.len()); - - // Start with one baseline row for every MIR local, then layer debug info on top. - for (local_idx, _) in local_decls.iter_enumerated() { - local_descs.push(self.make_mir_local_desc(frame, local_idx.index()).unwrap()); - } - - // FIXME: Finish classifying `var_debug_info` by keeping the source path - // and MIR storage path separate: - // - // - source side: `var_debug_info.name` plus - // `var_debug_info.composite.projection` - // - storage side: `VarDebugInfoContents::Place(place).local` plus - // `place.projection` - // - // Already handled by the `place.as_local()` path below: - // - whole source variable -> whole MIR local: - // `composite = None`, `Place(_N)` with empty projection. - // - source fragment -> whole MIR local: - // `composite = Some(source_proj)`, `Place(_N)` with empty projection. - // - // Remaining cases to represent or explicitly defer: - // - whole source variable -> projected MIR storage: - // `composite = None`, `Place(_N.proj)`. - // - source fragment -> projected MIR storage: - // `composite = Some(source_proj)`, `Place(_N.storage_proj)`. - // - source variable/fragment -> constant: - // `Const(...)`, with no MIR local id. - // - optimized-out/debug-only/unsupported shapes: - // explicit deferred state, not silent discard. - // - // Final output should be produced by walking `Vec`, - // then append explicit deferred/debug-info-only rows where needed. - // Related: SROA can split a source local like `_slice: ExtraSlice` into - // field locals whose debug paths should be printed as `_slice._slice` - // and `_slice._extra`, not as two separate locals both named `_slice`. - - // Whole-place debug entries enrich the direct storage-local description. - // Projected places are evaluated from their original MIR Place and use - // the same raw renderer as ordinary locals. - for var_debug_info in &frame.body().var_debug_info { - if let VarDebugInfoContents::Place(place) = &var_debug_info.value { - if let Some(local_idx) = place.as_local() - && local_descs[local_idx.index()].source_name.is_none() - { - let local_idx = local_idx.index(); - local_descs[local_idx].source_projection = - Self::render_source_projection(var_debug_info.composite.as_deref()); - local_descs[local_idx].source_name = Some(var_debug_info.name); - } else if !place.projection.is_empty() { - let storage_projection = Self::render_storage_projection(place.projection); - let source_projection = - Self::render_source_projection(var_debug_info.composite.as_deref()); - let value = self - .ecx - .eval_place_to_op(*place, None) - .map(|op| self.render_source_shaped_op(op)) - .unwrap_or_else(|err| { - format!("", interpret::format_interp_error(err)) - }); - - local_descs.push(LocalDesc { - source_name: Some(var_debug_info.name), - source_projection, - local: Some(place.local), - storage_projection, - ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(), - value, - }); - } - } - } - - local_descs - } -} - -enum DebuggerCommand { - StepI, - Step, - TerminateSession, - Continue, - Breakpoint(PathBuf, usize), - ListLocals, - Print(usize), - Follow(AllocId, usize), -} - -enum BreakpointSetResult { - Added(PathBuf, usize), - Duplicate, - // FIXME: add pending breakpoint support later if needed. -} - -enum CommandResult { - ExecutionStopped(StepResult), - BreakpointResult(BreakpointSetResult), - Locals(Vec), - SingleLocal(Option), - Memory(String), - // FIXME: distinguish terminating the debugger session from disconnecting a - // frontend and terminating the interpreted program once multiple frontends exist. - TerminateSession, -} - -struct Cli; - -impl Cli { - pub fn run_cli_loop<'tcx>(&self, session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { - loop { - print!("(priroda) "); - io::stdout().flush().unwrap(); - - let mut input = String::new(); - let bytes_read = io::stdin().read_line(&mut input).unwrap(); - - if bytes_read == 0 { - println!("stdin closed, stopping"); - return interp_ok(()); - } - - if let Some(command) = self.parse_command(&input) { - match session.run_command(command)? { - CommandResult::ExecutionStopped(result) => { - if matches!(result, StepResult::Breakpoint) { - println!("Hit breakpoint"); - } - self.print_location(session); - } - CommandResult::BreakpointResult(res) => - match res { - BreakpointSetResult::Added(path, line) => - println!("breakpoint added: {}:{}", path.display(), line), - - BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), - }, - CommandResult::Locals(locals_desc) => - if locals_desc.is_empty() { - println!("no locals"); - } else { - for local_desc in &locals_desc { - let source_projection = local_desc - .source_projection - .as_ref() - .map(|fields| { - fields - .iter() - .map(|field| field.to_string()) - .collect::() - }) - .unwrap_or_default(); - - let name = local_desc - .source_name - .map_or_else(|| "".to_string(), |name| name.to_string()); - - let display_name = format!("{name}{source_projection}"); - - let local_id = local_desc.local.map_or_else( - || "".to_string(), - |local_idx| format!("_{}", local_idx.index()), - ); - - let storage_projection = local_desc - .storage_projection - .iter() - .map(StorageProj::render) - .collect::(); - - let display_local_id = format!("{local_id}{storage_projection}"); - println!( - "Name: {}, Id: {}, Ty: {}, Value: {}", - display_name, display_local_id, local_desc.ty, local_desc.value - ); - } - }, - CommandResult::SingleLocal(local_desc) => - match local_desc { - Some(local_desc) => { - println!( - "Id: _{}, Ty: {}, Value: {}", - local_desc.local.unwrap().index(), - local_desc.ty, - local_desc.value - ); - } - None => println!("no local for this id"), - }, - CommandResult::Memory(memory) => println!("{memory}"), - CommandResult::TerminateSession => { - println!("quitting"); - return interp_ok(()); - } - } - } else { - println!("no command"); - } - - io::stdout().flush().unwrap(); - } - } - - fn parse_command(&self, input: &str) -> Option { - // TODO: look at the Spanned crate for how to easily produce errors in - // rustc's style while manually parsing text input. - // FIXME: we need to distinguish malformed input from the unknown commands by returning useful - // command error that describes if it malformed or non exist command - let input = input.trim(); - let mut parts = input.splitn(2, char::is_whitespace); - let command = parts.next().unwrap_or(""); - let args = parts.next().unwrap_or("").trim(); - - match command { - // FIXME: empty line should repats last command user typed not exeute specific command. - "" | "si" | "stepi" => Some(DebuggerCommand::StepI), - "s" | "step" => Some(DebuggerCommand::Step), - "q" | "quit" => Some(DebuggerCommand::TerminateSession), - "c" | "continue" => Some(DebuggerCommand::Continue), - "b" | "break" => self.parse_breakpoint(args), - "l" | "locals" => Some(DebuggerCommand::ListLocals), - "p" | "print" => self.parse_print_local(args), - "f" | "follow" => self.parse_follow(args), - _ => None, - } - } - - fn print_location(&self, session: &PrirodaContext) { - match &session.current_location { - Some(location) => - if let Some(path) = session.local_path(location) { - println!("{}:{}", path.display(), location.line); - } else { - let source_map = session.ecx.tcx.sess.source_map(); - println!("{}", source_map.span_to_diagnostic_string(location.span)); - }, - None => println!("no-location"), - } - io::stdout().flush().unwrap(); - } - - fn parse_breakpoint(&self, input: &str) -> Option { - // FIXME: return a typed CommandError so malformed breakpoint input is - // distinguishable from an unknown command. Semantic validation belongs - // in PrirodaContext::set_breakpoint so non-CLI frontends cannot bypass it. - let (path, line) = input.rsplit_once(':')?; - let line = line.parse().ok()?; - - Some(DebuggerCommand::Breakpoint(PathBuf::from(path), line)) - } - - fn parse_print_local(&self, input: &str) -> Option { - let local = input.parse().ok()?; - Some(DebuggerCommand::Print(local)) - } - - fn parse_follow(&self, input: &str) -> Option { - let mut parts = input.split_whitespace(); - let alloc_id = parts.next()?; - let offset = parts.next()?; - if parts.next().is_some() { - return None; - } - - let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?; - let alloc_id = AllocId(NonZeroU64::new(alloc_id)?); - let offset = offset.parse().ok()?; - Some(DebuggerCommand::Follow(alloc_id, offset)) - } -} diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs index 3b596fbf91f26..2bf7f22bd1d98 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/cli.rs @@ -33,11 +33,20 @@ fn main() -> Result<(), Box> { let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); + let crlf_regex = Regex::new(r"\r\n").unwrap(); + // DAP Content-Length headers embed the byte count of the following JSON, + // which changes when path normalisation alters the embedded file paths. + // Replace them with a placeholder so path-length differences between + // machines do not make Content-Length drift from the normalised body. + let content_length_regex = Regex::new(r"Content-Length: \d+").unwrap(); config.comment_defaults.base().normalize_stdout.extend([ (manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()), (miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()), (rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()), (pointer_regex.into(), b"{ALLOC_PTR}".to_vec()), + // DAP frames use CRLF headers; keep checked-in stdout fixtures readable. + (crlf_regex.into(), b"\n".to_vec()), + (content_length_regex.into(), b"Content-Length: {CONTENT_LENGTH}".to_vec()), ]); // Priroda CLI tests do not currently require annotation comments in the test files diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.rs b/src/tools/miri/priroda/tests/ui/dap_initialize.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize.stdin new file mode 100644 index 0000000000000..873743fad394b --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdin @@ -0,0 +1,3 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout new file mode 100644 index 0000000000000..4f6f29a60dbd7 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout @@ -0,0 +1,5 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin new file mode 100644 index 0000000000000..ae4ee94ca0e98 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin @@ -0,0 +1,5 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout new file mode 100644 index 0000000000000..7ba36709bd123 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout @@ -0,0 +1,7 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin new file mode 100644 index 0000000000000..106ce5dac35e0 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin @@ -0,0 +1,7 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout new file mode 100644 index 0000000000000..121232f9aa271 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout @@ -0,0 +1,11 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin new file mode 100644 index 0000000000000..c1dedb5404eca --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin @@ -0,0 +1,7 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 56 + +{"seq":2,"type":"request","command":"configurationDone"}Content-Length: 64 + +{"seq":3,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout new file mode 100644 index 0000000000000..4a4df53ea5889 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout @@ -0,0 +1,11 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin new file mode 100644 index 0000000000000..a582d4adc7fdb --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin @@ -0,0 +1,9 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 70 + +{"seq":3,"type":"request","command":"next","arguments":{"threadId":1}}Content-Length: 65 + +{"seq":4,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout new file mode 100644 index 0000000000000..796935374a8eb --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout @@ -0,0 +1,13 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":false,"message":"request requires a stopped frame","command":"next","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"response","request_seq":4,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin new file mode 100644 index 0000000000000..6b8fb8e08484a --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin @@ -0,0 +1,3 @@ +Content-Length: 70 + +{"seq":2,"type":"request","command":"next","arguments":{"threadId":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout new file mode 100644 index 0000000000000..7ad4e38819f8f --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout @@ -0,0 +1,3 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":2,"success":false,"message":"initialize must be sent first","command":"next","error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin new file mode 100644 index 0000000000000..d98039165e4d8 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin @@ -0,0 +1,11 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 56 + +{"seq":4,"type":"request","command":"configurationDone"}Content-Length: 65 + +{"seq":5,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout new file mode 100644 index 0000000000000..abc6e1cf7d694 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout @@ -0,0 +1,17 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":8,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs new file mode 100644 index 0000000000000..cd7ad8e0bb32f --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs @@ -0,0 +1,6 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let _ = x; +} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin new file mode 100644 index 0000000000000..a2a9dd1595bc8 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin @@ -0,0 +1,19 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":2}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":2}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":2}}Content-Length: 70 + +{"seq":7,"type":"request","command":"next","arguments":{"threadId":2}}Content-Length: 72 + +{"seq":8,"type":"request","command":"stepIn","arguments":{"threadId":2}}Content-Length: 65 + +{"seq":9,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout new file mode 100644 index 0000000000000..6baf6351f6a7b --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout @@ -0,0 +1,25 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"unknown threadId","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":7,"type":"response","request_seq":5,"success":false,"message":"unknown frameId","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":8,"type":"response","request_seq":6,"success":false,"message":"unknown variablesReference","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":9,"type":"response","request_seq":7,"success":false,"message":"unknown threadId","command":"next","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":10,"type":"response","request_seq":8,"success":false,"message":"unknown threadId","command":"stepIn","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":11,"type":"response","request_seq":9,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":12,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs new file mode 100644 index 0000000000000..081c3ce1d97c6 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs @@ -0,0 +1,7 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let y = true; + let _ = (x, y); +} diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin new file mode 100644 index 0000000000000..d1dd783eb96fa --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin @@ -0,0 +1,13 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout new file mode 100644 index 0000000000000..4cc848bc88369 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout @@ -0,0 +1,17 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs new file mode 100644 index 0000000000000..081c3ce1d97c6 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs @@ -0,0 +1,7 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let y = true; + let _ = (x, y); +} diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin new file mode 100644 index 0000000000000..40da18a5832da --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin @@ -0,0 +1,23 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":1}}Content-Length: 70 + +{"seq":7,"type":"request","command":"next","arguments":{"threadId":1}}Content-Length: 76 + +{"seq":8,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":9,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 86 + +{"seq":10,"type":"request","command":"variables","arguments":{"variablesReference":1}}Content-Length: 65 + +{"seq":11,"type":"request","command":"disconnect","arguments":{}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout new file mode 100644 index 0000000000000..558af9b383840 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout @@ -0,0 +1,31 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":9,"type":"response","request_seq":7,"success":true,"command":"next","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":13,"type":"response","request_seq":10,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"1_i32","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":14,"type":"response","request_seq":11,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":15,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs b/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin new file mode 100644 index 0000000000000..1056beef5712e --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin @@ -0,0 +1,11 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 46 + +{"seq":4,"type":"request","command":"threads"}Content-Length: 76 + +{"seq":5,"type":"request","command":"stackTrace","arguments":{"threadId":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout new file mode 100644 index 0000000000000..1056d39e468b1 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -0,0 +1,15 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_stack_trace.rs","path":"{MANIFEST_DIR}/tests/ui/dap_stack_trace.rs","sourceReference":0},"line":3,"column":11}],"totalFrames":1},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.rs b/src/tools/miri/priroda/tests/ui/dap_threads.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdin b/src/tools/miri/priroda/tests/ui/dap_threads.stdin new file mode 100644 index 0000000000000..a17c6406c9c73 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdin @@ -0,0 +1,9 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 46 + +{"seq":4,"type":"request","command":"threads"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdout b/src/tools/miri/priroda/tests/ui/dap_threads.stdout new file mode 100644 index 0000000000000..56702d4adc22e --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdout @@ -0,0 +1,13 @@ +Content-Length: {CONTENT_LENGTH} + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null} \ No newline at end of file diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 2f35beeceda8c..8ab1fcaae5225 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -73dc9167f1cd099e525c9ade2e068d1907b78564 +f73951df0a5566d94d13b7954acd9f4ab1fa3734 diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index d4fe89d4f0258..7e8c49bf9fba0 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -372,10 +372,7 @@ pub fn report_result<'tcx>( .. }) => { ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`) - bug!( - "This validation error should be impossible in Miri: {}", - res.to_string() - ); + bug!("This validation error should be impossible in Miri: {}", res.to_string()); } UndefinedBehavior(_) => "Undefined Behavior", ResourceExhaustion(_) => "resource exhaustion", diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 6953a27a39df8..a691cfc2df676 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -937,7 +937,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { target_feature: &str, ) -> InterpResult<'tcx, ()> { let this = self.eval_context_ref(); - if !this.tcx.sess.unstable_target_features.contains(&Symbol::intern(target_feature)) { + if !this.tcx.sess.internal_target_features.contains(&Symbol::intern(target_feature)) { throw_ub_format!( "attempted to call intrinsic `{intrinsic}` that requires missing target feature {target_feature}" ); diff --git a/src/tools/miri/src/intrinsics/math.rs b/src/tools/miri/src/intrinsics/math.rs index adb768e6bcffc..ad3881b0e6a6d 100644 --- a/src/tools/miri/src/intrinsics/math.rs +++ b/src/tools/miri/src/intrinsics/math.rs @@ -10,7 +10,7 @@ use crate::*; fn sqrt<'tcx, F: Float + FloatConvert + Into>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> { let [f] = check_intrinsic_arg_count(args)?; math::sqrt_op::(this, f, dest) @@ -45,7 +45,7 @@ fn is_host_unary_float_op(intrinsic_name: &str) -> Option<(FloatTy, HostUnaryFlo fn pow_intrinsic<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, @@ -69,7 +69,7 @@ where fn powi_intrinsic<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, @@ -98,7 +98,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { intrinsic_name: &str, _generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 0f55009db790b..7d7081fb609fb 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -53,12 +53,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let intrinsic_name = this.tcx.item_name(instance.def_id()); let intrinsic_name = intrinsic_name.as_str(); - // FIXME: avoid allocating memory - let dest = this.force_allocation(dest)?; - - let res = - this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)?; - res.jump_to_next_block(this, &dest, ret, Some(unwind), |this| { + let res = this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)?; + res.jump_to_next_block(this, dest, ret, Some(unwind), |this| { // We haven't handled the intrinsic, let's see if we can use a fallback body. if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden { throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`") @@ -88,7 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { intrinsic_name: &str, generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ret: Option, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); @@ -165,7 +161,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let link_name = this.tcx.codegen_fn_attrs(instance.def_id()).symbol_name.unwrap(); - // FIXME: avoid allocating memory + // These are anyway mostly vector intrinsics and vectors live in memory. let dest = this.force_allocation(dest)?; let res = 'handled: { @@ -250,7 +246,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // The rest either implements the logic, or falls back to `lookup_exported_symbol`. - res.jump_to_next_block(this, &dest, ret, None, |this| { + res.jump_to_next_block(this, &dest.clone().into(), ret, None, |this| { throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!( "can't call LLVM intrinsic `{link_name}` on architecture `{arch}`", arch = this.tcx.sess.target.arch, diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 74582bc58900e..1f2fd9a8a64df 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -14,7 +14,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, intrinsic_name: &str, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); match intrinsic_name { diff --git a/src/tools/miri/src/intrinsics/x86/mod.rs b/src/tools/miri/src/intrinsics/x86/mod.rs index d76d35cb722bc..25361a6435b0a 100644 --- a/src/tools/miri/src/intrinsics/x86/mod.rs +++ b/src/tools/miri/src/intrinsics/x86/mod.rs @@ -65,7 +65,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "sse2.pause" => { let [] = this.check_shim_sig_unadjusted(link_name, args)?; // Only exhibit the spin-loop hint behavior when SSE2 is enabled. - if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) { + if this.tcx.sess.internal_target_features.contains(&Symbol::intern("sse2")) { this.yield_active_thread(); } } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index f476614992041..29289df8d823f 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -12,12 +12,14 @@ use rand::rngs::StdRng; use rand::{RngExt, SeedableRng}; use rustc_abi::{Align, ExternAbi, Size}; use rustc_apfloat::{Float, FloatConvert}; +use rustc_ast::Mutability; use rustc_ast::expand::allocator::{self, SpecialAllocatorMethod}; use rustc_data_structures::either::Either; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; #[allow(unused)] use rustc_data_structures::static_assert_size; use rustc_hir::attrs::{InlineAttr, Linkage}; +use rustc_hir::def::DefKind; use rustc_log::tracing; use rustc_middle::middle::codegen_fn_attrs::TargetFeatureKind; use rustc_middle::mir; @@ -566,7 +568,7 @@ pub struct MiriMachine<'tcx> { /// Cache of `Instance` exported under the given `Symbol` name. /// `None` means no `Instance` exported under the given name is found. - pub(crate) exported_symbols_cache: FxHashMap>>, + pub(crate) exported_symbols_cache: RefCell>>>, /// Equivalent setting as RUST_BACKTRACE on encountering an error. pub(crate) backtrace_style: BacktraceStyle, @@ -776,7 +778,7 @@ impl<'tcx> MiriMachine<'tcx> { static_roots: Vec::new(), profiler, string_cache: Default::default(), - exported_symbols_cache: FxHashMap::default(), + exported_symbols_cache: RefCell::new(FxHashMap::default()), backtrace_style: config.backtrace_style, user_relevant_crates, extern_statics: FxHashMap::default(), @@ -1203,14 +1205,14 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { if attrs .target_features .iter() - .any(|feature| !ecx.tcx.sess.target_features.contains(&feature.name)) + .any(|feature| !ecx.tcx.sess.internal_target_features.contains(&feature.name)) { let unavailable = attrs .target_features .iter() .filter(|&feature| { feature.kind != TargetFeatureKind::Implied - && !ecx.tcx.sess.target_features.contains(&feature.name) + && !ecx.tcx.sess.internal_target_features.contains(&feature.name) }) .fold(String::new(), |mut s, feature| { if !s.is_empty() { @@ -1462,6 +1464,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { Some(_) => ecx.machine.extern_statics_imports.get(&link_name), }; if let Some(&ptr) = ptr { + ecx.check_shim_symbol_clash(link_name)?; // Various parts of the engine rely on `get_alloc_info` for size and alignment // information. That uses the type information of this static. // Make sure it matches the Miri allocation for this. @@ -1503,7 +1506,61 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { .expect("`missing_weak_symbol` should have been initialized"), ) } else { - throw_unsup_format!("extern static `{link_name}` is not supported by Miri") + // Look for a Rust static with this symbol name in the crate graph. + let Some(instance) = ecx.lookup_exported_static(link_name)? else { + throw_unsup_format!("extern static `{link_name}` is not supported by Miri"); + }; + // Evaluate the static to get its allocation. + let place = ecx.eval_global(instance)?; + let static_ptr = place.ptr().into_pointer_or_addr().unwrap(); + // Validate the allocation matches the declared size and alignment. + let alloc_id = static_ptr.provenance.get_alloc_id().unwrap(); + let info = ecx.get_alloc_info(alloc_id); + if extern_decl_layout.size != info.size || extern_decl_layout.align.abi != info.align { + throw_ub_format!( + "extern static `{link_name}` has been declared as `{krate}::{name}` \ + with a size of {decl_size} bytes and alignment of {decl_align} bytes, \ + but the exported static with that name has a size of {shim_size} bytes and \ + alignment of {shim_align} bytes", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + decl_size = extern_decl_layout.size.bytes(), + decl_align = extern_decl_layout.align.bytes(), + shim_size = info.size.bytes(), + shim_align = info.align.bytes(), + ) + } + // Check that the mutability of the declared static matches that of the backing. + // If the backing static can be modified (because it is a `static mut`, or because + // it is a `static` whose type has interior mutability) while the declaration here + // is a non-mut `static` with a `Freeze` type, then the compiler's assumption that + // the value never changes may be violated, so this may cause UB. + // This is somehow defensive, as the allocation might be mutable but no mutation + // ever happens, but this is probably the most precise thing we can do. + // Specially, the second case is very defensive and we may be able to lift it. + let DefKind::Static { mutability, .. } = ecx.tcx.def_kind(def_id) else { + unreachable!("`{def_id:?}` is not a static"); + }; + let decl_is_mut = + !(mutability == Mutability::Not && ecx.type_is_freeze(extern_decl_layout.ty)); + let backing_is_mut = ecx.get_alloc_mutability(alloc_id)? == Mutability::Mut; + if !decl_is_mut && backing_is_mut { + throw_ub_format!( + "extern static `{krate}::{name}` is declared as an immutable `static`, \ + but the backing static is mutable", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + ) + } + if decl_is_mut && !backing_is_mut { + throw_ub_format!( + "extern static `{krate}::{name}` is declared as an mutable `static`, \ + but the backing static is immutable", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + ) + } + interp_ok(static_ptr) } } diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs index f67831839b711..1cacc9dde86f8 100644 --- a/src/tools/miri/src/math.rs +++ b/src/tools/miri/src/math.rs @@ -462,7 +462,7 @@ pub(crate) fn sqrt(x: F) -> F { pub fn sqrt_op<'tcx, F: Float + FloatConvert + Into>( this: &mut MiriInterpCx<'tcx>, f: &OpTy<'tcx>, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> { let f: F = this.read_scalar(f)?.to_float()?; // Sqrt is specified to be fully precise. @@ -536,7 +536,7 @@ pub fn host_unary_float_op<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, f: &OpTy<'tcx>, op: HostUnaryFloatOp, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 683e9095f9b0c..80f5369a9bbc1 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -57,7 +57,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match *shim { Either::Left(other_fn) => { let handler = this - .lookup_exported_symbol(other_fn)? + .lookup_exported_fn(other_fn)? .expect("missing alloc error handler symbol"); return interp_ok(Some(handler)); } @@ -74,8 +74,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The rest either implements the logic, or falls back to `lookup_exported_symbol`. let res = this.emulate_foreign_item_inner(link_name, abi, args, &dest)?; - res.jump_to_next_block(this, &dest, ret, Some(unwind), |this| { - if let Some(body) = this.lookup_exported_symbol(link_name)? { + res.jump_to_next_block(this, &dest.clone().into(), ret, Some(unwind), |this| { + if let Some(body) = this.lookup_exported_fn(link_name)? { return interp_ok(Some(body)); } @@ -110,17 +110,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - /// Lookup the body of a function that has `link_name` as the symbol name. + /// Lookup the instance that has `link_name` as the symbol name. fn lookup_exported_symbol( - &mut self, + &self, link_name: Symbol, - ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> { - let this = self.eval_context_mut(); + ) -> InterpResult<'tcx, Option>> { + let this = self.eval_context_ref(); let tcx = this.tcx.tcx; // If the result was cached, just return it. // (Cannot use `or_insert` since the code below might have to throw an error.) - let entry = this.machine.exported_symbols_cache.entry(link_name); + let mut cache = this.machine.exported_symbols_cache.borrow_mut(); + let entry = cache.entry(link_name); let instance = *match entry { Entry::Occupied(e) => e.into_mut(), Entry::Vacant(e) => { @@ -206,25 +207,49 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) })?; - // Once we identified the instance corresponding to the symbol, ensure - // it is a function. It is okay to encounter non-functions in the search above - // as long as the final instance we arrive at is a function. - if let Some(SymbolTarget { instance, .. }) = symbol_target { - if !matches!(tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) { - throw_ub_format!( - "attempt to call an exported symbol that is not defined as a function" - ); - } - } - e.insert(symbol_target.map(|SymbolTarget { instance, .. }| instance)) } }; + drop(cache); + interp_ok(instance) + } + + /// Lookup the body of a function that has `link_name` as the symbol name. + fn lookup_exported_fn( + &self, + link_name: Symbol, + ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> { + let this = self.eval_context_ref(); + let instance = this.lookup_exported_symbol(link_name)?; + if let Some(instance) = &instance { + if !matches!(this.tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) { + throw_ub_format!( + "attempt to call an exported symbol that is not defined as a function" + ); + } + } match instance { - None => interp_ok(None), // no symbol with this name + None => interp_ok(None), Some(instance) => interp_ok(Some((this.load_mir(instance.def, None)?, instance))), } } + + /// Lookup the instance of a static that has `link_name` as the symbol name. + fn lookup_exported_static( + &self, + link_name: Symbol, + ) -> InterpResult<'tcx, Option>> { + let this = self.eval_context_ref(); + let instance = this.lookup_exported_symbol(link_name)?; + if let Some(instance) = &instance { + if !matches!(this.tcx.def_kind(instance.def_id()), DefKind::Static { .. }) { + throw_ub_format!( + "attempt to access an exported symbol `{link_name}` that is not defined as a static" + ); + } + } + interp_ok(instance) + } } impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index 56466b5f3a1f7..a41f1a5c8ec42 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -43,7 +43,7 @@ impl EmulateItemResult { pub fn jump_to_next_block<'tcx, T: Default>( self, ecx: &mut crate::MiriInterpCx<'tcx>, - dest: &crate::MPlaceTy<'tcx>, + dest: &crate::PlaceTy<'tcx>, ret: Option, unwind: Option, not_supported: impl FnOnce(&mut crate::MiriInterpCx<'tcx>) -> crate::InterpResult<'tcx, T>, @@ -52,7 +52,7 @@ impl EmulateItemResult { match self { EmulateItemResult::NeedsReturn => { - trace!("{:?}", ecx.dump_place(&dest.clone().into())); + trace!("{:?}", ecx.dump_place(dest)); ecx.return_to_block(ret)?; interp_ok(T::default()) } diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index b0b4bca3f517c..d40e9039f2b60 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -200,30 +200,29 @@ fn check_shim_abi<'tcx>( interp_ok(()) } -fn check_shim_symbol_clash<'tcx>( - this: &mut MiriInterpCx<'tcx>, - link_name: Symbol, -) -> InterpResult<'tcx, ()> { - if let Some((body, instance)) = this.lookup_exported_symbol(link_name)? { - // If compiler-builtins is providing the symbol, then don't treat it as a clash. - // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased - // performance. Note that this means we won't catch any undefined behavior in - // compiler-builtins when running other crates, but Miri can still be run on - // compiler-builtins itself (or any crate that uses it as a normal dependency) - if this.tcx.is_compiler_builtins(instance.def_id().krate) { - return interp_ok(()); - } +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Ensure the given symbol is not exported by the program. + fn check_shim_symbol_clash(&self, link_name: Symbol) -> InterpResult<'tcx, ()> { + let this = self.eval_context_ref(); + if let Some(instance) = this.lookup_exported_symbol(link_name)? { + // If compiler-builtins is providing the symbol, then don't treat it as a clash. + // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased + // performance. Note that this means we won't catch any undefined behavior in + // compiler-builtins when running other crates, but Miri can still be run on + // compiler-builtins itself (or any crate that uses it as a normal dependency) + if this.tcx.is_compiler_builtins(instance.def_id().krate) { + return interp_ok(()); + } - throw_machine_stop!(TerminationInfo::SymbolShimClashing { - link_name, - span: body.span.data(), - }) + throw_machine_stop!(TerminationInfo::SymbolShimClashing { + link_name, + span: this.tcx.def_span(instance.def_id()).data(), + }) + } + interp_ok(()) } - interp_ok(()) -} -impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} -pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_shim_sig_lenient<'a, const N: usize>( &mut self, abi: &FnAbi<'tcx, Ty<'tcx>>, @@ -231,8 +230,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { link_name: Symbol, args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if abi.conv != exp_abi { throw_ub_format!( @@ -283,7 +281,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Check everything. check_shim_abi(this, callee_fn_abi, caller_fn_abi)?; - check_shim_symbol_clash(this, link_name)?; + this.check_shim_symbol_clash(link_name)?; // Return arguments. if let Ok(ops) = caller_args.try_into() { @@ -304,8 +302,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { where &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if abi.conv != exp_abi { throw_ub_format!( @@ -342,8 +339,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { assert!(link_name.as_str().starts_with("llvm.")); - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if let Ok(ops) = args.try_into() { return interp_ok(ops); diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index c72d85bb87341..8594e7ea35e4e 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1255,88 +1255,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn macos_readdir_r( - &mut self, - dirp_op: &OpTy<'tcx>, - entry_op: &OpTy<'tcx>, - result_op: &OpTy<'tcx>, - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - - this.assert_target_os(Os::MacOs, "readdir_r"); - - let dirp = this.read_target_usize(dirp_op)?; - let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?; - - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`readdir_r`", reject_with)?; - // Return error code, do *not* set `errno`. - return interp_ok(this.eval_libc("EBADF")); - } - - let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| { - err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") - })?; - interp_ok(match open_dir.next_host_entry() { - Some(Ok(dir_entry)) => { - let dir_entry = this.dir_entry_fields(dir_entry)?; - // Write into entry, write pointer to result, return 0 on success. - // The name is written with write_os_str_to_c_str, while the rest of the - // dirent struct is written using write_int_fields. - - // For reference, on macOS this looks like: - // pub struct dirent { - // pub d_ino: u64, - // pub d_seekoff: u64, - // pub d_reclen: u16, - // pub d_namlen: u16, - // pub d_type: u8, - // pub d_name: [c_char; 1024], - // } - - let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?; - - // Write the name. - let name_place = this.project_field_named(&entry_place, "d_name")?; - let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( - &dir_entry.name, - name_place.ptr(), - name_place.layout.size.bytes(), - )?; - if !name_fits { - throw_unsup_format!( - "a directory entry had a name too large to fit in libc::dirent" - ); - } - - // Write the other fields. - this.write_int_fields_named( - &[ - ("d_reclen", entry_place.layout.size.bytes().into()), - ("d_namlen", file_name_buf_len.strict_sub(1).into()), - ("d_type", dir_entry.d_type.into()), - ("d_ino", dir_entry.ino.into()), - ("d_seekoff", 0), - ], - &entry_place, - )?; - this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; - - Scalar::from_i32(0) - } - None => { - // end of stream: return 0, assign *result=NULL - this.write_null(&result_place)?; - Scalar::from_i32(0) - } - Some(Err(e)) => { - // return positive error number on error (do *not* set last error) - this.host_error_to_errnum(e)? - } - }) - } - fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 3289d569173f4..9254031a8a4d1 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -71,12 +71,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.readdir(dirp, dest)?; } - "readdir_r" | "readdir_r$INODE64" => { - let [dirp, entry, result] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_readdir_r(dirp, entry, result)?; - this.write_scalar(result, dest)?; - } "realpath$DARWIN_EXTSN" => { let [path, resolved_path] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/unwind.rs b/src/tools/miri/src/shims/unwind.rs index e8a804a8b023b..820a78725eedc 100644 --- a/src/tools/miri/src/shims/unwind.rs +++ b/src/tools/miri/src/shims/unwind.rs @@ -62,7 +62,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { try_fn: &OpTy<'tcx>, data: &OpTy<'tcx>, catch_fn: &OpTy<'tcx>, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ret: Option, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -82,6 +82,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let try_fn = this.read_pointer(try_fn)?; let data = this.read_immediate(data)?; let catch_fn = this.read_pointer(catch_fn)?; + let dest = this.force_allocation(dest)?; // needs to be valid across fn calls // Now we make a function call, and pass `data` as first and only argument. let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?; @@ -97,14 +98,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). - this.write_null(dest)?; + this.write_null(&dest)?; // In unwind mode, we tag this frame with the extra data needed to catch unwinding. // This lets `handle_stack_pop` (below) know that we should stop unwinding // when we pop this frame. if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind { this.frame_mut().extra.catch_unwind = - Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret }); + Some(CatchUnwindData { catch_fn, data, dest, ret }); } interp_ok(()) diff --git a/src/tools/miri/tests/fail/extern_static/clashing.rs b/src/tools/miri/tests/fail/extern_static/clashing.rs new file mode 100644 index 0000000000000..266f4deb1aa83 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/clashing.rs @@ -0,0 +1,15 @@ +#[no_mangle] +static FOO: u8 = 1; +//~^ HELP: it's first defined here, in crate `clashing` + +#[export_name = "FOO"] +static BAR: u8 = 2; +//~^ HELP: then it's defined here again, in crate `clashing` + +fn main() { + extern "Rust" { + static FOO: u8; + } + let _val = &raw const FOO; + //~^ ERROR: multiple definitions of symbol `FOO` +} diff --git a/src/tools/miri/tests/fail/extern_static/clashing.stderr b/src/tools/miri/tests/fail/extern_static/clashing.stderr new file mode 100644 index 0000000000000..0c0c362639ac2 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/clashing.stderr @@ -0,0 +1,21 @@ +error: multiple definitions of symbol `FOO` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | let _val = &raw const FOO; + | ^^^ error occurred here + | +help: it's first defined here, in crate `clashing` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | static FOO: u8 = 1; + | ^^^^^^^^^^^^^^ +help: then it's defined here again, in crate `clashing` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | static BAR: u8 = 2; + | ^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static_in_const.rs b/src/tools/miri/tests/fail/extern_static/in_const.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static_in_const.rs rename to src/tools/miri/tests/fail/extern_static/in_const.rs diff --git a/src/tools/miri/tests/fail/extern_static_in_const.stderr b/src/tools/miri/tests/fail/extern_static/in_const.stderr similarity index 89% rename from src/tools/miri/tests/fail/extern_static_in_const.stderr rename to src/tools/miri/tests/fail/extern_static/in_const.stderr index f0f0966ea8afe..7cbc11bc6e4cf 100644 --- a/src/tools/miri/tests/fail/extern_static_in_const.stderr +++ b/src/tools/miri/tests/fail/extern_static/in_const.stderr @@ -1,5 +1,5 @@ error: unsupported operation: extern static `E` is not supported by Miri - --> tests/fail/extern_static_in_const.rs:LL:CC + --> tests/fail/extern_static/in_const.rs:LL:CC | LL | let _val = X; | ^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs new file mode 100644 index 0000000000000..02340b0c94dc5 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs @@ -0,0 +1,13 @@ +//! We want to reserve rights to be able to optimize statics declared as immutable, +//! so we defensively disallow immutable statics pointing to mutable allocations. + +#[export_name = "S"] +static mut BACKING_S: i32 = 42; + +fn main() { + extern "C" { + static S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an immutable `static`, but the backing static is mutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr new file mode 100644 index 0000000000000..11c16810edb9d --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch1::main::S` is declared as an immutable `static`, but the backing static is mutable + --> tests/fail/extern_static/mut_mismatch1.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs new file mode 100644 index 0000000000000..d2c44850f983e --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs @@ -0,0 +1,17 @@ +//! We want to reserve rights to be able to optimize statics declared as immutable, +//! so we defensively disallow immutable statics pointing to mutable allocations. + +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[export_name = "S"] +static INTERIOR_MUT_S: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn main() { + extern "C" { + static S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an immutable `static`, but the backing static is mutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr new file mode 100644 index 0000000000000..8d7d886c10032 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch2::main::S` is declared as an immutable `static`, but the backing static is mutable + --> tests/fail/extern_static/mut_mismatch2.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs new file mode 100644 index 0000000000000..c33066341ff13 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs @@ -0,0 +1,13 @@ +//! We want to reserve rights to be able to inject implicit writes to mutable declared statics, +//! so we defensively disallow mutable statics pointing to immutable allocations. + +#[export_name = "S"] +static IMMUT_S: i32 = 42; + +fn main() { + extern "C" { + static mut S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an mutable `static`, but the backing static is immutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr new file mode 100644 index 0000000000000..dcaff7c4bdf51 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch3::main::S` is declared as an mutable `static`, but the backing static is immutable + --> tests/fail/extern_static/mut_mismatch3.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs b/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs new file mode 100644 index 0000000000000..36bd4e87104f7 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs @@ -0,0 +1,15 @@ +//@only-target: linux # we need a specific extern supported on this target + +extern "C" { + static mut environ: *const *const u8; +} + +#[export_name = "environ"] +static mut MY_ENVIRON: *const *const u8 = std::ptr::null(); +//~^ HELP: the `environ` symbol is defined here + +fn main() { + let _val = &raw const MY_ENVIRON; + let _val = &raw const environ; + //~^ ERROR: found `environ` symbol definition that clashes with a built-in shim +} diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr b/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr new file mode 100644 index 0000000000000..59da90d986b39 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr @@ -0,0 +1,16 @@ +error: found `environ` symbol definition that clashes with a built-in shim + --> tests/fail/extern_static/shim_clashing1.rs:LL:CC + | +LL | let _val = &raw const environ; + | ^^^^^^^ error occurred here + | +help: the `environ` symbol is defined here + --> tests/fail/extern_static/shim_clashing1.rs:LL:CC + | +LL | static mut MY_ENVIRON: *const *const u8 = std::ptr::null(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs b/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs new file mode 100644 index 0000000000000..ae3f1380bb386 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs @@ -0,0 +1,13 @@ +//@only-target: linux # we need a specific extern supported on this target + +#[export_name = "environ"] +fn my_environ() {} +//~^ HELP: the `environ` symbol is defined here + +fn main() { + extern "C" { + static environ: *const *const u8; + } + let _val = &raw const environ; + //~^ ERROR: found `environ` symbol definition that clashes with a built-in shim +} diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr b/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr new file mode 100644 index 0000000000000..fd5f02a06e85f --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr @@ -0,0 +1,16 @@ +error: found `environ` symbol definition that clashes with a built-in shim + --> tests/fail/extern_static/shim_clashing2.rs:LL:CC + | +LL | let _val = &raw const environ; + | ^^^^^^^ error occurred here + | +help: the `environ` symbol is defined here + --> tests/fail/extern_static/shim_clashing2.rs:LL:CC + | +LL | fn my_environ() {} + | ^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/type_confusion.rs b/src/tools/miri/tests/fail/extern_static/type_confusion.rs new file mode 100644 index 0000000000000..6881cb8c178a7 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/type_confusion.rs @@ -0,0 +1,14 @@ +#[no_mangle] +static FOO: u8 = 42; + +fn main() { + extern "Rust" { + static FOO: bool; + } + // Type confusion between u8 (value 42) and bool: reading as bool is UB + // because 42 is not a valid boolean value (must be 0 or 1). + unsafe { + (&raw const FOO).read(); + //~^ ERROR: /constructing invalid value of type bool/ + } +} diff --git a/src/tools/miri/tests/fail/extern_static/type_confusion.stderr b/src/tools/miri/tests/fail/extern_static/type_confusion.stderr new file mode 100644 index 0000000000000..772f32c3b952b --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/type_confusion.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: constructing invalid value of type bool: encountered 0x2a, but expected a boolean + --> tests/fail/extern_static/type_confusion.rs:LL:CC + | +LL | (&raw const FOO).read(); + | ^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static.rs b/src/tools/miri/tests/fail/extern_static/unsupported.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static.rs rename to src/tools/miri/tests/fail/extern_static/unsupported.rs diff --git a/src/tools/miri/tests/fail/extern_static.stderr b/src/tools/miri/tests/fail/extern_static/unsupported.stderr similarity index 90% rename from src/tools/miri/tests/fail/extern_static.stderr rename to src/tools/miri/tests/fail/extern_static/unsupported.stderr index e4c51c0345d4c..02a6fdfa5be48 100644 --- a/src/tools/miri/tests/fail/extern_static.stderr +++ b/src/tools/miri/tests/fail/extern_static/unsupported.stderr @@ -1,5 +1,5 @@ error: unsupported operation: extern static `FOO` is not supported by Miri - --> tests/fail/extern_static.rs:LL:CC + --> tests/fail/extern_static/unsupported.rs:LL:CC | LL | let _val = std::ptr::addr_of!(FOO); | ^^^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/write_immutable.rs b/src/tools/miri/tests/fail/extern_static/write_immutable.rs new file mode 100644 index 0000000000000..d9420ecb86215 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/write_immutable.rs @@ -0,0 +1,29 @@ +//! This test is very similar to `mut_mismatch3`, but actually writes to the static. +//! In case we relaxed `mut_mismatch3` UB, we still want this to remain UB. + +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[no_mangle] +static IMMUT: i32 = 42; + +#[no_mangle] +static INTERIOR_MUT: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn main() { + unsafe { + extern "C" { + static mut INTERIOR_MUT: i32; + } + (&raw mut INTERIOR_MUT).write(7); + } + + unsafe { + extern "C" { + static mut IMMUT: i32; + } + (&raw mut IMMUT).write(7); + //~^ ERROR: is declared as an mutable `static`, but the backing static is immutable + } +} diff --git a/src/tools/miri/tests/fail/extern_static/write_immutable.stderr b/src/tools/miri/tests/fail/extern_static/write_immutable.stderr new file mode 100644 index 0000000000000..74259b36f351d --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/write_immutable.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `write_immutable::main::IMMUT` is declared as an mutable `static`, but the backing static is immutable + --> tests/fail/extern_static/write_immutable.rs:LL:CC + | +LL | (&raw mut IMMUT).write(7); + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/wrong_size.rs b/src/tools/miri/tests/fail/extern_static/wrong_size.rs new file mode 100644 index 0000000000000..d8a53a04df88e --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_size.rs @@ -0,0 +1,10 @@ +#[no_mangle] +static FOO: u8 = 42; + +fn main() { + extern "Rust" { + static FOO: u16; + } + let _val = unsafe { (&raw const FOO).read() }; + //~^ ERROR: extern static `FOO` has been declared as `wrong_size::main::FOO` with a size of 2 bytes +} diff --git a/src/tools/miri/tests/fail/extern_static/wrong_size.stderr b/src/tools/miri/tests/fail/extern_static/wrong_size.stderr new file mode 100644 index 0000000000000..8cea8376dfd2f --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_size.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `FOO` has been declared as `wrong_size::main::FOO` with a size of 2 bytes and alignment of 2 bytes, but the exported static with that name has a size of 1 bytes and alignment of 1 bytes + --> tests/fail/extern_static/wrong_size.rs:LL:CC + | +LL | let _val = unsafe { (&raw const FOO).read() }; + | ^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.rs b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static_wrong_size.rs rename to src/tools/miri/tests/fail/extern_static/wrong_size_shim.rs diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr similarity index 65% rename from src/tools/miri/tests/fail/extern_static_wrong_size.stderr rename to src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr index 0862f97792872..d3a0f0205ee3b 100644 --- a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr +++ b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr @@ -1,5 +1,5 @@ -error: unsupported operation: extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes - --> tests/fail/extern_static_wrong_size.rs:LL:CC +error: unsupported operation: extern static `environ` has been declared as `wrong_size_shim::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes + --> tests/fail/extern_static/wrong_size_shim.rs:LL:CC | LL | let _val = unsafe { environ }; | ^^^^^^^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/wrong_type.rs b/src/tools/miri/tests/fail/extern_static/wrong_type.rs new file mode 100644 index 0000000000000..81683b3ee9da2 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_type.rs @@ -0,0 +1,11 @@ +#[allow(non_snake_case)] +#[no_mangle] +fn FOO() {} + +fn main() { + extern "Rust" { + static FOO: (); + } + let _val = &raw const FOO; + //~^ ERROR: attempt to access an exported symbol `FOO` that is not defined as a static +} diff --git a/src/tools/miri/tests/fail/extern_static/wrong_type.stderr b/src/tools/miri/tests/fail/extern_static/wrong_type.stderr new file mode 100644 index 0000000000000..ba38acd3ce16b --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_type.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: attempt to access an exported symbol `FOO` that is not defined as a static + --> tests/fail/extern_static/wrong_type.rs:LL:CC + | +LL | let _val = &raw const FOO; + | ^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr index 0e2b4da5c0a03..5c013b862a7a0 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr @@ -7,11 +7,8 @@ LL | malloc(0); help: the `malloc` symbol is defined here --> tests/fail/function_calls/exported_symbol_shim_clashing.rs:LL:CC | -LL | / extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { -LL | | -LL | | unreachable!() -LL | | } - | |_^ +LL | extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 29f32df2dd0eb..647012eb8f7cf 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -60,8 +60,6 @@ fn main() { test_ioctl(); test_opendir_closedir(); test_readdir(); - #[cfg(target_os = "macos")] - test_readdir_r(); #[cfg(target_os = "linux")] test_statx_on_file_path(); #[cfg(target_os = "linux")] @@ -1023,52 +1021,6 @@ fn test_readdir() { remove_dir(&dir_path).unwrap(); } -// We only support `readdir_r` on macOS. -// (It is deprecated so we don't want to add more support.) -#[cfg(target_os = "macos")] -fn test_readdir_r() { - use std::fs::{create_dir, remove_dir, write}; - use std::mem::MaybeUninit; - - let dir_path = utils::prepare_dir("miri_test_libc_readdir_r"); - create_dir(&dir_path).ok(); - - // Create test files - let file1 = dir_path.join("file1.txt"); - let file2 = dir_path.join("file2.txt"); - write(&file1, b"content1").unwrap(); - write(&file2, b"content2").unwrap(); - - let c_path = CString::new(dir_path.as_os_str().as_bytes()).unwrap(); - - unsafe { - let dirp = libc::opendir(c_path.as_ptr()); - assert!(!dirp.is_null()); - let mut entries = Vec::new(); - loop { - let mut entry: MaybeUninit = MaybeUninit::uninit(); - let mut result: *mut libc::dirent = std::ptr::null_mut(); - let ret = libc::readdir_r(dirp, entry.as_mut_ptr(), &mut result); - assert_eq!(ret, 0); - let entry_ptr = result; - if entry_ptr.is_null() { - break; - } - let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; - let name = CStr::from_ptr(name_ptr); - let name_str = name.to_string_lossy(); - entries.push(name_str.into_owned()); - } - assert_eq!(libc::closedir(dirp), 0); - entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); - } - - remove_file(&file1).unwrap(); - remove_file(&file2).unwrap(); - remove_dir(&dir_path).unwrap(); -} - /// Check that all common fields of a `stat` struct are initialized. pub fn check_stat_fields(stat: &libc::stat) { let _st_nlink = stat.st_nlink; diff --git a/src/tools/miri/tests/pass/extern_static.rs b/src/tools/miri/tests/pass/extern_static.rs new file mode 100644 index 0000000000000..70b8ff304c086 --- /dev/null +++ b/src/tools/miri/tests/pass/extern_static.rs @@ -0,0 +1,83 @@ +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[no_mangle] +static FOO: u8 = 42; + +#[export_name = "BAR_EXPORTED"] +static BAR_LOCAL_NAME: u16 = 1000; + +#[no_mangle] +static mut MUTABLE_STATIC: i32 = -1; + +#[export_name = "MY_LINK_NAME"] +static RUST_SYMBOL: u32 = 7; + +#[no_mangle] +static FOO_U32: u32 = 42; + +#[no_mangle] +static INTERIOR_MUT: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn increase_mutable_static_by_original_def(add_val: i32) { + unsafe { + let new_val = (&raw mut MUTABLE_STATIC).read() + add_val; + (&raw mut MUTABLE_STATIC).write(new_val); + } +} + +fn main() { + // The loop ensures we hit both the uncached and cached case. + for _ in 0..3 { + extern "Rust" { + static FOO: u8; + } + + assert_eq!(unsafe { (&raw const FOO).read() }, 42); + + extern "C" { + static BAR_EXPORTED: u16; + } + + assert_eq!(unsafe { (&raw const BAR_EXPORTED).read() }, 1000); + + extern "C" { + #[link_name = "MY_LINK_NAME"] + static EXTERN_STATIC: u32; + } + + assert_eq!(unsafe { (&raw const EXTERN_STATIC).read() }, 7); + + // Ensure that SyncUnsafeCell and `static mut` are interchangable. + extern "C" { + #[link_name = "INTERIOR_MUT"] + static mut INTERIOR_MUT_AS_MUTABLE_STATIC: i32; + #[link_name = "MUTABLE_STATIC"] + static MUTABLE_STATIC_AS_INTERIOR_MUT: SyncUnsafeCell; + } + unsafe { + (&raw mut INTERIOR_MUT_AS_MUTABLE_STATIC).write(7); + MUTABLE_STATIC_AS_INTERIOR_MUT.get().write(3); + } + } + + extern "Rust" { + static mut MUTABLE_STATIC: i32; + } + + // Check what happens if we mix accesses via the two aliases: the original + // definition at the top of the file, and the extern declaration just above. + unsafe { + assert_eq!((&raw const MUTABLE_STATIC).read(), 3); + (&raw mut MUTABLE_STATIC).write(32); + increase_mutable_static_by_original_def(10); + assert_eq!((&raw const MUTABLE_STATIC).read(), 42); + } + + extern "Rust" { + static FOO_U32: i32; + } + // This is like a transmute between raw pointers, so not UB. + assert_eq!(unsafe { (&raw const FOO_U32).read() }, 42i32); +} diff --git a/tests/assembly-llvm/targets/targets-elf.rs b/tests/assembly-llvm/targets/targets-elf.rs index 0f9f68cfde787..49bced1dd5bd2 100644 --- a/tests/assembly-llvm/targets/targets-elf.rs +++ b/tests/assembly-llvm/targets/targets-elf.rs @@ -46,6 +46,9 @@ //@ revisions: aarch64_unknown_illumos //@ [aarch64_unknown_illumos] compile-flags: --target aarch64-unknown-illumos //@ [aarch64_unknown_illumos] needs-llvm-components: aarch64 +//@ revisions: aarch64_unknown_l4re_uclibc +//@ [aarch64_unknown_l4re_uclibc] compile-flags: --target aarch64-unknown-l4re-uclibc +//@ [aarch64_unknown_l4re_uclibc] needs-llvm-components: aarch64 //@ revisions: aarch64_unknown_linux_gnu //@ [aarch64_unknown_linux_gnu] compile-flags: --target aarch64-unknown-linux-gnu //@ [aarch64_unknown_linux_gnu] needs-llvm-components: aarch64 diff --git a/tests/pretty/delegation/self-mapping-output.pp b/tests/pretty/delegation/self-mapping-output.pp index 84e98d6e97b06..5bce43315e1d0 100644 --- a/tests/pretty/delegation/self-mapping-output.pp +++ b/tests/pretty/delegation/self-mapping-output.pp @@ -24,7 +24,7 @@ struct W(S); impl Trait for W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } //~^ WARN: function cannot return without recursing [unconditional_recursion] @@ -34,7 +34,7 @@ impl W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } #[attr = Inline(Hint)] diff --git a/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs new file mode 100644 index 0000000000000..7886cedd3a10a --- /dev/null +++ b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs @@ -0,0 +1,17 @@ +// Regression test for #142559 +//@ build-pass +//@ compile-flags: --crate-type=lib +#![feature(async_drop)] +#![allow(incomplete_features)] + +//@ edition: 2024 + +async fn run(f: impl Fn() -> F) { + f().await; +} + +pub async fn async_drop_async_closure() { + let x = async || async {}.await; + + run(x).await; +} diff --git a/tests/ui/c-variadic/not-async.stderr b/tests/ui/c-variadic/not-async.stderr index 921210382236c..9a81e0ce270d6 100644 --- a/tests/ui/c-variadic/not-async.stderr +++ b/tests/ui/c-variadic/not-async.stderr @@ -14,21 +14,19 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/not-async.rs:4:65 | LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of fn_cannot_be_async()}` captures lifetime `'_` + | ----------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of fn_cannot_be_async()}` captures the anonymous lifetime as defined here error[E0700]: hidden type for `impl Future` captures lifetime that does not appear in bounds --> $DIR/not-async.rs:11:73 | LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of S::method_cannot_be_async()}` captures lifetime `'_` + | --------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of S::method_cannot_be_async()}` captures the anonymous lifetime as defined here error: aborting due to 4 previous errors diff --git a/tests/ui/c-variadic/variadic-ffi-4.stderr b/tests/ui/c-variadic/variadic-ffi-4.stderr index d53f1f527748c..a92a5fd4bf61d 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.stderr +++ b/tests/ui/c-variadic/variadic-ffi-4.stderr @@ -30,9 +30,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2` | @@ -44,9 +44,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1` | diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs new file mode 100644 index 0000000000000..6ef2a4b72559c --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs @@ -0,0 +1,72 @@ +#![feature(fn_delegation)] + +mod pin_box_self { + use std::pin::Pin; + + trait MyAdd { + fn add(self, other: Self) -> Pin>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Pin> { + Pin::new(Box::new(self + other)) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Pin>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Pin>: From` is not satisfied + *self.0 + } +} + +mod many_froms { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>>> { + Box::new(Box::new(Box::new(Arc::new(Box::new(Rc::new(self + other)))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>>: From` is not satisfied + ******self.0 + } +} + +mod many_froms_2 { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>> { + Box::new(Arc::new(Rc::new(Box::new(Rc::new(self + other))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>: From` is not satisfied + *****self.0 + } +} + +fn main() { +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr new file mode 100644 index 0000000000000..d6290bc220966 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr @@ -0,0 +1,57 @@ +error[E0277]: the trait bound `Pin>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:19:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Pin>` + | +help: the trait `From` is not implemented for `Pin>` + but trait `From>` is implemented for it + --> $SRC_DIR/alloc/src/boxed/convert.rs:LL:COL + = help: for that trait implementation, expected `Box`, found `pin_box_self::W` + +error[E0277]: the trait bound `Box>>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:42:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | ******self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error[E0277]: the trait bound `Box>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:65:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *****self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.rs b/tests/ui/delegation/self-mapping-output-from-wrap.rs new file mode 100644 index 0000000000000..2dfff2882fbc6 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.rs @@ -0,0 +1,199 @@ +//@ run-pass +//@ check-run-results + +#![feature(fn_delegation)] + +mod simple_self { + trait MyAdd { + fn add(self, other: Self) -> Self; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> usize { + self + other + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(usize); + + reuse impl MyAdd for W { + println!("simple_self {self:?}"); + self.0 + } + + pub fn check() { + assert_eq!(W(1).add(W(2)), W(3)) + } +} + +mod box_self { + trait MyAdd { + fn add(self, other: Self) -> Box; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box { + Box::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box); + + reuse impl MyAdd for W { + println!("box_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Box::new(x)) + } + + assert_eq!(w(1).add(w(2)), Box::new(w(3))) + } +} + +mod rc_self { + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Rc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Rc { + Rc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Rc); + + reuse impl MyAdd for W { + println!("rc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Rc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Rc::new(w(3))) + } +} + +mod arc_self { + use std::sync::Arc; + + trait MyAdd { + fn add(self, other: Self) -> Arc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Arc { + Arc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Arc); + + reuse impl MyAdd for W { + println!("arc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Arc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Arc::new(w(3))) + } +} + +mod custom_froms { + #[derive(Debug)] + struct S1 { + a: A, + } + + impl From for S1 { + fn from(a: A) -> S1 { + S1 { a } + } + } + + #[derive(Debug)] + struct S2 { + t: T, + } + + impl From for S2 { + fn from(t: T) -> S2 { + S2 { t } + } + } + + #[derive(Debug)] + struct S3<'a, const C: usize, T, U, const B: bool> { + t: T, + pd: std::marker::PhantomData<&'a [(usize, U); C]> + } + + impl<'a, const C: usize, T, const B: bool> From for S3<'a, C, T, (), B> { + fn from(t: T) -> S3<'a, C, T, (), B> { + S3 { + t, + pd: std::marker::PhantomData::<&'a [(usize, ()); C]>, + } + } + } + + trait MyAdd: Sized { + fn add(self, other: Self) -> S1>>, (), true>>>; + } + + fn create_monster_struct(x: T) -> S1>>, (), true>>> { + S1::from(S1::from(S3::from(S2::from(S2::from(S1::from(x)))))) + } + + impl MyAdd for usize { + fn add(self, other: usize) -> S1>>, (), true>>> { + create_monster_struct(self + other) + } + } + + #[derive(Debug)] + struct W(S1>>, (), true>>>); + + impl From for S1>>, (), true>>> { + fn from(x: W) -> Self { + create_monster_struct(x) + } + } + + reuse impl MyAdd for W { + println!("custom_froms {self:?}"); + self.0.a.a.t.t.t.a + } + + pub fn check() { + fn w(x: usize) -> W { + W(create_monster_struct(x)) + } + + assert_eq!(w(1).add(w(2)).a.a.t.t.t.a.0.a.a.t.t.t.a, 3) + } +} + +fn main() { + simple_self::check(); + box_self::check(); + rc_self::check(); + arc_self::check(); + custom_froms::check(); +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout new file mode 100644 index 0000000000000..ee96199c54e07 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout @@ -0,0 +1,10 @@ +simple_self W(1) +simple_self W(2) +box_self W(1) +box_self W(2) +rc_self W(1) +rc_self W(2) +arc_self W(1) +arc_self W(2) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 1 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 2 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) diff --git a/tests/ui/inference/note-and-explain-ReVar-124973.stderr b/tests/ui/inference/note-and-explain-ReVar-124973.stderr index 3610fa82754b9..3ba76eb2ece18 100644 --- a/tests/ui/inference/note-and-explain-ReVar-124973.stderr +++ b/tests/ui/inference/note-and-explain-ReVar-124973.stderr @@ -8,11 +8,10 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/note-and-explain-ReVar-124973.rs:3:76 | LL | async unsafe extern "C" fn multiple_named_lifetimes<'a, 'b>(_: u8, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures lifetime `'_` + | ---------------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures the anonymous lifetime as defined here error: aborting due to 2 previous errors diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs index 6e4a05ac2a776..9b1eac8a6fa85 100644 --- a/tests/ui/splat/splat-fn-ptr-cast.rs +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -8,9 +8,8 @@ fn main() { // Bug #158603 regression test variants #[rustfmt::skip] - let _x: fn(#[rustc_splat] (f32,)) = None.unwrap(); - // FIXME(splat): causes an ICE until #158603 is fixed - //x(1.0); + let x: fn(#[rustc_splat] (f32,)) = None.unwrap(); + x(1.0); let x: fn((i32,)) = None.unwrap(); x((1,)); diff --git a/tests/ui/splat/splat-fn-ptr-generic.rs b/tests/ui/splat/splat-fn-ptr-generic.rs new file mode 100644 index 0000000000000..a41fb0aa2a43a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-generic.rs @@ -0,0 +1,58 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to generic functions. +//@ run-pass + +#![expect(incomplete_features)] +#![feature(splat, tuple_trait)] + +use std::fmt::Debug; +use std::marker::Tuple; + +fn generic(#[rustc_splat] a: T) -> String { + format!("{a:?}") +} + +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] +fn main() { + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic::<(u32, i8)> as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + // Now without explicit `as`, this requires turbofish + let fn_ptr: fn(#[rustc_splat] (f64, i8)) -> String = generic::<(f64, i8)>; + assert_eq!(fn_ptr(3.5, -2), "(3.5, -2)"); + assert_eq!(fn_ptr(3.5f64, -2i8), "(3.5, -2)"); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = generic; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); + + #[expect(unused_variables)] + let fn_ptr = generic::<(i8, u32, f64)>; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); +} diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs index fbe2d8c192f73..6473abce4b750 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -1,43 +1,113 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +use std::ptr; + +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (i8, u32) { + // Permute the returned values as a codegen test + (b, a) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (i8, f64, u32) { + // Permute the returned values as a codegen test + (b, c, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8)) - = tuple_args as *const fn(#[rustc_splat] (u32, i8)); + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = &(tuple_args as _); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_pp = &tuple_args; + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *const + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = ptr::from_ref(&(tuple_args as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_ref(&tuple_args); + // FIXME(unsafe): dereferencing *const should require unsafe + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *mut and non-terminal splat + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut(&mut (splat_non_terminal_arg as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_mut(&mut splat_non_terminal_arg); + // FIXME(unsafe): dereferencing *mut should require unsafe + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + + // Now with & as *const and non-terminal splat + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)); unsafe { - (*fn_pp)(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - (*fn_pp)(1u32, 2i8); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) = - splat_non_terminal_arg as *const fn(#[rustc_splat] (u32, i8), f64); + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as _); unsafe { - (*fn_pp)(1, 2, 3.5); - (*fn_pp)(1u32, 2i8, 3.5f64); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } } diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr deleted file mode 100644 index fd9fce68eb255..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-ptr-tuple.rs:31:9 - | -LL | (*fn_pp)(1, 2); - | ^^^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.rs b/tests/ui/splat/splat-fn-ptr-tuple-const.rs index 035c4db9ad5be..c95c20fc89772 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.rs @@ -1,17 +1,4 @@ //! Test using `#[rustc_splat]` on tuple arguments of generic function constants. -//! Currently ICEs (#158603), but if we fix it, we'll want to know and update this test to pass. - -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" #![allow(incomplete_features)] #![feature(splat, tuple_trait)] @@ -20,15 +7,12 @@ use std::marker::Tuple; fn f(#[rustc_splat] args: Args) {} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] const F2: fn(#[rustc_splat] (u8, u32)) = f::<(u8, u32)>; - const R2: () = F2(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R2: () = F2(1, 2); //~ ERROR function pointer calls are not allowed in constants - #[rustfmt::skip] const F1: fn(#[rustc_splat] ((u8, u32),)) = f::<((u8, u32),)>; - const R1: () = F1((1, 2)); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R1: () = F1((1, 2)); //~ ERROR function pointer calls are not allowed in constants } diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr index 1767782a9535e..f4b033445b3b2 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr @@ -1,52 +1,14 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:29:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:14:20 | LL | const R2: () = F2(1, 2); | ^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R2` -#1 [check_match] match-checking `main::R2` -#2 [mir_built] building MIR for `main::R2` -#3 [trivial_const] checking if `main::R2` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R2` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:33:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:17:20 | LL | const R1: () = F1((1, 2)); | ^^^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R1` -#1 [check_match] match-checking `main::R1` -#2 [mir_built] building MIR for `main::R1` -#3 [trivial_const] checking if `main::R1` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R1` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack error: aborting due to 2 previous errors diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.rs b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs new file mode 100644 index 0000000000000..a76f9f30cae32 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs @@ -0,0 +1,18 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to invalid simple functions. +//! Bug #158603 regression test +//@ run-fail +//@ check-run-results +//@ exec-env: RUST_BACKTRACE=0 + +//@ normalize-stderr: "thread '.*'" -> "thread 'NAME'" +//@ normalize-stderr: "note: run with.*\n" -> "" + +#![expect(incomplete_features)] +#![feature(splat)] + +fn main() { + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); + x(1); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr new file mode 100644 index 0000000000000..7536f99c69bd6 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr @@ -0,0 +1,3 @@ + +thread 'NAME' ($TID) panicked at $DIR/splat-fn-ptr-tuple-fail.rs:16:45: +called `Option::unwrap()` on a `None` value diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index 7fb06ad1c6bc1..23690865e9aaf 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,46 +1,43 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (u32, i8) { + (a, b) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (f64, i8, u32) { + // Permute the returned values as a codegen test + (c, b, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8)) = tuple_args; - fn_ptr(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - fn_ptr(1u32, 2i8); - - // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? - // Add a tupled test for each call if they are. - //fn_ptr((1, 2)); // ERROR this splatted function takes 2 arguments, but 1 was provided - - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) = splat_non_terminal_arg; - fn_ptr(1, 2, 3.5); - fn_ptr(1u32, 2i8, 3.5f64); - - // Bug #158603 regression test - #[rustfmt::skip] - let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); - x(1); + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) + = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) = tuple_args as _; + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + // Now without explicit `as` + let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) -> (f64, i8, u32) = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr deleted file mode 100644 index 4cc861cafe968..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - | -LL | fn_ptr(1, 2); - | ^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` -end of query stack -error: aborting due to 1 previous error -