diff options
Diffstat (limited to 'compiler/rustc_mir_transform/src')
| -rw-r--r-- | compiler/rustc_mir_transform/src/abort_unwinding_calls.rs | 7 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/add_retag.rs | 23 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/const_debuginfo.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/const_prop.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/const_prop_lint.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/ffi_unwind_calls.rs | 170 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/inline.rs | 100 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/inline/cycle.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/lib.rs | 5 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/nrvo.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/simplify.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/simplify_try.rs | 4 |
12 files changed, 275 insertions, 56 deletions
diff --git a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs index 11980382ffd..2c8389e532d 100644 --- a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs +++ b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs @@ -1,6 +1,5 @@ use crate::MirPass; use rustc_ast::InlineAsmOptions; -use rustc_hir::def::DefKind; use rustc_middle::mir::*; use rustc_middle::ty::layout; use rustc_middle::ty::{self, TyCtxt}; @@ -31,11 +30,7 @@ impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls { // We don't simplify the MIR of constants at this time because that // namely results in a cyclic query when we call `tcx.type_of` below. - let is_function = match kind { - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, - _ => tcx.is_closure(def_id), - }; - if !is_function { + if !kind.is_fn_like() { return; } diff --git a/compiler/rustc_mir_transform/src/add_retag.rs b/compiler/rustc_mir_transform/src/add_retag.rs index 0495439385b..0f87e638d26 100644 --- a/compiler/rustc_mir_transform/src/add_retag.rs +++ b/compiler/rustc_mir_transform/src/add_retag.rs @@ -33,8 +33,9 @@ fn is_stable(place: PlaceRef<'_>) -> bool { }) } -/// Determine whether this type may be a reference (or box), and thus needs retagging. -fn may_be_reference(ty: Ty<'_>) -> bool { +/// Determine whether this type may contain a reference (or box), and thus needs retagging. +/// We will only recurse `depth` times into Tuples/ADTs to bound the cost of this. +fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> bool { match ty.kind() { // Primitive types that are not references ty::Bool @@ -50,8 +51,20 @@ fn may_be_reference(ty: Ty<'_>) -> bool { // References ty::Ref(..) => true, ty::Adt(..) if ty.is_box() => true, - // Compound types are not references - ty::Array(..) | ty::Slice(..) | ty::Tuple(..) | ty::Adt(..) => false, + // Compound types: recurse + ty::Array(ty, _) | ty::Slice(ty) => { + // This does not branch so we keep the depth the same. + may_contain_reference(*ty, depth, tcx) + } + ty::Tuple(tys) => { + depth == 0 || tys.iter().any(|ty| may_contain_reference(ty, depth - 1, tcx)) + } + ty::Adt(adt, subst) => { + depth == 0 + || adt.variants().iter().any(|v| { + v.fields.iter().any(|f| may_contain_reference(f.ty(tcx, subst), depth - 1, tcx)) + }) + } // Conservative fallback _ => true, } @@ -83,7 +96,7 @@ impl<'tcx> MirPass<'tcx> for AddRetag { // FIXME: Instead of giving up for unstable places, we should introduce // a temporary and retag on that. is_stable(place.as_ref()) - && may_be_reference(place.ty(&*local_decls, tcx).ty) + && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx) && is_not_temp(&local_decls[place.local]) }; let place_base_raw = |place: &Place<'tcx>| { diff --git a/compiler/rustc_mir_transform/src/const_debuginfo.rs b/compiler/rustc_mir_transform/src/const_debuginfo.rs index 8944ebed9a7..48aea61b191 100644 --- a/compiler/rustc_mir_transform/src/const_debuginfo.rs +++ b/compiler/rustc_mir_transform/src/const_debuginfo.rs @@ -88,12 +88,12 @@ fn find_optimization_oportunities<'tcx>(body: &Body<'tcx>) -> Vec<(Local, Consta } impl Visitor<'_> for LocalUseVisitor { - fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { + fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { if context.is_mutating_use() { - self.local_mutating_uses[*local] = self.local_mutating_uses[*local].saturating_add(1); + self.local_mutating_uses[local] = self.local_mutating_uses[local].saturating_add(1); if context.is_place_assignment() { - self.local_assignment_locations[*local] = Some(location); + self.local_assignment_locations[local] = Some(location); } } } diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs index 412a5b4fc91..36844d5f6cf 100644 --- a/compiler/rustc_mir_transform/src/const_prop.rs +++ b/compiler/rustc_mir_transform/src/const_prop.rs @@ -882,7 +882,7 @@ impl CanConstProp { } impl Visitor<'_> for CanConstProp { - fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) { + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { use rustc_middle::mir::visit::PlaceContext::*; match context { // Projections are fine, because `&mut foo.x` will be caught by diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs index 15ad13009e5..dc3cb282c73 100644 --- a/compiler/rustc_mir_transform/src/const_prop_lint.rs +++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs @@ -773,7 +773,7 @@ impl CanConstProp { } impl Visitor<'_> for CanConstProp { - fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) { + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { use rustc_middle::mir::visit::PlaceContext::*; match context { // Projections are fine, because `&mut foo.x` will be caught by diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs new file mode 100644 index 00000000000..7728fdaffb0 --- /dev/null +++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs @@ -0,0 +1,170 @@ +use rustc_hir::def_id::{CrateNum, LocalDefId, LOCAL_CRATE}; +use rustc_middle::mir::*; +use rustc_middle::ty::layout; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::builtin::FFI_UNWIND_CALLS; +use rustc_target::spec::abi::Abi; +use rustc_target::spec::PanicStrategy; + +fn abi_can_unwind(abi: Abi) -> bool { + use Abi::*; + match abi { + C { unwind } + | System { unwind } + | Cdecl { unwind } + | Stdcall { unwind } + | Fastcall { unwind } + | Vectorcall { unwind } + | Thiscall { unwind } + | Aapcs { unwind } + | Win64 { unwind } + | SysV64 { unwind } => unwind, + PtxKernel + | Msp430Interrupt + | X86Interrupt + | AmdGpuKernel + | EfiApi + | AvrInterrupt + | AvrNonBlockingInterrupt + | CCmseNonSecureCall + | Wasm + | RustIntrinsic + | PlatformIntrinsic + | Unadjusted => false, + Rust | RustCall | RustCold => true, + } +} + +// Check if the body of this def_id can possibly leak a foreign unwind into Rust code. +fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { + debug!("has_ffi_unwind_calls({local_def_id:?})"); + + // Only perform check on functions because constants cannot call FFI functions. + let def_id = local_def_id.to_def_id(); + let kind = tcx.def_kind(def_id); + if !kind.is_fn_like() { + return false; + } + + let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow(); + + let body_ty = tcx.type_of(def_id); + let body_abi = match body_ty.kind() { + ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), + ty::Closure(..) => Abi::RustCall, + ty::Generator(..) => Abi::Rust, + _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty), + }; + let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi); + + // Foreign unwinds cannot leak past functions that themselves cannot unwind. + if !body_can_unwind { + return false; + } + + let mut tainted = false; + + for block in body.basic_blocks() { + if block.is_cleanup { + continue; + } + let Some(terminator) = &block.terminator else { continue }; + let TerminatorKind::Call { func, .. } = &terminator.kind else { continue }; + + let ty = func.ty(body, tcx); + let sig = ty.fn_sig(tcx); + + // Rust calls cannot themselves create foreign unwinds. + if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() { + continue; + }; + + let fn_def_id = match ty.kind() { + ty::FnPtr(_) => None, + &ty::FnDef(def_id, _) => { + // Rust calls cannot themselves create foreign unwinds. + if !tcx.is_foreign_item(def_id) { + continue; + } + Some(def_id) + } + _ => bug!("invalid callee of type {:?}", ty), + }; + + if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) { + // We have detected a call that can possibly leak foreign unwind. + // + // Because the function body itself can unwind, we are not aborting this function call + // upon unwind, so this call can possibly leak foreign unwind into Rust code if the + // panic runtime linked is panic-abort. + + let lint_root = body.source_scopes[terminator.source_info.scope] + .local_data + .as_ref() + .assert_crate_local() + .lint_root; + let span = terminator.source_info.span; + + tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| { + let msg = match fn_def_id { + Some(_) => "call to foreign function with FFI-unwind ABI", + None => "call to function pointer with FFI-unwind ABI", + }; + let mut db = lint.build(msg); + db.span_label(span, msg); + db.emit(); + }); + + tainted = true; + } + } + + tainted +} + +fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> { + assert_eq!(cnum, LOCAL_CRATE); + + if tcx.is_panic_runtime(LOCAL_CRATE) { + return Some(tcx.sess.panic_strategy()); + } + + if tcx.sess.panic_strategy() == PanicStrategy::Abort { + return Some(PanicStrategy::Abort); + } + + for def_id in tcx.hir().body_owners() { + if tcx.has_ffi_unwind_calls(def_id) { + // Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls` + // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception + // can enter Rust through these sites. + // + // On the other hand, crates compiled with `-C panic=abort` expects that all Rust + // functions cannot unwind (whether it's caused by Rust panic or foreign exception), + // and this expectation mismatch can cause unsoundness (#96926). + // + // To address this issue, we enforce that if FFI-unwind calls are used in a crate + // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`. + // This will ensure that no crates will have wrong unwindability assumption. + // + // It should be noted that it is okay to link `panic=unwind` into a `panic=abort` + // program if it contains no FFI-unwind calls. In such case foreign exception can only + // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also + // be no exceptions generated from Rust, so the assumption which `panic=abort` crates + // make, that no Rust function can unwind, indeed holds for crates compiled with + // `panic=unwind` as well. In such case this function returns `None`, indicating that + // the crate does not require a particular final panic strategy, and can be freely + // linked to crates with either strategy (we need such ability for libstd and its + // dependencies). + return Some(PanicStrategy::Unwind); + } + } + + // This crate can be linked with either runtime. + None +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers }; +} diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 49403ba03a4..ce387cb4453 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -1,14 +1,15 @@ //! Inlining pass for MIR functions use crate::deref_separator::deref_finder; use rustc_attr::InlineAttr; +use rustc_const_eval::transform::validate::equal_up_to_regions; use rustc_index::bit_set::BitSet; use rustc_index::vec::Idx; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::visit::*; use rustc_middle::mir::*; -use rustc_middle::traits::ObligationCause; use rustc_middle::ty::subst::Subst; use rustc_middle::ty::{self, ConstKind, Instance, InstanceDef, ParamEnv, Ty, TyCtxt}; +use rustc_session::config::OptLevel; use rustc_span::{hygiene::ExpnKind, ExpnData, LocalExpnId, Span}; use rustc_target::spec::abi::Abi; @@ -43,7 +44,15 @@ impl<'tcx> MirPass<'tcx> for Inline { return enabled; } - sess.opts.mir_opt_level() >= 3 + match sess.mir_opt_level() { + 0 | 1 => false, + 2 => { + (sess.opts.optimize == OptLevel::Default + || sess.opts.optimize == OptLevel::Aggressive) + && sess.opts.incremental == None + } + _ => true, + } } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -76,13 +85,6 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { } let param_env = tcx.param_env_reveal_all_normalized(def_id); - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); - let param_env = rustc_trait_selection::traits::normalize_param_env_or_error( - tcx, - def_id.to_def_id(), - param_env, - ObligationCause::misc(body.span, hir_id), - ); let mut this = Inliner { tcx, @@ -166,6 +168,45 @@ impl<'tcx> Inliner<'tcx> { return Err("failed to normalize callee body"); }; + // Check call signature compatibility. + // Normally, this shouldn't be required, but trait normalization failure can create a + // validation ICE. + let terminator = caller_body[callsite.block].terminator.as_ref().unwrap(); + let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() }; + let destination_ty = destination.ty(&caller_body.local_decls, self.tcx).ty; + let output_type = callee_body.return_ty(); + if !equal_up_to_regions(self.tcx, self.param_env, output_type, destination_ty) { + trace!(?output_type, ?destination_ty); + return Err("failed to normalize return type"); + } + if callsite.fn_sig.abi() == Abi::RustCall { + let mut args = args.into_iter(); + let _ = args.next(); // Skip `self` argument. + let arg_tuple_ty = args.next().unwrap().ty(&caller_body.local_decls, self.tcx); + assert!(args.next().is_none()); + + let ty::Tuple(arg_tuple_tys) = arg_tuple_ty.kind() else { + bug!("Closure arguments are not passed as a tuple"); + }; + + for (arg_ty, input) in arg_tuple_tys.iter().zip(callee_body.args_iter().skip(1)) { + let input_type = callee_body.local_decls[input].ty; + if !equal_up_to_regions(self.tcx, self.param_env, arg_ty, input_type) { + trace!(?arg_ty, ?input_type); + return Err("failed to normalize tuple argument type"); + } + } + } else { + for (arg, input) in args.iter().zip(callee_body.args_iter()) { + let input_type = callee_body.local_decls[input].ty; + let arg_ty = arg.ty(&caller_body.local_decls, self.tcx); + if !equal_up_to_regions(self.tcx, self.param_env, arg_ty, input_type) { + trace!(?arg_ty, ?input_type); + return Err("failed to normalize argument type"); + } + } + } + let old_blocks = caller_body.basic_blocks().next_index(); self.inline_call(caller_body, &callsite, callee_body); let new_blocks = old_blocks..caller_body.basic_blocks().next_index(); @@ -263,6 +304,10 @@ impl<'tcx> Inliner<'tcx> { return None; } + if self.history.contains(&callee) { + return None; + } + let fn_sig = self.tcx.bound_fn_sig(def_id).subst(self.tcx, substs); return Some(CallSite { @@ -285,8 +330,14 @@ impl<'tcx> Inliner<'tcx> { callsite: &CallSite<'tcx>, callee_attrs: &CodegenFnAttrs, ) -> Result<(), &'static str> { - if let InlineAttr::Never = callee_attrs.inline { - return Err("never inline hint"); + match callee_attrs.inline { + InlineAttr::Never => return Err("never inline hint"), + InlineAttr::Always | InlineAttr::Hint => {} + InlineAttr::None => { + if self.tcx.sess.mir_opt_level() <= 2 { + return Err("at mir-opt-level=2, only #[inline] is inlined"); + } + } } // Only inline local functions if they would be eligible for cross-crate @@ -407,22 +458,9 @@ impl<'tcx> Inliner<'tcx> { } TerminatorKind::Call { func: Operand::Constant(ref f), cleanup, .. } => { - if let ty::FnDef(def_id, substs) = + if let ty::FnDef(def_id, _) = *callsite.callee.subst_mir(self.tcx, &f.literal.ty()).kind() { - if let Ok(substs) = - self.tcx.try_normalize_erasing_regions(self.param_env, substs) - { - if let Ok(Some(instance)) = - Instance::resolve(self.tcx, self.param_env, def_id, substs) - { - if callsite.callee.def_id() == instance.def_id() { - return Err("self-recursion"); - } else if self.history.contains(&instance) { - return Err("already inlined"); - } - } - } // Don't give intrinsics the extra penalty for calls if tcx.is_intrinsic(def_id) { cost += INSTR_COST; @@ -482,14 +520,12 @@ impl<'tcx> Inliner<'tcx> { if let InlineAttr::Always = callee_attrs.inline { debug!("INLINING {:?} because inline(always) [cost={}]", callsite, cost); Ok(()) + } else if cost <= threshold { + debug!("INLINING {:?} [cost={} <= threshold={}]", callsite, cost, threshold); + Ok(()) } else { - if cost <= threshold { - debug!("INLINING {:?} [cost={} <= threshold={}]", callsite, cost, threshold); - Ok(()) - } else { - debug!("NOT inlining {:?} [cost={} > threshold={}]", callsite, cost, threshold); - Err("cost above threshold") - } + debug!("NOT inlining {:?} [cost={} > threshold={}]", callsite, cost, threshold); + Err("cost above threshold") } } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index fd7de2bd1dc..ee4a6bfba0e 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -48,7 +48,7 @@ pub(crate) fn mir_callgraph_reachable<'tcx>( trace!(?caller, ?param_env, ?substs, "cannot normalize, skipping"); continue; }; - let Some(callee) = ty::Instance::resolve(tcx, param_env, callee, substs).unwrap() else { + let Ok(Some(callee)) = ty::Instance::resolve(tcx, param_env, callee, substs) else { trace!(?callee, "cannot resolve, skipping"); continue; }; diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index b7caa61ef07..12fcb299ce3 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -59,6 +59,7 @@ pub mod dump_mir; mod early_otherwise_branch; mod elaborate_box_derefs; mod elaborate_drops; +mod ffi_unwind_calls; mod function_item_references; mod generator; mod inline; @@ -98,6 +99,7 @@ pub fn provide(providers: &mut Providers) { check_unsafety::provide(providers); check_packed_ref::provide(providers); coverage::query::provide(providers); + ffi_unwind_calls::provide(providers); shim::provide(providers); *providers = Providers { mir_keys, @@ -223,6 +225,9 @@ fn mir_const<'tcx>( } } + // has_ffi_unwind_calls query uses the raw mir, so make sure it is run. + tcx.ensure().has_ffi_unwind_calls(def.did); + let mut body = tcx.mir_built(def).steal(); rustc_middle::mir::dump_mir(tcx, None, "mir_map", &0, &body, |_, _| Ok(())); diff --git a/compiler/rustc_mir_transform/src/nrvo.rs b/compiler/rustc_mir_transform/src/nrvo.rs index 444b4126e88..d29d17399af 100644 --- a/compiler/rustc_mir_transform/src/nrvo.rs +++ b/compiler/rustc_mir_transform/src/nrvo.rs @@ -219,7 +219,7 @@ impl IsReturnPlaceRead { } impl<'tcx> Visitor<'tcx> for IsReturnPlaceRead { - fn visit_local(&mut self, &l: &Local, ctxt: PlaceContext, _: Location) { + fn visit_local(&mut self, l: Local, ctxt: PlaceContext, _: Location) { if l == mir::RETURN_PLACE && ctxt.is_use() && !ctxt.is_place_assignment() { self.0 = true; } diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index 8a78ea5c82b..980af984362 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -509,12 +509,12 @@ impl<'tcx> Visitor<'tcx> for UsedLocals { } } - fn visit_local(&mut self, local: &Local, _ctx: PlaceContext, _location: Location) { + fn visit_local(&mut self, local: Local, _ctx: PlaceContext, _location: Location) { if self.increment { - self.use_count[*local] += 1; + self.use_count[local] += 1; } else { - assert_ne!(self.use_count[*local], 0); - self.use_count[*local] -= 1; + assert_ne!(self.use_count[local], 0); + self.use_count[local] -= 1; } } } diff --git a/compiler/rustc_mir_transform/src/simplify_try.rs b/compiler/rustc_mir_transform/src/simplify_try.rs index b3d45c7a221..6902213ddad 100644 --- a/compiler/rustc_mir_transform/src/simplify_try.rs +++ b/compiler/rustc_mir_transform/src/simplify_try.rs @@ -462,14 +462,14 @@ impl LocalUseCounter { } impl Visitor<'_> for LocalUseCounter { - fn visit_local(&mut self, local: &Local, context: PlaceContext, _location: Location) { + fn visit_local(&mut self, local: Local, context: PlaceContext, _location: Location) { if context.is_storage_marker() || context == PlaceContext::NonUse(NonUseContext::VarDebugInfo) { return; } - self.local_uses[*local] += 1; + self.local_uses[local] += 1; } } |
