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