Skip to content

Commit 340acfa

Browse files
committed
Auto merge of #127113 - scottmcm:retune-inlining-again, r=<try>
Avoid MIR bloat in inlining try-job: test-various In #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 c3774be + 23c8ed1 commit 340acfa

18 files changed

+385
-845
lines changed

compiler/rustc_mir_transform/src/cost_checker.rs

+31
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,37 @@ impl<'b, 'tcx> CostChecker<'b, 'tcx> {
3131
CostChecker { tcx, param_env, callee_body, instance, penalty: 0, bonus: 0 }
3232
}
3333

34+
/// Add function-level costs not well-represented by the block-level costs.
35+
///
36+
/// Needed because the `CostChecker` is used sometimes for just blocks,
37+
/// and even the full `Inline` doesn't call `visit_body`, so there's nowhere
38+
/// to put this logic in the visitor.
39+
pub fn add_function_level_costs(&mut self) {
40+
fn is_call_like(bbd: &BasicBlockData<'_>) -> bool {
41+
use TerminatorKind::*;
42+
match bbd.terminator().kind {
43+
Call { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => true,
44+
45+
Goto { .. }
46+
| SwitchInt { .. }
47+
| UnwindResume
48+
| UnwindTerminate(_)
49+
| Return
50+
| Unreachable => false,
51+
52+
Yield { .. } | CoroutineDrop | FalseEdge { .. } | FalseUnwind { .. } => {
53+
unreachable!()
54+
}
55+
}
56+
}
57+
58+
// If the only has one Call (or similar), inlining isn't increasing the total
59+
// number of calls, so give extra encouragement to inlining that.
60+
if self.callee_body.basic_blocks.iter().filter(|bbd| is_call_like(bbd)).count() == 1 {
61+
self.bonus += CALL_PENALTY;
62+
}
63+
}
64+
3465
pub fn cost(&self) -> usize {
3566
usize::saturating_sub(self.penalty, self.bonus)
3667
}

compiler/rustc_mir_transform/src/inline.rs

+48-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_forwarder: matches!(
97+
codegen_fn_attrs.inline,
98+
InlineAttr::Hint | InlineAttr::Always
99+
) && body_is_forwarder(body),
95100
};
96101
let blocks = START_BLOCK..body.basic_blocks.next_index();
97102
this.process_blocks(body, blocks);
@@ -111,6 +116,9 @@ struct Inliner<'tcx> {
111116
history: Vec<DefId>,
112117
/// Indicates that the caller body has been modified.
113118
changed: bool,
119+
/// Indicates that the caller is #[inline] and just calls another function,
120+
/// and thus we can inline less into it as it'll be inlined itself.
121+
caller_is_inline_forwarder: bool,
114122
}
115123

116124
impl<'tcx> Inliner<'tcx> {
@@ -485,7 +493,9 @@ impl<'tcx> Inliner<'tcx> {
485493
) -> Result<(), &'static str> {
486494
let tcx = self.tcx;
487495

488-
let mut threshold = if cross_crate_inlinable {
496+
let mut threshold = if self.caller_is_inline_forwarder {
497+
self.tcx.sess.opts.unstable_opts.inline_mir_forwarder_threshold.unwrap_or(30)
498+
} else if cross_crate_inlinable {
489499
self.tcx.sess.opts.unstable_opts.inline_mir_hint_threshold.unwrap_or(100)
490500
} else {
491501
self.tcx.sess.opts.unstable_opts.inline_mir_threshold.unwrap_or(50)
@@ -504,6 +514,8 @@ impl<'tcx> Inliner<'tcx> {
504514
let mut checker =
505515
CostChecker::new(self.tcx, self.param_env, Some(callsite.callee), callee_body);
506516

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

compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,8 @@ options! {
17661766
"enable LLVM inlining (default: yes)"),
17671767
inline_mir: Option<bool> = (None, parse_opt_bool, [TRACKED],
17681768
"enable MIR inlining (default: no)"),
1769+
inline_mir_forwarder_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
1770+
"inlining threshold when the caller is a simple forwarding function (default: 30)"),
17691771
inline_mir_hint_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
17701772
"inlining threshold for functions with inline hint (default: 100)"),
17711773
inline_mir_preserve_debug: Option<bool> = (None, parse_opt_bool, [TRACKED],

library/alloc/src/raw_vec.rs

+17
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ impl<T, A: Allocator> RawVec<T, A> {
429429
///
430430
/// Aborts on OOM.
431431
#[cfg(not(no_global_oom_handling))]
432+
#[inline]
432433
pub fn shrink_to_fit(&mut self, cap: usize) {
433434
if let Err(err) = self.shrink(cap) {
434435
handle_error(err);
@@ -511,9 +512,25 @@ impl<T, A: Allocator> RawVec<T, A> {
511512
}
512513

513514
#[cfg(not(no_global_oom_handling))]
515+
#[inline]
514516
fn shrink(&mut self, cap: usize) -> Result<(), TryReserveError> {
515517
assert!(cap <= self.capacity(), "Tried to shrink to a larger capacity");
518+
// SAFETY: Just checked this isn't trying to grow
519+
unsafe { self.shrink_unchecked(cap) }
520+
}
516521

522+
/// `shrink`, but without the capacity check.
523+
///
524+
/// This is split out so that `shrink` can inline the check, since it
525+
/// optimizes out in things like `shrink_to_fit`, without needing to
526+
/// also inline all this code, as doing that ends up failing the
527+
/// `vec-shrink-panic` codegen test when `shrink_to_fit` ends up being too
528+
/// big for LLVM to be willing to inline.
529+
///
530+
/// # Safety
531+
/// `cap <= self.capacity()`
532+
#[cfg(not(no_global_oom_handling))]
533+
unsafe fn shrink_unchecked(&mut self, cap: usize) -> Result<(), TryReserveError> {
517534
let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
518535
// See current_memory() why this assert is here
519536
const { assert!(mem::size_of::<T>() % mem::align_of::<T>() == 0) };

library/alloc/src/vec/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,7 @@ impl<T, A: Allocator> Vec<T, A> {
11011101
/// ```
11021102
#[cfg(not(no_global_oom_handling))]
11031103
#[stable(feature = "rust1", since = "1.0.0")]
1104+
#[inline]
11041105
pub fn shrink_to_fit(&mut self) {
11051106
// The capacity is never less than the length, and there's nothing to do when
11061107
// they are equal, so we can avoid the panic case in `RawVec::shrink_to_fit`

tests/codegen-units/item-collection/drop_in_place_intrinsic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//
22
//@ compile-flags:-Zprint-mono-items=eager
33
//@ compile-flags:-Zinline-in-all-cgus
4+
//@ compile-flags:-Zinline-mir=no
45

56
#![feature(start)]
67

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+
}

0 commit comments

Comments
 (0)