Skip to content

Commit 5d5edf0

Browse files
committed
Auto merge of #116505 - saethlin:infer-inline, r=cjgillot
Automatically enable cross-crate inlining for small functions This is basically reviving #70550 The `#[inline]` attribute can have a significant impact on code generation or runtime performance (because it enables inlining between CGUs where it would normally not happen) and also on compile-time performance (because it enables MIR inlining). But it has to be added manually, which is awkward. This PR factors whether a DefId is cross-crate inlinable into a query, and replaces all uses of `CodegenFnAttrs::requests_inline` with this new query. The new query incorporates all the other logic that is used to determine whether a Def should be treated as cross-crate-inlinable, and as a last step inspects the function's optimized_mir to determine if it should be treated as cross-crate-inlinable. The heuristic implemented here is deliberately conservative; we only infer inlinability for functions whose optimized_mir does not contain any calls or asserts. I plan to study adjusting the cost model later, but for now the compile time implications of this change are so significant that I think this very crude heuristic is well worth landing.
2 parents ca89f73 + a76cae0 commit 5d5edf0

File tree

68 files changed

+457
-350
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+457
-350
lines changed

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ fn test_unstable_options_tracking_hash() {
770770
);
771771
tracked!(codegen_backend, Some("abc".to_string()));
772772
tracked!(crate_attr, vec!["abc".to_string()]);
773+
tracked!(cross_crate_inline_threshold, Some(200));
773774
tracked!(debug_info_for_profiling, true);
774775
tracked!(debug_macros, true);
775776
tracked!(dep_info_omit_d_target, true);

compiler/rustc_metadata/src/rmeta/decoder.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,10 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
12731273
self.root.tables.optimized_mir.get(self, id).is_some()
12741274
}
12751275

1276+
fn cross_crate_inlinable(self, id: DefIndex) -> bool {
1277+
self.root.tables.cross_crate_inlinable.get(self, id).unwrap_or(false)
1278+
}
1279+
12761280
fn get_fn_has_self_parameter(self, id: DefIndex, sess: &'a Session) -> bool {
12771281
self.root
12781282
.tables

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ provide! { tcx, def_id, other, cdata,
287287
item_attrs => { tcx.arena.alloc_from_iter(cdata.get_item_attrs(def_id.index, tcx.sess)) }
288288
is_mir_available => { cdata.is_item_mir_available(def_id.index) }
289289
is_ctfe_mir_available => { cdata.is_ctfe_mir_available(def_id.index) }
290+
cross_crate_inlinable => { cdata.cross_crate_inlinable(def_id.index) }
290291

291292
dylib_dependency_formats => { cdata.get_dylib_dependency_formats(tcx) }
292293
is_private_dep => {

compiler/rustc_metadata/src/rmeta/encoder.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ fn should_encode_mir(
10461046
|| (tcx.sess.opts.output_types.should_codegen()
10471047
&& reachable_set.contains(&def_id)
10481048
&& (generics.requires_monomorphization(tcx)
1049-
|| tcx.codegen_fn_attrs(def_id).requests_inline()));
1049+
|| tcx.cross_crate_inlinable(def_id)));
10501050
// The function has a `const` modifier or is in a `#[const_trait]`.
10511051
let is_const_fn = tcx.is_const_fn_raw(def_id.to_def_id())
10521052
|| tcx.is_const_default_method(def_id.to_def_id());
@@ -1615,6 +1615,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
16151615
debug!("EntryBuilder::encode_mir({:?})", def_id);
16161616
if encode_opt {
16171617
record!(self.tables.optimized_mir[def_id.to_def_id()] <- tcx.optimized_mir(def_id));
1618+
self.tables
1619+
.cross_crate_inlinable
1620+
.set(def_id.to_def_id().index, Some(self.tcx.cross_crate_inlinable(def_id)));
16181621
record!(self.tables.closure_saved_names_of_captured_variables[def_id.to_def_id()]
16191622
<- tcx.closure_saved_names_of_captured_variables(def_id));
16201623

compiler/rustc_metadata/src/rmeta/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ define_tables! {
427427
object_lifetime_default: Table<DefIndex, LazyValue<ObjectLifetimeDefault>>,
428428
optimized_mir: Table<DefIndex, LazyValue<mir::Body<'static>>>,
429429
mir_for_ctfe: Table<DefIndex, LazyValue<mir::Body<'static>>>,
430+
cross_crate_inlinable: Table<DefIndex, bool>,
430431
closure_saved_names_of_captured_variables: Table<DefIndex, LazyValue<IndexVec<FieldIdx, Symbol>>>,
431432
mir_generator_witnesses: Table<DefIndex, LazyValue<mir::GeneratorLayout<'static>>>,
432433
promoted_mir: Table<DefIndex, LazyValue<IndexVec<mir::Promoted, mir::Body<'static>>>>,

compiler/rustc_metadata/src/rmeta/table.rs

+24
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,30 @@ impl FixedSizeEncoding for bool {
299299
}
300300
}
301301

302+
impl FixedSizeEncoding for Option<bool> {
303+
type ByteArray = [u8; 1];
304+
305+
#[inline]
306+
fn from_bytes(b: &[u8; 1]) -> Self {
307+
match b[0] {
308+
0 => Some(false),
309+
1 => Some(true),
310+
2 => None,
311+
_ => unreachable!(),
312+
}
313+
}
314+
315+
#[inline]
316+
fn write_to_bytes(self, b: &mut [u8; 1]) {
317+
debug_assert!(!self.is_default());
318+
b[0] = match self {
319+
Some(false) => 0,
320+
Some(true) => 1,
321+
None => 2,
322+
};
323+
}
324+
}
325+
302326
impl FixedSizeEncoding for UnusedGenericParams {
303327
type ByteArray = [u8; 4];
304328

compiler/rustc_middle/src/middle/codegen_fn_attrs.rs

-8
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ impl CodegenFnAttrs {
126126
}
127127
}
128128

129-
/// Returns `true` if `#[inline]` or `#[inline(always)]` is present.
130-
pub fn requests_inline(&self) -> bool {
131-
match self.inline {
132-
InlineAttr::Hint | InlineAttr::Always => true,
133-
InlineAttr::None | InlineAttr::Never => false,
134-
}
135-
}
136-
137129
/// Returns `true` if it looks like this symbol needs to be exported, for example:
138130
///
139131
/// * `#[no_mangle]` is present

compiler/rustc_middle/src/query/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,11 @@ rustc_queries! {
22022202
query generics_require_sized_self(def_id: DefId) -> bool {
22032203
desc { "check whether the item has a `where Self: Sized` bound" }
22042204
}
2205+
2206+
query cross_crate_inlinable(def_id: DefId) -> bool {
2207+
desc { "whether the item should be made inlinable across crates" }
2208+
separate_provide_extern
2209+
}
22052210
}
22062211

22072212
rustc_query_append! { define_callbacks! }

compiler/rustc_middle/src/ty/instance.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,15 @@ impl<'tcx> InstanceDef<'tcx> {
245245
// drops of `Option::None` before LTO. We also respect the intent of
246246
// `#[inline]` on `Drop::drop` implementations.
247247
return ty.ty_adt_def().map_or(true, |adt_def| {
248-
adt_def.destructor(tcx).map_or_else(
249-
|| adt_def.is_enum(),
250-
|dtor| tcx.codegen_fn_attrs(dtor.did).requests_inline(),
251-
)
248+
adt_def
249+
.destructor(tcx)
250+
.map_or_else(|| adt_def.is_enum(), |dtor| tcx.cross_crate_inlinable(dtor.did))
252251
});
253252
}
254253
if let ty::InstanceDef::ThreadLocalShim(..) = *self {
255254
return false;
256255
}
257-
tcx.codegen_fn_attrs(self.def_id()).requests_inline()
256+
tcx.cross_crate_inlinable(self.def_id())
258257
}
259258

260259
pub fn requires_caller_location(&self, tcx: TyCtxt<'_>) -> bool {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use rustc_attr::InlineAttr;
2+
use rustc_hir::def::DefKind;
3+
use rustc_hir::def_id::LocalDefId;
4+
use rustc_middle::mir::visit::Visitor;
5+
use rustc_middle::mir::*;
6+
use rustc_middle::query::Providers;
7+
use rustc_middle::ty::TyCtxt;
8+
use rustc_session::config::OptLevel;
9+
10+
pub fn provide(providers: &mut Providers) {
11+
providers.cross_crate_inlinable = cross_crate_inlinable;
12+
}
13+
14+
fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
15+
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
16+
// If this has an extern indicator, then this function is globally shared and thus will not
17+
// generate cgu-internal copies which would make it cross-crate inlinable.
18+
if codegen_fn_attrs.contains_extern_indicator() {
19+
return false;
20+
}
21+
22+
// Obey source annotations first; this is important because it means we can use
23+
// #[inline(never)] to force code generation.
24+
match codegen_fn_attrs.inline {
25+
InlineAttr::Never => return false,
26+
InlineAttr::Hint | InlineAttr::Always => return true,
27+
_ => {}
28+
}
29+
30+
// This just reproduces the logic from Instance::requires_inline.
31+
match tcx.def_kind(def_id) {
32+
DefKind::Ctor(..) | DefKind::Closure => return true,
33+
DefKind::Fn | DefKind::AssocFn => {}
34+
_ => return false,
35+
}
36+
37+
// Don't do any inference when incremental compilation is enabled; the additional inlining that
38+
// inference permits also creates more work for small edits.
39+
if tcx.sess.opts.incremental.is_some() {
40+
return false;
41+
}
42+
43+
// Don't do any inference unless optimizations are enabled.
44+
if matches!(tcx.sess.opts.optimize, OptLevel::No) {
45+
return false;
46+
}
47+
48+
if !tcx.is_mir_available(def_id) {
49+
return false;
50+
}
51+
52+
let mir = tcx.optimized_mir(def_id);
53+
let mut checker =
54+
CostChecker { tcx, callee_body: mir, calls: 0, statements: 0, landing_pads: 0, resumes: 0 };
55+
checker.visit_body(mir);
56+
checker.calls == 0
57+
&& checker.resumes == 0
58+
&& checker.landing_pads == 0
59+
&& checker.statements
60+
<= tcx.sess.opts.unstable_opts.cross_crate_inline_threshold.unwrap_or(100)
61+
}
62+
63+
struct CostChecker<'b, 'tcx> {
64+
tcx: TyCtxt<'tcx>,
65+
callee_body: &'b Body<'tcx>,
66+
calls: usize,
67+
statements: usize,
68+
landing_pads: usize,
69+
resumes: usize,
70+
}
71+
72+
impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
73+
fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
74+
// Don't count StorageLive/StorageDead in the inlining cost.
75+
match statement.kind {
76+
StatementKind::StorageLive(_)
77+
| StatementKind::StorageDead(_)
78+
| StatementKind::Deinit(_)
79+
| StatementKind::Nop => {}
80+
_ => self.statements += 1,
81+
}
82+
}
83+
84+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _: Location) {
85+
let tcx = self.tcx;
86+
match terminator.kind {
87+
TerminatorKind::Drop { ref place, unwind, .. } => {
88+
let ty = place.ty(self.callee_body, tcx).ty;
89+
if !ty.is_trivially_pure_clone_copy() {
90+
self.calls += 1;
91+
if let UnwindAction::Cleanup(_) = unwind {
92+
self.landing_pads += 1;
93+
}
94+
}
95+
}
96+
TerminatorKind::Call { unwind, .. } => {
97+
self.calls += 1;
98+
if let UnwindAction::Cleanup(_) = unwind {
99+
self.landing_pads += 1;
100+
}
101+
}
102+
TerminatorKind::Assert { unwind, .. } => {
103+
self.calls += 1;
104+
if let UnwindAction::Cleanup(_) = unwind {
105+
self.landing_pads += 1;
106+
}
107+
}
108+
TerminatorKind::UnwindResume => self.resumes += 1,
109+
TerminatorKind::InlineAsm { unwind, .. } => {
110+
self.statements += 1;
111+
if let UnwindAction::Cleanup(_) = unwind {
112+
self.landing_pads += 1;
113+
}
114+
}
115+
TerminatorKind::Return => {}
116+
_ => self.statements += 1,
117+
}
118+
}
119+
}

compiler/rustc_mir_transform/src/inline.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ impl<'tcx> Inliner<'tcx> {
169169
caller_body: &mut Body<'tcx>,
170170
callsite: &CallSite<'tcx>,
171171
) -> Result<std::ops::Range<BasicBlock>, &'static str> {
172+
self.check_mir_is_available(caller_body, &callsite.callee)?;
173+
172174
let callee_attrs = self.tcx.codegen_fn_attrs(callsite.callee.def_id());
173-
self.check_codegen_attributes(callsite, callee_attrs)?;
175+
let cross_crate_inlinable = self.tcx.cross_crate_inlinable(callsite.callee.def_id());
176+
self.check_codegen_attributes(callsite, callee_attrs, cross_crate_inlinable)?;
174177

175178
let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
176179
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
@@ -183,9 +186,8 @@ impl<'tcx> Inliner<'tcx> {
183186
}
184187
}
185188

186-
self.check_mir_is_available(caller_body, &callsite.callee)?;
187189
let callee_body = try_instance_mir(self.tcx, callsite.callee.def)?;
188-
self.check_mir_body(callsite, callee_body, callee_attrs)?;
190+
self.check_mir_body(callsite, callee_body, callee_attrs, cross_crate_inlinable)?;
189191

190192
if !self.tcx.consider_optimizing(|| {
191193
format!("Inline {:?} into {:?}", callsite.callee, caller_body.source)
@@ -401,6 +403,7 @@ impl<'tcx> Inliner<'tcx> {
401403
&self,
402404
callsite: &CallSite<'tcx>,
403405
callee_attrs: &CodegenFnAttrs,
406+
cross_crate_inlinable: bool,
404407
) -> Result<(), &'static str> {
405408
if let InlineAttr::Never = callee_attrs.inline {
406409
return Err("never inline hint");
@@ -414,7 +417,7 @@ impl<'tcx> Inliner<'tcx> {
414417
.non_erasable_generics(self.tcx, callsite.callee.def_id())
415418
.next()
416419
.is_some();
417-
if !is_generic && !callee_attrs.requests_inline() {
420+
if !is_generic && !cross_crate_inlinable {
418421
return Err("not exported");
419422
}
420423

@@ -456,10 +459,11 @@ impl<'tcx> Inliner<'tcx> {
456459
callsite: &CallSite<'tcx>,
457460
callee_body: &Body<'tcx>,
458461
callee_attrs: &CodegenFnAttrs,
462+
cross_crate_inlinable: bool,
459463
) -> Result<(), &'static str> {
460464
let tcx = self.tcx;
461465

462-
let mut threshold = if callee_attrs.requests_inline() {
466+
let mut threshold = if cross_crate_inlinable {
463467
self.tcx.sess.opts.unstable_opts.inline_mir_hint_threshold.unwrap_or(100)
464468
} else {
465469
self.tcx.sess.opts.unstable_opts.inline_mir_threshold.unwrap_or(50)

compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ mod const_prop;
6262
mod const_prop_lint;
6363
mod copy_prop;
6464
mod coverage;
65+
mod cross_crate_inline;
6566
mod ctfe_limit;
6667
mod dataflow_const_prop;
6768
mod dead_store_elimination;
@@ -123,6 +124,7 @@ pub fn provide(providers: &mut Providers) {
123124
coverage::query::provide(providers);
124125
ffi_unwind_calls::provide(providers);
125126
shim::provide(providers);
127+
cross_crate_inline::provide(providers);
126128
*providers = Providers {
127129
mir_keys,
128130
mir_const,

0 commit comments

Comments
 (0)