1//! This module implements a dead store elimination (DSE) routine.
2//!
3//! This transformation was written specifically for the needs of dest prop. Although it is
4//! perfectly sound to use it in any context that might need it, its behavior should not be changed
5//! without analyzing the interaction this will have with dest prop. Specifically, in addition to
6//! the soundness of this pass in general, dest prop needs it to satisfy two additional conditions:
7//!
8//! 1. It's idempotent, meaning that running this pass a second time immediately after running it a
9//! first time will not cause any further changes.
10//! 2. This idempotence persists across dest prop's main transform, in other words inserting any
11//! number of iterations of dest prop between the first and second application of this transform
12//! will still not cause any further changes.
13//!
1415use rustc_middle::bug;
16use rustc_middle::mir::visit::Visitor;
17use rustc_middle::mir::*;
18use rustc_middle::ty::TyCtxt;
19use rustc_mir_dataflow::Analysis;
20use rustc_mir_dataflow::debuginfo::debuginfo_locals;
21use rustc_mir_dataflow::impls::{
22LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals,
23};
2425use crate::util::is_within_packed;
2627/// Performs the optimization on the body
28///
29/// The `borrowed` set must be a `DenseBitSet` of all the locals that are ever borrowed in this
30/// body. It can be generated via the [`borrowed_locals`] function.
31fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
32let borrowed_locals = borrowed_locals(body);
3334// If the user requests complete debuginfo, mark the locals that appear in it as live, so
35 // we don't remove assignments to them.
36let mut always_live = debuginfo_locals(body);
37always_live.union(&borrowed_locals);
3839let mut live = MaybeTransitiveLiveLocals::new(&always_live)
40 .iterate_to_fixpoint(tcx, body, None)
41 .into_results_cursor(body);
4243// For blocks with a call terminator, if an argument copy can be turned into a move,
44 // record it as (block, argument index).
45let mut call_operands_to_move = Vec::new();
46let mut patch = Vec::new();
4748for (bb, bb_data) in traversal::preorder(body) {
49if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind {
50let loc = Location { block: bb, statement_index: bb_data.statements.len() };
5152// Position ourselves between the evaluation of `args` and the write to `destination`.
53live.seek_to_block_end(bb);
54let mut state = live.get().clone();
5556for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() {
57if let Operand::Copy(place) = *arg
58 && !place.is_indirect()
59// Do not skip the transformation if the local is in debuginfo, as we do
60 // not really lose any information for this purpose.
61&& !borrowed_locals.contains(place.local)
62 && !state.contains(place.local)
63// If `place` is a projection of a disaligned field in a packed ADT,
64 // the move may be codegened as a pointer to that field.
65 // Using that disaligned pointer may trigger UB in the callee,
66 // so do nothing.
67&& is_within_packed(tcx, body, place).is_none()
68 {
69 call_operands_to_move.push((bb, index));
70 }
7172// Account that `arg` is read from, so we don't promote another argument to a move.
73LivenessTransferFunction(&mut state).visit_operand(arg, loc);
74 }
75 }
7677for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
78let loc = Location { block: bb, statement_index };
79if let StatementKind::Assign(assign) = &statement.kind {
80if !assign.1.is_safe_to_remove() {
81continue;
82 }
83 }
84match &statement.kind {
85 StatementKind::Assign(box (place, _))
86 | StatementKind::SetDiscriminant { place: box place, .. }
87 | StatementKind::Deinit(box place) => {
88if !place.is_indirect() && !always_live.contains(place.local) {
89 live.seek_before_primary_effect(loc);
90if !live.get().contains(place.local) {
91 patch.push(loc);
92 }
93 }
94 }
95 StatementKind::Retag(_, _)
96 | StatementKind::StorageLive(_)
97 | StatementKind::StorageDead(_)
98 | StatementKind::Coverage(_)
99 | StatementKind::Intrinsic(_)
100 | StatementKind::ConstEvalCounter
101 | StatementKind::PlaceMention(_)
102 | StatementKind::BackwardIncompatibleDropHint { .. }
103 | StatementKind::Nop => {}
104105 StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
106bug!("{:?} not found in this MIR phase!", statement.kind)
107 }
108 }
109 }
110 }
111112if patch.is_empty() && call_operands_to_move.is_empty() {
113return;
114 }
115116let bbs = body.basic_blocks.as_mut_preserves_cfg();
117for Location { block, statement_index } in patch {
118 bbs[block].statements[statement_index].make_nop();
119 }
120for (block, argument_index) in call_operands_to_move {
121let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else {
122bug!()
123 };
124let arg = &mut args[argument_index].node;
125let Operand::Copy(place) = *arg else { bug!() };
126*arg = Operand::Move(place);
127 }
128}
129130pub(super) enum DeadStoreElimination {
131 Initial,
132 Final,
133}
134135impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination {
136fn name(&self) -> &'static str {
137match self{
138DeadStoreElimination::Initial => "DeadStoreElimination-initial",
139DeadStoreElimination::Final => "DeadStoreElimination-final",
140 }
141 }
142143fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
144sess.mir_opt_level() >= 2
145}
146147fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
148eliminate(tcx, body);
149 }
150151fn is_required(&self) -> bool {
152false
153}
154}