Skip to content

Commit 73ca7da

Browse files
authored
Rollup merge of #142571 - cjgillot:borrowed-classes, r=oli-obk
Reason about borrowed classes in CopyProp. Fixes #141122 The current implementation of `CopyProp` avoids unifying two borrowed locals, as this would change the result of address comparison. However, the implementation was inconsistent with the general algorithm, which identifies equivalence classes of locals and then replaces all locals by a single representative of their equivalence class. This PR fixes it by forbidding the unification of two *classes* if any of those contain a borrowed local.
2 parents 865a6c8 + c4b67c6 commit 73ca7da

File tree

4 files changed

+105
-27
lines changed

4 files changed

+105
-27
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
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
- // MIR for `main` before CopyProp
2+
+ // MIR for `main` after CopyProp
3+
4+
fn main() -> () {
5+
let mut _0: ();
6+
let mut _1: *const char;
7+
let mut _2: char;
8+
let mut _3: char;
9+
let mut _4: char;
10+
let mut _5: char;
11+
let mut _6: &char;
12+
let mut _7: ();
13+
14+
bb0: {
15+
_1 = &raw const _2;
16+
_3 = const 'b';
17+
_5 = copy _3;
18+
_6 = &_3;
19+
- _4 = copy _5;
20+
(*_1) = copy (*_6);
21+
_6 = &_5;
22+
- _7 = dump_var::<char>(copy _4) -> [return: bb1, unwind unreachable];
23+
+ _7 = dump_var::<char>(copy _5) -> [return: bb1, unwind unreachable];
24+
}
25+
26+
bb1: {
27+
return;
28+
}
29+
}
30+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@ test-mir-pass: CopyProp
2+
3+
#![feature(custom_mir, core_intrinsics)]
4+
#![allow(internal_features)]
5+
6+
use std::intrinsics::mir::*;
7+
8+
#[custom_mir(dialect = "runtime")]
9+
fn main() {
10+
mir! {
11+
// Both _3 and _5 are borrowed, check that we do not unify them, and that we do not
12+
// introduce a write to any of them.
13+
let _1;
14+
let _2;
15+
let _3;
16+
let _4;
17+
let _5;
18+
let _6;
19+
let _7;
20+
// CHECK: bb0: {
21+
{
22+
// CHECK-NEXT: _1 = &raw const _2;
23+
_1 = core::ptr::addr_of!(_2);
24+
// CHECK-NEXT: _3 = const 'b';
25+
_3 = 'b';
26+
// CHECK-NEXT: _5 = copy _3;
27+
_5 = _3;
28+
// CHECK-NEXT: _6 = &_3;
29+
_6 = &_3;
30+
// CHECK-NOT: {{_.*}} = {{_.*}};
31+
_4 = _5;
32+
// CHECK-NEXT: (*_1) = copy (*_6);
33+
*_1 = *_6;
34+
// CHECK-NEXT: _6 = &_5;
35+
_6 = &_5;
36+
// CHECK-NEXT: _7 = dump_var::<char>(copy _5)
37+
Call(_7 = dump_var(_4), ReturnTo(bb1), UnwindUnreachable())
38+
}
39+
bb1 = { Return() }
40+
}
41+
}
42+
43+
fn dump_var<T>(_: T) {}
44+
45+
// EMIT_MIR write_to_borrowed.main.CopyProp.diff

0 commit comments

Comments
 (0)