Introduce MIR optimizations for simplifying x?
on Result
s. · rust-lang/rust@2f00e86 (original) (raw)
``
1
`+
//! The general point of the optimizations provided here is to simplify something like:
`
``
2
`+
//!
`
``
3
//! ```rust
``
4
`+
//! match x {
`
``
5
`+
//! Ok(x) => Ok(x),
`
``
6
`+
//! Err(x) => Err(x)
`
``
7
`+
//! }
`
``
8
//! ```
``
9
`+
//!
`
``
10
`` +
//! into just x
.
``
``
11
+
``
12
`+
use crate::transform::{MirPass, MirSource, simplify};
`
``
13
`+
use rustc::ty::{TyCtxt, Ty};
`
``
14
`+
use rustc::mir::*;
`
``
15
`+
use rustc_target::abi::VariantIdx;
`
``
16
`+
use itertools::Itertools as _;
`
``
17
+
``
18
`` +
/// Simplifies arms of form Variant(x) => Variant(x)
to just a move.
``
``
19
`+
///
`
``
20
`+
/// This is done by transforming basic blocks where the statements match:
`
``
21
`+
///
`
``
22
/// ```rust
``
23
`+
/// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY );
`
``
24
`+
/// ((_LOCAL_0 as Variant).FIELD: TY) = move _LOCAL_TMP;
`
``
25
`+
/// discriminant(_LOCAL_0) = VAR_IDX;
`
``
26
/// ```
``
27
`+
///
`
``
28
`+
/// into:
`
``
29
`+
///
`
``
30
/// ```rust
``
31
`+
/// _LOCAL_0 = move _LOCAL_1
`
``
32
/// ```
``
33
`+
pub struct SimplifyArmIdentity;
`
``
34
+
``
35
`+
impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity {
`
``
36
`+
fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) {
`
``
37
`+
for bb in body.basic_blocks_mut() {
`
``
38
`+
// Need 3 statements:
`
``
39
`+
let (s0, s1, s2) = match &mut *bb.statements {
`
``
40
`+
[s0, s1, s2] => (s0, s1, s2),
`
``
41
`+
_ => continue,
`
``
42
`+
};
`
``
43
+
``
44
`+
// Pattern match on the form we want:
`
``
45
`+
let (local_tmp_s0, local_1, vf_s0) = match match_get_variant_field(s0) {
`
``
46
`+
None => continue,
`
``
47
`+
Some(x) => x,
`
``
48
`+
};
`
``
49
`+
let (local_tmp_s1, local_0, vf_s1) = match match_set_variant_field(s1) {
`
``
50
`+
None => continue,
`
``
51
`+
Some(x) => x,
`
``
52
`+
};
`
``
53
`+
if local_tmp_s0 != local_tmp_s1
`
``
54
`+
|| vf_s0 != vf_s1
`
``
55
`+
|| Some((local_0, vf_s0.var_idx)) != match_set_discr(s2)
`
``
56
`+
{
`
``
57
`+
continue;
`
``
58
`+
}
`
``
59
+
``
60
`+
// Right shape; transform!
`
``
61
`+
match &mut s0.kind {
`
``
62
`+
StatementKind::Assign(box (place, rvalue)) => {
`
``
63
`+
*place = local_0.into();
`
``
64
`+
*rvalue = Rvalue::Use(Operand::Move(local_1.into()));
`
``
65
`+
}
`
``
66
`+
_ => unreachable!(),
`
``
67
`+
}
`
``
68
`+
s1.make_nop();
`
``
69
`+
s2.make_nop();
`
``
70
`+
}
`
``
71
`+
}
`
``
72
`+
}
`
``
73
+
``
74
`+
/// Match on:
`
``
75
/// ```rust
``
76
`+
/// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY);
`
``
77
/// ```
``
78
`+
fn match_get_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {
`
``
79
`+
match &stmt.kind {
`
``
80
`+
StatementKind::Assign(box (place_into, rvalue_from)) => match rvalue_from {
`
``
81
`+
Rvalue::Use(Operand::Copy(pf)) | Rvalue::Use(Operand::Move(pf)) => {
`
``
82
`+
let local_into = place_into.as_local()?;
`
``
83
`+
let (local_from, vf) = match_variant_field_place(&pf)?;
`
``
84
`+
Some((local_into, local_from, vf))
`
``
85
`+
}
`
``
86
`+
_ => None,
`
``
87
`+
},
`
``
88
`+
_ => None,
`
``
89
`+
}
`
``
90
`+
}
`
``
91
+
``
92
`+
/// Match on:
`
``
93
/// ```rust
``
94
`+
/// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO;
`
``
95
/// ```
``
96
`+
fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {
`
``
97
`+
match &stmt.kind {
`
``
98
`+
StatementKind::Assign(box (place_from, rvalue_into)) => match rvalue_into {
`
``
99
`+
Rvalue::Use(Operand::Move(place_into)) => {
`
``
100
`+
let local_into = place_into.as_local()?;
`
``
101
`+
let (local_from, vf) = match_variant_field_place(&place_from)?;
`
``
102
`+
Some((local_into, local_from, vf))
`
``
103
`+
}
`
``
104
`+
_ => None,
`
``
105
`+
},
`
``
106
`+
_ => None,
`
``
107
`+
}
`
``
108
`+
}
`
``
109
+
``
110
`+
/// Match on:
`
``
111
/// ```rust
``
112
`+
/// discriminant(_LOCAL_TO_SET) = VAR_IDX;
`
``
113
/// ```
``
114
`+
fn match_set_discr<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, VariantIdx)> {
`
``
115
`+
match &stmt.kind {
`
``
116
`+
StatementKind::SetDiscriminant { place, variant_index } => Some((
`
``
117
`+
place.as_local()?,
`
``
118
`+
*variant_index
`
``
119
`+
)),
`
``
120
`+
_ => None,
`
``
121
`+
}
`
``
122
`+
}
`
``
123
+
``
124
`+
#[derive(PartialEq)]
`
``
125
`+
struct VarField<'tcx> {
`
``
126
`+
field: Field,
`
``
127
`+
field_ty: Ty<'tcx>,
`
``
128
`+
var_idx: VariantIdx,
`
``
129
`+
}
`
``
130
+
``
131
`` +
/// Match on ((_LOCAL as Variant).FIELD: TY)
.
``
``
132
`+
fn match_variant_field_place<'tcx>(place: &Place<'tcx>) -> Option<(Local, VarField<'tcx>)> {
`
``
133
`+
match place.as_ref() {
`
``
134
`+
PlaceRef {
`
``
135
`+
base: &PlaceBase::Local(local),
`
``
136
`+
projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)],
`
``
137
`+
} => Some((local, VarField { field, field_ty: ty, var_idx })),
`
``
138
`+
_ => None,
`
``
139
`+
}
`
``
140
`+
}
`
``
141
+
``
142
`` +
/// Simplifies SwitchInt(_) -> [targets]
,
``
``
143
`` +
/// where all the targets
have the same form,
``
``
144
`` +
/// into goto -> target_first
.
``
``
145
`+
pub struct SimplifyBranchSame;
`
``
146
+
``
147
`+
impl<'tcx> MirPass<'tcx> for SimplifyBranchSame {
`
``
148
`+
fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) {
`
``
149
`+
let mut did_remove_blocks = false;
`
``
150
`+
let bbs = body.basic_blocks_mut();
`
``
151
`+
for bb_idx in bbs.indices() {
`
``
152
`+
let targets = match &bbs[bb_idx].terminator().kind {
`
``
153
`+
TerminatorKind::SwitchInt { targets, .. } => targets,
`
``
154
`+
_ => continue,
`
``
155
`+
};
`
``
156
+
``
157
`+
let mut iter_bbs_reachable = targets
`
``
158
`+
.iter()
`
``
159
`+
.map(|idx| (*idx, &bbs[*idx]))
`
``
160
`+
.filter(|(_, bb)| {
`
``
161
`` +
// Reaching unreachable
is UB so assume it doesn't happen.
``
``
162
`+
bb.terminator().kind != TerminatorKind::Unreachable
`
``
163
`` +
// But asm!(...)
could abort the program,
``
``
164
`` +
// so we cannot assume that the unreachable
terminator itself is reachable.
``
``
165
`+
// FIXME(Centril): use a normalization pass instead of a check.
`
``
166
`+
|| bb.statements.iter().any(|stmt| match stmt.kind {
`
``
167
`+
StatementKind::InlineAsm(..) => true,
`
``
168
`+
_ => false,
`
``
169
`+
})
`
``
170
`+
})
`
``
171
`+
.peekable();
`
``
172
+
``
173
`` +
// We want to goto -> bb_first
.
``
``
174
`+
let bb_first = iter_bbs_reachable
`
``
175
`+
.peek()
`
``
176
`+
.map(|(idx, _)| *idx)
`
``
177
`+
.unwrap_or(targets[0]);
`
``
178
+
``
179
`+
// All successor basic blocks should have the exact same form.
`
``
180
`+
let all_successors_equivalent = iter_bbs_reachable
`
``
181
`+
.map(|(_, bb)| bb)
`
``
182
`+
.tuple_windows()
`
``
183
`+
.all(|(bb_l, bb_r)| {
`
``
184
`+
bb_l.is_cleanup == bb_r.is_cleanup
`
``
185
`+
&& bb_l.terminator().kind == bb_r.terminator().kind
`
``
186
`+
&& bb_l.statements.iter().eq_by(&bb_r.statements, |x, y| x.kind == y.kind)
`
``
187
`+
});
`
``
188
+
``
189
`+
if all_successors_equivalent {
`
``
190
`` +
// Replace SwitchInt(..) -> [bb_first, ..];
with a goto -> bb_first;
.
``
``
191
`+
bbs[bb_idx].terminator_mut().kind = TerminatorKind::Goto { target: bb_first };
`
``
192
`+
did_remove_blocks = true;
`
``
193
`+
}
`
``
194
`+
}
`
``
195
+
``
196
`+
if did_remove_blocks {
`
``
197
`+
// We have dead blocks now, so remove those.
`
``
198
`+
simplify::remove_dead_blocks(body);
`
``
199
`+
}
`
``
200
`+
}
`
``
201
`+
}
`