|
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