Skip to content

Commit 089677e

Browse files
committed
Auto merge of rust-lang#111813 - scottmcm:pretty-mir, r=cjgillot
MIR: opt-in normalization of `BasicBlock` and `Local` numbering This doesn't matter at all for actual codegen, but after spending some time reading pre-codegen MIR, I was wishing I didn't have to jump around so much in reading post-inlining code. So this add two passes that are off by default for every mir level, but can be enabled (`-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals`) for humans.
2 parents 1c53407 + d69725d commit 089677e

33 files changed

+927
-771
lines changed

compiler/rustc_middle/src/mir/terminator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ pub struct Terminator<'tcx> {
105105
pub kind: TerminatorKind<'tcx>,
106106
}
107107

108-
pub type Successors<'a> = impl Iterator<Item = BasicBlock> + 'a;
108+
pub type Successors<'a> = impl DoubleEndedIterator<Item = BasicBlock> + 'a;
109109
pub type SuccessorsMut<'a> =
110110
iter::Chain<std::option::IntoIter<&'a mut BasicBlock>, slice::IterMut<'a, BasicBlock>>;
111111

compiler/rustc_middle/src/mir/traversal.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
149149
// B C
150150
// | |
151151
// | |
152-
// D |
152+
// | D
153153
// \ /
154154
// \ /
155155
// E
@@ -159,26 +159,26 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
159159
//
160160
// When the first call to `traverse_successor` happens, the following happens:
161161
//
162-
// [(B, [D]), // `B` taken from the successors of `A`, pushed to the
163-
// // top of the stack along with the successors of `B`
164-
// (A, [C])]
162+
// [(C, [D]), // `C` taken from the successors of `A`, pushed to the
163+
// // top of the stack along with the successors of `C`
164+
// (A, [B])]
165165
//
166-
// [(D, [E]), // `D` taken from successors of `B`, pushed to stack
167-
// (B, []),
168-
// (A, [C])]
166+
// [(D, [E]), // `D` taken from successors of `C`, pushed to stack
167+
// (C, []),
168+
// (A, [B])]
169169
//
170170
// [(E, []), // `E` taken from successors of `D`, pushed to stack
171171
// (D, []),
172-
// (B, []),
173-
// (A, [C])]
172+
// (C, []),
173+
// (A, [B])]
174174
//
175175
// Now that the top of the stack has no successors we can traverse, each item will
176-
// be popped off during iteration until we get back to `A`. This yields [E, D, B].
176+
// be popped off during iteration until we get back to `A`. This yields [E, D, C].
177177
//
178-
// When we yield `B` and call `traverse_successor`, we push `C` to the stack, but
178+
// When we yield `C` and call `traverse_successor`, we push `B` to the stack, but
179179
// since we've already visited `E`, that child isn't added to the stack. The last
180-
// two iterations yield `C` and finally `A` for a final traversal of [E, D, B, C, A]
181-
while let Some(&mut (_, ref mut iter)) = self.visit_stack.last_mut() && let Some(bb) = iter.next() {
180+
// two iterations yield `B` and finally `A` for a final traversal of [E, D, C, B, A]
181+
while let Some(&mut (_, ref mut iter)) = self.visit_stack.last_mut() && let Some(bb) = iter.next_back() {
182182
if self.visited.insert(bb) {
183183
if let Some(term) = &self.basic_blocks[bb].terminator {
184184
self.visit_stack.push((bb, term.successors()));

compiler/rustc_mir_transform/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![deny(rustc::diagnostic_outside_of_impl)]
44
#![feature(box_patterns)]
55
#![feature(drain_filter)]
6+
#![feature(is_sorted)]
67
#![feature(let_chains)]
78
#![feature(map_try_insert)]
89
#![feature(min_specialization)]
@@ -84,6 +85,7 @@ mod match_branches;
8485
mod multiple_return_terminators;
8586
mod normalize_array_len;
8687
mod nrvo;
88+
mod prettify;
8789
mod ref_prop;
8890
mod remove_noop_landing_pads;
8991
mod remove_storage_markers;
@@ -581,6 +583,9 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
581583
&large_enums::EnumSizeOpt { discrepancy: 128 },
582584
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
583585
&add_call_guards::CriticalCallEdges,
586+
// Cleanup for human readability, off by default.
587+
&prettify::ReorderBasicBlocks,
588+
&prettify::ReorderLocals,
584589
// Dump the end result for testing and debugging purposes.
585590
&dump_mir::Marker("PreCodegen"),
586591
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//! These two passes provide no value to the compiler, so are off at every level.
2+
//!
3+
//! However, they can be enabled on the command line
4+
//! (`-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals`)
5+
//! to make the MIR easier to read for humans.
6+
7+
use crate::MirPass;
8+
use rustc_index::{bit_set::BitSet, IndexSlice, IndexVec};
9+
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
10+
use rustc_middle::mir::*;
11+
use rustc_middle::ty::TyCtxt;
12+
use rustc_session::Session;
13+
14+
/// Rearranges the basic blocks into a *reverse post-order*.
15+
///
16+
/// Thus after this pass, all the successors of a block are later than it in the
17+
/// `IndexVec`, unless that successor is a back-edge (such as from a loop).
18+
pub struct ReorderBasicBlocks;
19+
20+
impl<'tcx> MirPass<'tcx> for ReorderBasicBlocks {
21+
fn is_enabled(&self, _session: &Session) -> bool {
22+
false
23+
}
24+
25+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
26+
let rpo: IndexVec<BasicBlock, BasicBlock> =
27+
body.basic_blocks.postorder().iter().copied().rev().collect();
28+
if rpo.iter().is_sorted() {
29+
return;
30+
}
31+
32+
let mut updater = BasicBlockUpdater { map: rpo.invert_bijective_mapping(), tcx };
33+
debug_assert_eq!(updater.map[START_BLOCK], START_BLOCK);
34+
updater.visit_body(body);
35+
36+
permute(body.basic_blocks.as_mut(), &updater.map);
37+
}
38+
}
39+
40+
/// Rearranges the locals into *use* order.
41+
///
42+
/// Thus after this pass, a local with a smaller [`Location`] where it was first
43+
/// assigned or referenced will have a smaller number.
44+
///
45+
/// (Does not reorder arguments nor the [`RETURN_PLACE`].)
46+
pub struct ReorderLocals;
47+
48+
impl<'tcx> MirPass<'tcx> for ReorderLocals {
49+
fn is_enabled(&self, _session: &Session) -> bool {
50+
false
51+
}
52+
53+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
54+
let mut finder =
55+
LocalFinder { map: IndexVec::new(), seen: BitSet::new_empty(body.local_decls.len()) };
56+
57+
// We can't reorder the return place or the arguments
58+
for local in (0..=body.arg_count).map(Local::from_usize) {
59+
finder.track(local);
60+
}
61+
62+
for (bb, bbd) in body.basic_blocks.iter_enumerated() {
63+
finder.visit_basic_block_data(bb, bbd);
64+
}
65+
66+
// track everything in case there are some locals that we never saw,
67+
// such as in non-block things like debug info or in non-uses.
68+
for local in body.local_decls.indices() {
69+
finder.track(local);
70+
}
71+
72+
if finder.map.iter().is_sorted() {
73+
return;
74+
}
75+
76+
let mut updater = LocalUpdater { map: finder.map.invert_bijective_mapping(), tcx };
77+
78+
for local in (0..=body.arg_count).map(Local::from_usize) {
79+
debug_assert_eq!(updater.map[local], local);
80+
}
81+
82+
updater.visit_body_preserves_cfg(body);
83+
84+
permute(&mut body.local_decls, &updater.map);
85+
}
86+
}
87+
88+
fn permute<I: rustc_index::Idx + Ord, T>(data: &mut IndexVec<I, T>, map: &IndexSlice<I, I>) {
89+
// FIXME: It would be nice to have a less-awkward way to apply permutations,
90+
// but I don't know one that exists. `sort_by_cached_key` has logic for it
91+
// internally, but not in a way that we're allowed to use here.
92+
let mut enumerated: Vec<_> = std::mem::take(data).into_iter_enumerated().collect();
93+
enumerated.sort_by_key(|p| map[p.0]);
94+
*data = enumerated.into_iter().map(|p| p.1).collect();
95+
}
96+
97+
struct BasicBlockUpdater<'tcx> {
98+
map: IndexVec<BasicBlock, BasicBlock>,
99+
tcx: TyCtxt<'tcx>,
100+
}
101+
102+
impl<'tcx> MutVisitor<'tcx> for BasicBlockUpdater<'tcx> {
103+
fn tcx(&self) -> TyCtxt<'tcx> {
104+
self.tcx
105+
}
106+
107+
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, _location: Location) {
108+
for succ in terminator.successors_mut() {
109+
*succ = self.map[*succ];
110+
}
111+
}
112+
}
113+
114+
struct LocalFinder {
115+
map: IndexVec<Local, Local>,
116+
seen: BitSet<Local>,
117+
}
118+
119+
impl LocalFinder {
120+
fn track(&mut self, l: Local) {
121+
if self.seen.insert(l) {
122+
self.map.push(l);
123+
}
124+
}
125+
}
126+
127+
impl<'tcx> Visitor<'tcx> for LocalFinder {
128+
fn visit_local(&mut self, l: Local, context: PlaceContext, _location: Location) {
129+
// Exclude non-uses to keep `StorageLive` from controlling where we put
130+
// a `Local`, since it might not actually be assigned until much later.
131+
if context.is_use() {
132+
self.track(l);
133+
}
134+
}
135+
}
136+
137+
struct LocalUpdater<'tcx> {
138+
pub map: IndexVec<Local, Local>,
139+
pub tcx: TyCtxt<'tcx>,
140+
}
141+
142+
impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
143+
fn tcx(&self) -> TyCtxt<'tcx> {
144+
self.tcx
145+
}
146+
147+
fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
148+
*l = self.map[*l];
149+
}
150+
}

src/tools/compiletest/src/runtest.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,10 @@ impl<'test> TestCx<'test> {
20452045
if let Some(pass) = &self.props.mir_unit_test {
20462046
rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
20472047
} else {
2048-
rustc.arg("-Zmir-opt-level=4");
2048+
rustc.args(&[
2049+
"-Zmir-opt-level=4",
2050+
"-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
2051+
]);
20492052
}
20502053

20512054
let mir_dump_dir = self.get_mir_dump_dir();

tests/mir-opt/deref-patterns/string.foo.PreCodegen.after.mir

+31-31
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,33 @@
33
fn foo(_1: Option<String>) -> i32 {
44
debug s => _1; // in scope 0 at $DIR/string.rs:+0:12: +0:13
55
let mut _0: i32; // return place in scope 0 at $DIR/string.rs:+0:34: +0:37
6-
let mut _2: &std::string::String; // in scope 0 at $DIR/string.rs:+2:14: +2:17
7-
let mut _3: &str; // in scope 0 at $DIR/string.rs:+2:14: +2:17
8-
let mut _4: bool; // in scope 0 at $DIR/string.rs:+2:14: +2:17
9-
let mut _5: isize; // in scope 0 at $DIR/string.rs:+2:9: +2:18
10-
let _6: std::option::Option<std::string::String>; // in scope 0 at $DIR/string.rs:+3:9: +3:10
11-
let mut _7: bool; // in scope 0 at $DIR/string.rs:+5:1: +5:2
6+
let mut _2: bool; // in scope 0 at $DIR/string.rs:+5:1: +5:2
7+
let mut _3: isize; // in scope 0 at $DIR/string.rs:+2:9: +2:18
8+
let mut _4: &std::string::String; // in scope 0 at $DIR/string.rs:+2:14: +2:17
9+
let mut _5: &str; // in scope 0 at $DIR/string.rs:+2:14: +2:17
10+
let mut _6: bool; // in scope 0 at $DIR/string.rs:+2:14: +2:17
11+
let _7: std::option::Option<std::string::String>; // in scope 0 at $DIR/string.rs:+3:9: +3:10
1212
scope 1 {
13-
debug s => _6; // in scope 1 at $DIR/string.rs:+3:9: +3:10
13+
debug s => _7; // in scope 1 at $DIR/string.rs:+3:9: +3:10
1414
}
1515

1616
bb0: {
17-
_7 = const false; // scope 0 at $DIR/string.rs:+1:11: +1:12
18-
_7 = const true; // scope 0 at $DIR/string.rs:+1:11: +1:12
19-
_5 = discriminant(_1); // scope 0 at $DIR/string.rs:+1:11: +1:12
20-
switchInt(move _5) -> [1: bb2, otherwise: bb1]; // scope 0 at $DIR/string.rs:+1:5: +1:12
17+
_2 = const false; // scope 0 at $DIR/string.rs:+1:11: +1:12
18+
_2 = const true; // scope 0 at $DIR/string.rs:+1:11: +1:12
19+
_3 = discriminant(_1); // scope 0 at $DIR/string.rs:+1:11: +1:12
20+
switchInt(move _3) -> [1: bb1, otherwise: bb5]; // scope 0 at $DIR/string.rs:+1:5: +1:12
2121
}
2222

2323
bb1: {
24-
StorageLive(_6); // scope 0 at $DIR/string.rs:+3:9: +3:10
25-
_7 = const false; // scope 0 at $DIR/string.rs:+3:9: +3:10
26-
_6 = move _1; // scope 0 at $DIR/string.rs:+3:9: +3:10
27-
_0 = const 4321_i32; // scope 1 at $DIR/string.rs:+3:14: +3:18
28-
drop(_6) -> [return: bb6, unwind unreachable]; // scope 0 at $DIR/string.rs:+3:17: +3:18
29-
}
30-
31-
bb2: {
32-
_2 = &((_1 as Some).0: std::string::String); // scope 0 at $DIR/string.rs:+2:14: +2:17
33-
_3 = <String as Deref>::deref(move _2) -> [return: bb3, unwind unreachable]; // scope 0 at $DIR/string.rs:+2:14: +2:17
24+
_4 = &((_1 as Some).0: std::string::String); // scope 0 at $DIR/string.rs:+2:14: +2:17
25+
_5 = <String as Deref>::deref(move _4) -> [return: bb2, unwind unreachable]; // scope 0 at $DIR/string.rs:+2:14: +2:17
3426
// mir::Constant
3527
// + span: $DIR/string.rs:9:14: 9:17
3628
// + literal: Const { ty: for<'a> fn(&'a String) -> &'a <String as Deref>::Target {<String as Deref>::deref}, val: Value(<ZST>) }
3729
}
3830

39-
bb3: {
40-
_4 = <str as PartialEq>::eq(_3, const "a") -> [return: bb4, unwind unreachable]; // scope 0 at $DIR/string.rs:+2:14: +2:17
31+
bb2: {
32+
_6 = <str as PartialEq>::eq(_5, const "a") -> [return: bb3, unwind unreachable]; // scope 0 at $DIR/string.rs:+2:14: +2:17
4133
// mir::Constant
4234
// + span: $DIR/string.rs:9:14: 9:17
4335
// + literal: Const { ty: for<'a, 'b> fn(&'a str, &'b str) -> bool {<str as PartialEq>::eq}, val: Value(<ZST>) }
@@ -46,29 +38,37 @@ fn foo(_1: Option<String>) -> i32 {
4638
// + literal: Const { ty: &str, val: Value(Slice(..)) }
4739
}
4840

41+
bb3: {
42+
switchInt(move _6) -> [0: bb5, otherwise: bb4]; // scope 0 at $DIR/string.rs:+2:14: +2:17
43+
}
44+
4945
bb4: {
50-
switchInt(move _4) -> [0: bb1, otherwise: bb5]; // scope 0 at $DIR/string.rs:+2:14: +2:17
46+
_0 = const 1234_i32; // scope 0 at $DIR/string.rs:+2:22: +2:26
47+
goto -> bb7; // scope 0 at $DIR/string.rs:+2:22: +2:26
5148
}
5249

5350
bb5: {
54-
_0 = const 1234_i32; // scope 0 at $DIR/string.rs:+2:22: +2:26
55-
goto -> bb9; // scope 0 at $DIR/string.rs:+2:22: +2:26
51+
StorageLive(_7); // scope 0 at $DIR/string.rs:+3:9: +3:10
52+
_2 = const false; // scope 0 at $DIR/string.rs:+3:9: +3:10
53+
_7 = move _1; // scope 0 at $DIR/string.rs:+3:9: +3:10
54+
_0 = const 4321_i32; // scope 1 at $DIR/string.rs:+3:14: +3:18
55+
drop(_7) -> [return: bb6, unwind unreachable]; // scope 0 at $DIR/string.rs:+3:17: +3:18
5656
}
5757

5858
bb6: {
59-
StorageDead(_6); // scope 0 at $DIR/string.rs:+3:17: +3:18
60-
goto -> bb9; // scope 0 at $DIR/string.rs:+3:17: +3:18
59+
StorageDead(_7); // scope 0 at $DIR/string.rs:+3:17: +3:18
60+
goto -> bb7; // scope 0 at $DIR/string.rs:+3:17: +3:18
6161
}
6262

6363
bb7: {
64-
return; // scope 0 at $DIR/string.rs:+5:2: +5:2
64+
switchInt(_2) -> [0: bb9, otherwise: bb8]; // scope 0 at $DIR/string.rs:+5:1: +5:2
6565
}
6666

6767
bb8: {
68-
drop(_1) -> [return: bb7, unwind unreachable]; // scope 0 at $DIR/string.rs:+5:1: +5:2
68+
drop(_1) -> [return: bb9, unwind unreachable]; // scope 0 at $DIR/string.rs:+5:1: +5:2
6969
}
7070

7171
bb9: {
72-
switchInt(_7) -> [0: bb7, otherwise: bb8]; // scope 0 at $DIR/string.rs:+5:1: +5:2
72+
return; // scope 0 at $DIR/string.rs:+5:2: +5:2
7373
}
7474
}

tests/mir-opt/inline/cycle.g.Inline.diff

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
+ let mut _5: (); // in scope 0 at $DIR/cycle.rs:6:5: 6:8
99
+ scope 1 (inlined f::<fn() {main}>) { // at $DIR/cycle.rs:12:5: 12:12
1010
+ debug g => _2; // in scope 1 at $DIR/cycle.rs:5:6: 5:7
11-
+ let _3: (); // in scope 1 at $DIR/cycle.rs:6:5: 6:8
12-
+ let mut _4: &fn() {main}; // in scope 1 at $DIR/cycle.rs:6:5: 6:6
11+
+ let mut _3: &fn() {main}; // in scope 1 at $DIR/cycle.rs:6:5: 6:6
12+
+ let _4: (); // in scope 1 at $DIR/cycle.rs:6:5: 6:8
1313
+ scope 2 (inlined <fn() {main} as Fn<()>>::call - shim(fn() {main})) { // at $DIR/cycle.rs:6:5: 6:8
1414
+ }
1515
+ }
@@ -25,16 +25,16 @@
2525
- // mir::Constant
2626
// + span: $DIR/cycle.rs:12:7: 12:11
2727
// + literal: Const { ty: fn() {main}, val: Value(<ZST>) }
28-
+ StorageLive(_3); // scope 0 at $DIR/cycle.rs:+1:5: +1:12
29-
+ StorageLive(_4); // scope 1 at $DIR/cycle.rs:6:5: 6:6
30-
+ _4 = &_2; // scope 1 at $DIR/cycle.rs:6:5: 6:6
28+
+ StorageLive(_4); // scope 0 at $DIR/cycle.rs:+1:5: +1:12
29+
+ StorageLive(_3); // scope 1 at $DIR/cycle.rs:6:5: 6:6
30+
+ _3 = &_2; // scope 1 at $DIR/cycle.rs:6:5: 6:6
3131
+ StorageLive(_5); // scope 1 at $DIR/cycle.rs:6:5: 6:8
3232
+ _5 = const (); // scope 1 at $DIR/cycle.rs:6:5: 6:8
33-
+ _3 = move (*_4)() -> [return: bb4, unwind: bb2]; // scope 2 at $SRC_DIR/core/src/ops/function.rs:LL:COL
33+
+ _4 = move (*_3)() -> [return: bb4, unwind: bb2]; // scope 2 at $SRC_DIR/core/src/ops/function.rs:LL:COL
3434
}
3535

3636
bb1: {
37-
+ StorageDead(_3); // scope 0 at $DIR/cycle.rs:+1:5: +1:12
37+
+ StorageDead(_4); // scope 0 at $DIR/cycle.rs:+1:5: +1:12
3838
+ StorageDead(_2); // scope 0 at $DIR/cycle.rs:+1:5: +1:12
3939
StorageDead(_1); // scope 0 at $DIR/cycle.rs:+1:12: +1:13
4040
_0 = const (); // scope 0 at $DIR/cycle.rs:+0:8: +2:2
@@ -51,7 +51,7 @@
5151
+
5252
+ bb4: {
5353
+ StorageDead(_5); // scope 1 at $DIR/cycle.rs:6:5: 6:8
54-
+ StorageDead(_4); // scope 1 at $DIR/cycle.rs:6:7: 6:8
54+
+ StorageDead(_3); // scope 1 at $DIR/cycle.rs:6:7: 6:8
5555
+ drop(_2) -> bb1; // scope 1 at $DIR/cycle.rs:7:1: 7:2
5656
}
5757
}

0 commit comments

Comments
 (0)