1
Fork 0

Auto merge of #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 #75993
Fixes https://github.com/rust-lang/rust/issues/108068

r? `@RalfJung`
cc `@JakobDegen`
This commit is contained in:
bors 2023-07-20 15:05:39 +00:00
commit 06a53ddc0b
32 changed files with 130 additions and 31 deletions

View file

@ -88,7 +88,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
}
}
struct TransferFunction<'a, T>(&'a mut T);
pub struct TransferFunction<'a, T>(pub &'a mut T);
impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T>
where

View file

@ -26,6 +26,7 @@ pub use self::borrowed_locals::borrowed_locals;
pub use self::borrowed_locals::MaybeBorrowedLocals;
pub use self::liveness::MaybeLiveLocals;
pub use self::liveness::MaybeTransitiveLiveLocals;
pub use self::liveness::TransferFunction as LivenessTransferFunction;
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
/// `MaybeInitializedPlaces` tracks all places that might be

View file

@ -13,9 +13,12 @@
//!
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_mir_dataflow::impls::{borrowed_locals, MaybeTransitiveLiveLocals};
use rustc_mir_dataflow::impls::{
borrowed_locals, LivenessTransferFunction, MaybeTransitiveLiveLocals,
};
use rustc_mir_dataflow::Analysis;
/// Performs the optimization on the body
@ -28,8 +31,33 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
.iterate_to_fixpoint()
.into_results_cursor(body);
// For blocks with a call terminator, if an argument copy can be turned into a move,
// record it as (block, argument index).
let mut call_operands_to_move = Vec::new();
let mut patch = Vec::new();
for (bb, bb_data) in traversal::preorder(body) {
if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind {
let loc = Location { block: bb, statement_index: bb_data.statements.len() };
// Position ourselves between the evaluation of `args` and the write to `destination`.
live.seek_to_block_end(bb);
let mut state = live.get().clone();
for (index, arg) in args.iter().enumerate().rev() {
if let Operand::Copy(place) = *arg
&& !place.is_indirect()
&& !borrowed.contains(place.local)
&& !state.contains(place.local)
{
call_operands_to_move.push((bb, index));
}
// Account that `arg` is read from, so we don't promote another argument to a move.
LivenessTransferFunction(&mut state).visit_operand(arg, loc);
}
}
for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
let loc = Location { block: bb, statement_index };
if let StatementKind::Assign(assign) = &statement.kind {
@ -64,7 +92,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
}
}
if patch.is_empty() {
if patch.is_empty() && call_operands_to_move.is_empty() {
return;
}
@ -72,6 +100,14 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
for Location { block, statement_index } in patch {
bbs[block].statements[statement_index].make_nop();
}
for (block, argument_index) in call_operands_to_move {
let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else {
bug!()
};
let arg = &mut args[argument_index];
let Operand::Copy(place) = *arg else { bug!() };
*arg = Operand::Move(place);
}
crate::simplify::simplify_locals(body, tcx)
}