blob: 3d74ef7e3278e277c23b034c19284e0aeeabebbf [file] [log] [blame]
//! This module implements a dead store elimination (DSE) routine.
//!
//! This transformation was written specifically for the needs of dest prop. Although it is
//! perfectly sound to use it in any context that might need it, its behavior should not be changed
//! without analyzing the interaction this will have with dest prop. Specifically, in addition to
//! the soundness of this pass in general, dest prop needs it to satisfy two additional conditions:
//!
//! 1. It's idempotent, meaning that running this pass a second time immediately after running it a
//! first time will not cause any further changes.
//! 2. This idempotence persists across dest prop's main transform, in other words inserting any
//! number of iterations of dest prop between the first and second application of this transform
//! will still not cause any further changes.
//!
use crate::util::is_within_packed;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_mir_dataflow::debuginfo::debuginfo_locals;
use rustc_mir_dataflow::impls::{
borrowed_locals, LivenessTransferFunction, MaybeTransitiveLiveLocals,
};
use rustc_mir_dataflow::Analysis;
/// Performs the optimization on the body
///
/// The `borrowed` set must be a `BitSet` of all the locals that are ever borrowed in this body. It
/// can be generated via the [`borrowed_locals`] function.
pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let borrowed_locals = borrowed_locals(body);
// If the user requests complete debuginfo, mark the locals that appear in it as live, so
// we don't remove assignements to them.
let mut always_live = debuginfo_locals(body);
always_live.union(&borrowed_locals);
let mut live = MaybeTransitiveLiveLocals::new(&always_live)
.into_engine(tcx, body)
.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()
// Do not skip the transformation if the local is in debuginfo, as we do
// not really lose any information for this purpose.
&& !borrowed_locals.contains(place.local)
&& !state.contains(place.local)
// If `place` is a projection of a disaligned field in a packed ADT,
// the move may be codegened as a pointer to that field.
// Using that disaligned pointer may trigger UB in the callee,
// so do nothing.
&& is_within_packed(tcx, body, place).is_none()
{
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 {
if !assign.1.is_safe_to_remove() {
continue;
}
}
match &statement.kind {
StatementKind::Assign(box (place, _))
| StatementKind::SetDiscriminant { place: box place, .. }
| StatementKind::Deinit(box place) => {
if !place.is_indirect() && !always_live.contains(place.local) {
live.seek_before_primary_effect(loc);
if !live.get().contains(place.local) {
patch.push(loc);
}
}
}
StatementKind::Retag(_, _)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Coverage(_)
| StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(_)
| StatementKind::Nop => (),
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
bug!("{:?} not found in this MIR phase!", &statement.kind)
}
}
}
}
if patch.is_empty() && call_operands_to_move.is_empty() {
return;
}
let bbs = body.basic_blocks.as_mut_preserves_cfg();
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)
}
pub struct DeadStoreElimination;
impl<'tcx> MirPass<'tcx> for DeadStoreElimination {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 2
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
eliminate(tcx, body);
}
}