Rollup merge of #155680 - Amanieu:call-arg-move-index, r=cjgillot

Handle index projections in call destinations in DSE

Since call destinations are evaluated after call arguments, we can't turn copy arguments into moves if the same local is later used as an index projection in the call destination.

DSE call arg optimization: rust-lang/rust#113758

r? @cjgillot
cc @RalfJung
This commit is contained in:
Jacob Pratt
2026-04-25 01:21:52 -04:00
committed by GitHub
4 changed files with 63 additions and 1 deletions
@@ -47,13 +47,21 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
let mut patch = Vec::new(); let mut patch = Vec::new();
for (bb, bb_data) in traversal::preorder(body) { for (bb, bb_data) in traversal::preorder(body) {
if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind { if let TerminatorKind::Call { ref args, ref destination, .. } = bb_data.terminator().kind {
let loc = Location { block: bb, statement_index: bb_data.statements.len() }; let loc = Location { block: bb, statement_index: bb_data.statements.len() };
// Position ourselves between the evaluation of `args` and the write to `destination`. // Position ourselves between the evaluation of `args` and the write to `destination`.
live.seek_to_block_end(bb); live.seek_to_block_end(bb);
let mut state = live.get().clone(); let mut state = live.get().clone();
// Don't turn into a move if the local is used as an index
// projection for the destination place.
LivenessTransferFunction(&mut state).visit_place(
destination,
visit::PlaceContext::MutatingUse(visit::MutatingUseContext::Call),
loc,
);
for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() { for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() {
if let Operand::Copy(place) = *arg if let Operand::Copy(place) = *arg
&& !place.is_indirect() && !place.is_indirect()
@@ -0,0 +1,15 @@
- // MIR for `move_index` before DeadStoreElimination-final
+ // MIR for `move_index` after DeadStoreElimination-final
fn move_index(_1: [usize; 10], _2: usize) -> () {
let mut _0: ();
bb0: {
_1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind unreachable];
}
bb1: {
return;
}
}
@@ -0,0 +1,15 @@
- // MIR for `move_index` before DeadStoreElimination-final
+ // MIR for `move_index` after DeadStoreElimination-final
fn move_index(_1: [usize; 10], _2: usize) -> () {
let mut _0: ();
bb0: {
_1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind continue];
}
bb1: {
return;
}
}
@@ -40,7 +40,31 @@ fn move_packed(packed: Packed) {
} }
} }
#[inline(never)]
fn passthrough_usize(a: usize) -> usize {
a
}
// EMIT_MIR call_arg_copy.move_index.DeadStoreElimination-final.diff
#[custom_mir(dialect = "analysis")]
fn move_index(a: [usize; 10], b: usize) {
// CHECK-LABEL: fn move_index(
// CHECK: = passthrough_usize(copy _2)
mir! {
{
// The index is used again after the operand is evaluated to
// evaluate the destionation place, so the argument cannot be turned
// into a move.
Call(a[b] = passthrough_usize(b), ReturnTo(ret), UnwindContinue())
}
ret = {
Return()
}
}
}
fn main() { fn main() {
move_simple(1); move_simple(1);
move_packed(Packed { x: 0, y: 1 }); move_packed(Packed { x: 0, y: 1 });
move_index([0; _], 1);
} }