Skip to content

Commit 2420bd3

Browse files
committed
Auto merge of #106428 - saethlin:inline-diverging-functions, r=cjgillot
Permit the MIR inliner to inline diverging functions This heuristic prevents inlining of `hint::unreachable_unchecked`, which in turn makes `Option/Result::unwrap_unchecked` a bad inlining candidate. I looked through the changes to `core`, `alloc`, `std`, and `hashbrown` by hand and they all seem reasonable. Let's see how this looks in perf... --- Based on rustc-perf it looks like this regresses ctfe-stress, and the cachegrind diff indicates that this regression is in `InterpCx::statement`. I don't know how to do any deeper analysis because that function is _enormous_ in the try toolchain, which has no debuginfo in it. And a local build produces significantly different codegen for that function, even with LTO.
2 parents 48ae1b3 + b932751 commit 2420bd3

23 files changed

+777
-83
lines changed

compiler/rustc_mir_transform/src/inline.rs

-7
Original file line numberDiff line numberDiff line change
@@ -424,13 +424,6 @@ impl<'tcx> Inliner<'tcx> {
424424
debug!(" final inline threshold = {}", threshold);
425425

426426
// FIXME: Give a bonus to functions with only a single caller
427-
let diverges = matches!(
428-
callee_body.basic_blocks[START_BLOCK].terminator().kind,
429-
TerminatorKind::Unreachable | TerminatorKind::Call { target: None, .. }
430-
);
431-
if diverges && !matches!(callee_attrs.inline, InlineAttr::Always) {
432-
return Err("callee diverges unconditionally");
433-
}
434427

435428
let mut checker = CostChecker {
436429
tcx: self.tcx,

compiler/rustc_mir_transform/src/instcombine.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::MirPass;
44
use rustc_hir::Mutability;
55
use rustc_middle::mir::{
66
BinOp, Body, Constant, ConstantKind, LocalDecls, Operand, Place, ProjectionElem, Rvalue,
7-
SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
7+
SourceInfo, Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, UnOp,
88
};
99
use rustc_middle::ty::layout::ValidityRequirement;
1010
use rustc_middle::ty::{self, ParamEnv, SubstsRef, Ty, TyCtxt};
@@ -44,6 +44,7 @@ impl<'tcx> MirPass<'tcx> for InstCombine {
4444
&mut block.terminator.as_mut().unwrap(),
4545
&mut block.statements,
4646
);
47+
ctx.combine_duplicate_switch_targets(&mut block.terminator.as_mut().unwrap());
4748
}
4849
}
4950
}
@@ -217,6 +218,19 @@ impl<'tcx> InstCombineContext<'tcx, '_> {
217218
terminator.kind = TerminatorKind::Goto { target: destination_block };
218219
}
219220

221+
fn combine_duplicate_switch_targets(&self, terminator: &mut Terminator<'tcx>) {
222+
let TerminatorKind::SwitchInt { targets, .. } = &mut terminator.kind
223+
else { return };
224+
225+
let otherwise = targets.otherwise();
226+
if targets.iter().any(|t| t.1 == otherwise) {
227+
*targets = SwitchTargets::new(
228+
targets.iter().filter(|t| t.1 != otherwise),
229+
targets.otherwise(),
230+
);
231+
}
232+
}
233+
220234
fn combine_intrinsic_assert(
221235
&self,
222236
terminator: &mut Terminator<'tcx>,

compiler/rustc_mir_transform/src/simplify.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
//! return.
2929
3030
use crate::MirPass;
31-
use rustc_data_structures::fx::FxHashSet;
31+
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
3232
use rustc_index::vec::{Idx, IndexVec};
3333
use rustc_middle::mir::coverage::*;
3434
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
@@ -48,6 +48,7 @@ impl SimplifyCfg {
4848

4949
pub fn simplify_cfg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
5050
CfgSimplifier::new(body).simplify();
51+
remove_duplicate_unreachable_blocks(tcx, body);
5152
remove_dead_blocks(tcx, body);
5253

5354
// FIXME: Should probably be moved into some kind of pass manager
@@ -259,6 +260,49 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
259260
}
260261
}
261262

263+
pub fn remove_duplicate_unreachable_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
264+
struct OptApplier<'tcx> {
265+
tcx: TyCtxt<'tcx>,
266+
duplicates: FxIndexSet<BasicBlock>,
267+
}
268+
269+
impl<'tcx> MutVisitor<'tcx> for OptApplier<'tcx> {
270+
fn tcx(&self) -> TyCtxt<'tcx> {
271+
self.tcx
272+
}
273+
274+
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
275+
for target in terminator.successors_mut() {
276+
// We don't have to check whether `target` is a cleanup block, because have
277+
// entirely excluded cleanup blocks in building the set of duplicates.
278+
if self.duplicates.contains(target) {
279+
*target = self.duplicates[0];
280+
}
281+
}
282+
283+
self.super_terminator(terminator, location);
284+
}
285+
}
286+
287+
let unreachable_blocks = body
288+
.basic_blocks
289+
.iter_enumerated()
290+
.filter(|(_, bb)| {
291+
// CfgSimplifier::simplify leaves behind some unreachable basic blocks without a
292+
// terminator. Those blocks will be deleted by remove_dead_blocks, but we run just
293+
// before then so we need to handle missing terminators.
294+
// We also need to prevent confusing cleanup and non-cleanup blocks. In practice we
295+
// don't emit empty unreachable cleanup blocks, so this simple check suffices.
296+
bb.terminator.is_some() && bb.is_empty_unreachable() && !bb.is_cleanup
297+
})
298+
.map(|(block, _)| block)
299+
.collect::<FxIndexSet<_>>();
300+
301+
if unreachable_blocks.len() > 1 {
302+
OptApplier { tcx, duplicates: unreachable_blocks }.visit_body(body);
303+
}
304+
}
305+
262306
pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
263307
let reachable = traversal::reachable_as_bitset(body);
264308
let num_blocks = body.basic_blocks.len();

src/bootstrap/builder.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,12 @@ impl<'a> Builder<'a> {
19411941
rustflags.arg("-Zvalidate-mir");
19421942
rustflags.arg(&format!("-Zmir-opt-level={}", mir_opt_level));
19431943
}
1944+
// Always enable inlining MIR when building the standard library.
1945+
// Without this flag, MIR inlining is disabled when incremental compilation is enabled.
1946+
// That causes some mir-opt tests which inline functions from the standard library to
1947+
// break when incremental compilation is enabled. So this overrides the "no inlining
1948+
// during incremental builds" heuristic for the standard library.
1949+
rustflags.arg("-Zinline-mir");
19441950
}
19451951

19461952
Cargo { command: cargo, rustflags, rustdocflags, allow_features }

tests/mir-opt/building/async_await.b-{closure#0}.generator_resume.0.mir

+15-19
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
9090

9191
bb0: {
9292
_39 = discriminant((*(_1.0: &mut [async fn body@$DIR/async_await.rs:14:18: 17:2]))); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
93-
switchInt(move _39) -> [0: bb1, 1: bb29, 3: bb27, 4: bb28, otherwise: bb30]; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
93+
switchInt(move _39) -> [0: bb1, 1: bb28, 3: bb26, 4: bb27, otherwise: bb29]; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
9494
}
9595

9696
bb1: {
@@ -263,7 +263,7 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
263263
StorageDead(_29); // scope 5 at $DIR/async_await.rs:+2:13: +2:14
264264
StorageDead(_26); // scope 5 at $DIR/async_await.rs:+2:13: +2:14
265265
_32 = discriminant(_25); // scope 4 at $DIR/async_await.rs:+2:8: +2:14
266-
switchInt(move _32) -> [0: bb22, 1: bb20, otherwise: bb21]; // scope 4 at $DIR/async_await.rs:+2:8: +2:14
266+
switchInt(move _32) -> [0: bb21, 1: bb20, otherwise: bb9]; // scope 4 at $DIR/async_await.rs:+2:8: +2:14
267267
}
268268

269269
bb20: {
@@ -281,10 +281,6 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
281281
}
282282

283283
bb21: {
284-
unreachable; // scope 4 at $DIR/async_await.rs:+2:8: +2:14
285-
}
286-
287-
bb22: {
288284
StorageLive(_33); // scope 4 at $DIR/async_await.rs:+2:5: +2:14
289285
_33 = ((_25 as Ready).0: ()); // scope 4 at $DIR/async_await.rs:+2:5: +2:14
290286
_37 = _33; // scope 6 at $DIR/async_await.rs:+2:5: +2:14
@@ -293,34 +289,34 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
293289
StorageDead(_28); // scope 4 at $DIR/async_await.rs:+2:13: +2:14
294290
StorageDead(_25); // scope 4 at $DIR/async_await.rs:+2:13: +2:14
295291
StorageDead(_24); // scope 4 at $DIR/async_await.rs:+2:13: +2:14
296-
goto -> bb24; // scope 0 at $DIR/async_await.rs:+2:13: +2:14
292+
goto -> bb23; // scope 0 at $DIR/async_await.rs:+2:13: +2:14
297293
}
298294

299-
bb23: {
295+
bb22: {
300296
StorageDead(_36); // scope 4 at $DIR/async_await.rs:+2:13: +2:14
301297
_38 = move _35; // scope 4 at $DIR/async_await.rs:+2:8: +2:14
302298
StorageDead(_35); // scope 4 at $DIR/async_await.rs:+2:13: +2:14
303299
_7 = const (); // scope 4 at $DIR/async_await.rs:+2:8: +2:14
304300
goto -> bb16; // scope 4 at $DIR/async_await.rs:+2:8: +2:14
305301
}
306302

307-
bb24: {
303+
bb23: {
308304
nop; // scope 0 at $DIR/async_await.rs:+2:13: +2:14
309-
goto -> bb25; // scope 0 at $DIR/async_await.rs:+3:1: +3:2
305+
goto -> bb24; // scope 0 at $DIR/async_await.rs:+3:1: +3:2
310306
}
311307

312-
bb25: {
308+
bb24: {
313309
StorageDead(_21); // scope 0 at $DIR/async_await.rs:+3:1: +3:2
314-
goto -> bb26; // scope 0 at $DIR/async_await.rs:+3:1: +3:2
310+
goto -> bb25; // scope 0 at $DIR/async_await.rs:+3:1: +3:2
315311
}
316312

317-
bb26: {
313+
bb25: {
318314
_0 = Poll::<()>::Ready(move _37); // scope 0 at $DIR/async_await.rs:+3:2: +3:2
319315
discriminant((*(_1.0: &mut [async fn body@$DIR/async_await.rs:14:18: 17:2]))) = 1; // scope 0 at $DIR/async_await.rs:+3:2: +3:2
320316
return; // scope 0 at $DIR/async_await.rs:+3:2: +3:2
321317
}
322318

323-
bb27: {
319+
bb26: {
324320
StorageLive(_3); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
325321
StorageLive(_4); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
326322
StorageLive(_19); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
@@ -329,19 +325,19 @@ fn b::{closure#0}(_1: Pin<&mut [async fn body@$DIR/async_await.rs:14:18: 17:2]>,
329325
goto -> bb11; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
330326
}
331327

332-
bb28: {
328+
bb27: {
333329
StorageLive(_21); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
334330
StorageLive(_35); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
335331
StorageLive(_36); // scope 0 at $DIR/async_await.rs:+0:18: +3:2
336332
_35 = move _2; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
337-
goto -> bb23; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
333+
goto -> bb22; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
338334
}
339335

340-
bb29: {
341-
assert(const false, "`async fn` resumed after completion") -> bb29; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
336+
bb28: {
337+
assert(const false, "`async fn` resumed after completion") -> bb28; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
342338
}
343339

344-
bb30: {
340+
bb29: {
345341
unreachable; // scope 0 at $DIR/async_await.rs:+0:18: +3:2
346342
}
347343
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![crate_type = "lib"]
2+
#![feature(unchecked_math)]
3+
4+
// ignore-debug: the debug assertions prevent the inlining we are testing for
5+
// compile-flags: -Zmir-opt-level=2 -Zinline-mir
6+
7+
// EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_smaller.Inline.diff
8+
// EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_smaller.PreCodegen.after.mir
9+
pub unsafe fn unchecked_shl_unsigned_smaller(a: u16, b: u32) -> u16 {
10+
a.unchecked_shl(b)
11+
}
12+
13+
// EMIT_MIR unchecked_shifts.unchecked_shr_signed_smaller.Inline.diff
14+
// EMIT_MIR unchecked_shifts.unchecked_shr_signed_smaller.PreCodegen.after.mir
15+
pub unsafe fn unchecked_shr_signed_smaller(a: i16, b: u32) -> i16 {
16+
a.unchecked_shr(b)
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
- // MIR for `unchecked_shl_unsigned_smaller` before Inline
2+
+ // MIR for `unchecked_shl_unsigned_smaller` after Inline
3+
4+
fn unchecked_shl_unsigned_smaller(_1: u16, _2: u32) -> u16 {
5+
debug a => _1; // in scope 0 at $DIR/unchecked_shifts.rs:+0:46: +0:47
6+
debug b => _2; // in scope 0 at $DIR/unchecked_shifts.rs:+0:54: +0:55
7+
let mut _0: u16; // return place in scope 0 at $DIR/unchecked_shifts.rs:+0:65: +0:68
8+
let mut _3: u16; // in scope 0 at $DIR/unchecked_shifts.rs:+1:5: +1:6
9+
let mut _4: u32; // in scope 0 at $DIR/unchecked_shifts.rs:+1:21: +1:22
10+
+ scope 1 (inlined core::num::<impl u16>::unchecked_shl) { // at $DIR/unchecked_shifts.rs:10:7: 10:23
11+
+ debug self => _3; // in scope 1 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
12+
+ debug rhs => _4; // in scope 1 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
13+
+ let mut _5: u16; // in scope 1 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
14+
+ let mut _6: std::option::Option<u16>; // in scope 1 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
15+
+ let mut _7: std::result::Result<u16, std::num::TryFromIntError>; // in scope 1 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
16+
+ scope 2 {
17+
+ scope 3 (inlined Result::<u16, TryFromIntError>::ok) { // at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
18+
+ debug self => _7; // in scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
19+
+ let mut _8: isize; // in scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
20+
+ let _9: u16; // in scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
21+
+ scope 4 {
22+
+ debug x => _9; // in scope 4 at $SRC_DIR/core/src/result.rs:LL:COL
23+
+ }
24+
+ scope 5 {
25+
+ scope 6 {
26+
+ debug x => const TryFromIntError(()); // in scope 6 at $SRC_DIR/core/src/result.rs:LL:COL
27+
+ }
28+
+ }
29+
+ }
30+
+ scope 7 (inlined #[track_caller] Option::<u16>::unwrap_unchecked) { // at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
31+
+ debug self => _6; // in scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
32+
+ let mut _10: &std::option::Option<u16>; // in scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
33+
+ let mut _11: isize; // in scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
34+
+ scope 8 {
35+
+ debug val => _5; // in scope 8 at $SRC_DIR/core/src/option.rs:LL:COL
36+
+ }
37+
+ scope 9 {
38+
+ scope 11 (inlined unreachable_unchecked) { // at $SRC_DIR/core/src/option.rs:LL:COL
39+
+ scope 12 {
40+
+ scope 13 (inlined unreachable_unchecked::runtime) { // at $SRC_DIR/core/src/intrinsics.rs:LL:COL
41+
+ }
42+
+ }
43+
+ }
44+
+ }
45+
+ scope 10 (inlined Option::<u16>::is_some) { // at $SRC_DIR/core/src/option.rs:LL:COL
46+
+ debug self => _10; // in scope 10 at $SRC_DIR/core/src/option.rs:LL:COL
47+
+ }
48+
+ }
49+
+ }
50+
+ }
51+
52+
bb0: {
53+
StorageLive(_3); // scope 0 at $DIR/unchecked_shifts.rs:+1:5: +1:6
54+
_3 = _1; // scope 0 at $DIR/unchecked_shifts.rs:+1:5: +1:6
55+
StorageLive(_4); // scope 0 at $DIR/unchecked_shifts.rs:+1:21: +1:22
56+
_4 = _2; // scope 0 at $DIR/unchecked_shifts.rs:+1:21: +1:22
57+
- _0 = core::num::<impl u16>::unchecked_shl(move _3, move _4) -> bb1; // scope 0 at $DIR/unchecked_shifts.rs:+1:5: +1:23
58+
+ StorageLive(_5); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
59+
+ StorageLive(_6); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
60+
+ StorageLive(_7); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
61+
+ _7 = <u32 as TryInto<u16>>::try_into(_4) -> bb1; // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
62+
// mir::Constant
63+
- // + span: $DIR/unchecked_shifts.rs:10:7: 10:20
64+
- // + literal: Const { ty: unsafe fn(u16, u32) -> u16 {core::num::<impl u16>::unchecked_shl}, val: Value(<ZST>) }
65+
+ // + span: $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
66+
+ // + literal: Const { ty: fn(u32) -> Result<u16, <u32 as TryInto<u16>>::Error> {<u32 as TryInto<u16>>::try_into}, val: Value(<ZST>) }
67+
}
68+
69+
bb1: {
70+
+ StorageLive(_9); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
71+
+ _8 = discriminant(_7); // scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
72+
+ switchInt(move _8) -> [0: bb6, 1: bb4, otherwise: bb5]; // scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
73+
+ }
74+
+
75+
+ bb2: {
76+
+ StorageDead(_9); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
77+
+ StorageDead(_7); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
78+
+ StorageLive(_10); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
79+
+ _11 = discriminant(_6); // scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
80+
+ switchInt(move _11) -> [1: bb7, otherwise: bb5]; // scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
81+
+ }
82+
+
83+
+ bb3: {
84+
+ StorageDead(_5); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
85+
StorageDead(_4); // scope 0 at $DIR/unchecked_shifts.rs:+1:22: +1:23
86+
StorageDead(_3); // scope 0 at $DIR/unchecked_shifts.rs:+1:22: +1:23
87+
return; // scope 0 at $DIR/unchecked_shifts.rs:+2:2: +2:2
88+
+ }
89+
+
90+
+ bb4: {
91+
+ _6 = Option::<u16>::None; // scope 6 at $SRC_DIR/core/src/result.rs:LL:COL
92+
+ goto -> bb2; // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
93+
+ }
94+
+
95+
+ bb5: {
96+
+ unreachable; // scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
97+
+ }
98+
+
99+
+ bb6: {
100+
+ _9 = move ((_7 as Ok).0: u16); // scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
101+
+ _6 = Option::<u16>::Some(move _9); // scope 4 at $SRC_DIR/core/src/result.rs:LL:COL
102+
+ goto -> bb2; // scope 3 at $SRC_DIR/core/src/result.rs:LL:COL
103+
+ }
104+
+
105+
+ bb7: {
106+
+ _5 = move ((_6 as Some).0: u16); // scope 7 at $SRC_DIR/core/src/option.rs:LL:COL
107+
+ StorageDead(_10); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
108+
+ StorageDead(_6); // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
109+
+ _0 = unchecked_shl::<u16>(_3, move _5) -> bb3; // scope 2 at $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
110+
+ // mir::Constant
111+
+ // + span: $SRC_DIR/core/src/num/uint_macros.rs:LL:COL
112+
+ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u16, u16) -> u16 {unchecked_shl::<u16>}, val: Value(<ZST>) }
113+
}
114+
}
115+

0 commit comments

Comments
 (0)