Skip to main content

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
8use crate::PassPolicy;
9use crate::strip_debuginfo::drop_invalid_debuginfos;
10
11/// Various parts of MIR building introduce temporaries that are commonly not needed.
12///
13/// Notably, `if CONST` and `match CONST` end up being used-once temporaries, which
14/// obfuscates the structure for other passes and codegen, which would like to always
15/// be able to just see the constant directly.
16///
17/// At higher optimization levels fancier passes like GVN will take care of this
18/// in a more general fashion, but this handles the easy cases so can run in debug.
19///
20/// This only removes constants with a single-use because re-evaluating constants
21/// isn't always an improvement, especially for large ones.
22///
23/// It also removes *never*-used constants, since it had all the information
24/// needed to do that too, including updating the debug info.
25pub(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            // We're only changing an operand, not the terminator kinds or successors
53            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            // Storage markers are irrelevant to this.
146            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        // If there's any path that gets here, rather than being understood elsewhere,
163        // then we'd better not do anything with this local.
164        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}