rustc_mir_transform/
dead_store_elimination.rs

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//!
14
15use 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::{
22    LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals,
23};
24
25use crate::util::is_within_packed;
26
27/// 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>) {
32    let borrowed_locals = borrowed_locals(body);
33
34    // If the user requests complete debuginfo, mark the locals that appear in it as live, so
35    // we don't remove assignments to them.
36    let mut always_live = debuginfo_locals(body);
37    always_live.union(&borrowed_locals);
38
39    let mut live = MaybeTransitiveLiveLocals::new(&always_live)
40        .iterate_to_fixpoint(tcx, body, None)
41        .into_results_cursor(body);
42
43    // For blocks with a call terminator, if an argument copy can be turned into a move,
44    // record it as (block, argument index).
45    let mut call_operands_to_move = Vec::new();
46    let mut patch = Vec::new();
47
48    for (bb, bb_data) in traversal::preorder(body) {
49        if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind {
50            let loc = Location { block: bb, statement_index: bb_data.statements.len() };
51
52            // Position ourselves between the evaluation of `args` and the write to `destination`.
53            live.seek_to_block_end(bb);
54            let mut state = live.get().clone();
55
56            for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() {
57                if 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                }
71
72                // Account that `arg` is read from, so we don't promote another argument to a move.
73                LivenessTransferFunction(&mut state).visit_operand(arg, loc);
74            }
75        }
76
77        for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
78            let loc = Location { block: bb, statement_index };
79            if let StatementKind::Assign(assign) = &statement.kind {
80                if !assign.1.is_safe_to_remove() {
81                    continue;
82                }
83            }
84            match &statement.kind {
85                StatementKind::Assign(box (place, _))
86                | StatementKind::SetDiscriminant { place: box place, .. }
87                | StatementKind::Deinit(box place) => {
88                    if !place.is_indirect() && !always_live.contains(place.local) {
89                        live.seek_before_primary_effect(loc);
90                        if !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 => {}
104
105                StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
106                    bug!("{:?} not found in this MIR phase!", statement.kind)
107                }
108            }
109        }
110    }
111
112    if patch.is_empty() && call_operands_to_move.is_empty() {
113        return;
114    }
115
116    let bbs = body.basic_blocks.as_mut_preserves_cfg();
117    for Location { block, statement_index } in patch {
118        bbs[block].statements[statement_index].make_nop();
119    }
120    for (block, argument_index) in call_operands_to_move {
121        let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else {
122            bug!()
123        };
124        let arg = &mut args[argument_index].node;
125        let Operand::Copy(place) = *arg else { bug!() };
126        *arg = Operand::Move(place);
127    }
128}
129
130pub(super) enum DeadStoreElimination {
131    Initial,
132    Final,
133}
134
135impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination {
136    fn name(&self) -> &'static str {
137        match self {
138            DeadStoreElimination::Initial => "DeadStoreElimination-initial",
139            DeadStoreElimination::Final => "DeadStoreElimination-final",
140        }
141    }
142
143    fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
144        sess.mir_opt_level() >= 2
145    }
146
147    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
148        eliminate(tcx, body);
149    }
150
151    fn is_required(&self) -> bool {
152        false
153    }
154}