rustc_mir_transform/
dead_store_elimination.rs1use rustc_middle::mir::visit::Visitor;
16use rustc_middle::mir::*;
17use rustc_middle::ty::TyCtxt;
18use rustc_mir_dataflow::Analysis;
19use rustc_mir_dataflow::debuginfo::debuginfo_locals;
20use rustc_mir_dataflow::impls::{
21 LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals,
22};
23use rustc_span::bug;
24
25use crate::PassPolicy;
26use crate::simplify::UsedInStmtLocals;
27use crate::util::most_packed_projection;
28
29fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
35 let borrowed_locals = borrowed_locals(body);
36
37 let debuginfo_locals = debuginfo_locals(body);
40
41 let mut live = MaybeTransitiveLiveLocals::new(&borrowed_locals, &debuginfo_locals)
42 .iterate_to_fixpoint(tcx, body, None)
43 .into_results_cursor(body);
44
45 let mut call_operands_to_move = Vec::new();
48 let mut patch = Vec::new();
49
50 for (bb, bb_data) in traversal::preorder(body) {
51 if let TerminatorKind::Call { ref args, ref destination, .. } = bb_data.terminator().kind {
52 let loc = Location { block: bb, statement_index: bb_data.statements.len() };
53
54 live.seek_to_block_end(bb);
56 let mut state = live.get().clone();
57
58 LivenessTransferFunction(&mut state).visit_place(
61 destination,
62 visit::PlaceContext::MutatingUse(visit::MutatingUseContext::Call),
63 loc,
64 );
65
66 if !destination.is_indirect() {
70 state.insert(destination.local);
71 }
72
73 for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() {
74 if let Operand::Copy(place) = *arg
75 && !place.is_indirect()
76 && !borrowed_locals.contains(place.local)
79 && !state.contains(place.local)
80 && most_packed_projection(tcx, body, place).is_none()
85 {
86 call_operands_to_move.push((bb, index));
87 }
88
89 LivenessTransferFunction(&mut state).visit_operand(arg, loc);
91 }
92 }
93
94 for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
95 if let Some(destination) = MaybeTransitiveLiveLocals::can_be_removed_if_dead(
96 &statement.kind,
97 &borrowed_locals,
98 &debuginfo_locals,
99 ) {
100 let loc = Location { block: bb, statement_index };
101 live.seek_before_primary_effect(loc);
102 if !live.get().contains(destination.local) {
103 let drop_debuginfo = !debuginfo_locals.contains(destination.local);
104 assert!(
107 drop_debuginfo || statement.kind.as_debuginfo().is_some(),
108 "don't know how to retain the debug information for {:?}",
109 statement.kind
110 );
111 patch.push((loc, drop_debuginfo));
112 }
113 }
114 }
115 }
116
117 if patch.is_empty() && call_operands_to_move.is_empty() {
118 return false;
119 }
120 let eliminated = !patch.is_empty();
121
122 let bbs = body.basic_blocks.as_mut_preserves_cfg();
123 for (Location { block, statement_index }, drop_debuginfo) in patch {
124 bbs[block].statements[statement_index].make_nop(drop_debuginfo);
125 }
126 for (block, argument_index) in call_operands_to_move {
127 let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else {
128 bug!()
129 };
130 let arg = &mut args[argument_index].node;
131 let Operand::Copy(place) = *arg else { bug!() };
132 *arg = Operand::Move(place);
133 }
134
135 eliminated
136}
137
138pub(super) enum DeadStoreElimination {
139 Initial,
140 Final,
141}
142
143impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination {
144 fn name(&self) -> &'static str {
145 match self {
146 DeadStoreElimination::Initial => "DeadStoreElimination-initial",
147 DeadStoreElimination::Final => "DeadStoreElimination-final",
148 }
149 }
150
151 fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
152 PassPolicy::optional(ctx.mir_opt_level() >= 2)
153 }
154
155 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
156 if eliminate(tcx, body) {
157 UsedInStmtLocals::new(body).remove_unused_storage_annotations(body);
158 for data in body.basic_blocks.as_mut_preserves_cfg() {
159 data.strip_nops();
160 }
161 }
162 }
163}