Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 06a53dd

Browse files
committed
Auto merge of rust-lang#113758 - cjgillot:move-dse, r=JakobDegen,oli-obk
Turn copy into moves during DSE. Dead store elimination computes whether removing a direct store to an unborrowed place is allowed. Where removing a store is allowed, writing `uninit` is too. This means that we can use this pass to transform `copy` operands into `move` operands. This is only interesting in call terminators, so we only handle those. Special care is taken for the `use_both(_1, _1)` case: - moving the second argument is ok, as `_1` is not live after the call; - moving the first argument is not, as the second argument reads `_1`. Fixes rust-lang#75993 Fixes rust-lang#108068 r? `@RalfJung` cc `@JakobDegen`
2 parents 6b53175 + 254bf60 commit 06a53dd

File tree

32 files changed

+130
-31
lines changed

32 files changed

+130
-31
lines changed

compiler/rustc_mir_dataflow/src/impls/liveness.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
8888
}
8989
}
9090

91-
struct TransferFunction<'a, T>(&'a mut T);
91+
pub struct TransferFunction<'a, T>(pub &'a mut T);
9292

9393
impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T>
9494
where

compiler/rustc_mir_dataflow/src/impls/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub use self::borrowed_locals::borrowed_locals;
2626
pub use self::borrowed_locals::MaybeBorrowedLocals;
2727
pub use self::liveness::MaybeLiveLocals;
2828
pub use self::liveness::MaybeTransitiveLiveLocals;
29+
pub use self::liveness::TransferFunction as LivenessTransferFunction;
2930
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
3031

3132
/// `MaybeInitializedPlaces` tracks all places that might be

compiler/rustc_mir_transform/src/dead_store_elimination.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
//!
1414
1515
use rustc_index::bit_set::BitSet;
16+
use rustc_middle::mir::visit::Visitor;
1617
use rustc_middle::mir::*;
1718
use rustc_middle::ty::TyCtxt;
18-
use rustc_mir_dataflow::impls::{borrowed_locals, MaybeTransitiveLiveLocals};
19+
use rustc_mir_dataflow::impls::{
20+
borrowed_locals, LivenessTransferFunction, MaybeTransitiveLiveLocals,
21+
};
1922
use rustc_mir_dataflow::Analysis;
2023

2124
/// Performs the optimization on the body
@@ -28,8 +31,33 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
2831
.iterate_to_fixpoint()
2932
.into_results_cursor(body);
3033

34+
// For blocks with a call terminator, if an argument copy can be turned into a move,
35+
// record it as (block, argument index).
36+
let mut call_operands_to_move = Vec::new();
3137
let mut patch = Vec::new();
38+
3239
for (bb, bb_data) in traversal::preorder(body) {
40+
if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind {
41+
let loc = Location { block: bb, statement_index: bb_data.statements.len() };
42+
43+
// Position ourselves between the evaluation of `args` and the write to `destination`.
44+
live.seek_to_block_end(bb);
45+
let mut state = live.get().clone();
46+
47+
for (index, arg) in args.iter().enumerate().rev() {
48+
if let Operand::Copy(place) = *arg
49+
&& !place.is_indirect()
50+
&& !borrowed.contains(place.local)
51+
&& !state.contains(place.local)
52+
{
53+
call_operands_to_move.push((bb, index));
54+
}
55+
56+
// Account that `arg` is read from, so we don't promote another argument to a move.
57+
LivenessTransferFunction(&mut state).visit_operand(arg, loc);
58+
}
59+
}
60+
3361
for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
3462
let loc = Location { block: bb, statement_index };
3563
if let StatementKind::Assign(assign) = &statement.kind {
@@ -64,14 +92,22 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
6492
}
6593
}
6694

67-
if patch.is_empty() {
95+
if patch.is_empty() && call_operands_to_move.is_empty() {
6896
return;
6997
}
7098

7199
let bbs = body.basic_blocks.as_mut_preserves_cfg();
72100
for Location { block, statement_index } in patch {
73101
bbs[block].statements[statement_index].make_nop();
74102
}
103+
for (block, argument_index) in call_operands_to_move {
104+
let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else {
105+
bug!()
106+
};
107+
let arg = &mut args[argument_index];
108+
let Operand::Copy(place) = *arg else { bug!() };
109+
*arg = Operand::Move(place);
110+
}
75111

76112
crate::simplify::simplify_locals(body, tcx)
77113
}

tests/codegen/iter-repeat-n-trivial-drop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub fn iter_repeat_n_next(it: &mut std::iter::RepeatN<NotCopy>) -> Option<NotCop
3333

3434
// CHECK: [[EMPTY]]:
3535
// CHECK-NOT: br
36-
// CHECK: phi i16 [ %[[VAL]], %[[NOT_EMPTY]] ], [ undef, %start ]
36+
// CHECK: phi i16
3737
// CHECK-NOT: br
3838
// CHECK: ret
3939

tests/codegen/move-operands.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// compile-flags: -C no-prepopulate-passes -Zmir-enable-passes=+DestinationPropagation,-CopyProp
1+
// Verify that optimized MIR only copies `a` once.
2+
// compile-flags: -O -C no-prepopulate-passes
23

34
#![crate_type = "lib"]
45

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
- // MIR for `move_simple` before DeadStoreElimination
2+
+ // MIR for `move_simple` after DeadStoreElimination
3+
4+
fn move_simple(_1: i32) -> () {
5+
debug x => _1;
6+
let mut _0: ();
7+
let _2: ();
8+
- let mut _3: i32;
9+
- let mut _4: i32;
10+
11+
bb0: {
12+
StorageLive(_2);
13+
- _2 = use_both(_1, _1) -> [return: bb1, unwind unreachable];
14+
+ _2 = use_both(_1, move _1) -> [return: bb1, unwind unreachable];
15+
}
16+
17+
bb1: {
18+
StorageDead(_2);
19+
_0 = const ();
20+
return;
21+
}
22+
}
23+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
- // MIR for `move_simple` before DeadStoreElimination
2+
+ // MIR for `move_simple` after DeadStoreElimination
3+
4+
fn move_simple(_1: i32) -> () {
5+
debug x => _1;
6+
let mut _0: ();
7+
let _2: ();
8+
- let mut _3: i32;
9+
- let mut _4: i32;
10+
11+
bb0: {
12+
StorageLive(_2);
13+
- _2 = use_both(_1, _1) -> [return: bb1, unwind continue];
14+
+ _2 = use_both(_1, move _1) -> [return: bb1, unwind continue];
15+
}
16+
17+
bb1: {
18+
StorageDead(_2);
19+
_0 = const ();
20+
return;
21+
}
22+
}
23+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
2+
// unit-test: DeadStoreElimination
3+
// compile-flags: -Zmir-enable-passes=+CopyProp
4+
5+
#[inline(never)]
6+
fn use_both(_: i32, _: i32) {}
7+
8+
// EMIT_MIR call_arg_copy.move_simple.DeadStoreElimination.diff
9+
fn move_simple(x: i32) {
10+
use_both(x, x);
11+
}
12+
13+
fn main() {
14+
move_simple(1);
15+
}

tests/mir-opt/inline/dyn_trait.get_query.Inline.panic-abort.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
- _0 = try_execute_query::<<Q as Query>::C>(move _4) -> [return: bb2, unwind unreachable];
3333
+ StorageLive(_5);
3434
+ _5 = _4 as &dyn Cache<V = <Q as Query>::V> (PointerCoercion(Unsize));
35-
+ _0 = <dyn Cache<V = <Q as Query>::V> as Cache>::store_nocache(_5) -> [return: bb2, unwind unreachable];
35+
+ _0 = <dyn Cache<V = <Q as Query>::V> as Cache>::store_nocache(move _5) -> [return: bb2, unwind unreachable];
3636
}
3737

3838
bb2: {

tests/mir-opt/inline/dyn_trait.get_query.Inline.panic-unwind.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
- _0 = try_execute_query::<<Q as Query>::C>(move _4) -> [return: bb2, unwind continue];
3333
+ StorageLive(_5);
3434
+ _5 = _4 as &dyn Cache<V = <Q as Query>::V> (PointerCoercion(Unsize));
35-
+ _0 = <dyn Cache<V = <Q as Query>::V> as Cache>::store_nocache(_5) -> [return: bb2, unwind continue];
35+
+ _0 = <dyn Cache<V = <Q as Query>::V> as Cache>::store_nocache(move _5) -> [return: bb2, unwind continue];
3636
}
3737

3838
bb2: {

0 commit comments

Comments
 (0)