rustc_mir_transform/
single_use_consts.rs1use rustc_index::IndexVec;
2use rustc_index::bit_set::DenseBitSet;
3use rustc_middle::bug;
4use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
5use rustc_middle::mir::*;
6use rustc_middle::ty::TyCtxt;
7
8use crate::PassPolicy;
9use crate::strip_debuginfo::drop_invalid_debuginfos;
10
11pub(super) struct SingleUseConsts;
26
27impl<'tcx> crate::MirPass<'tcx> for SingleUseConsts {
28 fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
29 PassPolicy::optimization(sess.mir_opt_level() > 0)
30 }
31
32 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
33 let mut finder = SingleUseConstsFinder {
34 ineligible_locals: DenseBitSet::new_empty(body.local_decls.len()),
35 locations: IndexVec::from_elem(LocationPair::new(), &body.local_decls),
36 locals_in_debug_info: DenseBitSet::new_empty(body.local_decls.len()),
37 };
38
39 finder.ineligible_locals.insert_range(..Local::arg(body.arg_count));
40
41 finder.visit_body(body);
42
43 for (local, locations) in finder.locations.iter_enumerated() {
44 if finder.ineligible_locals.contains(local) {
45 continue;
46 }
47
48 let Some(init_loc) = locations.init_loc else {
49 continue;
50 };
51
52 let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
54 let init_statement_kind = std::mem::replace(
55 &mut basic_blocks[init_loc.block].statements[init_loc.statement_index].kind,
56 StatementKind::Nop,
57 );
58 let StatementKind::Assign(place_and_rvalue) = init_statement_kind else {
59 bug!("No longer an assign?");
60 };
61 let (place, rvalue) = *place_and_rvalue;
62 assert_eq!(place.as_local(), Some(local));
63 let Rvalue::Use(operand, _) = rvalue else { bug!("No longer a use?") };
64
65 let mut replacer = LocalReplacer { tcx, local, operand: Some(operand) };
66
67 if finder.locals_in_debug_info.contains(local) {
68 for var_debug_info in &mut body.var_debug_info {
69 replacer.visit_var_debug_info(var_debug_info);
70 }
71 }
72
73 let Some(use_loc) = locations.use_loc else { continue };
74
75 let use_block = &mut basic_blocks[use_loc.block];
76 if let Some(use_statement) = use_block.statements.get_mut(use_loc.statement_index) {
77 replacer.visit_statement(use_statement, use_loc);
78 } else {
79 replacer.visit_terminator(use_block.terminator_mut(), use_loc);
80 }
81
82 if replacer.operand.is_some() {
83 bug!(
84 "operand wasn't used replacing local {local:?} with locations {locations:?} in body {body:#?}"
85 );
86 }
87 }
88
89 drop_invalid_debuginfos(body);
90 }
91}
92
93#[derive(Copy, Clone, Debug)]
94struct LocationPair {
95 init_loc: Option<Location>,
96 use_loc: Option<Location>,
97}
98
99impl LocationPair {
100 fn new() -> Self {
101 Self { init_loc: None, use_loc: None }
102 }
103}
104
105struct SingleUseConstsFinder {
106 ineligible_locals: DenseBitSet<Local>,
107 locations: IndexVec<Local, LocationPair>,
108 locals_in_debug_info: DenseBitSet<Local>,
109}
110
111impl<'tcx> Visitor<'tcx> for SingleUseConstsFinder {
112 fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
113 if let Some(local) = place.as_local()
114 && let Rvalue::Use(operand, _) = rvalue
115 && let Operand::Constant(_) = operand
116 {
117 let locations = &mut self.locations[local];
118 if locations.init_loc.is_some() {
119 self.ineligible_locals.insert(local);
120 } else {
121 locations.init_loc = Some(location);
122 }
123 } else {
124 self.super_assign(place, rvalue, location);
125 }
126 }
127
128 fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
129 if let Some(place) = operand.place()
130 && let Some(local) = place.as_local()
131 {
132 let locations = &mut self.locations[local];
133 if locations.use_loc.is_some() {
134 self.ineligible_locals.insert(local);
135 } else {
136 locations.use_loc = Some(location);
137 }
138 } else {
139 self.super_operand(operand, location);
140 }
141 }
142
143 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
144 match &statement.kind {
145 StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => {}
147 _ => self.super_statement(statement, location),
148 }
149 }
150
151 fn visit_var_debug_info(&mut self, var_debug_info: &VarDebugInfo<'tcx>) {
152 if let VarDebugInfoContents::Place(place) = &var_debug_info.value
153 && let Some(local) = place.as_local()
154 {
155 self.locals_in_debug_info.insert(local);
156 } else {
157 self.super_var_debug_info(var_debug_info);
158 }
159 }
160
161 fn visit_local(&mut self, local: Local, _context: PlaceContext, _location: Location) {
162 self.ineligible_locals.insert(local);
165 }
166}
167
168struct LocalReplacer<'tcx> {
169 tcx: TyCtxt<'tcx>,
170 local: Local,
171 operand: Option<Operand<'tcx>>,
172}
173
174impl<'tcx> MutVisitor<'tcx> for LocalReplacer<'tcx> {
175 fn tcx(&self) -> TyCtxt<'tcx> {
176 self.tcx
177 }
178
179 fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _location: Location) {
180 if let Operand::Copy(place) | Operand::Move(place) = operand
181 && let Some(local) = place.as_local()
182 && local == self.local
183 {
184 *operand = self.operand.take().unwrap_or_else(|| {
185 bug!("there was a second use of the operand");
186 });
187 }
188 }
189
190 fn visit_var_debug_info(&mut self, var_debug_info: &mut VarDebugInfo<'tcx>) {
191 if let VarDebugInfoContents::Place(place) = &var_debug_info.value
192 && let Some(local) = place.as_local()
193 && local == self.local
194 {
195 let const_op = *self
196 .operand
197 .as_ref()
198 .unwrap_or_else(|| {
199 bug!("the operand was already stolen");
200 })
201 .constant()
202 .unwrap();
203 var_debug_info.value = VarDebugInfoContents::Const(const_op);
204 }
205 }
206}