Automatically enable cross-crate inlining for small functions
This commit is contained in:
parent
09df6108c8
commit
33b0e4be06
67 changed files with 434 additions and 344 deletions
119
compiler/rustc_mir_transform/src/cross_crate_inline.rs
Normal file
119
compiler/rustc_mir_transform/src/cross_crate_inline.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use rustc_attr::InlineAttr;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::config::OptLevel;
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
providers.cross_crate_inlinable = cross_crate_inlinable;
|
||||
}
|
||||
|
||||
fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
|
||||
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
|
||||
// If this has an extern indicator, then this function is globally shared and thus will not
|
||||
// generate cgu-internal copies which would make it cross-crate inlinable.
|
||||
if codegen_fn_attrs.contains_extern_indicator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obey source annotations first; this is important because it means we can use
|
||||
// #[inline(never)] to force code generation.
|
||||
match codegen_fn_attrs.inline {
|
||||
InlineAttr::Never => return false,
|
||||
InlineAttr::Hint | InlineAttr::Always => return true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// This just reproduces the logic from Instance::requires_inline.
|
||||
match tcx.def_kind(def_id) {
|
||||
DefKind::Ctor(..) | DefKind::Closure => return true,
|
||||
DefKind::Fn | DefKind::AssocFn => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
// Don't do any inference when incremental compilation is enabled; the additional inlining that
|
||||
// inference permits also creates more work for small edits.
|
||||
if tcx.sess.opts.incremental.is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't do any inference unless optimizations are enabled.
|
||||
if matches!(tcx.sess.opts.optimize, OptLevel::No) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !tcx.is_mir_available(def_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mir = tcx.optimized_mir(def_id);
|
||||
let mut checker =
|
||||
CostChecker { tcx, callee_body: mir, calls: 0, statements: 0, landing_pads: 0, resumes: 0 };
|
||||
checker.visit_body(mir);
|
||||
checker.calls == 0
|
||||
&& checker.resumes == 0
|
||||
&& checker.landing_pads == 0
|
||||
&& checker.statements
|
||||
<= tcx.sess.opts.unstable_opts.cross_crate_inline_threshold.unwrap_or(100)
|
||||
}
|
||||
|
||||
struct CostChecker<'b, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
callee_body: &'b Body<'tcx>,
|
||||
calls: usize,
|
||||
statements: usize,
|
||||
landing_pads: usize,
|
||||
resumes: usize,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
|
||||
fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
|
||||
// Don't count StorageLive/StorageDead in the inlining cost.
|
||||
match statement.kind {
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Deinit(_)
|
||||
| StatementKind::Nop => {}
|
||||
_ => self.statements += 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _: Location) {
|
||||
let tcx = self.tcx;
|
||||
match terminator.kind {
|
||||
TerminatorKind::Drop { ref place, unwind, .. } => {
|
||||
let ty = place.ty(self.callee_body, tcx).ty;
|
||||
if !ty.is_trivially_pure_clone_copy() {
|
||||
self.calls += 1;
|
||||
if let UnwindAction::Cleanup(_) = unwind {
|
||||
self.landing_pads += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
TerminatorKind::Call { unwind, .. } => {
|
||||
self.calls += 1;
|
||||
if let UnwindAction::Cleanup(_) = unwind {
|
||||
self.landing_pads += 1;
|
||||
}
|
||||
}
|
||||
TerminatorKind::Assert { unwind, .. } => {
|
||||
self.calls += 1;
|
||||
if let UnwindAction::Cleanup(_) = unwind {
|
||||
self.landing_pads += 1;
|
||||
}
|
||||
}
|
||||
TerminatorKind::UnwindResume => self.resumes += 1,
|
||||
TerminatorKind::InlineAsm { unwind, .. } => {
|
||||
self.statements += 1;
|
||||
if let UnwindAction::Cleanup(_) = unwind {
|
||||
self.landing_pads += 1;
|
||||
}
|
||||
}
|
||||
TerminatorKind::Return => {}
|
||||
_ => self.statements += 1,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -169,8 +169,11 @@ impl<'tcx> Inliner<'tcx> {
|
|||
caller_body: &mut Body<'tcx>,
|
||||
callsite: &CallSite<'tcx>,
|
||||
) -> Result<std::ops::Range<BasicBlock>, &'static str> {
|
||||
self.check_mir_is_available(caller_body, &callsite.callee)?;
|
||||
|
||||
let callee_attrs = self.tcx.codegen_fn_attrs(callsite.callee.def_id());
|
||||
self.check_codegen_attributes(callsite, callee_attrs)?;
|
||||
let cross_crate_inlinable = self.tcx.cross_crate_inlinable(callsite.callee.def_id());
|
||||
self.check_codegen_attributes(callsite, callee_attrs, cross_crate_inlinable)?;
|
||||
|
||||
let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
|
||||
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
|
||||
|
@ -183,9 +186,8 @@ impl<'tcx> Inliner<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
self.check_mir_is_available(caller_body, &callsite.callee)?;
|
||||
let callee_body = try_instance_mir(self.tcx, callsite.callee.def)?;
|
||||
self.check_mir_body(callsite, callee_body, callee_attrs)?;
|
||||
self.check_mir_body(callsite, callee_body, callee_attrs, cross_crate_inlinable)?;
|
||||
|
||||
if !self.tcx.consider_optimizing(|| {
|
||||
format!("Inline {:?} into {:?}", callsite.callee, caller_body.source)
|
||||
|
@ -401,6 +403,7 @@ impl<'tcx> Inliner<'tcx> {
|
|||
&self,
|
||||
callsite: &CallSite<'tcx>,
|
||||
callee_attrs: &CodegenFnAttrs,
|
||||
cross_crate_inlinable: bool,
|
||||
) -> Result<(), &'static str> {
|
||||
if let InlineAttr::Never = callee_attrs.inline {
|
||||
return Err("never inline hint");
|
||||
|
@ -414,7 +417,7 @@ impl<'tcx> Inliner<'tcx> {
|
|||
.non_erasable_generics(self.tcx, callsite.callee.def_id())
|
||||
.next()
|
||||
.is_some();
|
||||
if !is_generic && !callee_attrs.requests_inline() {
|
||||
if !is_generic && !cross_crate_inlinable {
|
||||
return Err("not exported");
|
||||
}
|
||||
|
||||
|
@ -456,10 +459,11 @@ impl<'tcx> Inliner<'tcx> {
|
|||
callsite: &CallSite<'tcx>,
|
||||
callee_body: &Body<'tcx>,
|
||||
callee_attrs: &CodegenFnAttrs,
|
||||
cross_crate_inlinable: bool,
|
||||
) -> Result<(), &'static str> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
let mut threshold = if callee_attrs.requests_inline() {
|
||||
let mut threshold = if cross_crate_inlinable {
|
||||
self.tcx.sess.opts.unstable_opts.inline_mir_hint_threshold.unwrap_or(100)
|
||||
} else {
|
||||
self.tcx.sess.opts.unstable_opts.inline_mir_threshold.unwrap_or(50)
|
||||
|
|
|
@ -62,6 +62,7 @@ mod const_prop;
|
|||
mod const_prop_lint;
|
||||
mod copy_prop;
|
||||
mod coverage;
|
||||
mod cross_crate_inline;
|
||||
mod ctfe_limit;
|
||||
mod dataflow_const_prop;
|
||||
mod dead_store_elimination;
|
||||
|
@ -123,6 +124,7 @@ pub fn provide(providers: &mut Providers) {
|
|||
coverage::query::provide(providers);
|
||||
ffi_unwind_calls::provide(providers);
|
||||
shim::provide(providers);
|
||||
cross_crate_inline::provide(providers);
|
||||
*providers = Providers {
|
||||
mir_keys,
|
||||
mir_const,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue