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