Skip to content

Commit 632387f

Browse files
committed
Auto merge of #66329 - ktrianta:mir-opt-unreachable-propagation, r=oli-obk
Add unreachable propagation mir optimization pass @oli-obk suggested we create a MIR pass that optimizes away basic blocks that lead only to basic blocks with terminator kind **unreachable**. This is a first take on this, which we started with @gilescope at RustFest Impl Days. The test currently fails when the compiled program runs (undefined behaviour). Is there a way to avoid running the compiled program?
2 parents 4b172cc + 72710d6 commit 632387f

8 files changed

+449
-67
lines changed

src/librustc_mir/transform/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub mod simplify;
3636
pub mod simplify_branches;
3737
pub mod simplify_try;
3838
pub mod uninhabited_enum_branching;
39+
pub mod unreachable_prop;
3940

4041
pub(crate) fn provide(providers: &mut Providers<'_>) {
4142
self::check_unsafety::provide(providers);
@@ -299,6 +300,7 @@ fn run_optimization_passes<'tcx>(
299300
// From here on out, regions are gone.
300301
&erase_regions::EraseRegions,
301302
// Optimizations begin.
303+
&unreachable_prop::UnreachablePropagation,
302304
&uninhabited_enum_branching::UninhabitedEnumBranching,
303305
&simplify::SimplifyCfg::new("after-uninhabited-enum-branching"),
304306
&inline::Inline,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! A pass that propagates the unreachable terminator of a block to its predecessors
2+
//! when all of their successors are unreachable. This is achieved through a
3+
//! post-order traversal of the blocks.
4+
5+
use crate::transform::simplify;
6+
use crate::transform::{MirPass, MirSource};
7+
use rustc::mir::*;
8+
use rustc::ty::TyCtxt;
9+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10+
use std::borrow::Cow;
11+
12+
pub struct UnreachablePropagation;
13+
14+
impl MirPass<'_> for UnreachablePropagation {
15+
fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>) {
16+
if tcx.sess.opts.debugging_opts.mir_opt_level < 3 {
17+
// Enable only under -Zmir-opt-level=3 as in some cases (check the deeply-nested-opt
18+
// perf benchmark) LLVM may spend quite a lot of time optimizing the generated code.
19+
return;
20+
}
21+
22+
let mut unreachable_blocks = FxHashSet::default();
23+
let mut replacements = FxHashMap::default();
24+
25+
for (bb, bb_data) in traversal::postorder(body) {
26+
let terminator = bb_data.terminator();
27+
// HACK: If the block contains any asm statement it is not regarded as unreachable.
28+
// This is a temporary solution that handles possibly diverging asm statements.
29+
// Accompanying testcases: mir-opt/unreachable_asm.rs and mir-opt/unreachable_asm_2.rs
30+
let asm_stmt_in_block = || {
31+
bb_data.statements.iter().any(|stmt: &Statement<'_>| match stmt.kind {
32+
StatementKind::InlineAsm(..) => true,
33+
_ => false,
34+
})
35+
};
36+
37+
if terminator.kind == TerminatorKind::Unreachable && !asm_stmt_in_block() {
38+
unreachable_blocks.insert(bb);
39+
} else {
40+
let is_unreachable = |succ: BasicBlock| unreachable_blocks.contains(&succ);
41+
let terminator_kind_opt = remove_successors(&terminator.kind, is_unreachable);
42+
43+
if let Some(terminator_kind) = terminator_kind_opt {
44+
if terminator_kind == TerminatorKind::Unreachable && !asm_stmt_in_block() {
45+
unreachable_blocks.insert(bb);
46+
}
47+
replacements.insert(bb, terminator_kind);
48+
}
49+
}
50+
}
51+
52+
let replaced = !replacements.is_empty();
53+
for (bb, terminator_kind) in replacements {
54+
body.basic_blocks_mut()[bb].terminator_mut().kind = terminator_kind;
55+
}
56+
57+
if replaced {
58+
simplify::remove_dead_blocks(body);
59+
}
60+
}
61+
}
62+
63+
fn remove_successors<F>(
64+
terminator_kind: &TerminatorKind<'tcx>,
65+
predicate: F,
66+
) -> Option<TerminatorKind<'tcx>>
67+
where
68+
F: Fn(BasicBlock) -> bool,
69+
{
70+
match *terminator_kind {
71+
TerminatorKind::Goto { target } if predicate(target) => Some(TerminatorKind::Unreachable),
72+
TerminatorKind::SwitchInt { ref discr, switch_ty, ref values, ref targets } => {
73+
let original_targets_len = targets.len();
74+
let (otherwise, targets) = targets.split_last().unwrap();
75+
let retained = values
76+
.iter()
77+
.zip(targets.iter())
78+
.filter(|(_, &t)| !predicate(t))
79+
.collect::<Vec<_>>();
80+
let mut values = retained.iter().map(|&(v, _)| *v).collect::<Vec<_>>();
81+
let mut targets = retained.iter().map(|&(_, d)| *d).collect::<Vec<_>>();
82+
83+
if !predicate(*otherwise) {
84+
targets.push(*otherwise);
85+
} else {
86+
values.pop();
87+
}
88+
89+
let retained_targets_len = targets.len();
90+
91+
if targets.is_empty() {
92+
Some(TerminatorKind::Unreachable)
93+
} else if targets.len() == 1 {
94+
Some(TerminatorKind::Goto { target: targets[0] })
95+
} else if original_targets_len != retained_targets_len {
96+
Some(TerminatorKind::SwitchInt {
97+
discr: discr.clone(),
98+
switch_ty,
99+
values: Cow::from(values),
100+
targets,
101+
})
102+
} else {
103+
None
104+
}
105+
}
106+
_ => None,
107+
}
108+
}

src/test/mir-opt/simplify_try.rs

+10-16
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,22 @@ fn main() {
4747
// }
4848
// bb0: {
4949
// _5 = discriminant(_1);
50-
// switchInt(move _5) -> [0isize: bb4, 1isize: bb2, otherwise: bb1];
50+
// switchInt(move _5) -> [0isize: bb3, otherwise: bb1];
5151
// }
5252
// bb1: {
53-
// unreachable;
54-
// }
55-
// bb2: {
5653
// _6 = ((_1 as Err).0: i32);
5754
// ((_0 as Err).0: i32) = move _6;
5855
// discriminant(_0) = 1;
59-
// goto -> bb3;
56+
// goto -> bb2;
6057
// }
61-
// bb3: {
58+
// bb2: {
6259
// return;
6360
// }
64-
// bb4: {
61+
// bb3: {
6562
// _10 = ((_1 as Ok).0: u32);
6663
// ((_0 as Ok).0: u32) = move _10;
6764
// discriminant(_0) = 0;
68-
// goto -> bb3;
65+
// goto -> bb2;
6966
// }
7067
// }
7168
// END rustc.try_identity.SimplifyArmIdentity.before.mir
@@ -109,25 +106,22 @@ fn main() {
109106
// }
110107
// bb0: {
111108
// _5 = discriminant(_1);
112-
// switchInt(move _5) -> [0isize: bb4, 1isize: bb2, otherwise: bb1];
109+
// switchInt(move _5) -> [0isize: bb3, otherwise: bb1];
113110
// }
114111
// bb1: {
115-
// unreachable;
116-
// }
117-
// bb2: {
118112
// _0 = move _1;
119113
// nop;
120114
// nop;
121-
// goto -> bb3;
115+
// goto -> bb2;
122116
// }
123-
// bb3: {
117+
// bb2: {
124118
// return;
125119
// }
126-
// bb4: {
120+
// bb3: {
127121
// _0 = move _1;
128122
// nop;
129123
// nop;
130-
// goto -> bb3;
124+
// goto -> bb2;
131125
// }
132126
// }
133127
// END rustc.try_identity.SimplifyArmIdentity.after.mir

src/test/mir-opt/uninhabited_enum_branching.rs

+30-51
Original file line numberDiff line numberDiff line change
@@ -45,53 +45,47 @@ fn main() {
4545
// StorageLive(_2);
4646
// _2 = Test1::C;
4747
// _3 = discriminant(_2);
48-
// switchInt(move _3) -> [0isize: bb3, 1isize: bb4, 2isize: bb1, otherwise: bb2];
48+
// switchInt(move _3) -> [0isize: bb2, 1isize: bb3, otherwise: bb1];
4949
// }
5050
// bb1: {
5151
// StorageLive(_5);
5252
// _5 = const "C";
5353
// _1 = &(*_5);
5454
// StorageDead(_5);
55-
// goto -> bb5;
55+
// goto -> bb4;
5656
// }
5757
// bb2: {
58-
// unreachable;
59-
// }
60-
// bb3: {
6158
// _1 = const "A(Empty)";
62-
// goto -> bb5;
59+
// goto -> bb4;
6360
// }
64-
// bb4: {
61+
// bb3: {
6562
// StorageLive(_4);
6663
// _4 = const "B(Empty)";
6764
// _1 = &(*_4);
6865
// StorageDead(_4);
69-
// goto -> bb5;
66+
// goto -> bb4;
7067
// }
71-
// bb5: {
68+
// bb4: {
7269
// StorageDead(_2);
7370
// StorageDead(_1);
7471
// StorageLive(_6);
7572
// StorageLive(_7);
7673
// _7 = Test2::D;
7774
// _8 = discriminant(_7);
78-
// switchInt(move _8) -> [4isize: bb8, 5isize: bb6, otherwise: bb7];
75+
// switchInt(move _8) -> [4isize: bb6, otherwise: bb5];
7976
// }
80-
// bb6: {
77+
// bb5: {
8178
// StorageLive(_9);
8279
// _9 = const "E";
8380
// _6 = &(*_9);
8481
// StorageDead(_9);
85-
// goto -> bb9;
86-
// }
87-
// bb7: {
88-
// unreachable;
82+
// goto -> bb7;
8983
// }
90-
// bb8: {
84+
// bb6: {
9185
// _6 = const "D";
92-
// goto -> bb9;
86+
// goto -> bb7;
9387
// }
94-
// bb9: {
88+
// bb7: {
9589
// StorageDead(_7);
9690
// StorageDead(_6);
9791
// _0 = ();
@@ -114,53 +108,47 @@ fn main() {
114108
// StorageLive(_2);
115109
// _2 = Test1::C;
116110
// _3 = discriminant(_2);
117-
// switchInt(move _3) -> [2isize: bb1, otherwise: bb2];
111+
// switchInt(move _3) -> bb1;
118112
// }
119113
// bb1: {
120114
// StorageLive(_5);
121115
// _5 = const "C";
122116
// _1 = &(*_5);
123117
// StorageDead(_5);
124-
// goto -> bb5;
118+
// goto -> bb4;
125119
// }
126120
// bb2: {
127-
// unreachable;
128-
// }
129-
// bb3: {
130121
// _1 = const "A(Empty)";
131-
// goto -> bb5;
122+
// goto -> bb4;
132123
// }
133-
// bb4: {
124+
// bb3: {
134125
// StorageLive(_4);
135126
// _4 = const "B(Empty)";
136127
// _1 = &(*_4);
137128
// StorageDead(_4);
138-
// goto -> bb5;
129+
// goto -> bb4;
139130
// }
140-
// bb5: {
131+
// bb4: {
141132
// StorageDead(_2);
142133
// StorageDead(_1);
143134
// StorageLive(_6);
144135
// StorageLive(_7);
145136
// _7 = Test2::D;
146137
// _8 = discriminant(_7);
147-
// switchInt(move _8) -> [4isize: bb8, 5isize: bb6, otherwise: bb7];
138+
// switchInt(move _8) -> [4isize: bb6, otherwise: bb5];
148139
// }
149-
// bb6: {
140+
// bb5: {
150141
// StorageLive(_9);
151142
// _9 = const "E";
152143
// _6 = &(*_9);
153144
// StorageDead(_9);
154-
// goto -> bb9;
145+
// goto -> bb7;
155146
// }
156-
// bb7: {
157-
// unreachable;
158-
// }
159-
// bb8: {
147+
// bb6: {
160148
// _6 = const "D";
161-
// goto -> bb9;
149+
// goto -> bb7;
162150
// }
163-
// bb9: {
151+
// bb7: {
164152
// StorageDead(_7);
165153
// StorageDead(_6);
166154
// _0 = ();
@@ -183,9 +171,6 @@ fn main() {
183171
// StorageLive(_2);
184172
// _2 = Test1::C;
185173
// _3 = discriminant(_2);
186-
// switchInt(move _3) -> [2isize: bb1, otherwise: bb2];
187-
// }
188-
// bb1: {
189174
// StorageLive(_5);
190175
// _5 = const "C";
191176
// _1 = &(*_5);
@@ -196,26 +181,20 @@ fn main() {
196181
// StorageLive(_7);
197182
// _7 = Test2::D;
198183
// _8 = discriminant(_7);
199-
// switchInt(move _8) -> [4isize: bb5, 5isize: bb3, otherwise: bb4];
200-
// }
201-
// bb2: {
202-
// unreachable;
184+
// switchInt(move _8) -> [4isize: bb2, otherwise: bb1];
203185
// }
204-
// bb3: {
186+
// bb1: {
205187
// StorageLive(_9);
206188
// _9 = const "E";
207189
// _6 = &(*_9);
208190
// StorageDead(_9);
209-
// goto -> bb6;
210-
// }
211-
// bb4: {
212-
// unreachable;
191+
// goto -> bb3;
213192
// }
214-
// bb5: {
193+
// bb2: {
215194
// _6 = const "D";
216-
// goto -> bb6;
195+
// goto -> bb3;
217196
// }
218-
// bb6: {
197+
// bb3: {
219198
// StorageDead(_7);
220199
// StorageDead(_6);
221200
// _0 = ();

0 commit comments

Comments
 (0)