mir_transform: implement forced inlining
Adds `#[rustc_force_inline]` which is similar to always inlining but reports an error if the inlining was not possible, and which always attempts to inline annotated items, regardless of optimisation levels. It can only be applied to free functions to guarantee that the MIR inliner will be able to resolve calls.
This commit is contained in:
parent
336209eef1
commit
f86169a58f
47 changed files with 2130 additions and 709 deletions
|
@ -19,6 +19,17 @@ mir_transform_ffi_unwind_call = call to {$foreign ->
|
|||
mir_transform_fn_item_ref = taking a reference to a function item does not give a function pointer
|
||||
.suggestion = cast `{$ident}` to obtain a function pointer
|
||||
|
||||
mir_transform_force_inline =
|
||||
`{$callee}` could not be inlined into `{$caller}` but is required to be 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_force_inline_justification =
|
||||
`{$callee}` is required to be inlined to: {$sym}
|
||||
|
||||
mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be
|
||||
.label = the value is held across this suspend point
|
||||
.note = {$reason}
|
||||
|
|
|
@ -46,7 +46,7 @@ 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::Force { .. } => return true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -69,8 +69,9 @@ 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)
|
||||
{
|
||||
let inliner_will_run = pm::should_run_pass(tcx, &inline::Inline)
|
||||
|| inline::ForceInline::should_run_pass_for_callee(tcx, def_id.to_def_id());
|
||||
if matches!(tcx.sess.opts.optimize, OptLevel::No) && !inliner_will_run {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -142,3 +142,29 @@ pub(crate) struct MustNotSuspendReason {
|
|||
#[note(mir_transform_note2)]
|
||||
#[help]
|
||||
pub(crate) struct UndefinedTransmute;
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(mir_transform_force_inline)]
|
||||
#[note]
|
||||
pub(crate) struct ForceInlineFailure {
|
||||
#[label(mir_transform_caller)]
|
||||
pub caller_span: Span,
|
||||
#[label(mir_transform_callee)]
|
||||
pub callee_span: Span,
|
||||
#[label(mir_transform_attr)]
|
||||
pub attr_span: Span,
|
||||
#[primary_span]
|
||||
#[label(mir_transform_call)]
|
||||
pub call_span: Span,
|
||||
pub callee: String,
|
||||
pub caller: String,
|
||||
pub reason: &'static str,
|
||||
#[subdiagnostic]
|
||||
pub justification: Option<ForceInlineJustification>,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[note(mir_transform_force_inline_justification)]
|
||||
pub(crate) struct ForceInlineJustification {
|
||||
pub sym: Symbol,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -141,7 +141,7 @@ declare_passes! {
|
|||
mod gvn : GVN;
|
||||
// Made public so that `mir_drops_elaborated_and_const_checked` can be overridden
|
||||
// by custom rustc drivers, running all the steps by themselves. See #114628.
|
||||
pub mod inline : Inline;
|
||||
pub mod inline : Inline, ForceInline;
|
||||
mod instsimplify : InstSimplify { BeforeInline, AfterSimplifyCfg };
|
||||
mod jump_threading : JumpThreading;
|
||||
mod known_panics_lint : KnownPanicsLint;
|
||||
|
@ -488,7 +488,9 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
|
|||
let is_fn_like = tcx.def_kind(def).is_fn_like();
|
||||
if is_fn_like {
|
||||
// Do not compute the mir call graph without said call graph actually being used.
|
||||
if pm::should_run_pass(tcx, &inline::Inline) {
|
||||
if pm::should_run_pass(tcx, &inline::Inline)
|
||||
|| inline::ForceInline::should_run_pass_for_callee(tcx, def.to_def_id())
|
||||
{
|
||||
tcx.ensure_with_value().mir_inliner_callees(ty::InstanceKind::Item(def.to_def_id()));
|
||||
}
|
||||
}
|
||||
|
@ -664,6 +666,8 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
|||
// Perform instsimplify before inline to eliminate some trivial calls (like clone
|
||||
// shims).
|
||||
&instsimplify::InstSimplify::BeforeInline,
|
||||
// Perform inlining of `#[rustc_force_inline]`-annotated callees.
|
||||
&inline::ForceInline,
|
||||
// Perform inlining, which may add a lot of code.
|
||||
&inline::Inline,
|
||||
// Code from other crates may have storage markers, so this needs to happen after
|
||||
|
|
|
@ -79,6 +79,12 @@ pub(super) trait MirPass<'tcx> {
|
|||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if this pass can be overridden by `-Zenable-mir-passes`. This should be
|
||||
/// true for basically every pass other than those that are necessary for correctness.
|
||||
fn can_be_overridden(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>);
|
||||
|
||||
fn is_mir_dump_enabled(&self) -> bool {
|
||||
|
@ -176,6 +182,10 @@ where
|
|||
{
|
||||
let name = pass.name();
|
||||
|
||||
if !pass.can_be_overridden() {
|
||||
return pass.is_enabled(tcx.sess);
|
||||
}
|
||||
|
||||
let overridden_passes = &tcx.sess.opts.unstable_opts.mir_enable_passes;
|
||||
let overridden =
|
||||
overridden_passes.iter().rev().find(|(s, _)| s == &*name).map(|(_name, polarity)| {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Validates the MIR to ensure that invariants are upheld.
|
||||
|
||||
use rustc_abi::{ExternAbi, FIRST_VARIANT, Size};
|
||||
use rustc_attr_parsing::InlineAttr;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir::LangItem;
|
||||
use rustc_index::IndexVec;
|
||||
|
@ -79,7 +80,7 @@ impl<'tcx> crate::MirPass<'tcx> for Validator {
|
|||
cfg_checker.fail(location, msg);
|
||||
}
|
||||
|
||||
if let MirPhase::Runtime(_) = body.phase {
|
||||
if let MirPhase::Runtime(phase) = body.phase {
|
||||
if let ty::InstanceKind::Item(_) = body.source.instance {
|
||||
if body.has_free_regions() {
|
||||
cfg_checker.fail(
|
||||
|
@ -88,6 +89,27 @@ impl<'tcx> crate::MirPass<'tcx> for Validator {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if phase >= RuntimePhase::Optimized
|
||||
&& body
|
||||
.basic_blocks
|
||||
.iter()
|
||||
.filter_map(|bb| match &bb.terminator().kind {
|
||||
TerminatorKind::Call { func, .. }
|
||||
| TerminatorKind::TailCall { func, .. } => Some(func),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|func| match func.ty(&body.local_decls, tcx).kind() {
|
||||
ty::FnDef(did, ..) => Some(did),
|
||||
_ => None,
|
||||
})
|
||||
.any(|did| matches!(tcx.codegen_fn_attrs(did).inline, InlineAttr::Force { .. }))
|
||||
{
|
||||
cfg_checker.fail(
|
||||
Location::START,
|
||||
"`#[rustc_force_inline]`-annotated function not inlined",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue