]>
Commit | Line | Data |
---|---|---|
9ffffee4 | 1 | use rustc_index::bit_set::BitSet; |
49aad941 | 2 | use rustc_index::IndexSlice; |
9ffffee4 FG |
3 | use rustc_middle::mir::visit::*; |
4 | use rustc_middle::mir::*; | |
5 | use rustc_middle::ty::TyCtxt; | |
6 | use rustc_mir_dataflow::impls::borrowed_locals; | |
7 | ||
8 | use crate::ssa::SsaLocals; | |
9 | use crate::MirPass; | |
10 | ||
11 | /// Unify locals that copy each other. | |
12 | /// | |
13 | /// We consider patterns of the form | |
14 | /// _a = rvalue | |
15 | /// _b = move? _a | |
16 | /// _c = move? _a | |
17 | /// _d = move? _c | |
18 | /// where each of the locals is only assigned once. | |
19 | /// | |
20 | /// We want to replace all those locals by `_a`, either copied or moved. | |
21 | pub struct CopyProp; | |
22 | ||
23 | impl<'tcx> MirPass<'tcx> for CopyProp { | |
24 | fn is_enabled(&self, sess: &rustc_session::Session) -> bool { | |
25 | sess.mir_opt_level() >= 1 | |
26 | } | |
27 | ||
28 | #[instrument(level = "trace", skip(self, tcx, body))] | |
29 | fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
30 | debug!(def_id = ?body.source.def_id()); | |
31 | propagate_ssa(tcx, body); | |
32 | } | |
33 | } | |
34 | ||
35 | fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
9ffffee4 | 36 | let borrowed_locals = borrowed_locals(body); |
49aad941 | 37 | let ssa = SsaLocals::new(body); |
9ffffee4 FG |
38 | |
39 | let fully_moved = fully_moved_locals(&ssa, body); | |
40 | debug!(?fully_moved); | |
41 | ||
42 | let mut storage_to_remove = BitSet::new_empty(fully_moved.domain_size()); | |
43 | for (local, &head) in ssa.copy_classes().iter_enumerated() { | |
44 | if local != head { | |
45 | storage_to_remove.insert(head); | |
46 | } | |
47 | } | |
48 | ||
49 | let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h); | |
50 | ||
51 | Replacer { | |
52 | tcx, | |
53 | copy_classes: &ssa.copy_classes(), | |
54 | fully_moved, | |
55 | borrowed_locals, | |
56 | storage_to_remove, | |
57 | } | |
58 | .visit_body_preserves_cfg(body); | |
59 | ||
60 | if any_replacement { | |
61 | crate::simplify::remove_unused_definitions(body); | |
62 | } | |
63 | } | |
64 | ||
65 | /// `SsaLocals` computed equivalence classes between locals considering copy/move assignments. | |
66 | /// | |
67 | /// This function also returns whether all the `move?` in the pattern are `move` and not copies. | |
68 | /// A local which is in the bitset can be replaced by `move _a`. Otherwise, it must be | |
69 | /// replaced by `copy _a`, as we cannot move multiple times from `_a`. | |
70 | /// | |
71 | /// If an operand copies `_c`, it must happen before the assignment `_d = _c`, otherwise it is UB. | |
72 | /// This means that replacing it by a copy of `_a` if ok, since this copy happens before `_c` is | |
73 | /// moved, and therefore that `_d` is moved. | |
74 | #[instrument(level = "trace", skip(ssa, body))] | |
75 | fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> BitSet<Local> { | |
76 | let mut fully_moved = BitSet::new_filled(body.local_decls.len()); | |
77 | ||
49aad941 | 78 | for (_, rvalue, _) in ssa.assignments(body) { |
add651ee FG |
79 | let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) |
80 | | Rvalue::CopyForDeref(place)) = rvalue | |
81 | else { | |
82 | continue; | |
83 | }; | |
9ffffee4 FG |
84 | |
85 | let Some(rhs) = place.as_local() else { continue }; | |
86 | if !ssa.is_ssa(rhs) { | |
87 | continue; | |
88 | } | |
89 | ||
90 | if let Rvalue::Use(Operand::Copy(_)) | Rvalue::CopyForDeref(_) = rvalue { | |
91 | fully_moved.remove(rhs); | |
92 | } | |
93 | } | |
94 | ||
95 | ssa.meet_copy_equivalence(&mut fully_moved); | |
96 | ||
97 | fully_moved | |
98 | } | |
99 | ||
100 | /// Utility to help performing substitution of `*pattern` by `target`. | |
101 | struct Replacer<'a, 'tcx> { | |
102 | tcx: TyCtxt<'tcx>, | |
103 | fully_moved: BitSet<Local>, | |
104 | storage_to_remove: BitSet<Local>, | |
105 | borrowed_locals: BitSet<Local>, | |
353b0b11 | 106 | copy_classes: &'a IndexSlice<Local, Local>, |
9ffffee4 FG |
107 | } |
108 | ||
109 | impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> { | |
110 | fn tcx(&self) -> TyCtxt<'tcx> { | |
111 | self.tcx | |
112 | } | |
113 | ||
114 | fn visit_local(&mut self, local: &mut Local, ctxt: PlaceContext, _: Location) { | |
115 | let new_local = self.copy_classes[*local]; | |
116 | match ctxt { | |
117 | // Do not modify the local in storage statements. | |
118 | PlaceContext::NonUse(NonUseContext::StorageLive | NonUseContext::StorageDead) => {} | |
119 | // The local should have been marked as non-SSA. | |
120 | PlaceContext::MutatingUse(_) => assert_eq!(*local, new_local), | |
121 | // We access the value. | |
122 | _ => *local = new_local, | |
123 | } | |
124 | } | |
125 | ||
126 | fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) { | |
127 | if let Some(new_projection) = self.process_projection(&place.projection, loc) { | |
128 | place.projection = self.tcx().mk_place_elems(&new_projection); | |
129 | } | |
130 | ||
131 | let observes_address = match ctxt { | |
132 | PlaceContext::NonMutatingUse( | |
133 | NonMutatingUseContext::SharedBorrow | |
781aab86 | 134 | | NonMutatingUseContext::FakeBorrow |
9ffffee4 FG |
135 | | NonMutatingUseContext::AddressOf, |
136 | ) => true, | |
137 | // For debuginfo, merging locals is ok. | |
138 | PlaceContext::NonUse(NonUseContext::VarDebugInfo) => { | |
139 | self.borrowed_locals.contains(place.local) | |
140 | } | |
141 | _ => false, | |
142 | }; | |
143 | if observes_address && !place.is_indirect() { | |
144 | // We observe the address of `place.local`. Do not replace it. | |
145 | } else { | |
146 | self.visit_local( | |
147 | &mut place.local, | |
148 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy), | |
149 | loc, | |
150 | ) | |
151 | } | |
152 | } | |
153 | ||
154 | fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) { | |
155 | if let Operand::Move(place) = *operand | |
156 | // A move out of a projection of a copy is equivalent to a copy of the original projection. | |
add651ee | 157 | && !place.is_indirect_first_projection() |
9ffffee4 FG |
158 | && !self.fully_moved.contains(place.local) |
159 | { | |
160 | *operand = Operand::Copy(place); | |
161 | } | |
162 | self.super_operand(operand, loc); | |
163 | } | |
164 | ||
165 | fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) { | |
49aad941 FG |
166 | // When removing storage statements, we need to remove both (#107511). |
167 | if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = stmt.kind | |
168 | && self.storage_to_remove.contains(l) | |
169 | { | |
170 | stmt.make_nop(); | |
171 | return | |
172 | } | |
173 | ||
174 | self.super_statement(stmt, loc); | |
175 | ||
176 | // Do not leave tautological assignments around. | |
177 | if let StatementKind::Assign(box (lhs, ref rhs)) = stmt.kind | |
178 | && let Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)) | Rvalue::CopyForDeref(rhs) = *rhs | |
179 | && lhs == rhs | |
180 | { | |
181 | stmt.make_nop(); | |
9ffffee4 FG |
182 | } |
183 | } | |
184 | } |