1use rustc_index::{Idx, IndexSlice, IndexVec};
38use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
39use rustc_middle::mir::*;
40use rustc_middle::ty::TyCtxt;
41use rustc_span::DUMMY_SP;
42use smallvec::SmallVec;
43use tracing::{debug, trace};
44
45pub(super) enum SimplifyCfg {
46 Initial,
47 PromoteConsts,
48 RemoveFalseEdges,
49 PostAnalysis,
51 PreOptimizations,
54 Final,
55 MakeShim,
56 AfterUnreachableEnumBranching,
57}
58
59impl SimplifyCfg {
60 fn name(&self) -> &'static str {
61 match self {
62 SimplifyCfg::Initial => "SimplifyCfg-initial",
63 SimplifyCfg::PromoteConsts => "SimplifyCfg-promote-consts",
64 SimplifyCfg::RemoveFalseEdges => "SimplifyCfg-remove-false-edges",
65 SimplifyCfg::PostAnalysis => "SimplifyCfg-post-analysis",
66 SimplifyCfg::PreOptimizations => "SimplifyCfg-pre-optimizations",
67 SimplifyCfg::Final => "SimplifyCfg-final",
68 SimplifyCfg::MakeShim => "SimplifyCfg-make_shim",
69 SimplifyCfg::AfterUnreachableEnumBranching => {
70 "SimplifyCfg-after-unreachable-enum-branching"
71 }
72 }
73 }
74}
75
76pub(super) fn simplify_cfg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
77 if CfgSimplifier::new(tcx, body).simplify() {
78 body.basic_blocks.invalidate_cfg_cache();
81 }
82 remove_dead_blocks(body);
83
84 body.basic_blocks.as_mut_preserves_cfg().shrink_to_fit();
86}
87
88impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
89 fn name(&self) -> &'static str {
90 self.name()
91 }
92
93 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
94 debug!("SimplifyCfg({:?}) - simplifying {:?}", self.name(), body.source);
95 simplify_cfg(tcx, body);
96 }
97
98 fn is_required(&self) -> bool {
99 false
100 }
101}
102
103struct CfgSimplifier<'a, 'tcx> {
104 preserve_switch_reads: bool,
105 basic_blocks: &'a mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
106 pred_count: IndexVec<BasicBlock, u32>,
107}
108
109impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
110 fn new(tcx: TyCtxt<'tcx>, body: &'a mut Body<'tcx>) -> Self {
111 let mut pred_count = IndexVec::from_elem(0u32, &body.basic_blocks);
112
113 pred_count[START_BLOCK] = 1;
116
117 for (_, data) in traversal::preorder(body) {
118 if let Some(ref term) = data.terminator {
119 for tgt in term.successors() {
120 pred_count[tgt] += 1;
121 }
122 }
123 }
124
125 let preserve_switch_reads = matches!(body.phase, MirPhase::Built | MirPhase::Analysis(_))
127 || tcx.sess.opts.unstable_opts.mir_preserve_ub;
128 let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
130
131 CfgSimplifier { preserve_switch_reads, basic_blocks, pred_count }
132 }
133
134 #[must_use]
137 fn simplify(mut self) -> bool {
138 self.strip_nops();
139
140 let mut merged_blocks = Vec::new();
145 let mut outer_changed = false;
146 loop {
147 let mut changed = false;
148
149 for bb in self.basic_blocks.indices() {
150 if self.pred_count[bb] == 0 {
151 continue;
152 }
153
154 debug!("simplifying {:?}", bb);
155
156 let mut terminator =
157 self.basic_blocks[bb].terminator.take().expect("invalid terminator state");
158
159 terminator
160 .successors_mut(|successor| self.collapse_goto_chain(successor, &mut changed));
161
162 let mut inner_changed = true;
163 merged_blocks.clear();
164 while inner_changed {
165 inner_changed = false;
166 inner_changed |= self.simplify_branch(&mut terminator);
167 inner_changed |= self.merge_successor(&mut merged_blocks, &mut terminator);
168 changed |= inner_changed;
169 }
170
171 let statements_to_merge =
172 merged_blocks.iter().map(|&i| self.basic_blocks[i].statements.len()).sum();
173
174 if statements_to_merge > 0 {
175 let mut statements = std::mem::take(&mut self.basic_blocks[bb].statements);
176 statements.reserve(statements_to_merge);
177 for &from in &merged_blocks {
178 statements.append(&mut self.basic_blocks[from].statements);
179 }
180 self.basic_blocks[bb].statements = statements;
181 }
182
183 self.basic_blocks[bb].terminator = Some(terminator);
184 }
185
186 if !changed {
187 break;
188 }
189
190 outer_changed = true;
191 }
192
193 outer_changed
194 }
195
196 fn take_terminator_if_simple_goto(&mut self, bb: BasicBlock) -> Option<Terminator<'tcx>> {
201 match self.basic_blocks[bb] {
202 BasicBlockData {
203 ref statements,
204 terminator:
205 ref mut terminator @ Some(Terminator { kind: TerminatorKind::Goto { .. }, .. }),
206 ..
207 } if statements.is_empty() => terminator.take(),
208 _ => None,
211 }
212 }
213
214 fn collapse_goto_chain(&mut self, start: &mut BasicBlock, changed: &mut bool) {
216 let mut terminators: SmallVec<[_; 1]> = Default::default();
219 let mut current = *start;
220 while let Some(terminator) = self.take_terminator_if_simple_goto(current) {
221 let Terminator { kind: TerminatorKind::Goto { target }, .. } = terminator else {
222 unreachable!();
223 };
224 terminators.push((current, terminator));
225 current = target;
226 }
227 let last = current;
228 *start = last;
229 while let Some((current, mut terminator)) = terminators.pop() {
230 let Terminator { kind: TerminatorKind::Goto { ref mut target }, .. } = terminator
231 else {
232 unreachable!();
233 };
234 *changed |= *target != last;
235 *target = last;
236 debug!("collapsing goto chain from {:?} to {:?}", current, target);
237
238 if self.pred_count[current] == 1 {
239 self.pred_count[current] = 0;
242 } else {
243 self.pred_count[*target] += 1;
244 self.pred_count[current] -= 1;
245 }
246 self.basic_blocks[current].terminator = Some(terminator);
247 }
248 }
249
250 fn merge_successor(
252 &mut self,
253 merged_blocks: &mut Vec<BasicBlock>,
254 terminator: &mut Terminator<'tcx>,
255 ) -> bool {
256 let target = match terminator.kind {
257 TerminatorKind::Goto { target } if self.pred_count[target] == 1 => target,
258 _ => return false,
259 };
260
261 debug!("merging block {:?} into {:?}", target, terminator);
262 *terminator = match self.basic_blocks[target].terminator.take() {
263 Some(terminator) => terminator,
264 None => {
265 return false;
268 }
269 };
270
271 merged_blocks.push(target);
272 self.pred_count[target] = 0;
273
274 true
275 }
276
277 fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
279 if self.preserve_switch_reads {
283 return false;
284 }
285
286 let TerminatorKind::SwitchInt { .. } = terminator.kind else {
287 return false;
288 };
289
290 let first_succ = {
291 if let Some(first_succ) = terminator.successors().next() {
292 if terminator.successors().all(|s| s == first_succ) {
293 let count = terminator.successors().count();
294 self.pred_count[first_succ] -= (count - 1) as u32;
295 first_succ
296 } else {
297 return false;
298 }
299 } else {
300 return false;
301 }
302 };
303
304 debug!("simplifying branch {:?}", terminator);
305 terminator.kind = TerminatorKind::Goto { target: first_succ };
306 true
307 }
308
309 fn strip_nops(&mut self) {
310 for blk in self.basic_blocks.iter_mut() {
311 blk.statements.retain(|stmt| !matches!(stmt.kind, StatementKind::Nop))
312 }
313 }
314}
315
316pub(super) fn simplify_duplicate_switch_targets(terminator: &mut Terminator<'_>) {
317 if let TerminatorKind::SwitchInt { targets, .. } = &mut terminator.kind {
318 let otherwise = targets.otherwise();
319 if targets.iter().any(|t| t.1 == otherwise) {
320 *targets = SwitchTargets::new(
321 targets.iter().filter(|t| t.1 != otherwise),
322 targets.otherwise(),
323 );
324 }
325 }
326}
327
328pub(super) fn remove_dead_blocks(body: &mut Body<'_>) {
329 let should_deduplicate_unreachable = |bbdata: &BasicBlockData<'_>| {
330 bbdata.terminator.is_some() && bbdata.is_empty_unreachable() && !bbdata.is_cleanup
336 };
337
338 let reachable = traversal::reachable_as_bitset(body);
339 let empty_unreachable_blocks = body
340 .basic_blocks
341 .iter_enumerated()
342 .filter(|(bb, bbdata)| should_deduplicate_unreachable(bbdata) && reachable.contains(*bb))
343 .count();
344
345 let num_blocks = body.basic_blocks.len();
346 if num_blocks == reachable.count() && empty_unreachable_blocks <= 1 {
347 return;
348 }
349
350 let basic_blocks = body.basic_blocks.as_mut();
351
352 let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
353 let mut orig_index = 0;
354 let mut used_index = 0;
355 let mut kept_unreachable = None;
356 let mut deduplicated_unreachable = false;
357 basic_blocks.raw.retain(|bbdata| {
358 let orig_bb = BasicBlock::new(orig_index);
359 if !reachable.contains(orig_bb) {
360 orig_index += 1;
361 return false;
362 }
363
364 let used_bb = BasicBlock::new(used_index);
365 if should_deduplicate_unreachable(bbdata) {
366 let kept_unreachable = *kept_unreachable.get_or_insert(used_bb);
367 if kept_unreachable != used_bb {
368 replacements[orig_index] = kept_unreachable;
369 deduplicated_unreachable = true;
370 orig_index += 1;
371 return false;
372 }
373 }
374
375 replacements[orig_index] = used_bb;
376 used_index += 1;
377 orig_index += 1;
378 true
379 });
380
381 if deduplicated_unreachable {
385 basic_blocks[kept_unreachable.unwrap()].terminator_mut().source_info =
386 SourceInfo { span: DUMMY_SP, scope: OUTERMOST_SOURCE_SCOPE };
387 }
388
389 for block in basic_blocks {
390 block.terminator_mut().successors_mut(|target| *target = replacements[target.index()]);
391 }
392}
393
394pub(super) enum SimplifyLocals {
395 BeforeConstProp,
396 AfterGVN,
397 Final,
398}
399
400impl<'tcx> crate::MirPass<'tcx> for SimplifyLocals {
401 fn name(&self) -> &'static str {
402 match &self {
403 SimplifyLocals::BeforeConstProp => "SimplifyLocals-before-const-prop",
404 SimplifyLocals::AfterGVN => "SimplifyLocals-after-value-numbering",
405 SimplifyLocals::Final => "SimplifyLocals-final",
406 }
407 }
408
409 fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
410 sess.mir_opt_level() > 0
411 }
412
413 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
414 trace!("running SimplifyLocals on {:?}", body.source);
415
416 let mut used_locals = UsedLocals::new(body);
418
419 remove_unused_definitions_helper(&mut used_locals, body);
425
426 let map = make_local_map(&mut body.local_decls, &used_locals);
429
430 if map.iter().any(Option::is_none) {
432 let mut updater = LocalUpdater { map, tcx };
434 updater.visit_body_preserves_cfg(body);
435
436 body.local_decls.shrink_to_fit();
437 }
438 }
439
440 fn is_required(&self) -> bool {
441 false
442 }
443}
444
445pub(super) fn remove_unused_definitions<'tcx>(body: &mut Body<'tcx>) {
446 let mut used_locals = UsedLocals::new(body);
448
449 remove_unused_definitions_helper(&mut used_locals, body);
455}
456
457fn make_local_map<V>(
459 local_decls: &mut IndexVec<Local, V>,
460 used_locals: &UsedLocals,
461) -> IndexVec<Local, Option<Local>> {
462 let mut map: IndexVec<Local, Option<Local>> = IndexVec::from_elem(None, local_decls);
463 let mut used = Local::ZERO;
464
465 for alive_index in local_decls.indices() {
466 if !used_locals.is_used(alive_index) {
468 continue;
469 }
470
471 map[alive_index] = Some(used);
472 if alive_index != used {
473 local_decls.swap(alive_index, used);
474 }
475 used.increment_by(1);
476 }
477 local_decls.truncate(used.index());
478 map
479}
480
481struct UsedLocals {
483 increment: bool,
484 arg_count: u32,
485 use_count: IndexVec<Local, u32>,
486}
487
488impl UsedLocals {
489 fn new(body: &Body<'_>) -> Self {
491 let mut this = Self {
492 increment: true,
493 arg_count: body.arg_count.try_into().unwrap(),
494 use_count: IndexVec::from_elem(0, &body.local_decls),
495 };
496 this.visit_body(body);
497 this
498 }
499
500 fn is_used(&self, local: Local) -> bool {
504 trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]);
505 local.as_u32() <= self.arg_count || self.use_count[local] != 0
506 }
507
508 fn statement_removed(&mut self, statement: &Statement<'_>) {
510 self.increment = false;
511
512 let location = Location::START;
514 self.visit_statement(statement, location);
515 }
516
517 fn visit_lhs(&mut self, place: &Place<'_>, location: Location) {
519 if place.is_indirect() {
520 self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
522 } else {
523 self.super_projection(
527 place.as_ref(),
528 PlaceContext::MutatingUse(MutatingUseContext::Projection),
529 location,
530 );
531 }
532 }
533}
534
535impl<'tcx> Visitor<'tcx> for UsedLocals {
536 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
537 match statement.kind {
538 StatementKind::Intrinsic(..)
539 | StatementKind::Retag(..)
540 | StatementKind::Coverage(..)
541 | StatementKind::FakeRead(..)
542 | StatementKind::PlaceMention(..)
543 | StatementKind::AscribeUserType(..) => {
544 self.super_statement(statement, location);
545 }
546
547 StatementKind::ConstEvalCounter | StatementKind::Nop => {}
548
549 StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}
550
551 StatementKind::Assign(box (ref place, ref rvalue)) => {
552 if rvalue.is_safe_to_remove() {
553 self.visit_lhs(place, location);
554 self.visit_rvalue(rvalue, location);
555 } else {
556 self.super_statement(statement, location);
557 }
558 }
559
560 StatementKind::SetDiscriminant { ref place, variant_index: _ }
561 | StatementKind::Deinit(ref place)
562 | StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
563 self.visit_lhs(place, location);
564 }
565 }
566 }
567
568 fn visit_local(&mut self, local: Local, _ctx: PlaceContext, _location: Location) {
569 if self.increment {
570 self.use_count[local] += 1;
571 } else {
572 assert_ne!(self.use_count[local], 0);
573 self.use_count[local] -= 1;
574 }
575 }
576}
577
578fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Body<'_>) {
580 let mut modified = true;
586 while modified {
587 modified = false;
588
589 for data in body.basic_blocks.as_mut_preserves_cfg() {
590 data.statements.retain(|statement| {
592 let keep = match &statement.kind {
593 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
594 used_locals.is_used(*local)
595 }
596 StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
597
598 StatementKind::SetDiscriminant { place, .. }
599 | StatementKind::BackwardIncompatibleDropHint { place, reason: _ }
600 | StatementKind::Deinit(place) => used_locals.is_used(place.local),
601 StatementKind::Nop => false,
602 _ => true,
603 };
604
605 if !keep {
606 trace!("removing statement {:?}", statement);
607 modified = true;
608 used_locals.statement_removed(statement);
609 }
610
611 keep
612 });
613 }
614 }
615}
616
617struct LocalUpdater<'tcx> {
618 map: IndexVec<Local, Option<Local>>,
619 tcx: TyCtxt<'tcx>,
620}
621
622impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
623 fn tcx(&self) -> TyCtxt<'tcx> {
624 self.tcx
625 }
626
627 fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
628 *l = self.map[*l].unwrap();
629 }
630}