Skip to main content

rustc_mir_transform/
simplify.rs

1//! A number of passes which remove various redundancies in the CFG.
2//!
3//! The `SimplifyCfg` pass gets rid of unnecessary blocks in the CFG, whereas the `SimplifyLocals`
4//! gets rid of all the unnecessary local variable declarations.
5//!
6//! The `SimplifyLocals` pass is kinda expensive and therefore not very suitable to be run often.
7//! Most of the passes should not care or be impacted in meaningful ways due to extra locals
8//! either, so running the pass once, right before codegen, should suffice.
9//!
10//! On the other side of the spectrum, the `SimplifyCfg` pass is considerably cheap to run, thus
11//! one should run it after every pass which may modify CFG in significant ways. This pass must
12//! also be run before any analysis passes because it removes dead blocks, and some of these can be
13//! ill-typed.
14//!
15//! The cause of this typing issue is typeck allowing most blocks whose end is not reachable have
16//! an arbitrary return type, rather than having the usual () return type (as a note, typeck's
17//! notion of reachability is in fact slightly weaker than MIR CFG reachability - see #31617). A
18//! standard example of the situation is:
19//!
20//! ```rust
21//!   fn example() {
22//!       let _a: char = { return; };
23//!   }
24//! ```
25//!
26//! Here the block (`{ return; }`) has the return type `char`, rather than `()`, but the MIR we
27//! naively generate still contains the `_a = ()` write in the unreachable block "after" the
28//! return.
29//!
30//! **WARNING**: This is one of the few optimizations that runs on built and analysis MIR, and
31//! so its effects may affect the type-checking, borrow-checking, and other analysis of MIR.
32//! We must be extremely careful to only apply optimizations that preserve UB and all
33//! non-determinism, since changes here can affect which programs compile in an insta-stable way.
34//! The normal logic that a program with UB can be changed to do anything does not apply to
35//! pre-"runtime" MIR!
36
37use itertools::Itertools as _;
38use rustc_index::bit_set::DenseBitSet;
39use rustc_index::{Idx, IndexSlice, IndexVec};
40use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
41use rustc_middle::mir::*;
42use rustc_middle::ty::TyCtxt;
43use rustc_mir_dataflow::debuginfo::debuginfo_locals;
44use rustc_span::DUMMY_SP;
45use smallvec::SmallVec;
46use tracing::{debug, trace};
47
48use crate::PassPolicy;
49
50pub(super) enum SimplifyCfg {
51    Initial,
52    PromoteConsts,
53    RemoveFalseEdges,
54    /// Runs at the beginning of "analysis to runtime" lowering, *before* drop elaboration.
55    PostAnalysis,
56    /// Runs at the end of "analysis to runtime" lowering, *after* drop elaboration.
57    /// This is before the main optimization passes on runtime MIR kick in.
58    PreOptimizations,
59    Final,
60    MakeShim,
61    AfterUnreachableEnumBranching,
62}
63
64impl SimplifyCfg {
65    fn name(&self) -> &'static str {
66        match self {
67            SimplifyCfg::Initial => "SimplifyCfg-initial",
68            SimplifyCfg::PromoteConsts => "SimplifyCfg-promote-consts",
69            SimplifyCfg::RemoveFalseEdges => "SimplifyCfg-remove-false-edges",
70            SimplifyCfg::PostAnalysis => "SimplifyCfg-post-analysis",
71            SimplifyCfg::PreOptimizations => "SimplifyCfg-pre-optimizations",
72            SimplifyCfg::Final => "SimplifyCfg-final",
73            SimplifyCfg::MakeShim => "SimplifyCfg-make_shim",
74            SimplifyCfg::AfterUnreachableEnumBranching => {
75                "SimplifyCfg-after-unreachable-enum-branching"
76            }
77        }
78    }
79}
80
81pub(super) fn simplify_cfg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
82    if CfgSimplifier::new(tcx, body).simplify() {
83        // `simplify` returns that it changed something. We must invalidate the CFG caches as they
84        // are not consistent with the modified CFG any more.
85        body.basic_blocks.invalidate_cfg_cache();
86    }
87    remove_dead_blocks(body);
88
89    // FIXME: Should probably be moved into some kind of pass manager
90    body.basic_blocks.as_mut_preserves_cfg().shrink_to_fit();
91}
92
93impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
94    fn name(&self) -> &'static str {
95        self.name()
96    }
97
98    fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
99        PassPolicy::optimization(true)
100    }
101
102    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
103        debug!("SimplifyCfg({:?}) - simplifying {:?}", self.name(), body.source);
104        simplify_cfg(tcx, body);
105    }
106}
107
108struct CfgSimplifier<'a, 'tcx> {
109    preserve_switch_reads: bool,
110    basic_blocks: &'a mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
111    pred_count: IndexVec<BasicBlock, u32>,
112}
113
114impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
115    fn new(tcx: TyCtxt<'tcx>, body: &'a mut Body<'tcx>) -> Self {
116        let mut pred_count = IndexVec::from_elem(0u32, &body.basic_blocks);
117
118        // we can't use mir.predecessors() here because that counts
119        // dead blocks, which we don't want to.
120        pred_count[START_BLOCK] = 1;
121
122        for (_, data) in traversal::preorder(body) {
123            if let Some(ref term) = data.terminator {
124                for tgt in term.successors() {
125                    pred_count[tgt] += 1;
126                }
127            }
128        }
129
130        // Preserve `SwitchInt` reads on built and analysis MIR, or if `-Zmir-preserve-ub`.
131        let preserve_switch_reads = matches!(body.phase, MirPhase::Built | MirPhase::Analysis(_))
132            || tcx.sess.opts.unstable_opts.mir_preserve_ub;
133        // Do not clear caches yet. The caller to `simplify` will do it if anything changed.
134        let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
135
136        CfgSimplifier { preserve_switch_reads, basic_blocks, pred_count }
137    }
138
139    /// Returns whether we actually simplified anything. In that case, the caller *must* invalidate
140    /// the CFG caches of the MIR body.
141    #[must_use]
142    fn simplify(mut self) -> bool {
143        self.strip_nops();
144
145        // Vec of the blocks that should be merged. We store the indices here, instead of the
146        // statements itself to avoid moving the (relatively) large statements twice.
147        // We do not push the statements directly into the target block (`bb`) as that is slower
148        // due to additional reallocations
149        let mut merged_blocks: Vec<BasicBlock> = Vec::new();
150        let mut outer_changed = false;
151        loop {
152            let mut changed = false;
153
154            for bb in self.basic_blocks.indices() {
155                if self.pred_count[bb] == 0 {
156                    continue;
157                }
158
159                debug!("simplifying {:?}", bb);
160
161                let mut terminator =
162                    self.basic_blocks[bb].terminator.take().expect("invalid terminator state");
163
164                terminator.successors_mut(|successor| {
165                    self.collapse_goto_chain(successor, &mut changed);
166                });
167
168                let mut inner_changed = true;
169                merged_blocks.clear();
170                while inner_changed {
171                    inner_changed = false;
172                    inner_changed |= self.simplify_branch(&mut terminator);
173                    inner_changed |= self.merge_successor(&mut merged_blocks, &mut terminator);
174                    changed |= inner_changed;
175                }
176
177                let statements_to_merge =
178                    merged_blocks.iter().map(|&i| self.basic_blocks[i].statements.len()).sum();
179
180                if statements_to_merge > 0 {
181                    let mut statements = std::mem::take(&mut self.basic_blocks[bb].statements);
182                    statements.reserve(statements_to_merge);
183                    let mut parent_bb_last_debuginfos =
184                        std::mem::take(&mut self.basic_blocks[bb].after_last_stmt_debuginfos);
185                    for &from in &merged_blocks {
186                        if let Some(stmt) = self.basic_blocks[from].statements.first_mut() {
187                            stmt.debuginfos.prepend(&mut parent_bb_last_debuginfos);
188                        }
189                        statements.append(&mut self.basic_blocks[from].statements);
190                        parent_bb_last_debuginfos =
191                            std::mem::take(&mut self.basic_blocks[from].after_last_stmt_debuginfos);
192                    }
193                    self.basic_blocks[bb].statements = statements;
194                    self.basic_blocks[bb].after_last_stmt_debuginfos = parent_bb_last_debuginfos;
195                }
196
197                self.basic_blocks[bb].terminator = Some(terminator);
198            }
199
200            if !changed {
201                break;
202            }
203
204            outer_changed = true;
205        }
206
207        outer_changed
208    }
209
210    /// This function will return `None` if
211    /// * the block has statements
212    /// * the block has a terminator other than `goto`
213    /// * the block has no terminator (meaning some other part of the current optimization stole it)
214    fn take_terminator_if_simple_goto(&mut self, bb: BasicBlock) -> Option<Terminator<'tcx>> {
215        match self.basic_blocks[bb] {
216            BasicBlockData {
217                ref statements,
218                terminator:
219                    ref mut terminator @ Some(Terminator { kind: TerminatorKind::Goto { .. }, .. }),
220                ..
221            } if statements.is_empty() => terminator.take(),
222            // if `terminator` is None, this means we are in a loop. In that
223            // case, let all the loop collapse to its entry.
224            _ => None,
225        }
226    }
227
228    /// Collapse a goto chain starting from `start`
229    fn collapse_goto_chain(&mut self, start: &mut BasicBlock, changed: &mut bool) {
230        // Using `SmallVec` here, because in some logs on libcore oli-obk saw many single-element
231        // goto chains. We should probably benchmark different sizes.
232        let mut terminators: SmallVec<[_; 1]> = Default::default();
233        let mut current = *start;
234        // If each successor has only one predecessor, it's a trivial goto chain.
235        // We can move all debuginfos to the last basic block.
236        let mut trivial_goto_chain = true;
237        while let Some(terminator) = self.take_terminator_if_simple_goto(current) {
238            let Terminator { kind: TerminatorKind::Goto { target }, .. } = terminator else {
239                unreachable!();
240            };
241            trivial_goto_chain &= self.pred_count[target] == 1;
242            terminators.push((current, terminator));
243            current = target;
244        }
245        let last = current;
246        *changed |= *start != last;
247        *start = last;
248        while let Some((current, mut terminator)) = terminators.pop() {
249            let Terminator { kind: TerminatorKind::Goto { ref mut target }, .. } = terminator
250            else {
251                unreachable!();
252            };
253            if trivial_goto_chain {
254                let mut pred_debuginfos =
255                    std::mem::take(&mut self.basic_blocks[current].after_last_stmt_debuginfos);
256                let debuginfos = if let Some(stmt) = self.basic_blocks[last].statements.first_mut()
257                {
258                    &mut stmt.debuginfos
259                } else {
260                    &mut self.basic_blocks[last].after_last_stmt_debuginfos
261                };
262                debuginfos.prepend(&mut pred_debuginfos);
263            }
264            *changed |= *target != last;
265            *target = last;
266            debug!("collapsing goto chain from {:?} to {:?}", current, target);
267
268            if self.pred_count[current] == 1 {
269                // This is the last reference to current, so the pred-count to
270                // to target is moved into the current block.
271                self.pred_count[current] = 0;
272            } else {
273                self.pred_count[*target] += 1;
274                self.pred_count[current] -= 1;
275            }
276            self.basic_blocks[current].terminator = Some(terminator);
277        }
278    }
279
280    // merge a block with 1 `goto` predecessor to its parent
281    fn merge_successor(
282        &mut self,
283        merged_blocks: &mut Vec<BasicBlock>,
284        terminator: &mut Terminator<'tcx>,
285    ) -> bool {
286        let target = match terminator.kind {
287            TerminatorKind::Goto { target } if self.pred_count[target] == 1 => target,
288            _ => return false,
289        };
290
291        debug!("merging block {:?} into {:?}", target, terminator);
292        *terminator = match self.basic_blocks[target].terminator.take() {
293            Some(terminator) => terminator,
294            None => {
295                // unreachable loop - this should not be possible, as we
296                // don't strand blocks, but handle it correctly.
297                return false;
298            }
299        };
300
301        merged_blocks.push(target);
302        self.pred_count[target] = 0;
303
304        true
305    }
306
307    // turn a branch with all successors identical to a goto
308    fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
309        // Removing a `SwitchInt` terminator may remove reads that result in UB,
310        // so we must not apply this optimization before borrowck or when
311        // `-Zmir-preserve-ub` is set.
312        if self.preserve_switch_reads {
313            return false;
314        }
315
316        let TerminatorKind::SwitchInt { .. } = terminator.kind else {
317            return false;
318        };
319
320        let Ok(first_succ) = terminator.successors().all_equal_value() else {
321            return false;
322        };
323
324        let count = terminator.successors().count();
325        self.pred_count[first_succ] -= (count - 1) as u32;
326
327        debug!("simplifying branch {:?}", terminator);
328        terminator.kind = TerminatorKind::Goto { target: first_succ };
329        true
330    }
331
332    fn strip_nops(&mut self) {
333        for blk in self.basic_blocks.iter_mut() {
334            blk.strip_nops();
335        }
336    }
337}
338
339pub(super) fn simplify_duplicate_switch_targets(terminator: &mut Terminator<'_>) {
340    if let TerminatorKind::SwitchInt { targets, .. } = &mut terminator.kind {
341        let otherwise = targets.otherwise();
342        if targets.iter().any(|t| t.1 == otherwise) {
343            *targets = SwitchTargets::new(
344                targets.iter().filter(|t| t.1 != otherwise),
345                targets.otherwise(),
346            );
347        }
348    }
349}
350
351pub(super) fn remove_dead_blocks(body: &mut Body<'_>) {
352    let should_deduplicate_unreachable = |bbdata: &BasicBlockData<'_>| {
353        // CfgSimplifier::simplify leaves behind some unreachable basic blocks without a
354        // terminator. Those blocks will be deleted by remove_dead_blocks, but we run just
355        // before then so we need to handle missing terminators.
356        // We also need to prevent confusing cleanup and non-cleanup blocks. In practice we
357        // don't emit empty unreachable cleanup blocks, so this simple check suffices.
358        bbdata.terminator.is_some() && bbdata.is_empty_unreachable() && !bbdata.is_cleanup
359    };
360
361    let reachable = traversal::reachable_as_bitset(body);
362    let empty_unreachable_blocks = body
363        .basic_blocks
364        .iter_enumerated()
365        .filter(|(bb, bbdata)| should_deduplicate_unreachable(bbdata) && reachable.contains(*bb))
366        .count();
367
368    let num_blocks = body.basic_blocks.len();
369    if num_blocks == reachable.count() && empty_unreachable_blocks <= 1 {
370        return;
371    }
372
373    let basic_blocks = body.basic_blocks.as_mut();
374
375    let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
376    let mut orig_index = 0;
377    let mut used_index = 0;
378    let mut kept_unreachable = None;
379    let mut deduplicated_unreachable = false;
380    basic_blocks.raw.retain(|bbdata| {
381        let orig_bb = BasicBlock::new(orig_index);
382        if !reachable.contains(orig_bb) {
383            orig_index += 1;
384            return false;
385        }
386
387        let used_bb = BasicBlock::new(used_index);
388        if should_deduplicate_unreachable(bbdata) {
389            let kept_unreachable = *kept_unreachable.get_or_insert(used_bb);
390            if kept_unreachable != used_bb {
391                replacements[orig_index] = kept_unreachable;
392                deduplicated_unreachable = true;
393                orig_index += 1;
394                return false;
395            }
396        }
397
398        replacements[orig_index] = used_bb;
399        used_index += 1;
400        orig_index += 1;
401        true
402    });
403
404    // If we deduplicated unreachable blocks we erase their source_info as we
405    // can no longer attribute their code to a particular location in the
406    // source.
407    if deduplicated_unreachable {
408        basic_blocks[kept_unreachable.unwrap()].terminator_mut().source_info =
409            SourceInfo { span: DUMMY_SP, scope: OUTERMOST_SOURCE_SCOPE };
410    }
411
412    for block in basic_blocks {
413        block.terminator_mut().successors_mut(|target| *target = replacements[target.index()]);
414    }
415}
416
417pub(super) enum SimplifyLocals {
418    BeforeConstProp,
419    AfterGVN,
420    Final,
421}
422
423impl<'tcx> crate::MirPass<'tcx> for SimplifyLocals {
424    fn name(&self) -> &'static str {
425        match &self {
426            SimplifyLocals::BeforeConstProp => "SimplifyLocals-before-const-prop",
427            SimplifyLocals::AfterGVN => "SimplifyLocals-after-value-numbering",
428            SimplifyLocals::Final => "SimplifyLocals-final",
429        }
430    }
431
432    fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
433        PassPolicy::optimization(sess.mir_opt_level() > 0)
434    }
435
436    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
437        trace!("running SimplifyLocals on {:?}", body.source);
438
439        // First, we're going to get a count of *actual* uses for every `Local`.
440        let mut used_locals = UsedLocals::new(body);
441
442        // Next, we're going to remove any `Local` with zero actual uses. When we remove those
443        // `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
444        // count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
445        // `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
446        // fixedpoint where there are no more unused locals.
447        remove_unused_definitions_helper(&mut used_locals, body);
448
449        // Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the
450        // `Local`s.
451        let map = make_local_map(&mut body.local_decls, &used_locals);
452
453        // Only bother running the `LocalUpdater` if we actually found locals to remove.
454        if map.iter().any(Option::is_none) {
455            // Update references to all vars and tmps now
456            let mut updater = LocalUpdater { map, tcx };
457            updater.visit_body_preserves_cfg(body);
458
459            body.local_decls.shrink_to_fit();
460        }
461    }
462}
463
464pub(super) fn remove_unused_definitions<'tcx>(body: &mut Body<'tcx>) {
465    // First, we're going to get a count of *actual* uses for every `Local`.
466    let mut used_locals = UsedLocals::new(body);
467
468    // Next, we're going to remove any `Local` with zero actual uses. When we remove those
469    // `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
470    // count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
471    // `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
472    // fixedpoint where there are no more unused locals.
473    remove_unused_definitions_helper(&mut used_locals, body);
474}
475
476/// Construct the mapping while swapping out unused stuff out from the `vec`.
477fn make_local_map<V>(
478    local_decls: &mut IndexVec<Local, V>,
479    used_locals: &UsedLocals,
480) -> IndexVec<Local, Option<Local>> {
481    let mut map: IndexVec<Local, Option<Local>> = IndexVec::from_elem(None, local_decls);
482    let mut used = Local::ZERO;
483
484    for alive_index in local_decls.indices() {
485        // `is_used` treats the `RETURN_PLACE` and arguments as used.
486        if !used_locals.is_used(alive_index) {
487            continue;
488        }
489
490        map[alive_index] = Some(used);
491        if alive_index != used {
492            local_decls.swap(alive_index, used);
493        }
494        used.increment_by(1);
495    }
496    local_decls.truncate(used.index());
497    map
498}
499
500/// Keeps track of used & unused locals.
501struct UsedLocals {
502    increment: bool,
503    use_count: IndexVec<Local, u32>,
504    always_used: DenseBitSet<Local>,
505}
506
507impl UsedLocals {
508    /// Determines which locals are used & unused in the given body.
509    fn new(body: &Body<'_>) -> Self {
510        let mut always_used = debuginfo_locals(body);
511        always_used.insert(RETURN_PLACE);
512        for arg in body.args_iter() {
513            always_used.insert(arg);
514        }
515        let mut this = Self {
516            increment: true,
517            use_count: IndexVec::from_elem(0, &body.local_decls),
518            always_used,
519        };
520        this.visit_body(body);
521        this
522    }
523
524    /// Checks if local is used.
525    ///
526    /// Return place, arguments, var debuginfo are always considered used.
527    fn is_used(&self, local: Local) -> bool {
528        trace!(
529            "is_used({:?}): use_count: {:?}, always_used: {}",
530            local,
531            self.use_count[local],
532            self.always_used.contains(local)
533        );
534        // To keep things simple, we don't handle debugging information here, these are in DSE.
535        self.always_used.contains(local) || self.use_count[local] != 0
536    }
537
538    /// Updates the use counts to reflect the removal of given statement.
539    fn statement_removed(&mut self, statement: &Statement<'_>) {
540        self.increment = false;
541
542        // The location of the statement is irrelevant.
543        let location = Location::START;
544        self.visit_statement(statement, location);
545    }
546
547    /// Visits a left-hand side of an assignment.
548    fn visit_lhs(&mut self, place: &Place<'_>, location: Location) {
549        if place.is_indirect() {
550            // A use, not a definition.
551            self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
552        } else {
553            // A definition. The base local itself is not visited, so this occurrence is not counted
554            // toward its use count. There might be other locals still, used in an indexing
555            // projection.
556            self.super_projection(
557                place.as_ref(),
558                PlaceContext::MutatingUse(MutatingUseContext::Projection),
559                location,
560            );
561        }
562    }
563}
564
565impl<'tcx> Visitor<'tcx> for UsedLocals {
566    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
567        match statement.kind {
568            StatementKind::Intrinsic(..)
569            | StatementKind::Coverage(..)
570            | StatementKind::FakeRead(..)
571            | StatementKind::PlaceMention(..)
572            | StatementKind::AscribeUserType(..) => {
573                self.super_statement(statement, location);
574            }
575
576            StatementKind::ConstEvalCounter
577            | StatementKind::Nop
578            | StatementKind::StorageLive(..)
579            | StatementKind::StorageDead(..) => {}
580            StatementKind::Assign((ref place, ref rvalue)) => {
581                if rvalue.is_safe_to_remove() {
582                    self.visit_lhs(place, location);
583                    self.visit_rvalue(rvalue, location);
584                } else {
585                    self.super_statement(statement, location);
586                }
587            }
588
589            StatementKind::SetDiscriminant { ref place, variant_index: _ }
590            | StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
591                self.visit_lhs(place, location);
592            }
593        }
594    }
595
596    fn visit_local(&mut self, local: Local, ctx: PlaceContext, _location: Location) {
597        if matches!(ctx, PlaceContext::NonUse(_)) {
598            return;
599        }
600        if self.increment {
601            self.use_count[local] += 1;
602        } else {
603            assert_ne!(self.use_count[local], 0);
604            self.use_count[local] -= 1;
605        }
606    }
607}
608
609/// Removes unused definitions. Updates the used locals to reflect the changes made.
610fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Body<'_>) {
611    // The use counts are updated as we remove the statements. A local might become unused
612    // during the retain operation, leading to a temporary inconsistency (storage statements or
613    // definitions referencing the local might remain). For correctness it is crucial that this
614    // computation reaches a fixed point.
615
616    let mut modified = true;
617    while modified {
618        modified = false;
619
620        for data in body.basic_blocks.as_mut_preserves_cfg() {
621            // Remove unnecessary StorageLive and StorageDead annotations.
622            for statement in data.statements.iter_mut() {
623                let keep_statement = match &statement.kind {
624                    StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
625                        used_locals.is_used(*local)
626                    }
627                    StatementKind::Assign((place, _)) => used_locals.is_used(place.local),
628                    StatementKind::SetDiscriminant { place, .. }
629                    | StatementKind::BackwardIncompatibleDropHint { place, .. } => {
630                        used_locals.is_used(place.local)
631                    }
632                    _ => continue,
633                };
634                if keep_statement {
635                    continue;
636                }
637                trace!("removing statement {:?}", statement);
638                modified = true;
639                used_locals.statement_removed(statement);
640                statement.make_nop(true);
641            }
642            data.strip_nops();
643        }
644    }
645}
646
647struct LocalUpdater<'tcx> {
648    map: IndexVec<Local, Option<Local>>,
649    tcx: TyCtxt<'tcx>,
650}
651
652impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
653    fn tcx(&self) -> TyCtxt<'tcx> {
654        self.tcx
655    }
656
657    fn visit_statement_debuginfo(
658        &mut self,
659        stmt_debuginfo: &mut StmtDebugInfo<'tcx>,
660        location: Location,
661    ) {
662        match stmt_debuginfo {
663            StmtDebugInfo::AssignRef(local, place) => {
664                if place.as_ref().accessed_locals().any(|local| self.map[local].is_none()) {
665                    *stmt_debuginfo = StmtDebugInfo::InvalidAssign(*local);
666                }
667            }
668            StmtDebugInfo::InvalidAssign(_) => {}
669        }
670        self.super_statement_debuginfo(stmt_debuginfo, location);
671    }
672
673    fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
674        *l = self.map[*l].unwrap();
675    }
676}
677
678pub(crate) struct UsedInStmtLocals {
679    pub(crate) locals: DenseBitSet<Local>,
680}
681
682impl UsedInStmtLocals {
683    pub(crate) fn new(body: &Body<'_>) -> Self {
684        let mut this = Self { locals: DenseBitSet::new_empty(body.local_decls.len()) };
685        this.visit_body(body);
686        this
687    }
688
689    pub(crate) fn remove_unused_storage_annotations<'tcx>(&self, body: &mut Body<'tcx>) {
690        for data in body.basic_blocks.as_mut_preserves_cfg() {
691            // Remove unnecessary StorageLive and StorageDead annotations.
692            for statement in data.statements.iter_mut() {
693                let keep_statement = match &statement.kind {
694                    StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
695                        self.locals.contains(*local)
696                    }
697                    _ => continue,
698                };
699                if keep_statement {
700                    continue;
701                }
702                statement.make_nop(true);
703            }
704        }
705    }
706}
707
708impl<'tcx> Visitor<'tcx> for UsedInStmtLocals {
709    fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
710        if matches!(context, PlaceContext::NonUse(_)) {
711            return;
712        }
713        self.locals.insert(local);
714    }
715}