Skip to content

Commit c4b67c6

Browse files
committed
Reason about borrowed classes in CopyProp.
1 parent 71afc6f commit c4b67c6

File tree

4 files changed

+33
-31
lines changed

4 files changed

+33
-31
lines changed

compiler/rustc_mir_transform/src/copy_prop.rs

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
3030

3131
let typing_env = body.typing_env(tcx);
3232
let ssa = SsaLocals::new(tcx, body, typing_env);
33+
debug!(borrowed_locals = ?ssa.borrowed_locals());
34+
debug!(copy_classes = ?ssa.copy_classes());
3335

3436
let fully_moved = fully_moved_locals(&ssa, body);
3537
debug!(?fully_moved);
@@ -43,14 +45,8 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
4345

4446
let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h);
4547

46-
Replacer {
47-
tcx,
48-
copy_classes: ssa.copy_classes(),
49-
fully_moved,
50-
borrowed_locals: ssa.borrowed_locals(),
51-
storage_to_remove,
52-
}
53-
.visit_body_preserves_cfg(body);
48+
Replacer { tcx, copy_classes: ssa.copy_classes(), fully_moved, storage_to_remove }
49+
.visit_body_preserves_cfg(body);
5450

5551
if any_replacement {
5652
crate::simplify::remove_unused_definitions(body);
@@ -102,7 +98,6 @@ struct Replacer<'a, 'tcx> {
10298
tcx: TyCtxt<'tcx>,
10399
fully_moved: DenseBitSet<Local>,
104100
storage_to_remove: DenseBitSet<Local>,
105-
borrowed_locals: &'a DenseBitSet<Local>,
106101
copy_classes: &'a IndexSlice<Local, Local>,
107102
}
108103

@@ -111,34 +106,18 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
111106
self.tcx
112107
}
113108

109+
#[tracing::instrument(level = "trace", skip(self))]
114110
fn visit_local(&mut self, local: &mut Local, ctxt: PlaceContext, _: Location) {
115111
let new_local = self.copy_classes[*local];
116-
// We must not unify two locals that are borrowed. But this is fine if one is borrowed and
117-
// the other is not. We chose to check the original local, and not the target. That way, if
118-
// the original local is borrowed and the target is not, we do not pessimize the whole class.
119-
if self.borrowed_locals.contains(*local) {
120-
return;
121-
}
122112
match ctxt {
123113
// Do not modify the local in storage statements.
124114
PlaceContext::NonUse(NonUseContext::StorageLive | NonUseContext::StorageDead) => {}
125-
// The local should have been marked as non-SSA.
126-
PlaceContext::MutatingUse(_) => assert_eq!(*local, new_local),
127115
// We access the value.
128116
_ => *local = new_local,
129117
}
130118
}
131119

132-
fn visit_place(&mut self, place: &mut Place<'tcx>, _: PlaceContext, loc: Location) {
133-
if let Some(new_projection) = self.process_projection(place.projection, loc) {
134-
place.projection = self.tcx().mk_place_elems(&new_projection);
135-
}
136-
137-
// Any non-mutating use context is ok.
138-
let ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
139-
self.visit_local(&mut place.local, ctxt, loc)
140-
}
141-
120+
#[tracing::instrument(level = "trace", skip(self))]
142121
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) {
143122
if let Operand::Move(place) = *operand
144123
// A move out of a projection of a copy is equivalent to a copy of the original
@@ -151,6 +130,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
151130
self.super_operand(operand, loc);
152131
}
153132

133+
#[tracing::instrument(level = "trace", skip(self))]
154134
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
155135
// When removing storage statements, we need to remove both (#107511).
156136
if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = stmt.kind

compiler/rustc_mir_transform/src/ssa.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor<'_, 'tcx> {
293293
fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
294294
let mut direct_uses = std::mem::take(&mut ssa.direct_uses);
295295
let mut copies = IndexVec::from_fn_n(|l| l, body.local_decls.len());
296+
// We must not unify two locals that are borrowed. But this is fine if one is borrowed and
297+
// the other is not. This bitset is keyed by *class head* and contains whether any member of
298+
// the class is borrowed.
299+
let mut borrowed_classes = ssa.borrowed_locals().clone();
296300

297301
for (local, rvalue, _) in ssa.assignments(body) {
298302
let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place))
@@ -318,6 +322,11 @@ fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
318322
// visited before `local`, and we just have to copy the representing local.
319323
let head = copies[rhs];
320324

325+
// Do not unify two borrowed locals.
326+
if borrowed_classes.contains(local) && borrowed_classes.contains(head) {
327+
continue;
328+
}
329+
321330
if local == RETURN_PLACE {
322331
// `_0` is special, we cannot rename it. Instead, rename the class of `rhs` to
323332
// `RETURN_PLACE`. This is only possible if the class head is a temporary, not an
@@ -330,14 +339,21 @@ fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
330339
*h = RETURN_PLACE;
331340
}
332341
}
342+
if borrowed_classes.contains(head) {
343+
borrowed_classes.insert(RETURN_PLACE);
344+
}
333345
} else {
334346
copies[local] = head;
347+
if borrowed_classes.contains(local) {
348+
borrowed_classes.insert(head);
349+
}
335350
}
336351
direct_uses[rhs] -= 1;
337352
}
338353

339354
debug!(?copies);
340355
debug!(?direct_uses);
356+
debug!(?borrowed_classes);
341357

342358
// Invariant: `copies` must point to the head of an equivalence class.
343359
#[cfg(debug_assertions)]
@@ -346,6 +362,13 @@ fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
346362
}
347363
debug_assert_eq!(copies[RETURN_PLACE], RETURN_PLACE);
348364

365+
// Invariant: `borrowed_classes` must be true if any member of the class is borrowed.
366+
#[cfg(debug_assertions)]
367+
for &head in copies.iter() {
368+
let any_borrowed = ssa.borrowed_locals.iter().any(|l| copies[l] == head);
369+
assert_eq!(borrowed_classes.contains(head), any_borrowed);
370+
}
371+
349372
ssa.direct_uses = direct_uses;
350373
ssa.copy_classes = copies;
351374
}

tests/mir-opt/copy-prop/write_to_borrowed.main.CopyProp.diff

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
_5 = copy _3;
1818
_6 = &_3;
1919
- _4 = copy _5;
20-
+ _3 = copy _5;
2120
(*_1) = copy (*_6);
2221
_6 = &_5;
2322
- _7 = dump_var::<char>(copy _4) -> [return: bb1, unwind unreachable];
24-
+ _7 = dump_var::<char>(copy _3) -> [return: bb1, unwind unreachable];
23+
+ _7 = dump_var::<char>(copy _5) -> [return: bb1, unwind unreachable];
2524
}
2625

2726
bb1: {

tests/mir-opt/copy-prop/write_to_borrowed.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ fn main() {
2727
_5 = _3;
2828
// CHECK-NEXT: _6 = &_3;
2929
_6 = &_3;
30-
// CHECK-NEXT: _3 = copy _5
30+
// CHECK-NOT: {{_.*}} = {{_.*}};
3131
_4 = _5;
3232
// CHECK-NEXT: (*_1) = copy (*_6);
3333
*_1 = *_6;
3434
// CHECK-NEXT: _6 = &_5;
3535
_6 = &_5;
36-
// CHECK-NEXT: _7 = dump_var::<char>(copy _3)
36+
// CHECK-NEXT: _7 = dump_var::<char>(copy _5)
3737
Call(_7 = dump_var(_4), ReturnTo(bb1), UnwindUnreachable())
3838
}
3939
bb1 = { Return() }

0 commit comments

Comments
 (0)