|
1 | | -use std::borrow::Cow; |
2 | | - |
3 | 1 | use rustc_index::IndexSlice; |
4 | 2 | use rustc_index::bit_set::DenseBitSet; |
5 | 3 | use rustc_middle::mir::visit::*; |
6 | 4 | use rustc_middle::mir::*; |
7 | 5 | use rustc_middle::ty::TyCtxt; |
8 | | -use rustc_mir_dataflow::impls::{MaybeStorageDead, always_storage_live_locals}; |
| 6 | +use rustc_mir_dataflow::impls::MaybeUninitializedPlaces; |
| 7 | +use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData}; |
9 | 8 | use rustc_mir_dataflow::{Analysis, ResultsCursor}; |
10 | 9 | use tracing::{debug, instrument}; |
11 | 10 |
|
@@ -51,15 +50,20 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp { |
51 | 50 | let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h); |
52 | 51 |
|
53 | 52 | let storage_to_remove = if any_replacement { |
54 | | - let always_live_locals = &always_storage_live_locals(body); |
| 53 | + // The replacer will remove tautological moves from the head to the local, |
| 54 | + // so we remove them before running `MaybeUninitializedPlaces`. |
| 55 | + TautologicalMoveAssignmentRemover { tcx, copy_classes: ssa.copy_classes() } |
| 56 | + .visit_body_preserves_cfg(body); |
| 57 | + |
| 58 | + let move_data = MoveData::gather_moves(body, tcx, |_| true); |
55 | 59 |
|
56 | | - let maybe_storage_dead = MaybeStorageDead::new(Cow::Borrowed(always_live_locals)) |
57 | | - .iterate_to_fixpoint(tcx, body, None) |
| 60 | + let maybe_uninit = MaybeUninitializedPlaces::new(tcx, body, &move_data) |
| 61 | + .iterate_to_fixpoint(tcx, body, Some("mir_opt::copy_prop")) |
58 | 62 | .into_results_cursor(body); |
59 | 63 |
|
60 | 64 | let mut storage_checker = StorageChecker { |
61 | 65 | copy_classes: ssa.copy_classes(), |
62 | | - maybe_storage_dead, |
| 66 | + maybe_uninit, |
63 | 67 | head_storage_to_check, |
64 | 68 | storage_to_remove: DenseBitSet::new_empty(fully_moved.domain_size()), |
65 | 69 | }; |
@@ -126,6 +130,33 @@ fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> DenseBitSet<Local> { |
126 | 130 | fully_moved |
127 | 131 | } |
128 | 132 |
|
| 133 | +// The replacer will remove tautological moves from the head to the local, so we want the `MaybeUninitializedPlaces` |
| 134 | +struct TautologicalMoveAssignmentRemover<'a, 'tcx> { |
| 135 | + tcx: TyCtxt<'tcx>, |
| 136 | + copy_classes: &'a IndexSlice<Local, Local>, |
| 137 | +} |
| 138 | + |
| 139 | +impl<'tcx> MutVisitor<'tcx> for TautologicalMoveAssignmentRemover<'_, 'tcx> { |
| 140 | + fn tcx(&self) -> TyCtxt<'tcx> { |
| 141 | + self.tcx |
| 142 | + } |
| 143 | + |
| 144 | + fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) { |
| 145 | + // Similar to `Replacer::visit_statement`, but only handle moves. |
| 146 | + if let StatementKind::Assign(box (lhs, ref rhs)) = stmt.kind |
| 147 | + && let Rvalue::Use(Operand::Move(rhs)) = *rhs |
| 148 | + { |
| 149 | + let new_lhs_local = self.copy_classes[lhs.local]; |
| 150 | + let new_rhs_local = self.copy_classes[rhs.local]; |
| 151 | + |
| 152 | + if new_lhs_local == new_rhs_local && lhs.projection == rhs.projection { |
| 153 | + debug!(?loc, ?lhs, ?rhs, "removing a to-be-tautological assignment"); |
| 154 | + stmt.make_nop(); |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | +} |
| 159 | + |
129 | 160 | /// Utility to help performing substitution of `*pattern` by `target`. |
130 | 161 | struct Replacer<'a, 'tcx> { |
131 | 162 | tcx: TyCtxt<'tcx>, |
@@ -206,30 +237,41 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> { |
206 | 237 | struct StorageChecker<'a, 'tcx> { |
207 | 238 | storage_to_remove: DenseBitSet<Local>, |
208 | 239 | head_storage_to_check: DenseBitSet<Local>, |
209 | | - maybe_storage_dead: ResultsCursor<'a, 'tcx, MaybeStorageDead<'a>>, |
| 240 | + maybe_uninit: ResultsCursor<'a, 'tcx, MaybeUninitializedPlaces<'a, 'tcx>>, |
210 | 241 | copy_classes: &'a IndexSlice<Local, Local>, |
211 | 242 | } |
212 | 243 |
|
213 | 244 | impl<'a, 'tcx> Visitor<'tcx> for StorageChecker<'a, 'tcx> { |
214 | | - fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { |
| 245 | + fn visit_local(&mut self, local: Local, context: PlaceContext, loc: Location) { |
215 | 246 | if !context.is_use() { |
216 | 247 | return; |
217 | 248 | } |
218 | 249 |
|
219 | 250 | let head = self.copy_classes[local]; |
220 | | - if self.head_storage_to_check.contains(head) { |
221 | | - self.maybe_storage_dead.seek_after_primary_effect(location); |
222 | 251 |
|
223 | | - if self.maybe_storage_dead.get().contains(head) { |
| 252 | + if local == head { |
| 253 | + // Every original use of the head is known to be initialized, so we don't need to check it. |
| 254 | + return; |
| 255 | + } |
| 256 | + |
| 257 | + // The head must be initialized at the location of the local, otherwise we must remove it's storage statements. |
| 258 | + if self.head_storage_to_check.contains(head) |
| 259 | + && let Some(move_idx) = |
| 260 | + self.maybe_uninit.analysis().move_data().rev_lookup.find_local(head) |
| 261 | + { |
| 262 | + self.maybe_uninit.seek_after_primary_effect(loc); |
| 263 | + |
| 264 | + if self.maybe_uninit.get().contains(move_idx) { |
224 | 265 | debug!( |
225 | | - ?location, |
| 266 | + ?loc, |
226 | 267 | ?local, |
227 | 268 | ?head, |
228 | | - "found use of local with head at a location in which head is maybe dead, marking head for storage removal" |
| 269 | + ?move_idx, |
| 270 | + "found use of local with head at a location in which it is maybe uninit, marking head for storage statement removal" |
229 | 271 | ); |
230 | 272 | self.storage_to_remove.insert(head); |
231 | 273 |
|
232 | | - // Once we found a use of the head that is maybe dead, we do not need to check it again. |
| 274 | + // Once we found a use of the head that is maybe uninit, we do not need to check it again. |
233 | 275 | self.head_storage_to_check.remove(head); |
234 | 276 | } |
235 | 277 | } |
|
0 commit comments