]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/simplify_try.rs
Rollup merge of #69877 - CAD97:patch-1, r=dtolnay
[rust.git] / src / librustc_mir / transform / simplify_try.rs
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::{simplify, MirPass, MirSource};
13 use itertools::Itertools as _;
14 use rustc::mir::*;
15 use rustc::ty::{Ty, TyCtxt};
16 use rustc_target::abi::VariantIdx;
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 BodyAndCache<'tcx>) {
37         let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
38         for bb in basic_blocks {
39             // Need 3 statements:
40             let (s0, s1, s2) = match &mut *bb.statements {
41                 [s0, s1, s2] => (s0, s1, s2),
42                 _ => continue,
43             };
44
45             // Pattern match on the form we want:
46             let (local_tmp_s0, local_1, vf_s0) = match match_get_variant_field(s0) {
47                 None => continue,
48                 Some(x) => x,
49             };
50             let (local_tmp_s1, local_0, vf_s1) = match match_set_variant_field(s1) {
51                 None => continue,
52                 Some(x) => x,
53             };
54             if local_tmp_s0 != local_tmp_s1
55                 // Avoid moving into ourselves.
56                 || local_0 == local_1
57                 // The field-and-variant information match up.
58                 || vf_s0 != vf_s1
59                 // Source and target locals have the same type.
60                 // FIXME(Centril | oli-obk): possibly relax to same layout?
61                 || local_decls[local_0].ty != local_decls[local_1].ty
62                 // We're setting the discriminant of `local_0` to this variant.
63                 || Some((local_0, vf_s0.var_idx)) != match_set_discr(s2)
64             {
65                 continue;
66             }
67
68             // Right shape; transform!
69             s0.source_info = s2.source_info;
70             match &mut s0.kind {
71                 StatementKind::Assign(box (place, rvalue)) => {
72                     *place = local_0.into();
73                     *rvalue = Rvalue::Use(Operand::Move(local_1.into()));
74                 }
75                 _ => unreachable!(),
76             }
77             s1.make_nop();
78             s2.make_nop();
79         }
80     }
81 }
82
83 /// Match on:
84 /// ```rust
85 /// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY);
86 /// ```
87 fn match_get_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {
88     match &stmt.kind {
89         StatementKind::Assign(box (place_into, rvalue_from)) => match rvalue_from {
90             Rvalue::Use(Operand::Copy(pf)) | Rvalue::Use(Operand::Move(pf)) => {
91                 let local_into = place_into.as_local()?;
92                 let (local_from, vf) = match_variant_field_place(&pf)?;
93                 Some((local_into, local_from, vf))
94             }
95             _ => None,
96         },
97         _ => None,
98     }
99 }
100
101 /// Match on:
102 /// ```rust
103 /// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO;
104 /// ```
105 fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {
106     match &stmt.kind {
107         StatementKind::Assign(box (place_from, rvalue_into)) => match rvalue_into {
108             Rvalue::Use(Operand::Move(place_into)) => {
109                 let local_into = place_into.as_local()?;
110                 let (local_from, vf) = match_variant_field_place(&place_from)?;
111                 Some((local_into, local_from, vf))
112             }
113             _ => None,
114         },
115         _ => None,
116     }
117 }
118
119 /// Match on:
120 /// ```rust
121 /// discriminant(_LOCAL_TO_SET) = VAR_IDX;
122 /// ```
123 fn match_set_discr<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, VariantIdx)> {
124     match &stmt.kind {
125         StatementKind::SetDiscriminant { place, variant_index } => {
126             Some((place.as_local()?, *variant_index))
127         }
128         _ => None,
129     }
130 }
131
132 #[derive(PartialEq)]
133 struct VarField<'tcx> {
134     field: Field,
135     field_ty: Ty<'tcx>,
136     var_idx: VariantIdx,
137 }
138
139 /// Match on `((_LOCAL as Variant).FIELD: TY)`.
140 fn match_variant_field_place<'tcx>(place: &Place<'tcx>) -> Option<(Local, VarField<'tcx>)> {
141     match place.as_ref() {
142         PlaceRef {
143             local,
144             projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)],
145         } => Some((local, VarField { field, field_ty: ty, var_idx })),
146         _ => None,
147     }
148 }
149
150 /// Simplifies `SwitchInt(_) -> [targets]`,
151 /// where all the `targets` have the same form,
152 /// into `goto -> target_first`.
153 pub struct SimplifyBranchSame;
154
155 impl<'tcx> MirPass<'tcx> for SimplifyBranchSame {
156     fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>) {
157         let mut did_remove_blocks = false;
158         let bbs = body.basic_blocks_mut();
159         for bb_idx in bbs.indices() {
160             let targets = match &bbs[bb_idx].terminator().kind {
161                 TerminatorKind::SwitchInt { targets, .. } => targets,
162                 _ => continue,
163             };
164
165             let mut iter_bbs_reachable = targets
166                 .iter()
167                 .map(|idx| (*idx, &bbs[*idx]))
168                 .filter(|(_, bb)| {
169                     // Reaching `unreachable` is UB so assume it doesn't happen.
170                     bb.terminator().kind != TerminatorKind::Unreachable
171                     // But `asm!(...)` could abort the program,
172                     // so we cannot assume that the `unreachable` terminator itself is reachable.
173                     // FIXME(Centril): use a normalization pass instead of a check.
174                     || bb.statements.iter().any(|stmt| match stmt.kind {
175                         StatementKind::InlineAsm(..) => true,
176                         _ => false,
177                     })
178                 })
179                 .peekable();
180
181             // We want to `goto -> bb_first`.
182             let bb_first = iter_bbs_reachable.peek().map(|(idx, _)| *idx).unwrap_or(targets[0]);
183
184             // All successor basic blocks should have the exact same form.
185             let all_successors_equivalent =
186                 iter_bbs_reachable.map(|(_, bb)| bb).tuple_windows().all(|(bb_l, bb_r)| {
187                     bb_l.is_cleanup == bb_r.is_cleanup
188                         && bb_l.terminator().kind == bb_r.terminator().kind
189                         && bb_l.statements.iter().eq_by(&bb_r.statements, |x, y| x.kind == y.kind)
190                 });
191
192             if all_successors_equivalent {
193                 // Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`.
194                 bbs[bb_idx].terminator_mut().kind = TerminatorKind::Goto { target: bb_first };
195                 did_remove_blocks = true;
196             }
197         }
198
199         if did_remove_blocks {
200             // We have dead blocks now, so remove those.
201             simplify::remove_dead_blocks(body);
202         }
203     }
204 }