1 //! Trivial copy propagation pass.
3 //! This uses def-use analysis to remove values that have exactly one def and one use, which must
6 //! To give an example, we look for patterns that look like:
12 //! where `DEST` and `SRC` are both locals of some form. We replace that with:
18 //! The assignment `DEST = SRC` must be (a) the only mutation of `DEST` and (b) the only
19 //! (non-mutating) use of `SRC`. These restrictions are conservative and may be relaxed in the
22 use rustc::mir::{Constant, Local, LocalKind, Location, Place, Mir, Operand, Rvalue, StatementKind};
23 use rustc::mir::visit::MutVisitor;
24 use rustc::ty::TyCtxt;
25 use crate::transform::{MirPass, MirSource};
26 use crate::util::def_use::DefUseAnalysis;
28 pub struct CopyPropagation;
30 impl MirPass for CopyPropagation {
31 fn run_pass<'a, 'tcx>(&self,
32 tcx: TyCtxt<'a, 'tcx, 'tcx>,
33 _source: MirSource<'tcx>,
34 mir: &mut Mir<'tcx>) {
35 // We only run when the MIR optimization level is > 1.
36 // This avoids a slow pass, and messing up debug info.
37 if tcx.sess.opts.debugging_opts.mir_opt_level <= 1 {
41 let mut def_use_analysis = DefUseAnalysis::new(mir);
43 def_use_analysis.analyze(mir);
45 if eliminate_self_assignments(mir, &def_use_analysis) {
46 def_use_analysis.analyze(mir);
49 let mut changed = false;
50 for dest_local in mir.local_decls.indices() {
51 debug!("Considering destination local: {:?}", dest_local);
56 // The destination must have exactly one def.
57 let dest_use_info = def_use_analysis.local_info(dest_local);
58 let dest_def_count = dest_use_info.def_count_not_including_drop();
59 if dest_def_count == 0 {
60 debug!(" Can't copy-propagate local: dest {:?} undefined",
64 if dest_def_count > 1 {
65 debug!(" Can't copy-propagate local: dest {:?} defined {} times",
67 dest_use_info.def_count());
70 if dest_use_info.use_count() == 0 {
71 debug!(" Can't copy-propagate local: dest {:?} unused",
75 // Conservatively gives up if the dest is an argument,
76 // because there may be uses of the original argument value.
77 if mir.local_kind(dest_local) == LocalKind::Arg {
78 debug!(" Can't copy-propagate local: dest {:?} (argument)",
82 let dest_place_def = dest_use_info.defs_not_including_drop().next().unwrap();
83 location = dest_place_def.location;
85 let basic_block = &mir[location.block];
86 let statement_index = location.statement_index;
87 let statement = match basic_block.statements.get(statement_index) {
88 Some(statement) => statement,
90 debug!(" Can't copy-propagate local: used in terminator");
95 // That use of the source must be an assignment.
96 match statement.kind {
97 StatementKind::Assign(Place::Local(local), box Rvalue::Use(ref operand)) if
98 local == dest_local => {
99 let maybe_action = match *operand {
100 Operand::Copy(ref src_place) |
101 Operand::Move(ref src_place) => {
102 Action::local_copy(&mir, &def_use_analysis, src_place)
104 Operand::Constant(ref src_constant) => {
105 Action::constant(src_constant)
109 Some(this_action) => action = this_action,
114 debug!(" Can't copy-propagate local: source use is not an \
121 changed = action.perform(mir, &def_use_analysis, dest_local, location) || changed;
122 // FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
123 // regenerating the chains.
133 fn eliminate_self_assignments<'tcx>(
135 def_use_analysis: &DefUseAnalysis<'tcx>,
137 let mut changed = false;
139 for dest_local in mir.local_decls.indices() {
140 let dest_use_info = def_use_analysis.local_info(dest_local);
142 for def in dest_use_info.defs_not_including_drop() {
143 let location = def.location;
144 if let Some(stmt) = mir[location.block].statements.get(location.statement_index) {
146 StatementKind::Assign(
148 box Rvalue::Use(Operand::Copy(Place::Local(src_local))),
150 StatementKind::Assign(
152 box Rvalue::Use(Operand::Move(Place::Local(src_local))),
153 ) if local == dest_local && dest_local == src_local => {}
161 debug!("Deleting a self-assignment for {:?}", dest_local);
162 mir.make_statement_nop(location);
171 PropagateLocalCopy(Local),
172 PropagateConstant(Constant<'tcx>),
175 impl<'tcx> Action<'tcx> {
176 fn local_copy(mir: &Mir<'tcx>, def_use_analysis: &DefUseAnalysis<'_>, src_place: &Place<'tcx>)
177 -> Option<Action<'tcx>> {
178 // The source must be a local.
179 let src_local = if let Place::Local(local) = *src_place {
182 debug!(" Can't copy-propagate local: source is not a local");
186 // We're trying to copy propagate a local.
187 // There must be exactly one use of the source used in a statement (not in a terminator).
188 let src_use_info = def_use_analysis.local_info(src_local);
189 let src_use_count = src_use_info.use_count();
190 if src_use_count == 0 {
191 debug!(" Can't copy-propagate local: no uses");
194 if src_use_count != 1 {
195 debug!(" Can't copy-propagate local: {} uses", src_use_info.use_count());
199 // Verify that the source doesn't change in between. This is done conservatively for now,
200 // by ensuring that the source has exactly one mutation. The goal is to prevent things
207 // From being misoptimized into:
211 let src_def_count = src_use_info.def_count_not_including_drop();
212 // allow function arguments to be propagated
213 let is_arg = mir.local_kind(src_local) == LocalKind::Arg;
214 if (is_arg && src_def_count != 0) || (!is_arg && src_def_count != 1) {
216 " Can't copy-propagate local: {} defs of src{}",
218 if is_arg { " (argument)" } else { "" },
223 Some(Action::PropagateLocalCopy(src_local))
226 fn constant(src_constant: &Constant<'tcx>) -> Option<Action<'tcx>> {
227 Some(Action::PropagateConstant((*src_constant).clone()))
232 def_use_analysis: &DefUseAnalysis<'tcx>,
237 Action::PropagateLocalCopy(src_local) => {
238 // Eliminate the destination and the assignment.
240 // First, remove all markers.
242 // FIXME(pcwalton): Don't do this. Merge live ranges instead.
243 debug!(" Replacing all uses of {:?} with {:?} (local)",
246 for place_use in &def_use_analysis.local_info(dest_local).defs_and_uses {
247 if place_use.context.is_storage_marker() {
248 mir.make_statement_nop(place_use.location)
251 for place_use in &def_use_analysis.local_info(src_local).defs_and_uses {
252 if place_use.context.is_storage_marker() {
253 mir.make_statement_nop(place_use.location)
257 // Replace all uses of the destination local with the source local.
258 def_use_analysis.replace_all_defs_and_uses_with(dest_local, mir, src_local);
260 // Finally, zap the now-useless assignment instruction.
261 debug!(" Deleting assignment");
262 mir.make_statement_nop(location);
266 Action::PropagateConstant(src_constant) => {
267 // First, remove all markers.
269 // FIXME(pcwalton): Don't do this. Merge live ranges instead.
270 debug!(" Replacing all uses of {:?} with {:?} (constant)",
273 let dest_local_info = def_use_analysis.local_info(dest_local);
274 for place_use in &dest_local_info.defs_and_uses {
275 if place_use.context.is_storage_marker() {
276 mir.make_statement_nop(place_use.location)
280 // Replace all uses of the destination local with the constant.
281 let mut visitor = ConstantPropagationVisitor::new(dest_local,
283 for dest_place_use in &dest_local_info.defs_and_uses {
284 visitor.visit_location(mir, dest_place_use.location)
287 // Zap the assignment instruction if we eliminated all the uses. We won't have been
288 // able to do that if the destination was used in a projection, because projections
289 // must have places on their LHS.
290 let use_count = dest_local_info.use_count();
291 if visitor.uses_replaced == use_count {
292 debug!(" {} of {} use(s) replaced; deleting assignment",
293 visitor.uses_replaced,
295 mir.make_statement_nop(location);
297 } else if visitor.uses_replaced == 0 {
298 debug!(" No uses replaced; not deleting assignment");
301 debug!(" {} of {} use(s) replaced; not deleting assignment",
302 visitor.uses_replaced,
311 struct ConstantPropagationVisitor<'tcx> {
313 constant: Constant<'tcx>,
314 uses_replaced: usize,
317 impl<'tcx> ConstantPropagationVisitor<'tcx> {
318 fn new(dest_local: Local, constant: Constant<'tcx>)
319 -> ConstantPropagationVisitor<'tcx> {
320 ConstantPropagationVisitor {
328 impl<'tcx> MutVisitor<'tcx> for ConstantPropagationVisitor<'tcx> {
329 fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
330 self.super_operand(operand, location);
333 Operand::Copy(Place::Local(local)) |
334 Operand::Move(Place::Local(local)) if local == self.dest_local => {}
338 *operand = Operand::Constant(box self.constant.clone());
339 self.uses_replaced += 1