Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 53ba00a

Browse files
committedJun 29, 2024·
Auto merge of rust-lang#127113 - scottmcm:retune-inlining-again, r=<try>
Avoid MIR bloat in inlining In rust-lang#126578 we ended up with more binary size increases than expected. This change attempts to avoid inlining large things into small things, to avoid that kind of increase, in cases when top-down inlining will still be able to do that inlining later. r? ghost
2 parents d38cd22 + ba09e7c commit 53ba00a

15 files changed

+405
-845
lines changed
 

‎compiler/rustc_mir_transform/src/cost_checker.rs

+72
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const LANDINGPAD_PENALTY: usize = 50;
99
const RESUME_PENALTY: usize = 45;
1010
const LARGE_SWITCH_PENALTY: usize = 20;
1111
const CONST_SWITCH_BONUS: usize = 10;
12+
const MULTIPLE_MUT_PENALTY: usize = 50;
13+
const REF_COPY_BONUS: usize = 5;
14+
const MANY_PARAMETERS_BONUS: usize = 10;
1215

1316
/// Verify that the callee body is compatible with the caller.
1417
#[derive(Clone)]
@@ -31,6 +34,75 @@ impl<'b, 'tcx> CostChecker<'b, 'tcx> {
3134
CostChecker { tcx, param_env, callee_body, instance, penalty: 0, bonus: 0 }
3235
}
3336

37+
/// Add function-level costs not well-represented by the block-level costs.
38+
///
39+
/// Needed because the `CostChecker` is used sometimes for just blocks,
40+
/// and even the full `Inline` doesn't call `visit_body`, so there's nowhere
41+
/// to put this logic in the visitor.
42+
pub fn add_function_level_costs(&mut self) {
43+
// There's usually extra stack costs to passing lots of parameters,
44+
// so we'd rather inline that if the method isn't actually complex,
45+
// especially for trait impls that ignore some parameters.
46+
if self.callee_body.arg_count > 2 {
47+
self.bonus += MANY_PARAMETERS_BONUS;
48+
}
49+
50+
fn is_call_like(bbd: &BasicBlockData<'_>) -> bool {
51+
use TerminatorKind::*;
52+
match bbd.terminator().kind {
53+
Call { .. }
54+
| Drop { .. }
55+
| Assert { .. }
56+
| Yield { .. }
57+
| CoroutineDrop
58+
| InlineAsm { .. } => true,
59+
60+
Goto { .. }
61+
| SwitchInt { .. }
62+
| UnwindResume
63+
| UnwindTerminate(_)
64+
| Return
65+
| Unreachable => false,
66+
67+
FalseEdge { .. } | FalseUnwind { .. } => unreachable!(),
68+
}
69+
}
70+
71+
// If the only has one Call (or similar), inlining isn't increasing the total
72+
// number of calls, so give extra encouragement to inlining that.
73+
if self.callee_body.basic_blocks.iter().filter(|bbd| is_call_like(bbd)).count() == 1 {
74+
self.bonus += CALL_PENALTY;
75+
}
76+
77+
let callee_param_env = std::cell::LazyCell::new(|| {
78+
let def_id = self.callee_body.source.def_id();
79+
self.tcx.param_env(def_id)
80+
});
81+
82+
let mut mut_ref_count = 0;
83+
for local in self.callee_body.args_iter() {
84+
let ty = self.callee_body.local_decls[local].ty;
85+
if let ty::Ref(_, referee, mtbl) = ty.kind() {
86+
match mtbl {
87+
Mutability::Mut => mut_ref_count += 1,
88+
Mutability::Not => {
89+
// Encourage inlining `&impl Copy` parameters,
90+
// as that often lets us remove the indirection.
91+
if referee.is_copy_modulo_regions(self.tcx, *callee_param_env) {
92+
self.bonus += REF_COPY_BONUS;
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
// Discourage inlining something with multiple `&mut` parameters,
100+
// as the MIR inliner doesn't know how to preserve `noalias`.
101+
if mut_ref_count > 1 {
102+
self.penalty += MULTIPLE_MUT_PENALTY;
103+
}
104+
}
105+
34106
pub fn cost(&self) -> usize {
35107
usize::saturating_sub(self.penalty, self.bonus)
36108
}

‎compiler/rustc_mir_transform/src/inline.rs

+46-2
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,18 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
8585
}
8686

8787
let param_env = tcx.param_env_reveal_all_normalized(def_id);
88+
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
8889

8990
let mut this = Inliner {
9091
tcx,
9192
param_env,
92-
codegen_fn_attrs: tcx.codegen_fn_attrs(def_id),
93+
codegen_fn_attrs,
9394
history: Vec::new(),
9495
changed: false,
96+
caller_is_inline_shim: matches!(
97+
codegen_fn_attrs.inline,
98+
InlineAttr::Hint | InlineAttr::Always
99+
) && body_is_shim(body),
95100
};
96101
let blocks = START_BLOCK..body.basic_blocks.next_index();
97102
this.process_blocks(body, blocks);
@@ -111,6 +116,7 @@ struct Inliner<'tcx> {
111116
history: Vec<DefId>,
112117
/// Indicates that the caller body has been modified.
113118
changed: bool,
119+
caller_is_inline_shim: bool,
114120
}
115121

116122
impl<'tcx> Inliner<'tcx> {
@@ -485,7 +491,9 @@ impl<'tcx> Inliner<'tcx> {
485491
) -> Result<(), &'static str> {
486492
let tcx = self.tcx;
487493

488-
let mut threshold = if cross_crate_inlinable {
494+
let mut threshold = if self.caller_is_inline_shim {
495+
self.tcx.sess.opts.unstable_opts.inline_mir_shim_threshold.unwrap_or(30)
496+
} else if cross_crate_inlinable {
489497
self.tcx.sess.opts.unstable_opts.inline_mir_hint_threshold.unwrap_or(100)
490498
} else {
491499
self.tcx.sess.opts.unstable_opts.inline_mir_threshold.unwrap_or(50)
@@ -504,6 +512,8 @@ impl<'tcx> Inliner<'tcx> {
504512
let mut checker =
505513
CostChecker::new(self.tcx, self.param_env, Some(callsite.callee), callee_body);
506514

515+
checker.add_function_level_costs();
516+
507517
// Traverse the MIR manually so we can account for the effects of inlining on the CFG.
508518
let mut work_list = vec![START_BLOCK];
509519
let mut visited = BitSet::new_empty(callee_body.basic_blocks.len());
@@ -1091,3 +1101,37 @@ fn try_instance_mir<'tcx>(
10911101
}
10921102
Ok(tcx.instance_mir(instance))
10931103
}
1104+
1105+
fn body_is_shim(body: &Body<'_>) -> bool {
1106+
let TerminatorKind::Call { target, .. } = body.basic_blocks[START_BLOCK].terminator().kind
1107+
else {
1108+
return false;
1109+
};
1110+
if let Some(target) = target {
1111+
let TerminatorKind::Return = body.basic_blocks[target].terminator().kind else {
1112+
return false;
1113+
};
1114+
}
1115+
1116+
let max_blocks = if !body.is_polymorphic {
1117+
2
1118+
} else if target.is_none() {
1119+
3
1120+
} else {
1121+
4
1122+
};
1123+
if body.basic_blocks.len() > max_blocks {
1124+
return false;
1125+
}
1126+
1127+
body.basic_blocks.iter_enumerated().all(|(bb, bb_data)| {
1128+
bb == START_BLOCK
1129+
|| matches!(
1130+
bb_data.terminator().kind,
1131+
TerminatorKind::Return
1132+
| TerminatorKind::Drop { .. }
1133+
| TerminatorKind::UnwindResume
1134+
| TerminatorKind::UnwindTerminate(_)
1135+
)
1136+
})
1137+
}

‎compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,8 @@ options! {
17711771
inline_mir_preserve_debug: Option<bool> = (None, parse_opt_bool, [TRACKED],
17721772
"when MIR inlining, whether to preserve debug info for callee variables \
17731773
(default: preserve for debuginfo != None, otherwise remove)"),
1774+
inline_mir_shim_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
1775+
"a default MIR inlining threshold (default: 30)"),
17741776
inline_mir_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
17751777
"a default MIR inlining threshold (default: 50)"),
17761778
input_stats: bool = (false, parse_bool, [UNTRACKED],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `marked_inline_direct` after Inline
2+
3+
fn marked_inline_direct(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
9+
bb0: {
10+
StorageLive(_2);
11+
StorageLive(_3);
12+
_3 = _1;
13+
_2 = call_twice(move _3) -> [return: bb1, unwind unreachable];
14+
}
15+
16+
bb1: {
17+
StorageDead(_3);
18+
StorageDead(_2);
19+
_0 = const ();
20+
return;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `marked_inline_direct` after Inline
2+
3+
fn marked_inline_direct(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
9+
bb0: {
10+
StorageLive(_2);
11+
StorageLive(_3);
12+
_3 = _1;
13+
_2 = call_twice(move _3) -> [return: bb1, unwind continue];
14+
}
15+
16+
bb1: {
17+
StorageDead(_3);
18+
StorageDead(_2);
19+
_0 = const ();
20+
return;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// MIR for `marked_inline_indirect` after Inline
2+
3+
fn marked_inline_indirect(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
scope 1 (inlined marked_inline_direct) {
9+
let _4: ();
10+
}
11+
12+
bb0: {
13+
StorageLive(_2);
14+
StorageLive(_3);
15+
_3 = _1;
16+
StorageLive(_4);
17+
_4 = call_twice(move _3) -> [return: bb1, unwind unreachable];
18+
}
19+
20+
bb1: {
21+
StorageDead(_4);
22+
StorageDead(_3);
23+
StorageDead(_2);
24+
_0 = const ();
25+
return;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// MIR for `marked_inline_indirect` after Inline
2+
3+
fn marked_inline_indirect(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
scope 1 (inlined marked_inline_direct) {
9+
let _4: ();
10+
}
11+
12+
bb0: {
13+
StorageLive(_2);
14+
StorageLive(_3);
15+
_3 = _1;
16+
StorageLive(_4);
17+
_4 = call_twice(move _3) -> [return: bb1, unwind continue];
18+
}
19+
20+
bb1: {
21+
StorageDead(_4);
22+
StorageDead(_3);
23+
StorageDead(_2);
24+
_0 = const ();
25+
return;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// MIR for `monomorphic_not_inline` after Inline
2+
3+
fn monomorphic_not_inline(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
scope 1 (inlined call_twice) {
9+
let _4: ();
10+
let _5: ();
11+
}
12+
13+
bb0: {
14+
StorageLive(_2);
15+
StorageLive(_3);
16+
_3 = _1;
17+
StorageLive(_4);
18+
StorageLive(_5);
19+
_4 = other_thing(_3) -> [return: bb2, unwind unreachable];
20+
}
21+
22+
bb1: {
23+
StorageDead(_5);
24+
StorageDead(_4);
25+
StorageDead(_3);
26+
StorageDead(_2);
27+
_0 = const ();
28+
return;
29+
}
30+
31+
bb2: {
32+
_5 = other_thing(move _3) -> [return: bb1, unwind unreachable];
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// MIR for `monomorphic_not_inline` after Inline
2+
3+
fn monomorphic_not_inline(_1: i32) -> () {
4+
debug x => _1;
5+
let mut _0: ();
6+
let _2: ();
7+
let mut _3: i32;
8+
scope 1 (inlined call_twice) {
9+
let _4: ();
10+
let _5: ();
11+
}
12+
13+
bb0: {
14+
StorageLive(_2);
15+
StorageLive(_3);
16+
_3 = _1;
17+
StorageLive(_4);
18+
StorageLive(_5);
19+
_4 = other_thing(_3) -> [return: bb2, unwind continue];
20+
}
21+
22+
bb1: {
23+
StorageDead(_5);
24+
StorageDead(_4);
25+
StorageDead(_3);
26+
StorageDead(_2);
27+
_0 = const ();
28+
return;
29+
}
30+
31+
bb2: {
32+
_5 = other_thing(move _3) -> [return: bb1, unwind continue];
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
2+
//@ compile-flags: -O --crate-type lib
3+
4+
// To avoid MIR blow-up, don't inline large callees into simple shim callers,
5+
// but *do* inline other trivial things.
6+
7+
extern "Rust" {
8+
fn other_thing(x: i32);
9+
}
10+
11+
#[inline]
12+
unsafe fn call_twice(x: i32) {
13+
unsafe {
14+
other_thing(x);
15+
other_thing(x);
16+
}
17+
}
18+
19+
// EMIT_MIR inline_more_in_non_inline.monomorphic_not_inline.Inline.after.mir
20+
#[no_mangle]
21+
pub unsafe fn monomorphic_not_inline(x: i32) {
22+
// CHECK-LABEL: monomorphic_not_inline
23+
// CHECK: other_thing
24+
// CHECK: other_thing
25+
unsafe { call_twice(x) };
26+
}
27+
28+
// EMIT_MIR inline_more_in_non_inline.marked_inline_direct.Inline.after.mir
29+
#[inline]
30+
pub unsafe fn marked_inline_direct(x: i32) {
31+
// CHECK-LABEL: marked_inline_direct
32+
// CHECK-NOT: other_thing
33+
// CHECK: call_twice
34+
// CHECK-NOT: other_thing
35+
unsafe { call_twice(x) };
36+
}
37+
38+
// EMIT_MIR inline_more_in_non_inline.marked_inline_indirect.Inline.after.mir
39+
#[inline]
40+
pub unsafe fn marked_inline_indirect(x: i32) {
41+
// CHECK-LABEL: marked_inline_indirect
42+
// CHECK-NOT: other_thing
43+
// CHECK: call_twice
44+
// CHECK-NOT: other_thing
45+
unsafe { marked_inline_direct(x) };
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.