Skip to content

Commit 7383119

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 c8a31b7 commit 7383119

12 files changed

+74
-146
lines changed

compiler/rustc_mir_transform/src/copy_prop.rs

Lines changed: 10 additions & 53 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,10 +92,9 @@ 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);
140-
return;
14198
}
14299

143100
self.super_statement(stmt, loc);

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/reborrow.remut.CopyProp.panic-abort.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- StorageLive(_6);
3232
- _6 = move _4;
3333
- _5 = opaque::<&mut u8>(move _6) -> [return: bb1, unwind unreachable];
34-
+ _5 = opaque::<&mut u8>(move _2) -> [return: bb1, unwind unreachable];
34+
+ _5 = opaque::<&mut u8>(copy _2) -> [return: bb1, unwind unreachable];
3535
}
3636

3737
bb1: {

tests/mir-opt/copy-prop/reborrow.remut.CopyProp.panic-unwind.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- StorageLive(_6);
3232
- _6 = move _4;
3333
- _5 = opaque::<&mut u8>(move _6) -> [return: bb1, unwind continue];
34-
+ _5 = opaque::<&mut u8>(move _2) -> [return: bb1, unwind continue];
34+
+ _5 = opaque::<&mut u8>(copy _2) -> [return: bb1, unwind continue];
3535
}
3636

3737
bb1: {

tests/mir-opt/copy-prop/reborrow.reraw.CopyProp.panic-abort.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- StorageLive(_6);
3232
- _6 = move _4;
3333
- _5 = opaque::<&mut u8>(move _6) -> [return: bb1, unwind unreachable];
34-
+ _5 = opaque::<&mut u8>(move _2) -> [return: bb1, unwind unreachable];
34+
+ _5 = opaque::<&mut u8>(copy _2) -> [return: bb1, unwind unreachable];
3535
}
3636

3737
bb1: {

tests/mir-opt/copy-prop/reborrow.reraw.CopyProp.panic-unwind.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- StorageLive(_6);
3232
- _6 = move _4;
3333
- _5 = opaque::<&mut u8>(move _6) -> [return: bb1, unwind continue];
34-
+ _5 = opaque::<&mut u8>(move _2) -> [return: bb1, unwind continue];
34+
+ _5 = opaque::<&mut u8>(copy _2) -> [return: bb1, unwind continue];
3535
}
3636

3737
bb1: {

tests/ui/lint/large_assignments/inline_mir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
pub fn main() {
2121
let data = [10u8; 9999];
2222
let cell = std::cell::UnsafeCell::new(data); //~ ERROR large_assignments
23-
std::hint::black_box(cell);
23+
std::hint::black_box(cell); //~ ERROR large_assignments
2424
}

tests/ui/lint/large_assignments/inline_mir.stderr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,13 @@ note: the lint level is defined here
1111
LL | #![deny(large_assignments)]
1212
| ^^^^^^^^^^^^^^^^^
1313

14-
error: aborting due to 1 previous error
14+
error: moving 9999 bytes
15+
--> $DIR/inline_mir.rs:23:5
16+
|
17+
LL | std::hint::black_box(cell);
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ value moved from here
19+
|
20+
= note: The current maximum size is 1000, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]`
21+
22+
error: aborting due to 2 previous errors
1523

0 commit comments

Comments
 (0)