1use 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 PostAnalysis,
56 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 body.basic_blocks.invalidate_cfg_cache();
86 }
87 remove_dead_blocks(body);
88
89 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 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 let preserve_switch_reads = matches!(body.phase, MirPhase::Built | MirPhase::Analysis(_))
132 || tcx.sess.opts.unstable_opts.mir_preserve_ub;
133 let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
135
136 CfgSimplifier { preserve_switch_reads, basic_blocks, pred_count }
137 }
138
139 #[must_use]
142 fn simplify(mut self) -> bool {
143 self.strip_nops();
144
145 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 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 _ => None,
225 }
226 }
227
228 fn collapse_goto_chain(&mut self, start: &mut BasicBlock, changed: &mut bool) {
230 let mut terminators: SmallVec<[_; 1]> = Default::default();
233 let mut current = *start;
234 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 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 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 return false;
298 }
299 };
300
301 merged_blocks.push(target);
302 self.pred_count[target] = 0;
303
304 true
305 }
306
307 fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
309 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 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 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 let mut used_locals = UsedLocals::new(body);
441
442 remove_unused_definitions_helper(&mut used_locals, body);
448
449 let map = make_local_map(&mut body.local_decls, &used_locals);
452
453 if map.iter().any(Option::is_none) {
455 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 let mut used_locals = UsedLocals::new(body);
467
468 remove_unused_definitions_helper(&mut used_locals, body);
474}
475
476fn 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 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
500struct UsedLocals {
502 increment: bool,
503 use_count: IndexVec<Local, u32>,
504 always_used: DenseBitSet<Local>,
505}
506
507impl UsedLocals {
508 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 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 self.always_used.contains(local) || self.use_count[local] != 0
536 }
537
538 fn statement_removed(&mut self, statement: &Statement<'_>) {
540 self.increment = false;
541
542 let location = Location::START;
544 self.visit_statement(statement, location);
545 }
546
547 fn visit_lhs(&mut self, place: &Place<'_>, location: Location) {
549 if place.is_indirect() {
550 self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
552 } else {
553 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
609fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Body<'_>) {
611 let mut modified = true;
617 while modified {
618 modified = false;
619
620 for data in body.basic_blocks.as_mut_preserves_cfg() {
621 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 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}