1use super::PathTree;
2use crate::compat::{FxHashMap, FxHashSet};
3use crate::graphs::{
4 cfg::{CfgBlock, ControlFlowGraph},
5 scc::{Scc, SccInfo},
6};
7use rustc_middle::{
8 mir::{
9 AggregateKind, BasicBlock, BinOp, Local, Operand, ProjectionElem, Rvalue, StatementKind,
10 SwitchTargets, Terminator, TerminatorKind, UnOp, UnwindAction,
11 },
12 ty::{TyCtxt, TyKind, TypingEnv},
13};
14use rustc_span::def_id::DefId;
15use std::collections::hash_map::DefaultHasher;
16use std::hash::{Hash, Hasher};
17
18const WHOLE_CFG_PATH_LIMIT: usize = 4000;
20const WHOLE_CFG_PATH_DEPTH_LIMIT: usize = 256;
22const SCC_PATH_CACHE_LIMIT: usize = 2048;
24const SCC_MAX_DEPTH: usize = 128;
26const SCC_MAX_SEEN_PATHS: usize = 128;
28const SCC_MAX_PATH_LEN: usize = 200;
30
31fn check_postfix_segment(
37 path: &[usize],
38 enter: usize,
39 segment_counts: &mut FxHashMap<Vec<usize>, usize>,
40 max_repeats: usize,
41) -> bool {
42 let segment = extract_segment(path, enter);
43 let count = segment_counts.entry(segment).or_insert(0);
44 *count += 1;
45 *count == 1 || *count - 1 <= max_repeats
46}
47
48fn extract_segment(path: &[usize], enter: usize) -> Vec<usize> {
49 let prev_pos = path[..path.len() - 1]
50 .iter()
51 .rposition(|&node| node == enter)
52 .unwrap_or(0);
53 path[prev_pos + 1..path.len() - 1].to_vec()
54}
55
56#[derive(Clone, Debug)]
57pub struct SccPath {
68 pub blocks: Vec<usize>,
69 pub exit_successors: Vec<usize>,
70}
71
72#[derive(Clone, Debug, Default)]
76pub struct BlockConstantInfo {
77 pub assigned_locals: FxHashSet<usize>,
78 pub constants: FxHashMap<usize, usize>,
79 pub constraint_copies: FxHashMap<usize, usize>,
80 pub increments: FxHashMap<usize, (usize, usize)>,
82 pub remainders: FxHashMap<usize, (usize, usize)>,
84 pub comparison_sources: FxHashMap<usize, ComparisonSource>,
87 pub known_nonnull_locals: FxHashSet<usize>,
90 pub negation_sources: FxHashMap<usize, usize>,
91 pub and_sources: FxHashMap<usize, (usize, usize)>,
92}
93#[derive(Clone, Debug)]
96pub struct ComparisonSource {
97 pub op: rustc_middle::mir::BinOp,
98 pub lhs_local: usize,
99 pub rhs_local: usize,
100 pub rhs_is_constant: bool,
101}
102
103const AGGREGATE_FIELD_MULT: usize = 256;
105const AGGREGATE_FIELD_SENTINEL: usize = 1 << (usize::BITS as usize - 1);
106
107fn encode_aggregate_field(local: usize, field: usize) -> usize {
108 debug_assert!(field < AGGREGATE_FIELD_MULT);
109 AGGREGATE_FIELD_SENTINEL | (local * AGGREGATE_FIELD_MULT + field)
110}
111
112fn decode_aggregate_field(encoded: usize) -> Option<(usize, usize)> {
113 if encoded & AGGREGATE_FIELD_SENTINEL == 0 {
114 return None;
115 }
116 let raw = encoded & !AGGREGATE_FIELD_SENTINEL;
117 Some((raw / AGGREGATE_FIELD_MULT, raw % AGGREGATE_FIELD_MULT))
118}
119
120fn first_field_projection(place: &rustc_middle::mir::Place<'_>) -> Option<usize> {
121 for proj in place.projection.iter() {
122 if let rustc_middle::mir::ProjectionElem::Field(field, _) = proj {
123 return Some(field.as_usize());
124 }
125 }
126 None
127}
128
129#[derive(Clone, Debug, Default)]
136pub struct DiscriminantInfo {
137 pub source_of: FxHashMap<usize, usize>,
138 pub variant_count_of: FxHashMap<usize, usize>,
139}
140
141#[derive(Clone)]
150pub struct PathGraph<'tcx> {
151 pub cfg: ControlFlowGraph<'tcx>,
152 pub block_info: Vec<BlockConstantInfo>,
153 pub disc_info: DiscriminantInfo,
154 pub aggregate_field_sources: FxHashMap<usize, usize>,
156 pub cast_chains: FxHashMap<usize, usize>,
158 pub field_projection_source: FxHashMap<usize, usize>,
160}
161
162impl<'tcx> PathGraph<'tcx> {
163 pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> PathGraph<'tcx> {
164 let body = tcx.optimized_mir(def_id);
165 let basicblocks = &body.basic_blocks;
166 let mut cfg_blocks = Vec::<CfgBlock>::new();
167 let mut block_info = Vec::new();
168 let mut disc_info = DiscriminantInfo::default();
169 let mut aggregate_field_sources: FxHashMap<usize, usize> = FxHashMap::default();
170 let mut field_projection_source: FxHashMap<usize, usize> = FxHashMap::default();
171 let mut cast_chains: FxHashMap<usize, usize> = FxHashMap::default();
172
173 for i in 0..basicblocks.len() {
174 let bb = &basicblocks[BasicBlock::from(i)];
175 let mut cfg_block = CfgBlock::new(i, bb.is_cleanup);
176 let mut info = BlockConstantInfo::default();
177
178 for stmt in &bb.statements {
179 if let StatementKind::Assign(assign) = &stmt.kind {
180 let (place, rvalue) = &**assign;
181 let dest = place.local.as_usize();
182 let is_deref = place
185 .projection
186 .iter()
187 .any(|p| matches!(p, ProjectionElem::Deref));
188 if !is_deref {
189 info.assigned_locals.insert(dest);
190 }
191 match rvalue {
192 Rvalue::Use(Operand::Constant(c), ..) => {
193 let typing_env = TypingEnv::post_analysis(tcx, def_id);
194 let val = match c.const_.ty().kind() {
195 TyKind::Bool => c
196 .const_
197 .try_eval_bool(tcx, typing_env)
198 .map(|b| if b { 1 } else { 0 }),
199 TyKind::Int(_) | TyKind::Uint(_) => {
200 c.const_.try_eval_bits(tcx, typing_env).map(|v| v as usize)
201 }
202 _ => None,
203 };
204 if let Some(val) = val {
205 info.constants.insert(dest, val);
206 }
207 }
208 Rvalue::Use(Operand::Copy(src) | Operand::Move(src), ..) => {
209 let src_local = src.local.as_usize();
210 if let Some(field_proj) = first_field_projection(src) {
211 let encoded = encode_aggregate_field(src_local, field_proj);
212 field_projection_source.insert(dest, encoded);
213 }
214 info.constraint_copies.insert(dest, src_local);
215 }
216 Rvalue::Discriminant(rv_place) => {
217 disc_info.source_of.insert(dest, rv_place.local.as_usize());
218 let src_local = rv_place.local.as_usize();
219 if !disc_info.variant_count_of.contains_key(&src_local) {
220 let src_ty = body.local_decls[rv_place.local].ty;
221 if let TyKind::Adt(adt_def, _) = src_ty.kind() {
222 let num = adt_def.variants().len();
223 if num > 0 {
224 disc_info.variant_count_of.insert(src_local, num);
225 }
226 }
227 }
228 }
229 Rvalue::Aggregate(kind, operands) => {
230 if let AggregateKind::Adt(_, _, _, _, _) = kind.as_ref() {
231 let agg_local = place.local.as_usize();
232 for (field_idx, operand) in operands.iter().enumerate() {
233 if let Operand::Copy(src) | Operand::Move(src) = operand {
234 let key = encode_aggregate_field(agg_local, field_idx);
235 let src_local = src.local.as_usize();
236 aggregate_field_sources.insert(key, src_local);
237 }
238 }
239 }
240 let discr = match kind.as_ref() {
241 AggregateKind::Adt(_, variant_idx, _, _, _) => {
242 Some(variant_idx.as_usize())
243 }
244 _ => None,
245 };
246 if let Some(discr) = discr {
247 info.constants.insert(dest, discr);
248 if !disc_info.variant_count_of.contains_key(&dest) {
249 let dest_ty = body.local_decls[place.local].ty;
250 if let TyKind::Adt(adt_def, _) = dest_ty.kind() {
251 let num = adt_def.variants().len();
252 if num > 0 {
253 disc_info.variant_count_of.insert(dest, num);
254 }
255 }
256 }
257 }
258 }
259 Rvalue::BinaryOp(op, operands)
260 if matches!(
261 op,
262 BinOp::AddWithOverflow
263 ) =>
264 {
265 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) =
266 (&operands.0, &operands.1);
267 if let Some(lhs_local) = match lhs {
268 Operand::Copy(l) | Operand::Move(l)
269 if l.projection.is_empty() =>
270 {
271 Some(l.local.as_usize())
272 }
273 _ => None,
274 } {
275 let incr = match rhs {
276 Operand::Constant(c) => {
277 let typing_env =
278 TypingEnv::post_analysis(tcx, def_id);
279 c.const_
280 .try_eval_bits(tcx, typing_env)
281 .map(|v| v as usize)
282 }
283 _ => None,
284 };
285 if let Some(incr) = incr {
286 info.increments.insert(dest, (lhs_local, incr));
287 }
288 }
289 }
290 Rvalue::BinaryOp(op, operands)
291 if matches!(
292 op,
293 BinOp::Lt
294 | BinOp::Le
295 | BinOp::Gt
296 | BinOp::Ge
297 | BinOp::Eq
298 | BinOp::Ne
299 | BinOp::BitAnd
300 ) =>
301 {
302 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) =
303 (&operands.0, &operands.1);
304 let lhs_local = match lhs {
305 Operand::Copy(l) | Operand::Move(l)
306 if l.projection.is_empty() =>
307 {
308 Some(l.local.as_usize())
309 }
310 _ => None,
311 };
312 if let Some(lhs_local) = lhs_local {
313 let rhs_eval = match rhs {
314 Operand::Constant(c) => {
315 let typing_env =
316 TypingEnv::post_analysis(tcx, def_id);
317 c.const_
318 .try_eval_bits(tcx, typing_env)
319 .map(|v| (v as usize, true))
320 }
321 Operand::Copy(r) | Operand::Move(r)
322 if r.projection.is_empty() =>
323 {
324 Some((r.local.as_usize(), false))
325 }
326 _ => None,
327 };
328 let Some((rhs_local, rhs_is_constant)) = rhs_eval else {
329 continue;
330 };
331 info.comparison_sources.insert(
332 dest,
333 ComparisonSource {
334 op: *op,
335 lhs_local,
336 rhs_local,
337 rhs_is_constant,
338 },
339 );
340 if matches!(op, BinOp::BitAnd)
341 && matches!(
342 body.local_decls[place.local].ty.kind(),
343 TyKind::Bool
344 )
345 {
346 info.and_sources.insert(dest, (lhs_local, rhs_local));
347 }
348 }
349 }
350 Rvalue::BinaryOp(op, operands) if matches!(op, BinOp::Rem) => {
351 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) = (&operands.0, &operands.1);
352 if let Some(lhs_local) = match lhs {
353 Operand::Copy(l) | Operand::Move(l) if l.projection.is_empty() => {
354 Some(l.local.as_usize())
355 }
356 _ => None,
357 } {
358 let divisor = match rhs {
359 Operand::Constant(c) => {
360 let typing_env = TypingEnv::post_analysis(tcx, def_id);
361 c.const_
362 .try_eval_bits(tcx, typing_env)
363 .map(|v| v as usize)
364 }
365 _ => None,
366 };
367 if let Some(divisor) = divisor {
368 if divisor != 0 {
369 info.remainders.insert(dest, (lhs_local, divisor));
370 }
371 }
372 }
373 }
374 Rvalue::UnaryOp(unop, operand) => {
375 if matches!(unop, UnOp::Not)
376 && let Operand::Copy(src) | Operand::Move(src) = operand
377 {
378 info.negation_sources.insert(dest, src.local.as_usize());
379 }
380 }
381 Rvalue::Cast(_, operand, _) => {
382 if let Operand::Copy(src) | Operand::Move(src) = operand
383 && matches!(
384 body.local_decls[place.local].ty.kind(),
385 TyKind::RawPtr(..) | TyKind::Int(..) | TyKind::Uint(..)
386 )
387 {
388 cast_chains.insert(dest, src.local.as_usize());
389 }
390 }
391 _ => {} }
393 }
394 }
395
396 let Some(terminator) = &bb.terminator else {
397 cfg_blocks.push(cfg_block);
398 block_info.push(info);
399 continue;
400 };
401
402 if let TerminatorKind::Call {
403 destination,
404 ref func,
405 ..
406 } = terminator.kind
407 {
408 let name = crate::helpers::mir_utils::call_name(tcx, func);
409 if name.contains("::into_raw")
410 || (name.contains("::new") && name.contains("Box"))
411 || name.contains("::as_mut_ptr")
412 || name.contains("::as_ptr")
413 {
414 info.known_nonnull_locals
415 .insert(destination.local.as_usize());
416 }
417 if name.contains("null_mut") || (name.contains("null") && name.contains("ptr::")) {
418 info.constants.insert(destination.local.as_usize(), 0);
419 }
420 }
421
422 match terminator.kind.clone() {
423 TerminatorKind::Goto { ref target } => {
424 cfg_block.add_next(target.as_usize());
425 }
426 TerminatorKind::SwitchInt {
427 discr: _,
428 ref targets,
429 } => {
430 for (_, ref target) in targets.iter() {
431 cfg_block.add_next(target.as_usize());
432 }
433 cfg_block.add_next(targets.otherwise().as_usize());
434 }
435 TerminatorKind::Drop {
436 place: _,
437 target,
438 unwind,
439 replace: _,
440 drop: _,
441 #[cfg(not(rapx_ge_99))]
442 async_fut: _,
443 } => {
444 cfg_block.add_next(target.as_usize());
445 if let UnwindAction::Cleanup(target) = unwind {
446 cfg_block.add_next(target.as_usize());
447 }
448 }
449 TerminatorKind::Call {
450 ref target,
451 ref unwind,
452 ..
453 } => {
454 if let Some(tt) = target {
455 cfg_block.add_next(tt.as_usize());
456 }
457 if let UnwindAction::Cleanup(tt) = unwind {
458 cfg_block.add_next(tt.as_usize());
459 }
460 }
461 TerminatorKind::Assert {
462 cond: _,
463 expected: _,
464 msg: _,
465 ref target,
466 ref unwind,
467 } => {
468 cfg_block.add_next(target.as_usize());
469 if let UnwindAction::Cleanup(target) = unwind {
470 cfg_block.add_next(target.as_usize());
471 }
472 }
473 TerminatorKind::Yield {
474 value: _,
475 ref resume,
476 resume_arg: _,
477 ref drop,
478 } => {
479 cfg_block.add_next(resume.as_usize());
480 if let Some(target) = drop {
481 cfg_block.add_next(target.as_usize());
482 }
483 }
484 TerminatorKind::FalseEdge {
485 ref real_target,
486 imaginary_target: _,
487 } => {
488 cfg_block.add_next(real_target.as_usize());
489 }
490 TerminatorKind::FalseUnwind {
491 ref real_target,
492 unwind: _,
493 } => {
494 cfg_block.add_next(real_target.as_usize());
495 }
496 TerminatorKind::InlineAsm {
497 template: _,
498 operands: _,
499 options: _,
500 line_spans: _,
501 ref unwind,
502 targets,
503 asm_macro: _,
504 } => {
505 for target in targets {
506 cfg_block.add_next(target.as_usize());
507 }
508 if let UnwindAction::Cleanup(target) = unwind {
509 cfg_block.add_next(target.as_usize());
510 }
511 }
512 _ => {}
513 }
514
515 cfg_blocks.push(cfg_block);
516 block_info.push(info);
517 }
518
519 let cfg = ControlFlowGraph::new(def_id, tcx, cfg_blocks);
520
521 PathGraph {
522 cfg,
523 block_info,
524 disc_info,
525 aggregate_field_sources,
526 field_projection_source,
527 cast_chains,
528 }
529 }
530
531 pub fn find_scc(&mut self) {
532 self.cfg.find_scc();
533 self.populate_all_child_sccs();
534 }
535
536 pub fn def_id(&self) -> DefId {
537 self.cfg.def_id
538 }
539
540 pub fn tcx(&self) -> TyCtxt<'tcx> {
541 self.cfg.tcx
542 }
543
544 pub fn cfg_block(&self, index: usize) -> &CfgBlock {
545 self.cfg.block(index)
546 }
547
548 pub fn cfg_block_mut(&mut self, index: usize) -> &mut CfgBlock {
549 self.cfg.block_mut(index)
550 }
551
552 pub fn terminator(&self, index: usize) -> Option<&Terminator<'tcx>> {
554 self.cfg.terminator(index)
555 }
556
557 pub fn is_cleanup_block(&self, index: usize) -> bool {
558 self.cfg
559 .blocks
560 .get(index)
561 .map(|b| b.is_cleanup)
562 .unwrap_or(false)
563 }
564
565 fn get_variant_count(&self, local: usize) -> Option<usize> {
571 if let Some(&count) = self.disc_info.variant_count_of.get(&local) {
572 return Some(count);
573 }
574 let body = self.cfg.tcx.optimized_mir(self.cfg.def_id);
575 let mut ty = body.local_decls[Local::from_usize(local)].ty;
576 while let TyKind::Ref(_, inner_ty, _) | TyKind::RawPtr(inner_ty, _) = ty.kind() {
577 ty = *inner_ty;
578 }
579 match ty.kind() {
580 TyKind::Adt(adt_def, _) if adt_def.is_enum() => Some(adt_def.variants().len()),
581 _ => None,
582 }
583 }
584
585 pub fn check_transition(
589 &self,
590 cur: usize,
591 next: usize,
592 constraints: &mut FxHashMap<usize, usize>,
593 ) -> bool {
594 if cur >= self.cfg.blocks.len() || next >= self.cfg.blocks.len() {
595 return false;
596 }
597
598 if let Some(info) = self.block_info.get(cur) {
599 for local in &info.assigned_locals {
600 if let Some(&src) = info.constraint_copies.get(local) {
601 if let Some(val) = self.resolve_local_value(src, constraints) {
602 constraints.insert(*local, val);
603 continue;
604 }
605 if let Some(&dst_val) = constraints.get(local) {
606 constraints.insert(src, dst_val);
607 constraints.insert(*local, dst_val);
608 continue;
609 }
610 }
611 if let Some(&val) = info.constants.get(local) {
612 constraints.insert(*local, val);
613 continue;
614 }
615 constraints.remove(local);
616 }
617 for local in &info.known_nonnull_locals {
618 constraints.insert(*local, usize::MAX);
619 }
620 }
621
622 if let Some(terminator) = self.terminator(cur) {
627 let assigned = match &terminator.kind {
628 TerminatorKind::Call { destination, .. } => Some(destination.local.as_usize()),
629 TerminatorKind::Yield { resume_arg, .. } => Some(resume_arg.local.as_usize()),
630 _ => None,
631 };
632 if let Some(local) = assigned {
633 if let Some(block_info) = self.block_info.get(cur) {
635 if let Some(&val) = block_info.constants.get(&local) {
636 constraints.insert(local, val);
637 } else if block_info.known_nonnull_locals.contains(&local) {
638 constraints.insert(local, usize::MAX);
639 } else {
640 constraints.remove(&local);
641 }
642 } else {
643 constraints.remove(&local);
644 }
645 }
646 }
647
648 let successors = &self.cfg.block(cur).next;
649 if !successors.contains(&next) {
650 if !self.is_unwind_target(cur, next) {
651 return false;
652 }
653 }
654
655 if !self.check_switch_transition(cur, next, constraints) {
656 return false;
657 }
658
659 if !self.check_assert_transition(cur, next, constraints) {
660 return false;
661 }
662
663 true
664 }
665
666 fn check_assert_transition(
667 &self,
668 cur: usize,
669 next: usize,
670 constraints: &FxHashMap<usize, usize>,
671 ) -> bool {
672 let Some(terminator) = self.cfg.terminator(cur) else {
673 return true;
674 };
675 let TerminatorKind::Assert { cond, target, .. } = &terminator.kind else {
676 return true;
677 };
678 if next != target.as_usize() {
679 return true;
680 }
681 let cond_local = match cond {
682 Operand::Copy(p) | Operand::Move(p) => p.local.as_usize(),
683 Operand::Constant(c) => {
684 let typing_env =
685 rustc_middle::ty::TypingEnv::post_analysis(self.cfg.tcx, self.cfg.def_id);
686 return c
687 .const_
688 .try_eval_bool(self.cfg.tcx, typing_env)
689 .unwrap_or(true);
690 }
691 #[cfg(rapx_ge_99)]
692 Operand::RuntimeChecks(_) => return true,
693 };
694 self.resolve_bool_local(cond_local, constraints)
695 .map_or(true, |v| v == 1)
696 }
697
698 fn resolve_simple_bool(
699 &self,
700 local: usize,
701 constraints: &FxHashMap<usize, usize>,
702 ) -> Option<usize> {
703 if let Some(&val) = constraints.get(&local)
704 && val <= 1
705 {
706 return Some(val);
707 }
708 for info in &self.block_info {
709 if let Some(&val) = info.constants.get(&local)
710 && val <= 1
711 {
712 return Some(val);
713 }
714 if let Some(cmp) = info.comparison_sources.get(&local) {
715 if matches!(cmp.op, BinOp::Eq | BinOp::Ne) {
716 let is_eq = matches!(cmp.op, BinOp::Eq);
717 if cmp.rhs_is_constant {
718 if let Some(lhs_val) = self.resolve_local_value(cmp.lhs_local, constraints)
719 {
720 return Some(if is_eq {
721 if lhs_val == cmp.rhs_local { 1 } else { 0 }
722 } else {
723 if lhs_val != cmp.rhs_local { 1 } else { 0 }
724 });
725 }
726 } else {
727 let lhs_val = self.resolve_local_value(cmp.lhs_local, constraints);
728 let rhs_val = self.resolve_local_value(cmp.rhs_local, constraints);
729 if let Some(lhs_val) = lhs_val {
730 if let Some(rhs_val) = rhs_val {
731 return Some(if is_eq {
732 if lhs_val == rhs_val { 1 } else { 0 }
733 } else {
734 if lhs_val != rhs_val { 1 } else { 0 }
735 });
736 }
737 } else if let Some(rhs_val) = rhs_val {
738 return Some(if is_eq {
739 if cmp.lhs_local == rhs_val { 1 } else { 0 }
740 } else {
741 if cmp.lhs_local != rhs_val { 1 } else { 0 }
742 });
743 }
744 }
745 }
746 }
747 }
748 None
749 }
750
751 fn resolve_bool_local(
752 &self,
753 local: usize,
754 constraints: &FxHashMap<usize, usize>,
755 ) -> Option<usize> {
756 let mut stack = vec![(local, false)];
757 let mut seen = FxHashSet::default();
758 while let Some((cur, negated)) = stack.pop() {
759 let key = if negated { cur | (1 << 31) } else { cur };
760 if !seen.insert(key) {
761 continue;
762 }
763 if let Some(v) = self.resolve_simple_bool(cur, constraints) {
764 return Some(if negated { 1 - v } else { v });
765 }
766 for info in &self.block_info {
767 if let Some(&src) = info.constraint_copies.get(&cur) {
768 stack.push((src, negated));
769 }
770 if let Some(&src) = info.negation_sources.get(&cur) {
771 stack.push((src, !negated));
772 }
773 if let Some(&(lhs, rhs)) = info.and_sources.get(&cur) {
774 let a = self.resolve_simple_bool(lhs, constraints);
775 let b = self.resolve_simple_bool(rhs, constraints);
776 if let (Some(a), Some(b)) = (a, b) {
777 let r = if a == 1 && b == 1 { 1 } else { 0 };
778 return Some(if negated { 1 - r } else { r });
779 }
780 }
781 }
782 if let Some(&src) = self.cast_chains.get(&cur) {
783 stack.push((src, negated));
784 }
785 }
786 None
787 }
788
789 fn check_switch_transition(
794 &self,
795 cur: usize,
796 next: usize,
797 constraints: &mut FxHashMap<usize, usize>,
798 ) -> bool {
799 let Some(terminator) = self.cfg.terminator(cur) else {
800 return true;
801 };
802
803 match &terminator.kind {
804 TerminatorKind::SwitchInt { discr, targets } => {
805 let discr_local = discr.place().map(|p| p.local.as_usize());
806 let constraint_local = discr_local
807 .and_then(|l| self.disc_info.source_of.get(&l).copied())
808 .or(discr_local);
809
810 let all_targets: FxHashSet<usize> = targets
812 .iter()
813 .map(|(_, bb)| bb.as_usize())
814 .chain(std::iter::once(targets.otherwise().as_usize()))
815 .collect();
816
817 if !all_targets.contains(&next) {
818 return false;
819 }
820
821 let const_val = match discr {
823 Operand::Constant(c) => c
824 .const_
825 .try_eval_target_usize(
826 self.cfg.tcx,
827 TypingEnv::post_analysis(self.cfg.tcx, self.cfg.def_id),
828 )
829 .map(|v| v as usize),
830 _ => None,
831 };
832
833 if let Some(val) = const_val {
834 let expected = resolve_switch_target(targets, val as u128);
837 if next != expected {
838 return false;
839 }
840 if let Some(local) = constraint_local {
841 constraints.insert(local, val);
842 }
843 return true;
844 }
845
846 if let Some(local) = constraint_local {
847 if let Some(&known_val) = constraints.get(&local) {
848 let expected = resolve_switch_target(targets, known_val as u128);
849 if next != expected {
850 return false;
851 }
852 return true;
853 }
854 }
855
856 if let Some(discr_local) = discr_local
859 && let Some(info) = self.block_info.get(cur)
860 && let Some(cmp) = info.comparison_sources.get(&discr_local)
861 && matches!(cmp.op, BinOp::Ne | BinOp::Eq)
862 {
863 let is_ne = matches!(cmp.op, BinOp::Ne);
864 let pointer_is_nonnull =
865 self.local_is_known_nonnull(constraints, cmp.lhs_local);
866 let pointer_is_null = self.local_is_known_null(constraints, cmp.lhs_local);
867 if pointer_is_nonnull {
868 let expected_val = if is_ne { 1 } else { 0 };
869 let expected = resolve_switch_target(targets, expected_val);
870 let val = expected_val as usize;
871 constraints.insert(discr_local, val);
872 if let Some(local) = constraint_local {
873 constraints.insert(local, val);
874 }
875 if next != expected {
876 return false;
877 }
878 return true;
879 }
880 if pointer_is_null {
881 let expected_val = if is_ne { 0 } else { 1 };
882 let expected = resolve_switch_target(targets, expected_val);
883 let val = expected_val as usize;
884 constraints.insert(discr_local, val);
885 if let Some(local) = constraint_local {
886 constraints.insert(local, val);
887 }
888 if next != expected {
889 return false;
890 }
891 return true;
892 }
893
894 let is_eq = matches!(cmp.op, BinOp::Eq);
897 let lhs_val = self.resolve_local_value(cmp.lhs_local, constraints);
898 let rhs_val = if cmp.rhs_is_constant {
899 Some(cmp.rhs_local)
900 } else {
901 self.resolve_local_value(cmp.rhs_local, constraints)
902 };
903 if let (Some(lhs), Some(rhs)) = (lhs_val, rhs_val) {
904 let val = if is_eq {
905 if lhs == rhs { 1 } else { 0 }
906 } else {
907 if lhs != rhs { 1 } else { 0 }
908 };
909 let expected = resolve_switch_target(targets, val as u128);
910 if let Some(local) = constraint_local {
911 constraints.insert(local, val);
912 }
913 if next != expected {
914 return false;
915 }
916 return true;
917 }
918 }
919
920 if let Some(discr_local) = discr_local
927 && let Some(info) = self.block_info.get(cur)
928 && let Some(cmp) = info.comparison_sources.get(&discr_local)
929 && matches!(cmp.op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge)
930 {
931 let lhs_val = self.resolve_local_value_direct(cmp.lhs_local, constraints);
933 let rhs_val = if cmp.rhs_is_constant {
934 Some(cmp.rhs_local)
935 } else {
936 self.resolve_local_value_direct(cmp.rhs_local, constraints)
937 };
938 if let (Some(lhs), Some(rhs)) = (lhs_val, rhs_val) {
939 let val = match cmp.op {
940 BinOp::Lt => if lhs < rhs { 1 } else { 0 },
941 BinOp::Le => if lhs <= rhs { 1 } else { 0 },
942 BinOp::Gt => if lhs > rhs { 1 } else { 0 },
943 BinOp::Ge => if lhs >= rhs { 1 } else { 0 },
944 _ => unreachable!(),
945 };
946 let expected = resolve_switch_target(targets, val as u128);
947 if let Some(local) = constraint_local {
948 constraints.insert(local, val);
949 }
950 if next != expected {
951 return false;
952 }
953 return true;
954 }
955 }
956
957 if next == targets.otherwise().as_usize() {
960 if let Some(local) = constraint_local {
961 if let Some(num_variants) = self.get_variant_count(local) {
962 let all_covered = (0..num_variants)
963 .all(|v| targets.iter().any(|(tv, _)| tv == v as u128));
964 if all_covered {
965 return false;
966 }
967 }
968 }
969 }
970
971 self.learn_constraint_with_backprop(
972 cur,
973 constraint_local,
974 &targets,
975 next,
976 constraints,
977 );
978
979 true
980 }
981 _ => true,
982 }
983 }
984
985 fn learn_constraint_with_backprop(
990 &self,
991 cur: usize,
992 constraint_local: Option<usize>,
993 targets: &SwitchTargets,
994 next: usize,
995 constraints: &mut FxHashMap<usize, usize>,
996 ) {
997 let Some(local) = constraint_local else {
998 return;
999 };
1000 let Some((val, _)) = targets.iter().find(|(_, bb)| bb.as_usize() == next) else {
1001 if let Some(inferred) = self.infer_otherwise_value(targets, local) {
1002 constraints.insert(local, inferred);
1003 self.backprop_constraint(cur, local, inferred, constraints);
1004 }
1005 return;
1006 };
1007 let val = val as usize;
1008 constraints.insert(local, val);
1009 self.backprop_constraint(cur, local, val, constraints);
1010 }
1011
1012 fn backprop_constraint(
1013 &self,
1014 cur: usize,
1015 local: usize,
1016 val: usize,
1017 constraints: &mut FxHashMap<usize, usize>,
1018 ) {
1019 let Some(info) = self.block_info.get(cur) else {
1020 return;
1021 };
1022 let mut current = local;
1023 while let Some(&src) = info.constraint_copies.get(¤t) {
1024 if current == src {
1025 break;
1026 }
1027 constraints.insert(src, val);
1028 current = src;
1029 }
1030 }
1031
1032 fn resolve_local_value_direct(
1037 &self,
1038 local: usize,
1039 constraints: &FxHashMap<usize, usize>,
1040 ) -> Option<usize> {
1041 let mut stack = vec![local];
1042 let mut seen = FxHashSet::default();
1043 while let Some(cur) = stack.pop() {
1044 if !seen.insert(cur) {
1045 continue;
1046 }
1047 if let Some(&val) = constraints.get(&cur) {
1048 if val != usize::MAX {
1049 return Some(val);
1050 }
1051 }
1052 for info in &self.block_info {
1053 if let Some(&src) = info.constraint_copies.get(&cur) {
1054 stack.push(src);
1055 }
1056 if let Some(&val) = info.constants.get(&cur) {
1057 return Some(val);
1058 }
1059 }
1060 if let Some(&cast_src) = self.cast_chains.get(&cur) {
1061 stack.push(cast_src);
1062 }
1063 }
1064 None
1065 }
1066
1067 fn resolve_local_value(
1068 &self,
1069 local: usize,
1070 constraints: &FxHashMap<usize, usize>,
1071 ) -> Option<usize> {
1072 let mut stack = vec![(local, 0isize)];
1073 let mut seen = FxHashSet::default();
1074 while let Some((cur, offset)) = stack.pop() {
1075 if !seen.insert(cur) {
1076 continue;
1077 }
1078 if let Some(&val) = constraints.get(&cur) {
1079 if val == usize::MAX {
1080 } else if offset >= 0 {
1082 return Some(val + offset as usize);
1083 } else {
1084 return val.checked_sub((-offset) as usize);
1085 }
1086 }
1087 for info in &self.block_info {
1089 if let Some(&src) = info.constraint_copies.get(&cur) {
1090 stack.push((src, offset));
1091 }
1092 if let Some(&val) = info.constants.get(&cur) {
1093 if offset >= 0 {
1094 return Some(val + offset as usize);
1095 } else {
1096 return val.checked_sub((-offset) as usize);
1097 }
1098 }
1099 if let Some(&(incr_src, incr_amt)) = info.increments.get(&cur) {
1102 stack.push((incr_src, offset + incr_amt as isize));
1103 }
1104 if let Some(&(rem_src, rem_div)) = info.remainders.get(&cur) {
1107 if let Some(src_val) = self.resolve_local_value(rem_src, constraints) {
1108 let rem = src_val % rem_div;
1109 let result = if offset >= 0 {
1110 Some(rem + offset as usize)
1111 } else {
1112 rem.checked_sub((-offset) as usize)
1113 };
1114 if let Some(v) = result {
1115 return Some(v);
1116 }
1117 }
1118 }
1119 }
1120 if let Some(&cast_src) = self.cast_chains.get(&cur) {
1122 stack.push((cast_src, offset));
1123 }
1124 if let Some(&encoded) = self.field_projection_source.get(&cur) {
1126 if let Some((agg_local, field_idx)) = decode_aggregate_field(encoded) {
1127 let key = encode_aggregate_field(agg_local, field_idx);
1128 if let Some(&source) = self.aggregate_field_sources.get(&key) {
1129 stack.push((source, offset));
1130 }
1131 }
1132 }
1133 }
1134 None
1135 }
1136
1137 fn infer_otherwise_value(&self, targets: &SwitchTargets, discr_local: usize) -> Option<usize> {
1141 let body = self.cfg.tcx.optimized_mir(self.cfg.def_id);
1142 let mut discr_ty = body.local_decls[Local::from_usize(discr_local)].ty;
1143 while let TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) = discr_ty.kind() {
1144 discr_ty = *inner;
1145 }
1146
1147 let possible_values: Vec<usize> = match discr_ty.kind() {
1148 TyKind::Bool => vec![0, 1],
1149 TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(),
1150 _ => return None,
1151 };
1152
1153 let explicit_values: FxHashSet<usize> = targets.iter().map(|(v, _)| v as usize).collect();
1154 let remaining: Vec<usize> = possible_values
1155 .into_iter()
1156 .filter(|v| !explicit_values.contains(v))
1157 .collect();
1158
1159 if remaining.len() == 1 {
1160 Some(remaining[0])
1161 } else {
1162 None
1163 }
1164 }
1165
1166 fn is_unwind_target(&self, cur: usize, next: usize) -> bool {
1169 let Some(terminator) = self.cfg.terminator(cur) else {
1170 return false;
1171 };
1172
1173 let unwind = match &terminator.kind {
1174 TerminatorKind::Call { unwind, .. }
1175 | TerminatorKind::Drop { unwind, .. }
1176 | TerminatorKind::Assert { unwind, .. } => unwind,
1177 _ => return false,
1178 };
1179
1180 if let UnwindAction::Cleanup(target) = unwind {
1181 return target.as_usize() == next;
1182 }
1183 false
1184 }
1185
1186 fn local_is_known_nonnull(&self, constraints: &FxHashMap<usize, usize>, local: usize) -> bool {
1188 let Some(&val) = constraints.get(&local) else {
1189 return false;
1190 };
1191 val > 0
1192 }
1193
1194 fn local_is_known_null(&self, constraints: &FxHashMap<usize, usize>, local: usize) -> bool {
1196 let Some(&val) = constraints.get(&local) else {
1197 return false;
1198 };
1199 val == 0
1200 }
1201
1202 fn populate_child_sccs(&mut self, enter: usize) {
1206 let nodes: Vec<usize> = self.cfg.block(enter).scc.nodes.iter().cloned().collect();
1207 let mut child_enters = Vec::new();
1208 let mut seen = FxHashSet::default();
1209
1210 for node in nodes {
1211 if let Some(block) = self.cfg.blocks.get(node) {
1212 let node_enter = block.scc.enter;
1213 let non_trivial = !block.scc.nodes.is_empty();
1214 if node_enter != enter && non_trivial && seen.insert(node_enter) {
1215 child_enters.push(node_enter);
1216 }
1217 }
1218 }
1219
1220 self.cfg.block_mut(enter).scc.child_sccs = child_enters;
1221
1222 let child_count = self.cfg.block(enter).scc.child_sccs.len();
1223 for i in 0..child_count {
1224 let child_enter = self.cfg.block(enter).scc.child_sccs[i];
1225 self.populate_child_sccs(child_enter);
1226 }
1227 }
1228
1229 fn populate_all_child_sccs(&mut self) {
1230 let mut visited = FxHashSet::default();
1231 let block_count = self.cfg.blocks.len();
1232 for i in 0..block_count {
1233 let scc = &self.cfg.block(i).scc;
1234 let enter = scc.enter;
1235 if scc.nodes.is_empty() || !visited.insert(enter) {
1236 continue;
1237 }
1238 self.populate_child_sccs(enter);
1239 }
1240 }
1241}
1242
1243#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
1250pub struct ConstraintHash(u64);
1251
1252impl ConstraintHash {
1253 fn from_path(path: &[usize], graph: &PathGraph<'_>) -> Self {
1254 let mut hasher = DefaultHasher::new();
1255 let mut constraints: FxHashMap<usize, usize> = FxHashMap::default();
1256
1257 for &block in path.iter() {
1258 if let Some(info) = graph.block_info.get(block) {
1259 for local in &info.assigned_locals {
1260 if let Some(&src) = info.constraint_copies.get(local) {
1261 if let Some(&src_val) = constraints.get(&src) {
1262 constraints.insert(*local, src_val);
1263 continue;
1264 }
1265 if let Some(&dst_val) = constraints.get(local) {
1266 constraints.insert(src, dst_val);
1267 constraints.insert(*local, dst_val);
1268 continue;
1269 }
1270 }
1271 if let Some(&val) = info.constants.get(local) {
1272 constraints.insert(*local, val);
1273 continue;
1274 }
1275 constraints.remove(local);
1276 }
1277 }
1278 }
1279
1280 let mut entries: Vec<(usize, usize)> = constraints.into_iter().collect();
1281 entries.sort();
1282 entries.hash(&mut hasher);
1283 ConstraintHash(hasher.finish())
1284 }
1285}
1286
1287#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1295pub struct SccKey {
1296 pub entry: usize,
1297 pub repeat: usize,
1298 pub constraint: ConstraintHash,
1299}
1300
1301pub struct PathEnumerator<'g, 'tcx> {
1326 graph: &'g PathGraph<'tcx>,
1327 scc_paths: FxHashMap<SccKey, Vec<SccPath>>,
1328 visited_sccs: FxHashSet<SccKey>,
1329}
1330
1331impl<'g, 'tcx> PathEnumerator<'g, 'tcx> {
1332 pub fn new(graph: &'g PathGraph<'tcx>) -> Self {
1333 PathEnumerator {
1334 graph,
1335 scc_paths: FxHashMap::default(),
1336 visited_sccs: FxHashSet::default(),
1337 }
1338 }
1339
1340 pub fn enumerate_paths(&mut self) -> PathTree {
1345 self.enumerate_paths_repeat(0)
1346 }
1347
1348 pub fn enumerate_paths_repeat(&mut self, postfix_repeat: usize) -> PathTree {
1352 let mut tree = PathTree::new();
1353
1354 if self.graph.cfg.blocks.is_empty() {
1355 return tree;
1356 }
1357
1358 self.collect_whole_cfg_paths(
1359 0,
1360 &mut vec![0],
1361 &mut tree,
1362 0,
1363 postfix_repeat,
1364 &FxHashMap::default(),
1365 );
1366
1367 tree
1368 }
1369
1370 pub fn find_scc_paths_repeat(
1376 &mut self,
1377 start: usize,
1378 scc: &SccInfo,
1379 postfix_repeat: usize,
1380 ) -> Vec<SccPath> {
1381 let cache_key = SccKey {
1382 entry: scc.enter,
1383 repeat: postfix_repeat,
1384 constraint: ConstraintHash::default(),
1385 };
1386 if let Some(cached) = self.scc_paths.get(&cache_key) {
1387 return cached.clone();
1388 }
1389
1390 let mut out = Vec::new();
1391 let mut seen: FxHashSet<Vec<usize>> = FxHashSet::default();
1392 let mut path = vec![start];
1393 let mut segment_counts = FxHashMap::default();
1394
1395 self.dfs_scc_tree(
1396 scc,
1397 start,
1398 &mut path,
1399 &mut segment_counts,
1400 postfix_repeat,
1401 &mut out,
1402 &mut seen,
1403 0,
1404 );
1405
1406 if self.scc_paths.len() >= SCC_PATH_CACHE_LIMIT {
1407 self.scc_paths.clear();
1408 }
1409 self.scc_paths.insert(cache_key, out.clone());
1410
1411 out
1412 }
1413
1414 #[allow(clippy::too_many_arguments)]
1426 fn dfs_scc_tree(
1427 &mut self,
1428 scc: &SccInfo,
1429 cur: usize,
1430 path: &mut Vec<usize>,
1431 segment_counts: &mut FxHashMap<Vec<usize>, usize>,
1432 postfix_repeat: usize,
1433 out: &mut Vec<SccPath>,
1434 seen_paths: &mut FxHashSet<Vec<usize>>,
1435 depth: usize,
1436 ) {
1437 if depth > SCC_MAX_DEPTH {
1438 return;
1439 }
1440 if out.len() >= SCC_MAX_SEEN_PATHS {
1441 return;
1442 }
1443 if path.len() > SCC_MAX_PATH_LEN {
1444 return;
1445 }
1446 if cur != scc.enter && !scc.nodes.contains(&cur) {
1447 return;
1448 }
1449
1450 if cur == scc.enter && path.len() > 1 {
1451 if !check_postfix_segment(path, scc.enter, segment_counts, postfix_repeat) {
1452 if (postfix_repeat > 0 || segment_counts.len() > 1)
1453 && scc.exits.iter().any(|e| e.exit == cur)
1454 {
1455 self.record_unique_path(path, scc, out, seen_paths);
1456 }
1457 return;
1458 }
1459 }
1460
1461 if scc.exits.iter().any(|e| e.exit == cur) {
1462 self.record_unique_path(path, scc, out, seen_paths);
1463 }
1464
1465 let is_child = scc.child_sccs.contains(&cur);
1466
1467 if is_child {
1468 let ctx = self.constraint_context(path);
1469 if !self.visited_sccs.insert(SccKey {
1470 entry: cur,
1471 repeat: 0,
1472 constraint: ctx,
1473 }) {
1474 return;
1475 }
1476
1477 let child_scc = self.graph.cfg_block(cur).scc.clone();
1478 let child_paths = self.find_scc_paths_repeat(cur, &child_scc, postfix_repeat);
1479
1480 for child_path in &child_paths {
1481 let orig_len = path.len();
1482 if child_path.blocks.len() > 1 {
1483 path.extend(&child_path.blocks[1..]);
1484 }
1485
1486 let mut branch_counts = segment_counts.clone();
1487 for &next in &child_path.exit_successors {
1488 path.push(next);
1489 self.dfs_scc_tree(
1490 scc,
1491 next,
1492 path,
1493 &mut branch_counts,
1494 postfix_repeat,
1495 out,
1496 seen_paths,
1497 depth + 1,
1498 );
1499 path.pop();
1500 }
1501 path.truncate(orig_len);
1502 }
1503 return;
1504 }
1505
1506 let successors: Vec<usize> = self.graph.cfg.block(cur).next.iter().copied().collect();
1507 let saved_counts = segment_counts.clone();
1508 for next in successors {
1509 if next != scc.enter && !scc.nodes.contains(&next) {
1510 self.record_unique_path(path, scc, out, seen_paths);
1511 continue;
1512 }
1513 let mut branch_counts = saved_counts.clone();
1514 path.push(next);
1515 self.dfs_scc_tree(
1516 scc,
1517 next,
1518 path,
1519 &mut branch_counts,
1520 postfix_repeat,
1521 out,
1522 seen_paths,
1523 depth + 1,
1524 );
1525 path.pop();
1526 }
1527 }
1528
1529 fn constraint_context(&self, path: &[usize]) -> ConstraintHash {
1531 ConstraintHash::from_path(path, self.graph)
1532 }
1533
1534 fn collect_whole_cfg_paths(
1542 &mut self,
1543 current: usize,
1544 path: &mut Vec<usize>,
1545 tree: &mut PathTree,
1546 depth: usize,
1547 postfix_repeat: usize,
1548 constraints: &FxHashMap<usize, usize>,
1549 ) {
1550 if current >= self.graph.cfg.blocks.len() {
1551 return;
1552 }
1553 if depth > WHOLE_CFG_PATH_DEPTH_LIMIT || tree.len() >= WHOLE_CFG_PATH_LIMIT {
1554 return;
1555 }
1556
1557 let scc_info = self.graph.cfg_block(current).scc.clone();
1558 let is_scc = current == scc_info.enter && !scc_info.nodes.is_empty();
1559 if is_scc {
1560 let scc = self.sort_scc_tree(&scc_info);
1561 let segments = self.find_scc_paths_repeat(current, &scc, postfix_repeat);
1562
1563 if segments.is_empty() {
1564 tree.insert(path);
1565 return;
1566 }
1567
1568 for seg in segments {
1569 if tree.len() >= WHOLE_CFG_PATH_LIMIT {
1570 break;
1571 }
1572
1573 let orig_len = path.len();
1574 let mut seg_constraints = constraints.clone();
1575 let mut reachable = true;
1576
1577 if seg.blocks.len() > 1 {
1578 for i in 0..seg.blocks.len() - 1 {
1579 if !self.graph.check_transition(
1580 seg.blocks[i],
1581 seg.blocks[i + 1],
1582 &mut seg_constraints,
1583 ) {
1584 reachable = false;
1585 break;
1586 }
1587 }
1588 if reachable {
1589 path.extend_from_slice(&seg.blocks[1..]);
1590 }
1591 }
1592
1593 if reachable {
1594 if seg.exit_successors.is_empty() {
1595 tree.insert(path);
1596 } else {
1597 for &next in &seg.exit_successors {
1598 let mut next_constraints = seg_constraints.clone();
1599 let last = *path.last().unwrap();
1600 if self
1601 .graph
1602 .check_transition(last, next, &mut next_constraints)
1603 {
1604 path.push(next);
1605 self.collect_whole_cfg_paths(
1606 next,
1607 path,
1608 tree,
1609 depth + 1,
1610 postfix_repeat,
1611 &next_constraints,
1612 );
1613 path.pop();
1614 }
1615 }
1616 }
1617 }
1618
1619 path.truncate(orig_len);
1620 }
1621 return;
1622 }
1623
1624 let successors: Vec<usize> = self.graph.cfg_block(current).next.iter().copied().collect();
1626 if successors.is_empty() {
1627 tree.insert(path);
1628 return;
1629 }
1630
1631 for next in successors {
1632 let mut next_constraints = constraints.clone();
1633 if self
1634 .graph
1635 .check_transition(current, next, &mut next_constraints)
1636 {
1637 path.push(next);
1638 self.collect_whole_cfg_paths(
1639 next,
1640 path,
1641 tree,
1642 depth + 1,
1643 postfix_repeat,
1644 &next_constraints,
1645 );
1646 path.pop();
1647 }
1648 }
1649 }
1650
1651 fn sort_scc_tree(&self, scc: &SccInfo) -> SccInfo {
1652 self.graph.cfg_block(scc.enter).scc.clone()
1653 }
1654
1655 fn record_unique_path(
1656 &self,
1657 path: &[usize],
1658 scc: &SccInfo,
1659 out: &mut Vec<SccPath>,
1660 seen_paths: &mut FxHashSet<Vec<usize>>,
1661 ) {
1662 if !seen_paths.insert(path.to_vec()) {
1663 return;
1664 }
1665 let exit_successors = self.compute_exit_successors(path, scc);
1666 out.push(SccPath {
1667 blocks: path.to_vec(),
1668 exit_successors,
1669 });
1670 }
1671
1672 fn compute_exit_successors(&self, path: &[usize], scc: &SccInfo) -> Vec<usize> {
1673 let Some(&last) = path.last() else {
1674 return vec![];
1675 };
1676 scc.exits
1677 .iter()
1678 .filter(|e| e.exit == last)
1679 .map(|e| e.to)
1680 .filter(|&n| {
1681 !scc.child_sccs
1682 .contains(&self.graph.cfg.block(n).scc.enter())
1683 })
1684 .collect()
1685 }
1686}
1687
1688fn resolve_switch_target(targets: &SwitchTargets, val: u128) -> usize {
1691 targets
1692 .iter()
1693 .find(|(v, _)| *v == val)
1694 .map(|(_, bb)| bb.as_usize())
1695 .unwrap_or_else(|| targets.otherwise().as_usize())
1696}