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, Rvalue, StatementKind, SwitchTargets,
10 Terminator, TerminatorKind, 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 comparison_sources: FxHashMap<usize, ComparisonSource>,
83}
84
85#[derive(Clone, Debug)]
88pub struct ComparisonSource {
89 pub op: rustc_middle::mir::BinOp,
90 pub lhs_local: usize,
91 pub rhs_local: usize,
92}
93
94#[derive(Clone, Debug, Default)]
101pub struct DiscriminantInfo {
102 pub source_of: FxHashMap<usize, usize>,
103 pub variant_count_of: FxHashMap<usize, usize>,
104}
105
106#[derive(Clone)]
115pub struct PathGraph<'tcx> {
116 pub cfg: ControlFlowGraph<'tcx>,
117 pub block_info: Vec<BlockConstantInfo>,
118 pub disc_info: DiscriminantInfo,
119}
120
121impl<'tcx> PathGraph<'tcx> {
122 pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> PathGraph<'tcx> {
123 let body = tcx.optimized_mir(def_id);
124 let basicblocks = &body.basic_blocks;
125 let mut cfg_blocks = Vec::<CfgBlock>::new();
126 let mut block_info = Vec::new();
127 let mut disc_info = DiscriminantInfo::default();
128
129 for i in 0..basicblocks.len() {
130 let bb = &basicblocks[BasicBlock::from(i)];
131 let mut cfg_block = CfgBlock::new(i, bb.is_cleanup);
132 let mut info = BlockConstantInfo::default();
133
134 for stmt in &bb.statements {
135 if let StatementKind::Assign(assign) = &stmt.kind {
136 let (place, rvalue) = &**assign;
137 let dest = place.local.as_usize();
138 info.assigned_locals.insert(dest);
139 match rvalue {
140 Rvalue::Use(Operand::Constant(c), ..) => {
141 let typing_env = TypingEnv::post_analysis(tcx, def_id);
142 let val = match c.const_.ty().kind() {
143 TyKind::Bool => c
144 .const_
145 .try_eval_bool(tcx, typing_env)
146 .map(|b| if b { 1 } else { 0 }),
147 TyKind::Int(_) | TyKind::Uint(_) => {
148 c.const_.try_eval_bits(tcx, typing_env).map(|v| v as usize)
149 }
150 _ => None,
151 };
152 if let Some(val) = val {
153 info.constants.insert(dest, val);
154 }
155 }
156 Rvalue::Use(Operand::Copy(src) | Operand::Move(src), ..) => {
157 info.constraint_copies.insert(dest, src.local.as_usize());
158 }
159 Rvalue::Discriminant(rv_place) => {
160 disc_info.source_of.insert(dest, rv_place.local.as_usize());
161 let src_local = rv_place.local.as_usize();
162 if !disc_info.variant_count_of.contains_key(&src_local) {
163 let src_ty = body.local_decls[rv_place.local].ty;
164 if let TyKind::Adt(adt_def, _) = src_ty.kind() {
165 let num = adt_def.variants().len();
166 if num > 0 {
167 disc_info.variant_count_of.insert(src_local, num);
168 }
169 }
170 }
171 }
172 Rvalue::Aggregate(kind, _) => {
173 if let AggregateKind::Adt(_, variant_idx, _, _, _) = kind.as_ref() {
174 let discr = variant_idx.as_usize();
175 info.constants.insert(dest, discr);
176 if !disc_info.variant_count_of.contains_key(&dest) {
177 let dest_ty = body.local_decls[place.local].ty;
178 if let TyKind::Adt(adt_def, _) = dest_ty.kind() {
179 let num = adt_def.variants().len();
180 if num > 0 {
181 disc_info.variant_count_of.insert(dest, num);
182 }
183 }
184 }
185 }
186 }
187 Rvalue::BinaryOp(op, operands)
188 if matches!(
189 op,
190 BinOp::Lt
191 | BinOp::Le
192 | BinOp::Gt
193 | BinOp::Ge
194 | BinOp::Eq
195 | BinOp::Ne
196 ) =>
197 {
198 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) = (&operands.0, &operands.1);
199 let (lhs_local, rhs_local) =
200 match (lhs, rhs) {
201 (Operand::Copy(l) | Operand::Move(l), Operand::Copy(r) | Operand::Move(r)) => {
202 (l.local, r.local)
203 }
204 _ => {
205 continue;
206 }
207 };
208 let lhs_local = lhs_local.as_usize();
209 let rhs_local = rhs_local.as_usize();
210 info.comparison_sources.insert(
211 dest,
212 ComparisonSource {
213 op: *op,
214 lhs_local,
215 rhs_local,
216 },
217 );
218 }
219 _ => {} }
221 }
222 }
223
224 let Some(terminator) = &bb.terminator else {
225 continue;
226 };
227
228 match terminator.kind.clone() {
229 TerminatorKind::Goto { ref target } => {
230 cfg_block.add_next(target.as_usize());
231 }
232 TerminatorKind::SwitchInt {
233 discr: _,
234 ref targets,
235 } => {
236 for (_, ref target) in targets.iter() {
237 cfg_block.add_next(target.as_usize());
238 }
239 cfg_block.add_next(targets.otherwise().as_usize());
240 }
241 TerminatorKind::Drop {
242 place: _,
243 target,
244 unwind,
245 replace: _,
246 drop: _,
247 #[cfg(not(rapx_rustc_ge_198))]
248 async_fut: _,
249 } => {
250 cfg_block.add_next(target.as_usize());
251 if let UnwindAction::Cleanup(target) = unwind {
252 cfg_block.add_next(target.as_usize());
253 }
254 }
255 TerminatorKind::Call {
256 ref target,
257 ref unwind,
258 ..
259 } => {
260 if let Some(tt) = target {
261 cfg_block.add_next(tt.as_usize());
262 }
263 if let UnwindAction::Cleanup(tt) = unwind {
264 cfg_block.add_next(tt.as_usize());
265 }
266 }
267 TerminatorKind::Assert {
268 cond: _,
269 expected: _,
270 msg: _,
271 ref target,
272 ref unwind,
273 } => {
274 cfg_block.add_next(target.as_usize());
275 if let UnwindAction::Cleanup(target) = unwind {
276 cfg_block.add_next(target.as_usize());
277 }
278 }
279 TerminatorKind::Yield {
280 value: _,
281 ref resume,
282 resume_arg: _,
283 ref drop,
284 } => {
285 cfg_block.add_next(resume.as_usize());
286 if let Some(target) = drop {
287 cfg_block.add_next(target.as_usize());
288 }
289 }
290 TerminatorKind::FalseEdge {
291 ref real_target,
292 imaginary_target: _,
293 } => {
294 cfg_block.add_next(real_target.as_usize());
295 }
296 TerminatorKind::FalseUnwind {
297 ref real_target,
298 unwind: _,
299 } => {
300 cfg_block.add_next(real_target.as_usize());
301 }
302 TerminatorKind::InlineAsm {
303 template: _,
304 operands: _,
305 options: _,
306 line_spans: _,
307 ref unwind,
308 targets,
309 asm_macro: _,
310 } => {
311 for target in targets {
312 cfg_block.add_next(target.as_usize());
313 }
314 if let UnwindAction::Cleanup(target) = unwind {
315 cfg_block.add_next(target.as_usize());
316 }
317 }
318 _ => {}
319 }
320
321 cfg_blocks.push(cfg_block);
322 block_info.push(info);
323 }
324
325 let cfg = ControlFlowGraph::new(def_id, tcx, cfg_blocks);
326
327 PathGraph {
328 cfg,
329 block_info,
330 disc_info,
331 }
332 }
333
334 pub fn find_scc(&mut self) {
335 self.cfg.find_scc();
336 self.populate_all_child_sccs();
337 }
338
339 pub fn def_id(&self) -> DefId {
340 self.cfg.def_id
341 }
342
343 pub fn tcx(&self) -> TyCtxt<'tcx> {
344 self.cfg.tcx
345 }
346
347 pub fn cfg_block(&self, index: usize) -> &CfgBlock {
348 self.cfg.block(index)
349 }
350
351 pub fn cfg_block_mut(&mut self, index: usize) -> &mut CfgBlock {
352 self.cfg.block_mut(index)
353 }
354
355 pub fn terminator(&self, index: usize) -> Option<&Terminator<'tcx>> {
357 self.cfg.terminator(index)
358 }
359
360 pub fn is_cleanup_block(&self, index: usize) -> bool {
361 self.cfg
362 .blocks
363 .get(index)
364 .map(|b| b.is_cleanup)
365 .unwrap_or(false)
366 }
367
368 fn get_variant_count(&self, local: usize) -> Option<usize> {
374 if let Some(&count) = self.disc_info.variant_count_of.get(&local) {
375 return Some(count);
376 }
377 let body = self.cfg.tcx.optimized_mir(self.cfg.def_id);
378 let mut ty = body.local_decls[Local::from_usize(local)].ty;
379 while let TyKind::Ref(_, inner_ty, _) | TyKind::RawPtr(inner_ty, _) = ty.kind() {
380 ty = *inner_ty;
381 }
382 match ty.kind() {
383 TyKind::Adt(adt_def, _) if adt_def.is_enum() => Some(adt_def.variants().len()),
384 _ => None,
385 }
386 }
387
388 pub fn check_transition(
392 &self,
393 cur: usize,
394 next: usize,
395 constraints: &mut FxHashMap<usize, usize>,
396 ) -> bool {
397 if cur >= self.cfg.blocks.len() || next >= self.cfg.blocks.len() {
398 return false;
399 }
400
401 if let Some(info) = self.block_info.get(cur) {
402 for local in &info.assigned_locals {
403 if let Some(&src) = info.constraint_copies.get(local) {
404 if let Some(&src_val) = constraints.get(&src) {
405 constraints.insert(*local, src_val);
406 continue;
407 }
408 if let Some(&dst_val) = constraints.get(local) {
409 constraints.insert(src, dst_val);
410 constraints.insert(*local, dst_val);
411 continue;
412 }
413 }
414 if let Some(&val) = info.constants.get(local) {
415 constraints.insert(*local, val);
416 continue;
417 }
418 constraints.remove(local);
419 }
420 }
421
422 if let Some(terminator) = self.terminator(cur) {
427 let assigned = match &terminator.kind {
428 TerminatorKind::Call {
429 destination, ..
430 } => Some(destination.local.as_usize()),
431 TerminatorKind::Yield {
432 resume_arg, ..
433 } => Some(resume_arg.local.as_usize()),
434 _ => None,
435 };
436 if let Some(local) = assigned {
437 constraints.remove(&local);
438 }
439 }
440
441 let successors = &self.cfg.block(cur).next;
442 if !successors.contains(&next) {
443 if !self.is_unwind_target(cur, next) {
444 return false;
445 }
446 }
447
448 if !self.check_switch_transition(cur, next, constraints) {
449 return false;
450 }
451
452 true
453 }
454
455 fn check_switch_transition(
460 &self,
461 cur: usize,
462 next: usize,
463 constraints: &mut FxHashMap<usize, usize>,
464 ) -> bool {
465 let Some(terminator) = self.cfg.terminator(cur) else {
466 return true;
467 };
468
469 match &terminator.kind {
470 TerminatorKind::SwitchInt { discr, targets } => {
471 let discr_local = discr.place().map(|p| p.local.as_usize());
472 let constraint_local = discr_local
473 .and_then(|l| self.disc_info.source_of.get(&l).copied())
474 .or(discr_local);
475
476 let all_targets: FxHashSet<usize> = targets
478 .iter()
479 .map(|(_, bb)| bb.as_usize())
480 .chain(std::iter::once(targets.otherwise().as_usize()))
481 .collect();
482
483 if !all_targets.contains(&next) {
484 return false;
485 }
486
487 let const_val = match discr {
489 Operand::Constant(c) => c
490 .const_
491 .try_eval_target_usize(
492 self.cfg.tcx,
493 TypingEnv::post_analysis(self.cfg.tcx, self.cfg.def_id),
494 )
495 .map(|v| v as usize),
496 _ => None,
497 };
498
499 if let Some(val) = const_val {
500 let expected = resolve_switch_target(targets, val as u128);
503 if next != expected {
504 return false;
505 }
506 if let Some(local) = constraint_local {
507 constraints.insert(local, val);
508 }
509 return true;
510 }
511
512 if let Some(local) = constraint_local {
513 if let Some(&known_val) = constraints.get(&local) {
514 let expected = resolve_switch_target(targets, known_val as u128);
515 if next != expected {
516 return false;
517 }
518 return true;
519 }
520 }
521
522 if next == targets.otherwise().as_usize() {
525 if let Some(local) = constraint_local {
526 if let Some(num_variants) = self.get_variant_count(local) {
527 let all_covered = (0..num_variants)
528 .all(|v| targets.iter().any(|(tv, _)| tv == v as u128));
529 if all_covered {
530 return false;
531 }
532 }
533 }
534 }
535
536 self.learn_constraint_with_backprop(
537 cur,
538 constraint_local,
539 &targets,
540 next,
541 constraints,
542 );
543
544 true
545 }
546 _ => true,
547 }
548 }
549
550 fn learn_constraint_with_backprop(
555 &self,
556 cur: usize,
557 constraint_local: Option<usize>,
558 targets: &SwitchTargets,
559 next: usize,
560 constraints: &mut FxHashMap<usize, usize>,
561 ) {
562 let Some(local) = constraint_local else {
563 return;
564 };
565 let Some((val, _)) = targets.iter().find(|(_, bb)| bb.as_usize() == next) else {
566 if let Some(inferred) = self.infer_otherwise_value(targets, local) {
567 constraints.insert(local, inferred);
568 self.backprop_constraint(cur, local, inferred, constraints);
569 }
570 return;
571 };
572 let val = val as usize;
573 constraints.insert(local, val);
574 self.backprop_constraint(cur, local, val, constraints);
575 }
576
577 fn backprop_constraint(
578 &self,
579 cur: usize,
580 local: usize,
581 val: usize,
582 constraints: &mut FxHashMap<usize, usize>,
583 ) {
584 let Some(info) = self.block_info.get(cur) else {
585 return;
586 };
587 let mut current = local;
588 while let Some(&src) = info.constraint_copies.get(¤t) {
589 if current == src {
590 break;
591 }
592 constraints.insert(src, val);
593 current = src;
594 }
595 }
596
597 fn infer_otherwise_value(&self, targets: &SwitchTargets, discr_local: usize) -> Option<usize> {
601 let body = self.cfg.tcx.optimized_mir(self.cfg.def_id);
602 let mut discr_ty = body.local_decls[Local::from_usize(discr_local)].ty;
603 while let TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) = discr_ty.kind() {
604 discr_ty = *inner;
605 }
606
607 let possible_values: Vec<usize> = match discr_ty.kind() {
608 TyKind::Bool => vec![0, 1],
609 TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(),
610 _ => return None,
611 };
612
613 let explicit_values: FxHashSet<usize> = targets.iter().map(|(v, _)| v as usize).collect();
614 let remaining: Vec<usize> = possible_values
615 .into_iter()
616 .filter(|v| !explicit_values.contains(v))
617 .collect();
618
619 if remaining.len() == 1 {
620 Some(remaining[0])
621 } else {
622 None
623 }
624 }
625
626 fn is_unwind_target(&self, cur: usize, next: usize) -> bool {
629 let Some(terminator) = self.cfg.terminator(cur) else {
630 return false;
631 };
632
633 let unwind = match &terminator.kind {
634 TerminatorKind::Call { unwind, .. }
635 | TerminatorKind::Drop { unwind, .. }
636 | TerminatorKind::Assert { unwind, .. } => unwind,
637 _ => return false,
638 };
639
640 if let UnwindAction::Cleanup(target) = unwind {
641 return target.as_usize() == next;
642 }
643 false
644 }
645
646 fn populate_child_sccs(&mut self, enter: usize) {
650 let nodes: Vec<usize> = self.cfg.block(enter).scc.nodes.iter().cloned().collect();
651 let mut child_enters = Vec::new();
652 let mut seen = FxHashSet::default();
653
654 for node in nodes {
655 if let Some(block) = self.cfg.blocks.get(node) {
656 let node_enter = block.scc.enter;
657 let non_trivial = !block.scc.nodes.is_empty();
658 if node_enter != enter && non_trivial && seen.insert(node_enter) {
659 child_enters.push(node_enter);
660 }
661 }
662 }
663
664 self.cfg.block_mut(enter).scc.child_sccs = child_enters;
665
666 let child_count = self.cfg.block(enter).scc.child_sccs.len();
667 for i in 0..child_count {
668 let child_enter = self.cfg.block(enter).scc.child_sccs[i];
669 self.populate_child_sccs(child_enter);
670 }
671 }
672
673 fn populate_all_child_sccs(&mut self) {
674 let mut visited = FxHashSet::default();
675 let block_count = self.cfg.blocks.len();
676 for i in 0..block_count {
677 let scc = &self.cfg.block(i).scc;
678 let enter = scc.enter;
679 if scc.nodes.is_empty() || !visited.insert(enter) {
680 continue;
681 }
682 self.populate_child_sccs(enter);
683 }
684 }
685}
686
687#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
694pub struct ConstraintHash(u64);
695
696impl ConstraintHash {
697 fn from_path(path: &[usize], graph: &PathGraph<'_>) -> Self {
698 let mut hasher = DefaultHasher::new();
699 let mut constraints: FxHashMap<usize, usize> = FxHashMap::default();
700
701 for &block in path.iter() {
702 if let Some(info) = graph.block_info.get(block) {
703 for local in &info.assigned_locals {
704 if let Some(&src) = info.constraint_copies.get(local) {
705 if let Some(&src_val) = constraints.get(&src) {
706 constraints.insert(*local, src_val);
707 continue;
708 }
709 if let Some(&dst_val) = constraints.get(local) {
710 constraints.insert(src, dst_val);
711 constraints.insert(*local, dst_val);
712 continue;
713 }
714 }
715 if let Some(&val) = info.constants.get(local) {
716 constraints.insert(*local, val);
717 continue;
718 }
719 constraints.remove(local);
720 }
721 }
722 }
723
724 let mut entries: Vec<(usize, usize)> = constraints.into_iter().collect();
725 entries.sort();
726 entries.hash(&mut hasher);
727 ConstraintHash(hasher.finish())
728 }
729}
730
731#[derive(Debug, Clone, Hash, PartialEq, Eq)]
739pub struct SccKey {
740 pub entry: usize,
741 pub repeat: usize,
742 pub constraint: ConstraintHash,
743}
744
745pub struct PathEnumerator<'g, 'tcx> {
770 graph: &'g PathGraph<'tcx>,
771 scc_paths: FxHashMap<SccKey, Vec<SccPath>>,
772 visited_sccs: FxHashSet<SccKey>,
773}
774
775impl<'g, 'tcx> PathEnumerator<'g, 'tcx> {
776 pub fn new(graph: &'g PathGraph<'tcx>) -> Self {
777 PathEnumerator {
778 graph,
779 scc_paths: FxHashMap::default(),
780 visited_sccs: FxHashSet::default(),
781 }
782 }
783
784 pub fn enumerate_paths(&mut self) -> PathTree {
789 self.enumerate_paths_repeat(0)
790 }
791
792 pub fn enumerate_paths_repeat(&mut self, postfix_repeat: usize) -> PathTree {
796 let mut tree = PathTree::new();
797
798 if self.graph.cfg.blocks.is_empty() {
799 return tree;
800 }
801
802 self.collect_whole_cfg_paths(
803 0,
804 &mut vec![0],
805 &mut tree,
806 0,
807 postfix_repeat,
808 &FxHashMap::default(),
809 );
810
811 tree
812 }
813
814 pub fn find_scc_paths_repeat(
820 &mut self,
821 start: usize,
822 scc: &SccInfo,
823 postfix_repeat: usize,
824 ) -> Vec<SccPath> {
825 let cache_key = SccKey {
826 entry: scc.enter,
827 repeat: postfix_repeat,
828 constraint: ConstraintHash::default(),
829 };
830 if let Some(cached) = self.scc_paths.get(&cache_key) {
831 return cached.clone();
832 }
833
834 let mut out = Vec::new();
835 let mut seen: FxHashSet<Vec<usize>> = FxHashSet::default();
836 let mut path = vec![start];
837 let mut segment_counts = FxHashMap::default();
838
839 self.dfs_scc_tree(
840 scc,
841 start,
842 &mut path,
843 &mut segment_counts,
844 postfix_repeat,
845 &mut out,
846 &mut seen,
847 0,
848 );
849
850 if self.scc_paths.len() >= SCC_PATH_CACHE_LIMIT {
851 self.scc_paths.clear();
852 }
853 self.scc_paths.insert(cache_key, out.clone());
854
855 out
856 }
857
858 #[allow(clippy::too_many_arguments)]
870 fn dfs_scc_tree(
871 &mut self,
872 scc: &SccInfo,
873 cur: usize,
874 path: &mut Vec<usize>,
875 segment_counts: &mut FxHashMap<Vec<usize>, usize>,
876 postfix_repeat: usize,
877 out: &mut Vec<SccPath>,
878 seen_paths: &mut FxHashSet<Vec<usize>>,
879 depth: usize,
880 ) {
881 if depth > SCC_MAX_DEPTH {
882 return;
883 }
884 if out.len() >= SCC_MAX_SEEN_PATHS {
885 return;
886 }
887 if path.len() > SCC_MAX_PATH_LEN {
888 return;
889 }
890 if cur != scc.enter && !scc.nodes.contains(&cur) {
891 return;
892 }
893
894 if cur == scc.enter && path.len() > 1 {
895 if !check_postfix_segment(path, scc.enter, segment_counts, postfix_repeat) {
896 if (postfix_repeat > 0 || segment_counts.len() > 1)
897 && scc.exits.iter().any(|e| e.exit == cur)
898 {
899 self.record_unique_path(path, scc, out, seen_paths);
900 }
901 return;
902 }
903 }
904
905 if scc.exits.iter().any(|e| e.exit == cur) {
906 self.record_unique_path(path, scc, out, seen_paths);
907 }
908
909 let is_child = scc.child_sccs.contains(&cur);
910
911 if is_child {
912 let ctx = self.constraint_context(path);
913 if !self.visited_sccs.insert(SccKey {
914 entry: cur,
915 repeat: 0,
916 constraint: ctx,
917 }) {
918 return;
919 }
920
921 let child_scc = self.graph.cfg_block(cur).scc.clone();
922 let child_paths = self.find_scc_paths_repeat(cur, &child_scc, postfix_repeat);
923
924 for child_path in &child_paths {
925 let orig_len = path.len();
926 if child_path.blocks.len() > 1 {
927 path.extend(&child_path.blocks[1..]);
928 }
929
930 let mut branch_counts = segment_counts.clone();
931 for &next in &child_path.exit_successors {
932 path.push(next);
933 self.dfs_scc_tree(
934 scc,
935 next,
936 path,
937 &mut branch_counts,
938 postfix_repeat,
939 out,
940 seen_paths,
941 depth + 1,
942 );
943 path.pop();
944 }
945 path.truncate(orig_len);
946 }
947 return;
948 }
949
950 let successors: Vec<usize> = self.graph.cfg.block(cur).next.iter().copied().collect();
951 let saved_counts = segment_counts.clone();
952 for next in successors {
953 if next != scc.enter && !scc.nodes.contains(&next) {
954 self.record_unique_path(path, scc, out, seen_paths);
955 continue;
956 }
957 let mut branch_counts = saved_counts.clone();
958 path.push(next);
959 self.dfs_scc_tree(
960 scc,
961 next,
962 path,
963 &mut branch_counts,
964 postfix_repeat,
965 out,
966 seen_paths,
967 depth + 1,
968 );
969 path.pop();
970 }
971 }
972
973 fn constraint_context(&self, path: &[usize]) -> ConstraintHash {
975 ConstraintHash::from_path(path, self.graph)
976 }
977
978 fn collect_whole_cfg_paths(
986 &mut self,
987 current: usize,
988 path: &mut Vec<usize>,
989 tree: &mut PathTree,
990 depth: usize,
991 postfix_repeat: usize,
992 constraints: &FxHashMap<usize, usize>,
993 ) {
994 if current >= self.graph.cfg.blocks.len() {
995 return;
996 }
997 if depth > WHOLE_CFG_PATH_DEPTH_LIMIT || tree.len() >= WHOLE_CFG_PATH_LIMIT {
998 return;
999 }
1000
1001 let scc_info = self.graph.cfg_block(current).scc.clone();
1002 let is_scc = current == scc_info.enter && !scc_info.nodes.is_empty();
1003 if is_scc {
1004 let scc = self.sort_scc_tree(&scc_info);
1005 let segments = self.find_scc_paths_repeat(current, &scc, postfix_repeat);
1006
1007 if segments.is_empty() {
1008 tree.insert(path);
1009 return;
1010 }
1011
1012 for seg in segments {
1013 if tree.len() >= WHOLE_CFG_PATH_LIMIT {
1014 break;
1015 }
1016
1017 let orig_len = path.len();
1018 let mut seg_constraints = constraints.clone();
1019 let mut reachable = true;
1020
1021 if seg.blocks.len() > 1 {
1022 for i in 0..seg.blocks.len() - 1 {
1023 if !self.graph.check_transition(
1024 seg.blocks[i],
1025 seg.blocks[i + 1],
1026 &mut seg_constraints,
1027 ) {
1028 reachable = false;
1029 break;
1030 }
1031 }
1032 if reachable {
1033 path.extend_from_slice(&seg.blocks[1..]);
1034 }
1035 }
1036
1037 if reachable {
1038 if seg.exit_successors.is_empty() {
1039 tree.insert(path);
1040 } else {
1041 for &next in &seg.exit_successors {
1042 let mut next_constraints = seg_constraints.clone();
1043 let last = *path.last().unwrap();
1044 if self
1045 .graph
1046 .check_transition(last, next, &mut next_constraints)
1047 {
1048 path.push(next);
1049 self.collect_whole_cfg_paths(
1050 next,
1051 path,
1052 tree,
1053 depth + 1,
1054 postfix_repeat,
1055 &next_constraints,
1056 );
1057 path.pop();
1058 }
1059 }
1060 }
1061 }
1062
1063 path.truncate(orig_len);
1064 }
1065 return;
1066 }
1067
1068 let successors: Vec<usize> = self.graph.cfg_block(current).next.iter().copied().collect();
1070 if successors.is_empty() {
1071 tree.insert(path);
1072 return;
1073 }
1074
1075 for next in successors {
1076 let mut next_constraints = constraints.clone();
1077 if self
1078 .graph
1079 .check_transition(current, next, &mut next_constraints)
1080 {
1081 path.push(next);
1082 self.collect_whole_cfg_paths(
1083 next,
1084 path,
1085 tree,
1086 depth + 1,
1087 postfix_repeat,
1088 &next_constraints,
1089 );
1090 path.pop();
1091 }
1092 }
1093 }
1094
1095 fn sort_scc_tree(&self, scc: &SccInfo) -> SccInfo {
1096 self.graph.cfg_block(scc.enter).scc.clone()
1097 }
1098
1099 fn record_unique_path(
1100 &self,
1101 path: &[usize],
1102 scc: &SccInfo,
1103 out: &mut Vec<SccPath>,
1104 seen_paths: &mut FxHashSet<Vec<usize>>,
1105 ) {
1106 if !seen_paths.insert(path.to_vec()) {
1107 return;
1108 }
1109 let exit_successors = self.compute_exit_successors(path, scc);
1110 out.push(SccPath {
1111 blocks: path.to_vec(),
1112 exit_successors,
1113 });
1114 }
1115
1116 fn compute_exit_successors(&self, path: &[usize], scc: &SccInfo) -> Vec<usize> {
1117 let Some(&last) = path.last() else {
1118 return vec![];
1119 };
1120 scc.exits
1121 .iter()
1122 .filter(|e| e.exit == last)
1123 .map(|e| e.to)
1124 .filter(|&n| {
1125 !scc.child_sccs
1126 .contains(&self.graph.cfg.block(n).scc.enter())
1127 })
1128 .collect()
1129 }
1130}
1131
1132fn resolve_switch_target(targets: &SwitchTargets, val: u128) -> usize {
1135 targets
1136 .iter()
1137 .find(|(v, _)| *v == val)
1138 .map(|(_, bb)| bb.as_usize())
1139 .unwrap_or_else(|| targets.otherwise().as_usize())
1140}