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 *changed |= *start != last;
229 *start = last;
230 while let Some((current, mut terminator)) = terminators.pop() {
231 let Terminator { kind: TerminatorKind::Goto { ref mut target }, .. } = terminator
232 else {
233 unreachable!();
234 };
235 *changed |= *target != last;
236 *target = last;
237 debug!("collapsing goto chain from {:?} to {:?}", current, target);
238
239 if self.pred_count[current] == 1 {
240 self.pred_count[current] = 0;
243 } else {
244 self.pred_count[*target] += 1;
245 self.pred_count[current] -= 1;
246 }
247 self.basic_blocks[current].terminator = Some(terminator);
248 }
249 }
250
251 fn merge_successor(
253 &mut self,
254 merged_blocks: &mut Vec<BasicBlock>,
255 terminator: &mut Terminator<'tcx>,
256 ) -> bool {
257 let target = match terminator.kind {
258 TerminatorKind::Goto { target } if self.pred_count[target] == 1 => target,
259 _ => return false,
260 };
261
262 debug!("merging block {:?} into {:?}", target, terminator);
263 *terminator = match self.basic_blocks[target].terminator.take() {
264 Some(terminator) => terminator,
265 None => {
266 return false;
269 }
270 };
271
272 merged_blocks.push(target);
273 self.pred_count[target] = 0;
274
275 true
276 }
277
278 fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
280 if self.preserve_switch_reads {
284 return false;
285 }
286
287 let TerminatorKind::SwitchInt { .. } = terminator.kind else {
288 return false;
289 };
290
291 let first_succ = {
292 if let Some(first_succ) = terminator.successors().next() {
293 if terminator.successors().all(|s| s == first_succ) {
294 let count = terminator.successors().count();
295 self.pred_count[first_succ] -= (count - 1) as u32;
296 first_succ
297 } else {
298 return false;
299 }
300 } else {
301 return false;
302 }
303 };
304
305 debug!("simplifying branch {:?}", terminator);
306 terminator.kind = TerminatorKind::Goto { target: first_succ };
307 true
308 }
309
310 fn strip_nops(&mut self) {
311 for blk in self.basic_blocks.iter_mut() {
312 blk.statements.retain(|stmt| !matches!(stmt.kind, StatementKind::Nop))
313 }
314 }
315}
316
317pub(super) fn simplify_duplicate_switch_targets(terminator: &mut Terminator<'_>) {
318 if let TerminatorKind::SwitchInt { targets, .. } = &mut terminator.kind {
319 let otherwise = targets.otherwise();
320 if targets.iter().any(|t| t.1 == otherwise) {
321 *targets = SwitchTargets::new(
322 targets.iter().filter(|t| t.1 != otherwise),
323 targets.otherwise(),
324 );
325 }
326 }
327}
328
329pub(super) fn remove_dead_blocks(body: &mut Body<'_>) {
330 let should_deduplicate_unreachable = |bbdata: &BasicBlockData<'_>| {
331 bbdata.terminator.is_some() && bbdata.is_empty_unreachable() && !bbdata.is_cleanup
337 };
338
339 let reachable = traversal::reachable_as_bitset(body);
340 let empty_unreachable_blocks = body
341 .basic_blocks
342 .iter_enumerated()
343 .filter(|(bb, bbdata)| should_deduplicate_unreachable(bbdata) && reachable.contains(*bb))
344 .count();
345
346 let num_blocks = body.basic_blocks.len();
347 if num_blocks == reachable.count() && empty_unreachable_blocks <= 1 {
348 return;
349 }
350
351 let basic_blocks = body.basic_blocks.as_mut();
352
353 let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
354 let mut orig_index = 0;
355 let mut used_index = 0;
356 let mut kept_unreachable = None;
357 let mut deduplicated_unreachable = false;
358 basic_blocks.raw.retain(|bbdata| {
359 let orig_bb = BasicBlock::new(orig_index);
360 if !reachable.contains(orig_bb) {
361 orig_index += 1;
362 return false;
363 }
364
365 let used_bb = BasicBlock::new(used_index);
366 if should_deduplicate_unreachable(bbdata) {
367 let kept_unreachable = *kept_unreachable.get_or_insert(used_bb);
368 if kept_unreachable != used_bb {
369 replacements[orig_index] = kept_unreachable;
370 deduplicated_unreachable = true;
371 orig_index += 1;
372 return false;
373 }
374 }
375
376 replacements[orig_index] = used_bb;
377 used_index += 1;
378 orig_index += 1;
379 true
380 });
381
382 if deduplicated_unreachable {
386 basic_blocks[kept_unreachable.unwrap()].terminator_mut().source_info =
387 SourceInfo { span: DUMMY_SP, scope: OUTERMOST_SOURCE_SCOPE };
388 }
389
390 for block in basic_blocks {
391 block.terminator_mut().successors_mut(|target| *target = replacements[target.index()]);
392 }
393}
394
395pub(super) enum SimplifyLocals {
396 BeforeConstProp,
397 AfterGVN,
398 Final,
399}
400
401impl<'tcx> crate::MirPass<'tcx> for SimplifyLocals {
402 fn name(&self) -> &'static str {
403 match &self {
404 SimplifyLocals::BeforeConstProp => "SimplifyLocals-before-const-prop",
405 SimplifyLocals::AfterGVN => "SimplifyLocals-after-value-numbering",
406 SimplifyLocals::Final => "SimplifyLocals-final",
407 }
408 }
409
410 fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
411 sess.mir_opt_level() > 0
412 }
413
414 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
415 trace!("running SimplifyLocals on {:?}", body.source);
416
417 let mut used_locals = UsedLocals::new(body);
419
420 remove_unused_definitions_helper(&mut used_locals, body);
426
427 let map = make_local_map(&mut body.local_decls, &used_locals);
430
431 if map.iter().any(Option::is_none) {
433 let mut updater = LocalUpdater { map, tcx };
435 updater.visit_body_preserves_cfg(body);
436
437 body.local_decls.shrink_to_fit();
438 }
439 }
440
441 fn is_required(&self) -> bool {
442 false
443 }
444}
445
446pub(super) fn remove_unused_definitions<'tcx>(body: &mut Body<'tcx>) {
447 let mut used_locals = UsedLocals::new(body);
449
450 remove_unused_definitions_helper(&mut used_locals, body);
456}
457
458fn make_local_map<V>(
460 local_decls: &mut IndexVec<Local, V>,
461 used_locals: &UsedLocals,
462) -> IndexVec<Local, Option<Local>> {
463 let mut map: IndexVec<Local, Option<Local>> = IndexVec::from_elem(None, local_decls);
464 let mut used = Local::ZERO;
465
466 for alive_index in local_decls.indices() {
467 if !used_locals.is_used(alive_index) {
469 continue;
470 }
471
472 map[alive_index] = Some(used);
473 if alive_index != used {
474 local_decls.swap(alive_index, used);
475 }
476 used.increment_by(1);
477 }
478 local_decls.truncate(used.index());
479 map
480}
481
482struct UsedLocals {
484 increment: bool,
485 arg_count: u32,
486 use_count: IndexVec<Local, u32>,
487}
488
489impl UsedLocals {
490 fn new(body: &Body<'_>) -> Self {
492 let mut this = Self {
493 increment: true,
494 arg_count: body.arg_count.try_into().unwrap(),
495 use_count: IndexVec::from_elem(0, &body.local_decls),
496 };
497 this.visit_body(body);
498 this
499 }
500
501 fn is_used(&self, local: Local) -> bool {
505 trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]);
506 local.as_u32() <= self.arg_count || self.use_count[local] != 0
507 }
508
509 fn statement_removed(&mut self, statement: &Statement<'_>) {
511 self.increment = false;
512
513 let location = Location::START;
515 self.visit_statement(statement, location);
516 }
517
518 fn visit_lhs(&mut self, place: &Place<'_>, location: Location) {
520 if place.is_indirect() {
521 self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
523 } else {
524 self.super_projection(
528 place.as_ref(),
529 PlaceContext::MutatingUse(MutatingUseContext::Projection),
530 location,
531 );
532 }
533 }
534}
535
536impl<'tcx> Visitor<'tcx> for UsedLocals {
537 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
538 match statement.kind {
539 StatementKind::Intrinsic(..)
540 | StatementKind::Retag(..)
541 | StatementKind::Coverage(..)
542 | StatementKind::FakeRead(..)
543 | StatementKind::PlaceMention(..)
544 | StatementKind::AscribeUserType(..) => {
545 self.super_statement(statement, location);
546 }
547
548 StatementKind::ConstEvalCounter | StatementKind::Nop => {}
549
550 StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}
551
552 StatementKind::Assign(box (ref place, ref rvalue)) => {
553 if rvalue.is_safe_to_remove() {
554 self.visit_lhs(place, location);
555 self.visit_rvalue(rvalue, location);
556 } else {
557 self.super_statement(statement, location);
558 }
559 }
560
561 StatementKind::SetDiscriminant { ref place, variant_index: _ }
562 | StatementKind::Deinit(ref place)
563 | StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
564 self.visit_lhs(place, location);
565 }
566 }
567 }
568
569 fn visit_local(&mut self, local: Local, _ctx: PlaceContext, _location: Location) {
570 if self.increment {
571 self.use_count[local] += 1;
572 } else {
573 assert_ne!(self.use_count[local], 0);
574 self.use_count[local] -= 1;
575 }
576 }
577}
578
579fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Body<'_>) {
581 let mut modified = true;
587 while modified {
588 modified = false;
589
590 for data in body.basic_blocks.as_mut_preserves_cfg() {
591 data.statements.retain(|statement| {
593 let keep = match &statement.kind {
594 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
595 used_locals.is_used(*local)
596 }
597 StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
598
599 StatementKind::SetDiscriminant { place, .. }
600 | StatementKind::BackwardIncompatibleDropHint { place, reason: _ }
601 | StatementKind::Deinit(place) => used_locals.is_used(place.local),
602 StatementKind::Nop => false,
603 _ => true,
604 };
605
606 if !keep {
607 trace!("removing statement {:?}", statement);
608 modified = true;
609 used_locals.statement_removed(statement);
610 }
611
612 keep
613 });
614 }
615 }
616}
617
618struct LocalUpdater<'tcx> {
619 map: IndexVec<Local, Option<Local>>,
620 tcx: TyCtxt<'tcx>,
621}
622
623impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
624 fn tcx(&self) -> TyCtxt<'tcx> {
625 self.tcx
626 }
627
628 fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
629 *l = self.map[*l].unwrap();
630 }
631}