Skip to content

Commit 7379f8a

Browse files
Auto merge of #147804 - tmiasko:move-copy, r=<try>
Turn moves into copies after copy propagation
2 parents c8a31b7 + 391b45e commit 7379f8a

13 files changed

+76
-147
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/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-fulldeps/rustc_public/projections.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ fn test_place_projections() -> ControlFlow<()> {
100100
{
101101
match &args[..] {
102102
[
103-
rustc_public::mir::Operand::Move(rustc_public::mir::Place {
103+
rustc_public::mir::Operand::Copy(rustc_public::mir::Place {
104104
local: _,
105105
projection: arg1_proj,
106106
}),
107-
rustc_public::mir::Operand::Move(rustc_public::mir::Place {
107+
rustc_public::mir::Operand::Copy(rustc_public::mir::Place {
108108
local: _,
109109
projection: arg2_proj,
110110
}),

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
}

0 commit comments

Comments
 (0)