diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index c1db4d07dfc86..3220e337db713 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -47,6 +47,34 @@ pub enum InlineAttr { Hint, Always, Never, + /// `#[inline(must)]` always attempts to inline in the MIR inliner, regardless of optimisation + /// level, and emits a warn-by-default lint if this is not possible. + Must { + attr_span: Span, + reason: Option<Symbol>, + }, + /// `#[inline(required)]` always attempts to inline in the MIR inliner, regardless of + /// optimisation level, and emits a error-by-default lint if this is not possible. + Required { + attr_span: Span, + reason: Option<Symbol>, + }, +} + +impl InlineAttr { + pub fn required(&self) -> bool { + match self { + InlineAttr::Must { .. } | InlineAttr::Required { .. } => true, + InlineAttr::None | InlineAttr::Hint | InlineAttr::Always | InlineAttr::Never => false, + } + } + + pub fn always(&self) -> bool { + match self { + InlineAttr::Always | InlineAttr::Must { .. } | InlineAttr::Required { .. } => true, + InlineAttr::None | InlineAttr::Hint | InlineAttr::Never => false, + } + } } #[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, HashStable_Generic)] diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index d20e13e15b944..7c54ada7ece6e 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -20,7 +20,9 @@ fn inline_attr<'gcc, 'tcx>( ) -> Option<FnAttribute<'gcc>> { match inline { InlineAttr::Hint => Some(FnAttribute::Inline), - InlineAttr::Always => Some(FnAttribute::AlwaysInline), + InlineAttr::Always | InlineAttr::Required { .. } | InlineAttr::Must { .. } => { + Some(FnAttribute::AlwaysInline) + } InlineAttr::Never => { if cx.sess().target.arch != "amdgpu" { Some(FnAttribute::NoInline) diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 2c5ec9dad59f1..bbd5e7e7a93b1 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -37,7 +37,9 @@ fn inline_attr<'ll>(cx: &CodegenCx<'ll, '_>, inline: InlineAttr) -> Option<&'ll } match inline { InlineAttr::Hint => Some(AttributeKind::InlineHint.create_attr(cx.llcx)), - InlineAttr::Always => Some(AttributeKind::AlwaysInline.create_attr(cx.llcx)), + InlineAttr::Always | InlineAttr::Required { .. } | InlineAttr::Must { .. } => { + Some(AttributeKind::AlwaysInline.create_attr(cx.llcx)) + } InlineAttr::Never => { if cx.sess().target.arch != "amdgpu" { Some(AttributeKind::NoInline.create_attr(cx.llcx)) diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index d536419ab3c20..b2e6549f8b065 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -1,4 +1,4 @@ -use rustc_ast::{MetaItemInner, MetaItemKind, ast, attr}; +use rustc_ast::{MetaItem, MetaItemInner, MetaItemKind, ast, attr}; use rustc_attr::{InlineAttr, InstructionSetAttr, OptimizeAttr, list_contains_name}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::codes::*; @@ -17,7 +17,7 @@ use rustc_middle::ty::{self as ty, TyCtxt}; use rustc_session::parse::feature_err; use rustc_session::{Session, lint}; use rustc_span::symbol::Ident; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; use rustc_target::spec::{SanitizerSet, abi}; use crate::errors::{self, MissingFeatures, TargetFeatureDisableOrEnable}; @@ -525,10 +525,36 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { struct_span_code_err!(tcx.dcx(), attr.span, E0534, "expected one argument") .emit(); InlineAttr::None + } else if list_contains_name(items, sym::must) && tcx.features().required_inlining { + parse_inline_must_required( + tcx, + items, + attr.span, + sym::must, + |attr_span, reason| InlineAttr::Must { attr_span, reason }, + ) + } else if list_contains_name(items, sym::required) + && tcx.features().required_inlining + { + parse_inline_must_required( + tcx, + items, + attr.span, + sym::required, + |attr_span, reason| InlineAttr::Required { attr_span, reason }, + ) } else if list_contains_name(items, sym::always) { InlineAttr::Always } else if list_contains_name(items, sym::never) { InlineAttr::Never + } else if tcx.features().required_inlining { + struct_span_code_err!(tcx.dcx(), items[0].span(), E0535, "invalid argument") + .with_help( + "valid inline arguments are `required`, `must`, `always` and `never`", + ) + .emit(); + + InlineAttr::None } else { struct_span_code_err!(tcx.dcx(), items[0].span(), E0535, "invalid argument") .with_help("valid inline arguments are `always` and `never`") @@ -586,7 +612,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { // is probably a poor usage of `#[inline(always)]` and easily avoided by not using the attribute. if tcx.features().target_feature_11 && tcx.is_closure_like(did.to_def_id()) - && codegen_fn_attrs.inline != InlineAttr::Always + && !codegen_fn_attrs.inline.always() { let owner_id = tcx.parent(did.to_def_id()); if tcx.def_kind(owner_id).has_codegen_attrs() { @@ -600,8 +626,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { // purpose functions as they wouldn't have the right target features // enabled. For that reason we also forbid #[inline(always)] as it can't be // respected. - if !codegen_fn_attrs.target_features.is_empty() && codegen_fn_attrs.inline == InlineAttr::Always - { + if !codegen_fn_attrs.target_features.is_empty() && codegen_fn_attrs.inline.always() { if let Some(span) = inline_span { tcx.dcx().span_err( span, @@ -611,7 +636,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { } } - if !codegen_fn_attrs.no_sanitize.is_empty() && codegen_fn_attrs.inline == InlineAttr::Always { + if !codegen_fn_attrs.no_sanitize.is_empty() && codegen_fn_attrs.inline.always() { if let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span) { let hir_id = tcx.local_def_id_to_hir_id(did); tcx.node_span_lint( @@ -706,6 +731,37 @@ pub fn check_tied_features( None } +fn parse_inline_must_required<'tcx>( + tcx: TyCtxt<'tcx>, + items: &[MetaItemInner], + attr_span: Span, + expected_symbol: Symbol, + create: impl Fn(Span, Option<Symbol>) -> InlineAttr, +) -> InlineAttr { + match items.iter().find(|i| i.has_name(expected_symbol)).expect("called on items w/out sym") { + MetaItemInner::MetaItem(mi @ MetaItem { kind: MetaItemKind::Word, .. }) => { + debug_assert!(mi.has_name(expected_symbol)); + create(attr_span, None) + } + nested => { + if let Some((found_symbol, reason)) = nested.singleton_lit_list() + && reason.kind.is_str() + { + debug_assert_eq!(found_symbol, expected_symbol); + create(attr_span, reason.kind.str()) + } else { + struct_span_code_err!(tcx.dcx(), attr_span, E0535, "invalid argument") + .with_help(format!( + "expected one string argument to `#[inline({})]`", + expected_symbol.as_str() + )) + .emit(); + create(attr_span, None) + } + } + } +} + /// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller /// applied to the method prototype. fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index c0398db9c101f..7b476ee9c58db 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -221,6 +221,8 @@ declare_features! ( (internal, prelude_import, "1.2.0", None), /// Used to identify crates that contain the profiler runtime. (internal, profiler_runtime, "1.18.0", None), + /// Allows using `#[inline(required)]`/`#[inline(must)]` + (internal, required_inlining, "CURRENT_RUSTC_VERSION", None), /// Allows using `rustc_*` attributes (RFC 572). (internal, rustc_attrs, "1.0.0", None), /// Allows using the `#[stable]` and `#[unstable]` attributes. diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 23dc5214fe2ea..edfab278e274a 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -67,6 +67,7 @@ declare_lint_pass! { MISSING_ABI, MISSING_FRAGMENT_SPECIFIER, MISSING_UNSAFE_ON_EXTERN, + MUST_INLINE, MUST_NOT_SUSPEND, NAMED_ARGUMENTS_USED_POSITIONALLY, NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE, @@ -88,6 +89,7 @@ declare_lint_pass! { REFINING_IMPL_TRAIT_REACHABLE, RENAMED_AND_REMOVED_LINTS, REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, + REQUIRED_INLINE, RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, RUST_2021_INCOMPATIBLE_OR_PATTERNS, RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, @@ -5082,3 +5084,39 @@ declare_lint! { }; crate_level_only } + +declare_lint! { + /// The `must_inline` lint is emitted when a function annotated with + /// `#[inline(must)]` was not able to be inlined. + /// + /// ### Explanation + /// + /// Functions can be marked as `#[inline(must)]` in the standard + /// library if they must be inlined in order to guarantee performance + /// characteristics or some other similar guarantee. + /// + /// In some circumstances, these functions cannot be inlined and a + /// reason will be provided, this can either be rectified or the + /// lint can be silenced if the risk is acceptable. + pub MUST_INLINE, + Warn, + "`#[inline(must)]`-annotated function could not be inlined" +} + +declare_lint! { + /// The `required_inline` lint is emitted when a function annotated with + /// `#[inline(required)]` was not able to be inlined. + /// + /// ### Explanation + /// + /// Functions can be marked as `#[inline(required)]` in the standard + /// library if they are required to be inlined in order to uphold + /// security properties or some other similar guarantee. + /// + /// In some circumstances, these functions cannot be inlined and a + /// reason will be provided, this can either be rectified or the + /// lint can be silenced if the risk is acceptable. + pub REQUIRED_INLINE, + Deny, + "`#[inline(required)]`-annotated function could not be inlined" +} diff --git a/compiler/rustc_middle/src/mir/mono.rs b/compiler/rustc_middle/src/mir/mono.rs index 56ca9167d4daa..ba241a6775e7e 100644 --- a/compiler/rustc_middle/src/mir/mono.rs +++ b/compiler/rustc_middle/src/mir/mono.rs @@ -1,7 +1,6 @@ use std::fmt; use std::hash::Hash; -use rustc_attr::InlineAttr; use rustc_data_structures::base_n::{BaseNString, CASE_INSENSITIVE, ToBaseN}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxIndexMap; @@ -138,9 +137,10 @@ impl<'tcx> MonoItem<'tcx> { // creating one copy of this `#[inline]` function which may // conflict with upstream crates as it could be an exported // symbol. - match tcx.codegen_fn_attrs(instance.def_id()).inline { - InlineAttr::Always => InstantiationMode::LocalCopy, - _ => InstantiationMode::GloballyShared { may_conflict: true }, + if tcx.codegen_fn_attrs(instance.def_id()).inline.always() { + InstantiationMode::LocalCopy + } else { + InstantiationMode::GloballyShared { may_conflict: true } } } MonoItem::Static(..) | MonoItem::GlobalAsm(..) => { diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index c8992b8b83430..a24b8b501d1f8 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -25,6 +25,17 @@ mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspen .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point mir_transform_operation_will_panic = this operation will panic at runtime +mir_transform_required_inline = + `{$callee}` could not be inlined into `{$caller}` but {$requires_or_must} inlined + .call = ...`{$callee}` called here + .attr = inlining due to this annotation + .caller = within `{$caller}`... + .callee = `{$callee}` defined here + .note = could not be inlined due to: {$reason} + +mir_transform_required_inline_justification = + `{$callee}` {$requires_or_must} inlined to: {$sym} + mir_transform_unaligned_packed_ref = reference to packed field is unaligned .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs index 42cbece32d8c9..f69e8fe61ef64 100644 --- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs +++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs @@ -8,8 +8,6 @@ use rustc_middle::ty::TyCtxt; use rustc_session::config::{InliningThreshold, OptLevel}; use rustc_span::sym; -use crate::{inline, pass_manager as pm}; - pub(super) fn provide(providers: &mut Providers) { providers.cross_crate_inlinable = cross_crate_inlinable; } @@ -46,7 +44,10 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // #[inline(never)] to force code generation. match codegen_fn_attrs.inline { InlineAttr::Never => return false, - InlineAttr::Hint | InlineAttr::Always => return true, + InlineAttr::Hint + | InlineAttr::Always + | InlineAttr::Required { .. } + | InlineAttr::Must { .. } => return true, _ => {} } @@ -59,7 +60,8 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // Don't do any inference if codegen optimizations are disabled and also MIR inlining is not // enabled. This ensures that we do inference even if someone only passes -Zinline-mir, // which is less confusing than having to also enable -Copt-level=1. - if matches!(tcx.sess.opts.optimize, OptLevel::No) && !pm::should_run_pass(tcx, &inline::Inline) + if matches!(tcx.sess.opts.optimize, OptLevel::No) + && !crate::inline::should_run_pass_for_item(tcx, def_id.to_def_id()) { return false; } diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 8b309147c64cd..971031ae32d18 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -4,8 +4,8 @@ use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::mir::AssertKind; use rustc_middle::ty::TyCtxt; use rustc_session::lint::{self, Lint}; -use rustc_span::Span; use rustc_span::def_id::DefId; +use rustc_span::{Span, Symbol}; use crate::fluent_generated as fluent; @@ -136,3 +136,29 @@ pub(crate) struct MustNotSuspendReason { #[note(mir_transform_note2)] #[help] pub(crate) struct UndefinedTransmute; + +#[derive(LintDiagnostic)] +#[diag(mir_transform_required_inline)] +#[note] +pub(crate) struct RequiredInline { + pub requires_or_must: &'static str, + #[label(mir_transform_caller)] + pub caller_span: Span, + #[label(mir_transform_callee)] + pub callee_span: Span, + #[label(mir_transform_attr)] + pub attr_span: Span, + #[label(mir_transform_call)] + pub call_span: Span, + pub callee: String, + pub caller: String, + pub reason: &'static str, + #[subdiagnostic] + pub justification: Option<RequiredInlineJustification>, +} + +#[derive(Subdiagnostic)] +#[note(mir_transform_required_inline_justification)] +pub(crate) struct RequiredInlineJustification { + pub sym: Symbol, +} diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index c9f24764cc2a1..33abec90f87a2 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -5,7 +5,7 @@ use std::ops::{Range, RangeFrom}; use rustc_attr::InlineAttr; use rustc_hir::def::DefKind; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_index::Idx; use rustc_index::bit_set::BitSet; use rustc_middle::bug; @@ -16,6 +16,7 @@ use rustc_middle::ty::{ self, Instance, InstanceKind, ParamEnv, Ty, TyCtxt, TypeFlags, TypeVisitableExt, }; use rustc_session::config::{DebugInfo, OptLevel}; +use rustc_session::lint::builtin::{MUST_INLINE, REQUIRED_INLINE}; use rustc_span::source_map::Spanned; use rustc_span::sym; use rustc_target::abi::FieldIdx; @@ -24,6 +25,7 @@ use tracing::{debug, instrument, trace, trace_span}; use crate::cost_checker::CostChecker; use crate::deref_separator::deref_finder; +use crate::errors::RequiredInlineJustification; use crate::simplify::simplify_cfg; use crate::util; use crate::validate::validate_types; @@ -45,26 +47,8 @@ struct CallSite<'tcx> { } impl<'tcx> crate::MirPass<'tcx> for Inline { - fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - // FIXME(#127234): Coverage instrumentation currently doesn't handle inlined - // MIR correctly when Modified Condition/Decision Coverage is enabled. - if sess.instrument_coverage_mcdc() { - return false; - } - - if let Some(enabled) = sess.opts.unstable_opts.inline_mir { - return enabled; - } - - 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 is_enabled(&self, _: &rustc_session::Session) -> bool { + true } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -78,6 +62,30 @@ impl<'tcx> crate::MirPass<'tcx> for Inline { } } +/// Is this pass enabled for a given item? Can't just rely on `MirPass::is_enabled` as a body having +/// `#[inline(required)]` should be inlined even if every other item isn't. +pub fn should_run_pass_for_item<'tcx>(tcx: TyCtxt<'tcx>, did: DefId) -> bool { + if crate::pass_manager::pass_is_overridden(tcx, &Inline) { + return true; + } + + if let Some(enabled) = tcx.sess.opts.unstable_opts.inline_mir { + return enabled; + } + + let codegen_fn_attrs = tcx.codegen_fn_attrs(did); + codegen_fn_attrs.inline.required() + || match tcx.sess.mir_opt_level() { + 0 | 1 => false, + 2 => { + (tcx.sess.opts.optimize == OptLevel::Default + || tcx.sess.opts.optimize == OptLevel::Aggressive) + && tcx.sess.opts.incremental == None + } + _ => true, + } +} + fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { let def_id = body.source.def_id().expect_local(); @@ -101,12 +109,16 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { let mut this = Inliner { tcx, param_env, + def_id, codegen_fn_attrs, history: Vec::new(), changed: false, caller_is_inline_forwarder: matches!( codegen_fn_attrs.inline, - InlineAttr::Hint | InlineAttr::Always + InlineAttr::Hint + | InlineAttr::Always + | InlineAttr::Required { .. } + | InlineAttr::Must { .. } ) && body_is_forwarder(body), }; let blocks = START_BLOCK..body.basic_blocks.next_index(); @@ -117,6 +129,8 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { struct Inliner<'tcx> { tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, + /// `DefId` of caller. + def_id: LocalDefId, /// Caller codegen attributes. codegen_fn_attrs: &'tcx CodegenFnAttrs, /// Stack of inlined instances. @@ -155,9 +169,20 @@ impl<'tcx> Inliner<'tcx> { let span = trace_span!("process_blocks", %callsite.callee, ?bb); let _guard = span.enter(); + if !should_run_pass_for_item(self.tcx, callsite.callee.def_id()) { + debug!("not enabled"); + continue; + } + match self.try_inlining(caller_body, &callsite) { Err(reason) => { debug!("not-inlined {} [{}]", callsite.callee, reason); + if matches!( + self.tcx.codegen_fn_attrs(callsite.callee.def_id()).inline, + InlineAttr::Required { .. } | InlineAttr::Must { .. } + ) { + self.report_required_error(&callsite, reason); + } } Ok(new_blocks) => { debug!("inlined {}", callsite.callee); @@ -194,8 +219,10 @@ impl<'tcx> Inliner<'tcx> { // Intrinsic fallback bodies are automatically made cross-crate inlineable, // but at this stage we don't know whether codegen knows the intrinsic, // so just conservatively don't inline it. - if self.tcx.has_attr(callsite.callee.def_id(), sym::rustc_intrinsic) { - return Err("Callee is an intrinsic, do not inline fallback bodies"); + if self.tcx.has_attr(callsite.callee.def_id(), sym::rustc_intrinsic) + && !callee_attrs.inline.required() + { + return Err("callee is an intrinsic"); } let terminator = caller_body[callsite.block].terminator.as_ref().unwrap(); @@ -205,7 +232,7 @@ impl<'tcx> Inliner<'tcx> { if !arg.node.ty(&caller_body.local_decls, self.tcx).is_sized(self.tcx, self.param_env) { // We do not allow inlining functions with unsized params. Inlining these functions // could create unsized locals, which are unsound and being phased out. - return Err("Call has unsized argument"); + return Err("call has unsized argument"); } } @@ -214,7 +241,8 @@ impl<'tcx> Inliner<'tcx> { if !self.tcx.consider_optimizing(|| { format!("Inline {:?} into {:?}", callsite.callee, caller_body.source) - }) { + }) && !callee_attrs.inline.required() + { return Err("optimization fuel exhausted"); } @@ -223,7 +251,8 @@ impl<'tcx> Inliner<'tcx> { self.param_env, ty::EarlyBinder::bind(callee_body.clone()), ) else { - return Err("failed to normalize callee body"); + debug!("failed to normalize callee body"); + return Err("implementation limitation"); }; // Normally, this shouldn't be required, but trait normalization failure can create a @@ -237,7 +266,8 @@ impl<'tcx> Inliner<'tcx> { ) .is_empty() { - return Err("failed to validate callee body"); + debug!("failed to validate callee body"); + return Err("implementation limitation"); } // Check call signature compatibility. @@ -247,14 +277,15 @@ impl<'tcx> Inliner<'tcx> { if !util::relate_types(self.tcx, self.param_env, ty::Covariant, output_type, destination_ty) { trace!(?output_type, ?destination_ty); - return Err("failed to normalize return type"); + debug!("failed to normalize return type"); + return Err("implementation limitation"); } if callsite.fn_sig.abi() == Abi::RustCall { // FIXME: Don't inline user-written `extern "rust-call"` functions, // since this is generally perf-negative on rustc, and we hope that // LLVM will inline these functions instead. if callee_body.spread_arg.is_some() { - return Err("do not inline user-written rust-call functions"); + return Err("user-written rust-call functions"); } let (self_arg, arg_tuple) = match &args[..] { @@ -278,7 +309,8 @@ impl<'tcx> Inliner<'tcx> { if !util::relate_types(self.tcx, self.param_env, ty::Covariant, input_type, arg_ty) { trace!(?arg_ty, ?input_type); - return Err("failed to normalize tuple argument type"); + debug!("failed to normalize tuple argument type"); + return Err("implementation limitation"); } } } else { @@ -288,7 +320,8 @@ impl<'tcx> Inliner<'tcx> { if !util::relate_types(self.tcx, self.param_env, ty::Covariant, input_type, arg_ty) { trace!(?arg_ty, ?input_type); - return Err("failed to normalize argument type"); + debug!("failed to normalize argument type"); + return Err("implementation limitation"); } } } @@ -317,12 +350,14 @@ impl<'tcx> Inliner<'tcx> { // because it has no MIR because it's an extern function), then the inliner // won't cause cycles on this. if !self.tcx.is_mir_available(callee_def_id) { - return Err("item MIR unavailable"); + debug!("item MIR unavailable"); + return Err("implementation limitation"); } } // These have no own callable MIR. InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => { - return Err("instance without MIR (intrinsic / virtual)"); + debug!("instance without MIR (intrinsic / virtual)"); + return Err("implementation limitation"); } // FIXME(#127030): `ConstParamHasTy` has bad interactions with @@ -331,7 +366,8 @@ impl<'tcx> Inliner<'tcx> { // the MIR for this instance until all of its const params are // substituted. InstanceKind::DropGlue(_, Some(ty)) if ty.has_type_flags(TypeFlags::HAS_CT_PARAM) => { - return Err("still needs substitution"); + debug!("still needs substitution"); + return Err("implementation limitation"); } // This cannot result in an immediate cycle since the callee MIR is a shim, which does @@ -360,7 +396,8 @@ impl<'tcx> Inliner<'tcx> { // If we know for sure that the function we're calling will itself try to // call us, then we avoid inlining that function. if self.tcx.mir_callgraph_reachable((callee, caller_def_id.expect_local())) { - return Err("caller might be reachable from callee (query cycle avoidance)"); + debug!("query cycle avoidance"); + return Err("caller might be reachable from callee"); } Ok(()) @@ -434,7 +471,13 @@ impl<'tcx> Inliner<'tcx> { } if let InlineAttr::Never = callee_attrs.inline { - return Err("never inline hint"); + return Err("never inline attribute"); + } + + // FIXME(#127234): Coverage instrumentation currently doesn't handle inlined + // MIR correctly when Modified Condition/Decision Coverage is enabled. + if self.tcx.sess.instrument_coverage_mcdc() { + return Err("incompatible with MC/DC coverage"); } // Reachability pass defines which functions are eligible for inlining. Generally inlining @@ -497,7 +540,14 @@ impl<'tcx> Inliner<'tcx> { let tcx = self.tcx; if let Some(_) = callee_body.tainted_by_errors { - return Err("Body is tainted"); + return Err("body has errors"); + } + + // Callees which require inlining must be inlined to maintain security properties or + // for performance reasons, so skip any heuristics. + if callee_attrs.inline.required() { + debug!("INLINING {:?} [required]", callsite); + return Ok(()); } let mut threshold = if self.caller_is_inline_forwarder { @@ -556,7 +606,7 @@ impl<'tcx> Inliner<'tcx> { // assign one. However, during this stage we require an exact match when any // inline-asm is detected. LLVM will still possibly do an inline later on // if the no-attribute function ends up with the same instruction set anyway. - return Err("Cannot move inline-asm across instruction sets"); + return Err("cannot move inline-asm across instruction sets"); } else if let TerminatorKind::TailCall { .. } = term.kind { // FIXME(explicit_tail_calls): figure out how exactly functions containing tail // calls can be inlined (and if they even should) @@ -893,6 +943,34 @@ impl<'tcx> Inliner<'tcx> { local } + + fn report_required_error(&self, callsite: &CallSite<'tcx>, reason: &'static str) { + let (deny, attr_span, justification) = + match self.tcx.codegen_fn_attrs(callsite.callee.def_id()).inline { + InlineAttr::Required { attr_span, reason } => (true, attr_span, reason), + InlineAttr::Must { attr_span, reason } => (false, attr_span, reason), + _ => bug!("called on item without required inlining"), + }; + + let hir_id = self.tcx.local_def_id_to_hir_id(self.def_id); + let call_span = callsite.source_info.span; + self.tcx.emit_node_span_lint( + if deny { REQUIRED_INLINE } else { MUST_INLINE }, + hir_id, + call_span, + crate::errors::RequiredInline { + requires_or_must: if deny { "is required to be" } else { "must be" }, + call_span, + attr_span, + caller_span: self.tcx.def_span(self.def_id), + caller: self.tcx.def_path_str(self.def_id), + callee_span: self.tcx.def_span(callsite.callee.def_id()), + callee: self.tcx.def_path_str(callsite.callee.def_id()), + reason, + justification: justification.map(|sym| RequiredInlineJustification { sym }), + }, + ); + } } /** diff --git a/compiler/rustc_mir_transform/src/pass_manager.rs b/compiler/rustc_mir_transform/src/pass_manager.rs index 29f8b4f6e4ddf..b90bfa118d5b0 100644 --- a/compiler/rustc_mir_transform/src/pass_manager.rs +++ b/compiler/rustc_mir_transform/src/pass_manager.rs @@ -169,7 +169,7 @@ pub(super) fn run_passes<'tcx>( run_passes_inner(tcx, body, passes, phase_change, true); } -pub(super) fn should_run_pass<'tcx, P>(tcx: TyCtxt<'tcx>, pass: &P) -> bool +pub(super) fn pass_is_overridden<'tcx, P>(tcx: TyCtxt<'tcx>, pass: &P) -> bool where P: MirPass<'tcx> + ?Sized, { @@ -185,7 +185,14 @@ where ); *polarity }); - overridden.unwrap_or_else(|| pass.is_enabled(tcx.sess)) + overridden.unwrap_or(false) +} + +pub(super) fn should_run_pass<'tcx, P>(tcx: TyCtxt<'tcx>, pass: &P) -> bool +where + P: MirPass<'tcx> + ?Sized, +{ + pass_is_overridden(tcx, pass) || pass.is_enabled(tcx.sess) } fn run_passes_inner<'tcx>( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cc3bda99a117b..669e9e8346a16 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1283,6 +1283,7 @@ symbols! { mul_assign, mul_with_overflow, multiple_supertrait_upcastable, + must, must_not_suspend, must_use, mut_preserve_binding_mode_2024, @@ -1606,6 +1607,8 @@ symbols! { repr_simd, repr_transparent, require, + required, + required_inlining, residual, result, result_ffi_guarantees, diff --git a/tests/mir-opt/inline/required_no_opt.caller.Inline.diff b/tests/mir-opt/inline/required_no_opt.caller.Inline.diff new file mode 100644 index 0000000000000..1d0b3c59e679f --- /dev/null +++ b/tests/mir-opt/inline/required_no_opt.caller.Inline.diff @@ -0,0 +1,30 @@ +- // MIR for `caller` before Inline ++ // MIR for `caller` after Inline + + fn caller() -> () { + let mut _0: (); + let _1: (); + let _2: (); ++ scope 1 (inlined callee_required) { ++ } ++ scope 2 (inlined callee_must) { ++ } + + bb0: { + StorageLive(_1); +- _1 = callee_required() -> [return: bb1, unwind continue]; +- } +- +- bb1: { + StorageDead(_1); + StorageLive(_2); +- _2 = callee_must() -> [return: bb2, unwind continue]; +- } +- +- bb2: { + StorageDead(_2); + _0 = const (); + return; + } + } + diff --git a/tests/mir-opt/inline/required_no_opt.rs b/tests/mir-opt/inline/required_no_opt.rs new file mode 100644 index 0000000000000..6069fcf4aba0e --- /dev/null +++ b/tests/mir-opt/inline/required_no_opt.rs @@ -0,0 +1,18 @@ +// EMIT_MIT_FOR_EACH_PANIC_STRATEGY +//@ compile-flags: -Copt-level=0 --crate-type=lib +#![feature(required_inlining)] + +#[inline(required)] +pub fn callee_required() {} + +#[inline(must)] +pub fn callee_must() {} + +// EMIT_MIR required_no_opt.caller.Inline.diff +pub fn caller() { + callee_required(); + callee_must(); + // CHECK-LABEL: fn caller( + // CHECK: (inlined callee_required) + // CHECK: (inlined callee_must) +} diff --git a/tests/ui/required-inlining/auxiliary/callees.rs b/tests/ui/required-inlining/auxiliary/callees.rs new file mode 100644 index 0000000000000..70c96abc5ff89 --- /dev/null +++ b/tests/ui/required-inlining/auxiliary/callees.rs @@ -0,0 +1,10 @@ +//@ compile-flags: --crate-type=lib +#![feature(required_inlining)] + +#[inline(required("maintain security properties"))] +pub fn required() { +} + +#[inline(must("maintain security properties"))] +pub fn must() { +} diff --git a/tests/ui/required-inlining/cross-crate.rs b/tests/ui/required-inlining/cross-crate.rs new file mode 100644 index 0000000000000..481ae9baed985 --- /dev/null +++ b/tests/ui/required-inlining/cross-crate.rs @@ -0,0 +1,14 @@ +//@ aux-build:callees.rs +//@ build-pass +//@ compile-flags: --crate-type=lib +#![feature(required_inlining)] + +extern crate callees; + +// Test that required inlining across crates works as expected. + +pub fn caller() { + callees::required(); + + callees::must(); +} diff --git a/tests/ui/required-inlining/deny.rs b/tests/ui/required-inlining/deny.rs new file mode 100644 index 0000000000000..a49fbc427722d --- /dev/null +++ b/tests/ui/required-inlining/deny.rs @@ -0,0 +1,34 @@ +//@ build-fail +//@ compile-flags: --crate-type=lib +#![allow(internal_features)] +#![feature(rustc_attrs)] +#![feature(required_inlining)] + +// Test that required inlining w/ errors works as expected. + +#[rustc_no_mir_inline] +#[inline(required)] +pub fn callee() { +} + +#[rustc_no_mir_inline] +#[inline(required("maintain security properties"))] +pub fn callee_justified() { +} + +pub fn caller() { + callee(); +//~^ ERROR `callee` could not be inlined into `caller` but is required to be inlined + + callee_justified(); +//~^ ERROR `callee_justified` could not be inlined into `caller` but is required to be inlined +} + +#[warn(required_inline)] +pub fn caller_overridden() { + callee(); +//~^ WARN `callee` could not be inlined into `caller_overridden` but is required to be inlined + + callee_justified(); +//~^ WARN `callee_justified` could not be inlined into `caller_overridden` but is required to be inlined +} diff --git a/tests/ui/required-inlining/deny.stderr b/tests/ui/required-inlining/deny.stderr new file mode 100644 index 0000000000000..e4e92e525f3b2 --- /dev/null +++ b/tests/ui/required-inlining/deny.stderr @@ -0,0 +1,72 @@ +error: `callee` could not be inlined into `caller` but is required to be inlined + --> $DIR/deny.rs:20:5 + | +LL | #[inline(required)] + | ------------------- inlining due to this annotation +LL | pub fn callee() { + | --------------- `callee` defined here +... +LL | pub fn caller() { + | --------------- within `caller`... +LL | callee(); + | ^^^^^^^^ ...`callee` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `#[deny(required_inline)]` on by default + +error: `callee_justified` could not be inlined into `caller` but is required to be inlined + --> $DIR/deny.rs:23:5 + | +LL | #[inline(required("maintain security properties"))] + | --------------------------------------------------- inlining due to this annotation +LL | pub fn callee_justified() { + | ------------------------- `callee_justified` defined here +... +LL | pub fn caller() { + | --------------- within `caller`... +... +LL | callee_justified(); + | ^^^^^^^^^^^^^^^^^^ ...`callee_justified` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `callee_justified` is required to be inlined to: maintain security properties + +warning: `callee` could not be inlined into `caller_overridden` but is required to be inlined + --> $DIR/deny.rs:29:5 + | +LL | #[inline(required)] + | ------------------- inlining due to this annotation +LL | pub fn callee() { + | --------------- `callee` defined here +... +LL | pub fn caller_overridden() { + | -------------------------- within `caller_overridden`... +LL | callee(); + | ^^^^^^^^ ...`callee` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] +note: the lint level is defined here + --> $DIR/deny.rs:27:8 + | +LL | #[warn(required_inline)] + | ^^^^^^^^^^^^^^^ + +warning: `callee_justified` could not be inlined into `caller_overridden` but is required to be inlined + --> $DIR/deny.rs:32:5 + | +LL | #[inline(required("maintain security properties"))] + | --------------------------------------------------- inlining due to this annotation +LL | pub fn callee_justified() { + | ------------------------- `callee_justified` defined here +... +LL | pub fn caller_overridden() { + | -------------------------- within `caller_overridden`... +... +LL | callee_justified(); + | ^^^^^^^^^^^^^^^^^^ ...`callee_justified` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `callee_justified` is required to be inlined to: maintain security properties + +error: aborting due to 2 previous errors; 2 warnings emitted + diff --git a/tests/ui/required-inlining/gate.rs b/tests/ui/required-inlining/gate.rs new file mode 100644 index 0000000000000..7eae08f6c246d --- /dev/null +++ b/tests/ui/required-inlining/gate.rs @@ -0,0 +1,32 @@ +// gate-test-required_inlining +//@ compile-flags: --crate-type=lib +#![allow(internal_features)] +#![feature(rustc_attrs)] + +#[rustc_no_mir_inline] +#[inline(required)] +//~^ ERROR invalid argument +//~^^ HELP valid inline arguments are `always` and `never` +pub fn bare_required() { +} + +#[rustc_no_mir_inline] +#[inline(required("justification"))] +//~^ ERROR invalid argument +//~^^ HELP valid inline arguments are `always` and `never` +pub fn justified_required() { +} + +#[rustc_no_mir_inline] +#[inline(must)] +//~^ ERROR invalid argument +//~^^ HELP valid inline arguments are `always` and `never` +pub fn bare_must() { +} + +#[rustc_no_mir_inline] +#[inline(must("justification"))] +//~^ ERROR invalid argument +//~^^ HELP valid inline arguments are `always` and `never` +pub fn justified_must() { +} diff --git a/tests/ui/required-inlining/gate.stderr b/tests/ui/required-inlining/gate.stderr new file mode 100644 index 0000000000000..f976aaf6120ea --- /dev/null +++ b/tests/ui/required-inlining/gate.stderr @@ -0,0 +1,35 @@ +error[E0535]: invalid argument + --> $DIR/gate.rs:6:10 + | +LL | #[inline(required)] + | ^^^^^^^^ + | + = help: valid inline arguments are `always` and `never` + +error[E0535]: invalid argument + --> $DIR/gate.rs:13:10 + | +LL | #[inline(required("justification"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: valid inline arguments are `always` and `never` + +error[E0535]: invalid argument + --> $DIR/gate.rs:20:10 + | +LL | #[inline(must)] + | ^^^^ + | + = help: valid inline arguments are `always` and `never` + +error[E0535]: invalid argument + --> $DIR/gate.rs:27:10 + | +LL | #[inline(must("justification"))] + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: valid inline arguments are `always` and `never` + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0535`. diff --git a/tests/ui/required-inlining/invalid.rs b/tests/ui/required-inlining/invalid.rs new file mode 100644 index 0000000000000..87292c37a6193 --- /dev/null +++ b/tests/ui/required-inlining/invalid.rs @@ -0,0 +1,60 @@ +//@ compile-flags: --crate-type=lib +#![allow(internal_features)] +#![feature(rustc_attrs)] +#![feature(required_inlining)] + +// Test that invalid required/must inlining attributes error as expected. + +#[inline(required = "bar")] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(required)]` +pub fn reqd1() { +} + +#[inline(required(bar, baz))] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(required)]` +pub fn reqd2() { +} + +#[inline(required(2))] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(required)]` +pub fn reqd3() { +} + +#[inline(required = 2)] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(required)]` +pub fn reqd4() { +} + +#[inline(must = "bar")] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(must)]` +pub fn must1() { +} + +#[inline(must(bar, baz))] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(must)]` +pub fn must2() { +} + +#[inline(must(2))] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(must)]` +pub fn must3() { +} + +#[inline(must = 2)] +//~^ ERROR invalid argument +//~^^ HELP expected one string argument to `#[inline(must)]` +pub fn must4() { +} + +#[inline(banana)] +//~^ ERROR invalid argument +//~^^ HELP valid inline arguments are `required`, `must`, `always` and `never` +pub fn bare() { +} diff --git a/tests/ui/required-inlining/invalid.stderr b/tests/ui/required-inlining/invalid.stderr new file mode 100644 index 0000000000000..95c5f9d747bb2 --- /dev/null +++ b/tests/ui/required-inlining/invalid.stderr @@ -0,0 +1,75 @@ +error[E0535]: invalid argument + --> $DIR/invalid.rs:8:1 + | +LL | #[inline(required = "bar")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(required)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:14:1 + | +LL | #[inline(required(bar, baz))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(required)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:20:1 + | +LL | #[inline(required(2))] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(required)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:26:1 + | +LL | #[inline(required = 2)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(required)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:32:1 + | +LL | #[inline(must = "bar")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(must)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:38:1 + | +LL | #[inline(must(bar, baz))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(must)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:44:1 + | +LL | #[inline(must(2))] + | ^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(must)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:50:1 + | +LL | #[inline(must = 2)] + | ^^^^^^^^^^^^^^^^^^^ + | + = help: expected one string argument to `#[inline(must)]` + +error[E0535]: invalid argument + --> $DIR/invalid.rs:56:10 + | +LL | #[inline(banana)] + | ^^^^^^ + | + = help: valid inline arguments are `required`, `must`, `always` and `never` + +error: aborting due to 9 previous errors + +For more information about this error, try `rustc --explain E0535`. diff --git a/tests/ui/required-inlining/warn.rs b/tests/ui/required-inlining/warn.rs new file mode 100644 index 0000000000000..08c87933ceef7 --- /dev/null +++ b/tests/ui/required-inlining/warn.rs @@ -0,0 +1,34 @@ +//@ build-fail +//@ compile-flags: --crate-type=lib +#![allow(internal_features)] +#![feature(rustc_attrs)] +#![feature(required_inlining)] + +// Test that required inlining w/ warnings works as expected. + +#[rustc_no_mir_inline] +#[inline(must)] +pub fn callee() { +} + +#[rustc_no_mir_inline] +#[inline(must("maintain performance characteristics"))] +pub fn callee_justified() { +} + +pub fn caller() { + callee(); +//~^ WARN `callee` could not be inlined into `caller` but must be inlined + + callee_justified(); +//~^ WARN `callee_justified` could not be inlined into `caller` but must be inlined +} + +#[deny(must_inline)] +pub fn caller_overridden() { + callee(); +//~^ ERROR `callee` could not be inlined into `caller_overridden` but must be inlined + + callee_justified(); +//~^ ERROR `callee_justified` could not be inlined into `caller_overridden` but must be inlined +} diff --git a/tests/ui/required-inlining/warn.stderr b/tests/ui/required-inlining/warn.stderr new file mode 100644 index 0000000000000..aa748de95889b --- /dev/null +++ b/tests/ui/required-inlining/warn.stderr @@ -0,0 +1,72 @@ +warning: `callee` could not be inlined into `caller` but must be inlined + --> $DIR/warn.rs:20:5 + | +LL | #[inline(must)] + | --------------- inlining due to this annotation +LL | pub fn callee() { + | --------------- `callee` defined here +... +LL | pub fn caller() { + | --------------- within `caller`... +LL | callee(); + | ^^^^^^^^ ...`callee` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `#[warn(must_inline)]` on by default + +warning: `callee_justified` could not be inlined into `caller` but must be inlined + --> $DIR/warn.rs:23:5 + | +LL | #[inline(must("maintain performance characteristics"))] + | ------------------------------------------------------- inlining due to this annotation +LL | pub fn callee_justified() { + | ------------------------- `callee_justified` defined here +... +LL | pub fn caller() { + | --------------- within `caller`... +... +LL | callee_justified(); + | ^^^^^^^^^^^^^^^^^^ ...`callee_justified` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `callee_justified` must be inlined to: maintain performance characteristics + +error: `callee` could not be inlined into `caller_overridden` but must be inlined + --> $DIR/warn.rs:29:5 + | +LL | #[inline(must)] + | --------------- inlining due to this annotation +LL | pub fn callee() { + | --------------- `callee` defined here +... +LL | pub fn caller_overridden() { + | -------------------------- within `caller_overridden`... +LL | callee(); + | ^^^^^^^^ ...`callee` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] +note: the lint level is defined here + --> $DIR/warn.rs:27:8 + | +LL | #[deny(must_inline)] + | ^^^^^^^^^^^ + +error: `callee_justified` could not be inlined into `caller_overridden` but must be inlined + --> $DIR/warn.rs:32:5 + | +LL | #[inline(must("maintain performance characteristics"))] + | ------------------------------------------------------- inlining due to this annotation +LL | pub fn callee_justified() { + | ------------------------- `callee_justified` defined here +... +LL | pub fn caller_overridden() { + | -------------------------- within `caller_overridden`... +... +LL | callee_justified(); + | ^^^^^^^^^^^^^^^^^^ ...`callee_justified` called here + | + = note: could not be inlined due to: #[rustc_no_mir_inline] + = note: `callee_justified` must be inlined to: maintain performance characteristics + +error: aborting due to 2 previous errors; 2 warnings emitted +