Skip to content

Commit 4ae2c39

Browse files
committed
Turn moves into copies after copy propagation
Previously copy propagation presumed that there is further unspecified distinction between move operands and copy operands in assignments and propagated moves from assignments into terminators. This is inconsistent with current operational semantics. Turn moves into copies after copy propagation to preserve existing behavior. Fixes #137936. Fixes #146423.
1 parent 28c4c7d commit 4ae2c39

File tree

46 files changed

+170
-227
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+170
-227
lines changed

compiler/rustc_mir_transform/src/copy_prop.rs

Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::ssa::SsaLocals;
1616
/// _d = move? _c
1717
/// where each of the locals is only assigned once.
1818
///
19-
/// We want to replace all those locals by `_a`, either copied or moved.
19+
/// We want to replace all those locals by `copy _a`.
2020
pub(super) struct CopyProp;
2121

2222
impl<'tcx> crate::MirPass<'tcx> for CopyProp {
@@ -34,23 +34,21 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
3434
debug!(copy_classes = ?ssa.copy_classes());
3535

3636
let mut any_replacement = false;
37-
let mut storage_to_remove = DenseBitSet::new_empty(body.local_decls.len());
37+
// Locals that participate in copy propagation either as a source or a destination.
38+
let mut unified = DenseBitSet::new_empty(body.local_decls.len());
3839
for (local, &head) in ssa.copy_classes().iter_enumerated() {
3940
if local != head {
4041
any_replacement = true;
41-
storage_to_remove.insert(head);
42+
unified.insert(head);
43+
unified.insert(local);
4244
}
4345
}
4446

4547
if !any_replacement {
4648
return;
4749
}
4850

49-
let fully_moved = fully_moved_locals(&ssa, body);
50-
debug!(?fully_moved);
51-
52-
Replacer { tcx, copy_classes: ssa.copy_classes(), fully_moved, storage_to_remove }
53-
.visit_body_preserves_cfg(body);
51+
Replacer { tcx, copy_classes: ssa.copy_classes(), unified }.visit_body_preserves_cfg(body);
5452

5553
crate::simplify::remove_unused_definitions(body);
5654
}
@@ -60,44 +58,10 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
6058
}
6159
}
6260

63-
/// `SsaLocals` computed equivalence classes between locals considering copy/move assignments.
64-
///
65-
/// This function also returns whether all the `move?` in the pattern are `move` and not copies.
66-
/// A local which is in the bitset can be replaced by `move _a`. Otherwise, it must be
67-
/// replaced by `copy _a`, as we cannot move multiple times from `_a`.
68-
///
69-
/// If an operand copies `_c`, it must happen before the assignment `_d = _c`, otherwise it is UB.
70-
/// This means that replacing it by a copy of `_a` if ok, since this copy happens before `_c` is
71-
/// moved, and therefore that `_d` is moved.
72-
#[instrument(level = "trace", skip(ssa, body))]
73-
fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> DenseBitSet<Local> {
74-
let mut fully_moved = DenseBitSet::new_filled(body.local_decls.len());
75-
76-
for (_, rvalue, _) in ssa.assignments(body) {
77-
let Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) = rvalue else {
78-
continue;
79-
};
80-
81-
let Some(rhs) = place.as_local() else { continue };
82-
if !ssa.is_ssa(rhs) {
83-
continue;
84-
}
85-
86-
if let Rvalue::Use(Operand::Copy(_)) = rvalue {
87-
fully_moved.remove(rhs);
88-
}
89-
}
90-
91-
ssa.meet_copy_equivalence(&mut fully_moved);
92-
93-
fully_moved
94-
}
95-
9661
/// Utility to help performing substitution of `*pattern` by `target`.
9762
struct Replacer<'a, 'tcx> {
9863
tcx: TyCtxt<'tcx>,
99-
fully_moved: DenseBitSet<Local>,
100-
storage_to_remove: DenseBitSet<Local>,
64+
unified: DenseBitSet<Local>,
10165
copy_classes: &'a IndexSlice<Local, Local>,
10266
}
10367

@@ -108,13 +72,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
10872

10973
#[tracing::instrument(level = "trace", skip(self))]
11074
fn visit_local(&mut self, local: &mut Local, ctxt: PlaceContext, _: Location) {
111-
let new_local = self.copy_classes[*local];
112-
match ctxt {
113-
// Do not modify the local in storage statements.
114-
PlaceContext::NonUse(NonUseContext::StorageLive | NonUseContext::StorageDead) => {}
115-
// We access the value.
116-
_ => *local = new_local,
117-
}
75+
*local = self.copy_classes[*local];
11876
}
11977

12078
#[tracing::instrument(level = "trace", skip(self))]
@@ -123,7 +81,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
12381
// A move out of a projection of a copy is equivalent to a copy of the original
12482
// projection.
12583
&& !place.is_indirect_first_projection()
126-
&& !self.fully_moved.contains(place.local)
84+
&& self.unified.contains(place.local)
12785
{
12886
*operand = Operand::Copy(place);
12987
}
@@ -134,7 +92,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
13492
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
13593
// When removing storage statements, we need to remove both (#107511).
13694
if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = stmt.kind
137-
&& self.storage_to_remove.contains(l)
95+
&& self.unified.contains(l)
13896
{
13997
stmt.make_nop(true);
14098
return;

tests/mir-opt/copy-prop/borrowed_local.borrow_in_loop.CopyProp.panic-abort.diff

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
- StorageLive(_6);
4747
StorageLive(_7);
4848
_7 = copy (*_2);
49-
_6 = Not(move _7);
49+
- _6 = Not(move _7);
50+
+ _6 = Not(copy _7);
5051
StorageDead(_7);
5152
- StorageLive(_8);
5253
StorageLive(_9);
5354
_9 = copy (*_2);
54-
_8 = Not(move _9);
55+
- _8 = Not(move _9);
56+
+ _8 = Not(copy _9);
5557
StorageDead(_9);
5658
- StorageLive(_10);
5759
- _10 = copy _6;
@@ -62,7 +64,8 @@
6264
StorageLive(_12);
6365
_12 = &_1;
6466
_11 = &(*_12);
65-
_2 = move _11;
67+
- _2 = move _11;
68+
+ _2 = copy _11;
6669
StorageDead(_11);
6770
StorageDead(_12);
6871
StorageLive(_13);
@@ -71,8 +74,9 @@
7174
- StorageLive(_15);
7275
- _15 = copy _8;
7376
- _13 = Ne(move _14, move _15);
77+
- switchInt(move _13) -> [0: bb3, otherwise: bb2];
7478
+ _13 = Ne(copy _6, copy _8);
75-
switchInt(move _13) -> [0: bb3, otherwise: bb2];
79+
+ switchInt(copy _13) -> [0: bb3, otherwise: bb2];
7680
}
7781

7882
bb2: {

tests/mir-opt/copy-prop/cycle.main.CopyProp.panic-abort.diff

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
StorageLive(_5);
3939
StorageLive(_6);
4040
_6 = copy _1;
41-
_5 = std::mem::drop::<i32>(move _6) -> [return: bb2, unwind unreachable];
41+
- _5 = std::mem::drop::<i32>(move _6) -> [return: bb2, unwind unreachable];
42+
+ _5 = std::mem::drop::<i32>(copy _6) -> [return: bb2, unwind unreachable];
4243
}
4344

4445
bb2: {

tests/mir-opt/copy-prop/dead_stores_79191.f.CopyProp.after.panic-abort.mir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn f(_1: usize) -> usize {
1616
_1 = copy _2;
1717
StorageLive(_4);
1818
_4 = copy _1;
19-
_0 = id::<usize>(move _4) -> [return: bb1, unwind unreachable];
19+
_0 = id::<usize>(copy _4) -> [return: bb1, unwind unreachable];
2020
}
2121

2222
bb1: {

tests/mir-opt/copy-prop/dead_stores_better.f.CopyProp.after.panic-abort.mir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn f(_1: usize) -> usize {
1616
_1 = copy _2;
1717
StorageLive(_4);
1818
_4 = copy _1;
19-
_0 = id::<usize>(move _4) -> [return: bb1, unwind unreachable];
19+
_0 = id::<usize>(copy _4) -> [return: bb1, unwind unreachable];
2020
}
2121

2222
bb1: {

tests/mir-opt/copy-prop/issue_107511.main.CopyProp.panic-abort.diff

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,27 @@
4646
StorageLive(_6);
4747
StorageLive(_7);
4848
_7 = &_2;
49-
_6 = move _7 as &[i32] (PointerCoercion(Unsize, Implicit));
49+
- _6 = move _7 as &[i32] (PointerCoercion(Unsize, Implicit));
50+
+ _6 = copy _7 as &[i32] (PointerCoercion(Unsize, Implicit));
5051
StorageDead(_7);
51-
_5 = core::slice::<impl [i32]>::len(move _6) -> [return: bb1, unwind unreachable];
52+
- _5 = core::slice::<impl [i32]>::len(move _6) -> [return: bb1, unwind unreachable];
53+
+ _5 = core::slice::<impl [i32]>::len(copy _6) -> [return: bb1, unwind unreachable];
5254
}
5355

5456
bb1: {
5557
StorageDead(_6);
56-
_4 = std::ops::Range::<usize> { start: const 0_usize, end: move _5 };
58+
- _4 = std::ops::Range::<usize> { start: const 0_usize, end: move _5 };
59+
+ _4 = std::ops::Range::<usize> { start: const 0_usize, end: copy _5 };
5760
StorageDead(_5);
58-
_3 = <std::ops::Range<usize> as IntoIterator>::into_iter(move _4) -> [return: bb2, unwind unreachable];
61+
- _3 = <std::ops::Range<usize> as IntoIterator>::into_iter(move _4) -> [return: bb2, unwind unreachable];
62+
+ _3 = <std::ops::Range<usize> as IntoIterator>::into_iter(copy _4) -> [return: bb2, unwind unreachable];
5963
}
6064

6165
bb2: {
6266
StorageDead(_4);
6367
StorageLive(_8);
64-
_8 = move _3;
68+
- _8 = move _3;
69+
+ _8 = copy _3;
6570
goto -> bb3;
6671
}
6772

@@ -72,13 +77,15 @@
7277
StorageLive(_13);
7378
_13 = &mut _8;
7479
_12 = &mut (*_13);
75-
_11 = <std::ops::Range<usize> as Iterator>::next(move _12) -> [return: bb4, unwind unreachable];
80+
- _11 = <std::ops::Range<usize> as Iterator>::next(move _12) -> [return: bb4, unwind unreachable];
81+
+ _11 = <std::ops::Range<usize> as Iterator>::next(copy _12) -> [return: bb4, unwind unreachable];
7682
}
7783

7884
bb4: {
7985
StorageDead(_12);
8086
_14 = discriminant(_11);
81-
switchInt(move _14) -> [0: bb7, 1: bb6, otherwise: bb5];
87+
- switchInt(move _14) -> [0: bb7, 1: bb6, otherwise: bb5];
88+
+ switchInt(copy _14) -> [0: bb7, 1: bb6, otherwise: bb5];
8289
}
8390

8491
bb5: {
@@ -94,7 +101,7 @@
94101
- _19 = Lt(copy _18, const 4_usize);
95102
- assert(move _19, "index out of bounds: the length is {} but the index is {}", const 4_usize, copy _18) -> [success: bb8, unwind unreachable];
96103
+ _19 = Lt(copy _16, const 4_usize);
97-
+ assert(move _19, "index out of bounds: the length is {} but the index is {}", const 4_usize, copy _16) -> [success: bb8, unwind unreachable];
104+
+ assert(copy _19, "index out of bounds: the length is {} but the index is {}", const 4_usize, copy _16) -> [success: bb8, unwind unreachable];
98105
}
99106

100107
bb7: {
@@ -111,8 +118,9 @@
111118

112119
bb8: {
113120
- _17 = copy _2[_18];
121+
- _1 = Add(copy _1, move _17);
114122
+ _17 = copy _2[_16];
115-
_1 = Add(copy _1, move _17);
123+
+ _1 = Add(copy _1, copy _17);
116124
StorageDead(_17);
117125
- StorageDead(_18);
118126
- _10 = const ();

tests/mir-opt/copy-prop/move_arg.f.CopyProp.panic-abort.diff

Lines changed: 0 additions & 37 deletions
This file was deleted.

tests/mir-opt/copy-prop/move_arg.f.CopyProp.panic-unwind.diff

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
1-
// skip-filecheck
2-
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
31
// Test that we do not move multiple times from the same local.
42
//@ test-mir-pass: CopyProp
3+
//@ compile-flags: --crate-type=lib -Cpanic=abort
4+
#![feature(custom_mir, core_intrinsics)]
5+
use core::intrinsics::mir::*;
6+
use core::mem::MaybeUninit;
7+
extern crate core;
58

6-
// EMIT_MIR move_arg.f.CopyProp.diff
7-
pub fn f<T: Copy>(a: T) {
8-
let b = a;
9-
g(a, b);
9+
#[custom_mir(dialect = "runtime", phase = "initial")]
10+
pub fn moved_and_copied<T: Copy>(_1: T) {
11+
// CHECK-LABEL: fn moved_and_copied(
12+
// CHECK: _0 = f::<T>(copy _1, copy _1)
13+
mir! {
14+
{
15+
let _2 = _1;
16+
Call(RET = f(Move(_1), Move(_2)), ReturnTo(bb1), UnwindUnreachable())
17+
}
18+
bb1 = {
19+
Return()
20+
}
21+
}
1022
}
1123

12-
#[inline(never)]
13-
pub fn g<T: Copy>(_: T, _: T) {}
14-
15-
fn main() {
16-
f(5)
24+
#[custom_mir(dialect = "runtime", phase = "initial")]
25+
pub fn moved_twice<T: Copy>(_1: MaybeUninit<T>) {
26+
// In a future we would like to propagate moves instead of copies here. The resulting program
27+
// would have an undefined behavior due to overlap in a call terminator, so we need to change
28+
// operational semantics to explain why the original program has undefined behavior.
29+
// https://github.com/rust-lang/unsafe-code-guidelines/issues/556
30+
//
31+
// CHECK-LABEL: fn moved_twice(
32+
// CHECK: _0 = f::<MaybeUninit<T>>(copy _1, copy _1)
33+
mir! {
34+
{
35+
let _2 = Move(_1);
36+
let _3 = Move(_1);
37+
Call(RET = f(Move(_2), Move(_3)), ReturnTo(bb1), UnwindUnreachable())
38+
}
39+
bb1 = {
40+
Return()
41+
}
42+
}
1743
}
44+
45+
#[inline(never)]
46+
pub fn f<T: Copy>(_: T, _: T) {}

tests/mir-opt/copy-prop/move_projection.f.CopyProp.panic-abort.diff

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
}
1616

1717
bb1: {
18-
_0 = opaque::<u8>(move _3) -> [return: bb2, unwind unreachable];
18+
- _0 = opaque::<u8>(move _3) -> [return: bb2, unwind unreachable];
19+
+ _0 = opaque::<u8>(copy _3) -> [return: bb2, unwind unreachable];
1920
}
2021

2122
bb2: {

0 commit comments

Comments
 (0)