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