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
18use crate::limit::{
19 LOCAL_INLINE_BLOCK_LIMIT, SCC_MAX_DEPTH, SCC_MAX_PATH_LEN, SCC_MAX_SEEN_PATHS,
20 SCC_PATH_CACHE_LIMIT, WHOLE_CFG_PATH_DEPTH_LIMIT,
21};
22
23fn check_postfix_segment(
29 path: &[usize],
30 enter: usize,
31 segment_counts: &mut FxHashMap<Vec<usize>, usize>,
32 max_repeats: usize,
33) -> bool {
34 let segment = extract_segment(path, enter);
35 let count = segment_counts.entry(segment).or_insert(0);
36 *count += 1;
37 *count == 1 || *count - 1 <= max_repeats
38}
39
40fn extract_segment(path: &[usize], enter: usize) -> Vec<usize> {
41 let prev_pos = path[..path.len() - 1]
42 .iter()
43 .rposition(|&node| node == enter)
44 .unwrap_or(0);
45 path[prev_pos + 1..path.len() - 1].to_vec()
46}
47
48#[derive(Clone, Debug)]
49pub struct SccPath {
60 pub blocks: Vec<usize>,
61 pub exit_successors: Vec<usize>,
62}
63
64#[derive(Clone, Debug, Default)]
68pub struct BlockConstantInfo {
69 pub assigned_locals: FxHashSet<usize>,
70 pub constants: FxHashMap<usize, usize>,
71 pub constraint_copies: FxHashMap<usize, usize>,
72 pub increments: FxHashMap<usize, (usize, usize)>,
74 pub remainders: FxHashMap<usize, (usize, usize)>,
76 pub comparison_sources: FxHashMap<usize, ComparisonSource>,
79 pub known_nonnull_locals: FxHashSet<usize>,
82 pub negation_sources: FxHashMap<usize, usize>,
83 pub and_sources: FxHashMap<usize, (usize, usize)>,
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 pub rhs_is_constant: bool,
93}
94
95const AGGREGATE_FIELD_MULT: usize = 256;
97const AGGREGATE_FIELD_SENTINEL: usize = 1 << (usize::BITS as usize - 1);
98
99fn encode_aggregate_field(local: usize, field: usize) -> usize {
100 debug_assert!(field < AGGREGATE_FIELD_MULT);
101 AGGREGATE_FIELD_SENTINEL | (local * AGGREGATE_FIELD_MULT + field)
102}
103
104fn decode_aggregate_field(encoded: usize) -> Option<(usize, usize)> {
105 if encoded & AGGREGATE_FIELD_SENTINEL == 0 {
106 return None;
107 }
108 let raw = encoded & !AGGREGATE_FIELD_SENTINEL;
109 Some((raw / AGGREGATE_FIELD_MULT, raw % AGGREGATE_FIELD_MULT))
110}
111
112fn first_field_projection(place: &rustc_middle::mir::Place<'_>) -> Option<usize> {
113 for proj in place.projection.iter() {
114 if let rustc_middle::mir::ProjectionElem::Field(field, _) = proj {
115 return Some(field.as_usize());
116 }
117 }
118 None
119}
120
121#[derive(Clone, Debug, Default)]
128pub struct DiscriminantInfo {
129 pub source_of: FxHashMap<usize, usize>,
130 pub variant_count_of: FxHashMap<usize, usize>,
131}
132
133#[derive(Clone)]
142pub struct PathGraph<'tcx> {
143 pub cfg: ControlFlowGraph<'tcx>,
144 pub block_info: Vec<BlockConstantInfo>,
145 pub disc_info: DiscriminantInfo,
146 pub aggregate_field_sources: FxHashMap<usize, usize>,
148 pub cast_chains: FxHashMap<usize, usize>,
150 pub field_projection_source: FxHashMap<usize, usize>,
152 pub inline_bindings: FxHashMap<usize, InlineBinding>,
155 pub inline_parents: FxHashMap<usize, DefId>,
158 pub inlined_call_blocks: FxHashSet<usize>,
161 local_bases: FxHashMap<DefId, usize>,
167 next_local_base: usize,
169}
170
171#[derive(Clone, Debug)]
173pub struct InlineBinding {
174 pub arg_locals: Vec<usize>,
176 pub dest_local: usize,
178}
179
180#[allow(clippy::type_complexity)]
186fn build_function_info(
187 tcx: TyCtxt<'_>,
188 def_id: DefId,
189 local_base: usize,
190) -> (
191 Vec<BlockConstantInfo>,
192 DiscriminantInfo,
193 FxHashMap<usize, usize>,
194 FxHashMap<usize, usize>,
195 FxHashMap<usize, usize>,
196) {
197 let body = tcx.optimized_mir(def_id);
198 let basicblocks = &body.basic_blocks;
199 let mut block_info = Vec::new();
200 let mut disc_info = DiscriminantInfo::default();
201 let mut aggregate_field_sources: FxHashMap<usize, usize> = FxHashMap::default();
202 let mut field_projection_source: FxHashMap<usize, usize> = FxHashMap::default();
203 let mut cast_chains: FxHashMap<usize, usize> = FxHashMap::default();
204
205 for i in 0..basicblocks.len() {
206 let bb = &basicblocks[BasicBlock::from(i)];
207 let mut info = BlockConstantInfo::default();
208
209 for stmt in &bb.statements {
210 if let StatementKind::Assign(assign) = &stmt.kind {
211 let (place, rvalue) = &**assign;
212 let dest = local_base + place.local.as_usize();
213 let is_deref = place
216 .projection
217 .iter()
218 .any(|p| matches!(p, ProjectionElem::Deref));
219 if !is_deref {
220 info.assigned_locals.insert(dest);
221 }
222 match rvalue {
223 Rvalue::Use(Operand::Constant(c), ..) => {
224 let typing_env = TypingEnv::post_analysis(tcx, def_id);
225 let val = match c.const_.ty().kind() {
226 TyKind::Bool => c
227 .const_
228 .try_eval_bool(tcx, typing_env)
229 .map(|b| if b { 1 } else { 0 }),
230 TyKind::Int(_) | TyKind::Uint(_) => {
231 c.const_.try_eval_bits(tcx, typing_env).map(|v| v as usize)
232 }
233 _ => None,
234 };
235 if let Some(val) = val {
236 info.constants.insert(dest, val);
237 }
238 }
239 Rvalue::Use(Operand::Copy(src) | Operand::Move(src), ..) => {
240 let src_local = local_base + src.local.as_usize();
241 if let Some(field_proj) = first_field_projection(src) {
242 let encoded = encode_aggregate_field(src_local, field_proj);
243 field_projection_source.insert(dest, encoded);
244 }
245 info.constraint_copies.insert(dest, src_local);
246 }
247 Rvalue::Discriminant(rv_place) => {
248 let src_local = local_base + rv_place.local.as_usize();
249 disc_info.source_of.insert(dest, src_local);
250 if !disc_info.variant_count_of.contains_key(&src_local) {
251 let src_ty = body.local_decls[rv_place.local].ty;
252 if let TyKind::Adt(adt_def, _) = src_ty.kind() {
253 let num = adt_def.variants().len();
254 if num > 0 {
255 disc_info.variant_count_of.insert(src_local, num);
256 }
257 }
258 }
259 }
260 Rvalue::Aggregate(kind, operands) => {
261 if let AggregateKind::Adt(_, _, _, _, _) = kind.as_ref() {
262 let agg_local = local_base + place.local.as_usize();
263 for (field_idx, operand) in operands.iter().enumerate() {
264 if let Operand::Copy(src) | Operand::Move(src) = operand {
265 let key = encode_aggregate_field(agg_local, field_idx);
266 let src_local = local_base + src.local.as_usize();
267 aggregate_field_sources.insert(key, src_local);
268 }
269 }
270 }
271 let discr = match kind.as_ref() {
272 AggregateKind::Adt(_, variant_idx, _, _, _) => Some(variant_idx.as_usize()),
273 _ => None,
274 };
275 if let Some(discr) = discr {
276 info.constants.insert(dest, discr);
277 if !disc_info.variant_count_of.contains_key(&dest) {
278 let dest_ty = body.local_decls[place.local].ty;
279 if let TyKind::Adt(adt_def, _) = dest_ty.kind() {
280 let num = adt_def.variants().len();
281 if num > 0 {
282 disc_info.variant_count_of.insert(dest, num);
283 }
284 }
285 }
286 }
287 }
288 Rvalue::BinaryOp(op, operands) if matches!(op, BinOp::AddWithOverflow) => {
289 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) =
290 (&operands.0, &operands.1);
291 if let Some(lhs_local) = match lhs {
292 Operand::Copy(l) | Operand::Move(l) if l.projection.is_empty() => {
293 Some(local_base + l.local.as_usize())
294 }
295 _ => None,
296 } {
297 let incr = match rhs {
298 Operand::Constant(c) => {
299 let typing_env = TypingEnv::post_analysis(tcx, def_id);
300 c.const_.try_eval_bits(tcx, typing_env).map(|v| v as usize)
301 }
302 _ => None,
303 };
304 if let Some(incr) = incr {
305 info.increments.insert(dest, (lhs_local, incr));
306 }
307 }
308 }
309 Rvalue::BinaryOp(op, operands)
310 if matches!(
311 op,
312 BinOp::Lt
313 | BinOp::Le
314 | BinOp::Gt
315 | BinOp::Ge
316 | BinOp::Eq
317 | BinOp::Ne
318 | BinOp::BitAnd
319 ) =>
320 {
321 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) =
322 (&operands.0, &operands.1);
323 let lhs_local = match lhs {
324 Operand::Copy(l) | Operand::Move(l) if l.projection.is_empty() => {
325 Some(local_base + l.local.as_usize())
326 }
327 _ => None,
328 };
329 if let Some(lhs_local) = lhs_local {
330 let rhs_eval = match rhs {
331 Operand::Constant(c) => {
332 let typing_env = TypingEnv::post_analysis(tcx, def_id);
333 c.const_
334 .try_eval_bits(tcx, typing_env)
335 .map(|v| (v as usize, true))
336 }
337 Operand::Copy(r) | Operand::Move(r)
338 if r.projection.is_empty() =>
339 {
340 Some((local_base + r.local.as_usize(), false))
341 }
342 _ => None,
343 };
344 let Some((rhs_local, rhs_is_constant)) = rhs_eval else {
345 continue;
346 };
347 info.comparison_sources.insert(
348 dest,
349 ComparisonSource {
350 op: *op,
351 lhs_local,
352 rhs_local,
353 rhs_is_constant,
354 },
355 );
356 if matches!(op, BinOp::BitAnd)
357 && matches!(body.local_decls[place.local].ty.kind(), TyKind::Bool)
358 {
359 info.and_sources.insert(dest, (lhs_local, rhs_local));
360 }
361 }
362 }
363 Rvalue::BinaryOp(op, operands) if matches!(op, BinOp::Rem) => {
364 let (lhs, rhs): (&Operand<'_>, &Operand<'_>) =
365 (&operands.0, &operands.1);
366 if let Some(lhs_local) = match lhs {
367 Operand::Copy(l) | Operand::Move(l) if l.projection.is_empty() => {
368 Some(local_base + l.local.as_usize())
369 }
370 _ => None,
371 } {
372 let divisor = match rhs {
373 Operand::Constant(c) => {
374 let typing_env = TypingEnv::post_analysis(tcx, def_id);
375 c.const_.try_eval_bits(tcx, typing_env).map(|v| v as usize)
376 }
377 _ => None,
378 };
379 if let Some(divisor) = divisor {
380 if divisor != 0 {
381 info.remainders.insert(dest, (lhs_local, divisor));
382 }
383 }
384 }
385 }
386 Rvalue::UnaryOp(unop, operand) => {
387 if matches!(unop, UnOp::Not)
388 && let Operand::Copy(src) | Operand::Move(src) = operand
389 {
390 info.negation_sources
391 .insert(dest, local_base + src.local.as_usize());
392 }
393 }
394 Rvalue::Cast(_, operand, _) => {
395 if let Operand::Copy(src) | Operand::Move(src) = operand
396 && matches!(
397 body.local_decls[place.local].ty.kind(),
398 TyKind::RawPtr(..) | TyKind::Int(..) | TyKind::Uint(..)
399 )
400 {
401 cast_chains.insert(dest, local_base + src.local.as_usize());
402 }
403 }
404 _ => {} }
406 }
407 }
408
409 if let Some(terminator) = &bb.terminator {
410 if let TerminatorKind::Call {
411 destination,
412 ref func,
413 ..
414 } = terminator.kind
415 {
416 let name = crate::helpers::mir_utils::call_name(tcx, func);
417 if name.contains("::into_raw")
418 || (name.contains("::new") && name.contains("Box"))
419 || name.contains("::as_mut_ptr")
420 || name.contains("::as_ptr")
421 {
422 info.known_nonnull_locals
423 .insert(local_base + destination.local.as_usize());
424 }
425 if name.contains("null_mut") || (name.contains("null") && name.contains("ptr::")) {
426 info.constants
427 .insert(local_base + destination.local.as_usize(), 0);
428 }
429 }
430 }
431
432 block_info.push(info);
433 }
434
435 (
436 block_info,
437 disc_info,
438 aggregate_field_sources,
439 field_projection_source,
440 cast_chains,
441 )
442}
443
444impl<'tcx> PathGraph<'tcx> {
445 pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> PathGraph<'tcx> {
446 let body = tcx.optimized_mir(def_id);
447 let basicblocks = &body.basic_blocks;
448 let mut cfg_blocks = Vec::<CfgBlock>::new();
449
450 for i in 0..basicblocks.len() {
451 let bb = &basicblocks[BasicBlock::from(i)];
452 let mut cfg_block = CfgBlock::new(def_id, i, bb.is_cleanup);
453
454 let Some(terminator) = &bb.terminator else {
455 cfg_blocks.push(cfg_block);
456 continue;
457 };
458
459 match terminator.kind.clone() {
460 TerminatorKind::Goto { ref target } => {
461 cfg_block.add_next(target.as_usize());
462 }
463 TerminatorKind::SwitchInt {
464 discr: _,
465 ref targets,
466 } => {
467 for (_, ref target) in targets.iter() {
468 cfg_block.add_next(target.as_usize());
469 }
470 cfg_block.add_next(targets.otherwise().as_usize());
471 }
472 TerminatorKind::Drop {
473 place: _,
474 target,
475 unwind,
476 replace: _,
477 drop: _,
478 #[cfg(not(rapx_ge_99))]
479 async_fut: _,
480 } => {
481 cfg_block.add_next(target.as_usize());
482 if let UnwindAction::Cleanup(target) = unwind {
483 cfg_block.add_next(target.as_usize());
484 }
485 }
486 TerminatorKind::Call {
487 ref target,
488 ref unwind,
489 ..
490 } => {
491 if let Some(tt) = target {
492 cfg_block.add_next(tt.as_usize());
493 }
494 if let UnwindAction::Cleanup(tt) = unwind {
495 cfg_block.add_next(tt.as_usize());
496 }
497 }
498 TerminatorKind::Assert {
499 cond: _,
500 expected: _,
501 msg: _,
502 ref target,
503 ref unwind,
504 } => {
505 cfg_block.add_next(target.as_usize());
506 if let UnwindAction::Cleanup(target) = unwind {
507 cfg_block.add_next(target.as_usize());
508 }
509 }
510 TerminatorKind::Yield {
511 value: _,
512 ref resume,
513 resume_arg: _,
514 ref drop,
515 } => {
516 cfg_block.add_next(resume.as_usize());
517 if let Some(target) = drop {
518 cfg_block.add_next(target.as_usize());
519 }
520 }
521 TerminatorKind::FalseEdge {
522 ref real_target,
523 imaginary_target: _,
524 } => {
525 cfg_block.add_next(real_target.as_usize());
526 }
527 TerminatorKind::FalseUnwind {
528 ref real_target,
529 unwind: _,
530 } => {
531 cfg_block.add_next(real_target.as_usize());
532 }
533 TerminatorKind::InlineAsm {
534 template: _,
535 operands: _,
536 options: _,
537 line_spans: _,
538 ref unwind,
539 targets,
540 asm_macro: _,
541 } => {
542 for target in targets {
543 cfg_block.add_next(target.as_usize());
544 }
545 if let UnwindAction::Cleanup(target) = unwind {
546 cfg_block.add_next(target.as_usize());
547 }
548 }
549 _ => {}
550 }
551
552 cfg_blocks.push(cfg_block);
553 }
554
555 let cfg = ControlFlowGraph::new(def_id, tcx, cfg_blocks);
556 let (block_info, disc_info, aggregate_field_sources, field_projection_source, cast_chains) =
557 build_function_info(tcx, def_id, 0);
558
559 let mut local_bases = FxHashMap::default();
560 local_bases.insert(def_id, 0);
561
562 PathGraph {
563 cfg,
564 block_info,
565 disc_info,
566 aggregate_field_sources,
567 field_projection_source,
568 cast_chains,
569 inline_bindings: FxHashMap::default(),
570 inline_parents: FxHashMap::default(),
571 inlined_call_blocks: FxHashSet::default(),
572 local_bases,
573 next_local_base: body.local_decls.len(),
574 }
575 }
576
577 pub fn inline_callees(&mut self) {
605 let tcx = self.tcx();
606 let caller_def_id = self.def_id();
607
608 let mut expanded: FxHashSet<DefId> = FxHashSet::default();
609 expanded.insert(caller_def_id);
610
611 loop {
612 let mut pending: Vec<(usize, DefId)> = Vec::new();
613 for i in 0..self.cfg.blocks.len() {
614 if self.cfg.block(i).def_id.as_local().is_none() {
617 continue;
618 }
619 let Some(term) = self.terminator(i) else {
620 continue;
621 };
622 let TerminatorKind::Call { func, target, .. } = &term.kind else {
623 continue;
624 };
625 if target.is_none() {
629 continue;
630 }
631 let Some(base_callee) = crate::helpers::mir_utils::dep_callee_def_id(func) else {
637 continue;
638 };
639 let cross_crate = base_callee.as_local().is_none();
640 let callee = if cross_crate {
641 base_callee
642 } else {
643 crate::helpers::mir_utils::dep_callee_resolved_def_id(
644 tcx,
645 self.cfg.block(i).def_id,
646 func,
647 )
648 .unwrap_or(base_callee)
649 };
650 let has_fn_sim =
651 crate::verify::call_summary::builtin_models::is_modeled(Some(callee));
652 let is_container_as_ptr =
659 crate::verify::api_classify::is_container_as_ptr(tcx, callee);
660 if !tcx.is_mir_available(callee)
661 || expanded.contains(&callee)
662 || has_fn_sim
663 || is_container_as_ptr
664 {
665 continue;
666 }
667 let small_local = !cross_crate && {
674 let body = tcx.optimized_mir(callee);
675 body.basic_blocks.len() <= LOCAL_INLINE_BLOCK_LIMIT
676 };
677 if cross_crate || small_local {
678 pending.push((i, callee));
679 }
680 }
681
682 if pending.is_empty() {
683 break;
684 }
685
686 for (_, callee) in &pending {
690 expanded.insert(*callee);
691 }
692
693 for (caller_idx, callee) in pending {
694 self.inline_one(caller_idx, callee);
695 }
696 }
697 }
698
699 fn inline_one(&mut self, caller_idx: usize, callee: DefId) {
702 let tcx = self.tcx();
703 let caller_base = caller_idx - self.cfg.block(caller_idx).local_index;
706 let (caller_target, caller_unwind, arg_locals, dest_local) = match self.terminator(caller_idx)
707 {
708 Some(term) => match &term.kind {
709 TerminatorKind::Call {
710 target,
711 unwind,
712 args,
713 destination,
714 ..
715 } => {
716 let target = target.map(|t| caller_base + t.as_usize());
717 let unwind = match unwind {
718 UnwindAction::Cleanup(t) => Some(caller_base + t.as_usize()),
719 _ => None,
720 };
721 let arg_locals: Vec<usize> = args
722 .iter()
723 .filter_map(|a| a.node.place().map(|p| p.local.as_usize()))
724 .collect();
725 (target, unwind, arg_locals, destination.local.as_usize())
726 }
727 _ => (None, None, Vec::new(), 0),
728 },
729 None => (None, None, Vec::new(), 0),
730 };
731 let Some(target) = caller_target else {
732 return;
733 };
734
735 let body = tcx.optimized_mir(callee);
736 let base = self.cfg.blocks.len();
737 let block_count = body.basic_blocks.len();
738
739 let local_base = self.assign_local_base(callee);
742 let (callee_info, callee_disc, callee_agg, callee_fproj, callee_cast) =
743 build_function_info(tcx, callee, local_base);
744
745 for i in 0..block_count {
746 let bb = &body.basic_blocks[BasicBlock::from(i)];
747 let mut cb = CfgBlock {
748 index: base + i,
749 def_id: callee,
750 local_index: i,
751 is_cleanup: bb.is_cleanup,
752 next: FxHashSet::default(),
753 scc: SccInfo::new(base + i),
754 };
755 if let Some(term) = &bb.terminator {
756 match &term.kind {
757 TerminatorKind::Goto { target: t } => {
758 cb.add_next(base + t.as_usize());
759 }
760 TerminatorKind::SwitchInt { targets, .. } => {
761 for (_, t) in targets.iter() {
762 cb.add_next(base + t.as_usize());
763 }
764 cb.add_next(base + targets.otherwise().as_usize());
765 }
766 TerminatorKind::Return => {
767 cb.add_next(target);
768 }
769 TerminatorKind::UnwindResume => {
770 if let Some(u) = caller_unwind {
774 cb.add_next(u);
775 }
776 }
777 TerminatorKind::Call {
778 target: t,
779 unwind,
780 ..
781 } => {
782 if let Some(t) = t {
783 cb.add_next(base + t.as_usize());
784 }
785 if let UnwindAction::Cleanup(tt) = unwind {
786 cb.add_next(base + tt.as_usize());
787 }
788 }
789 TerminatorKind::Drop {
790 target: t, unwind, ..
791 } => {
792 cb.add_next(base + t.as_usize());
793 if let UnwindAction::Cleanup(tt) = unwind {
794 cb.add_next(base + tt.as_usize());
795 }
796 }
797 TerminatorKind::Assert {
798 target: t,
799 unwind,
800 ..
801 } => {
802 cb.add_next(base + t.as_usize());
803 if let UnwindAction::Cleanup(tt) = unwind {
804 cb.add_next(base + tt.as_usize());
805 }
806 }
807 TerminatorKind::FalseEdge { real_target, .. }
808 | TerminatorKind::FalseUnwind { real_target, .. } => {
809 cb.add_next(base + real_target.as_usize());
810 }
811 _ => {}
812 }
813 }
814 self.cfg.blocks.push(cb);
815 }
816
817 self.block_info.extend(callee_info);
820 self.disc_info.source_of.extend(callee_disc.source_of);
821 self.disc_info.variant_count_of.extend(callee_disc.variant_count_of);
822 self.aggregate_field_sources.extend(callee_agg);
823 self.field_projection_source.extend(callee_fproj);
824 self.cast_chains.extend(callee_cast);
825
826 self.inline_bindings.insert(
828 base,
829 InlineBinding {
830 arg_locals,
831 dest_local,
832 },
833 );
834 self.inline_parents
835 .insert(base, self.cfg.block(caller_idx).def_id);
836 self.inlined_call_blocks.insert(caller_idx);
837
838 let caller_block = self.cfg.block_mut(caller_idx);
840 caller_block.next.remove(&target);
841 caller_block.next.insert(base);
842 }
843
844 pub fn find_scc(&mut self) {
845 self.cfg.find_scc();
846 self.populate_all_child_sccs();
847 }
848
849 pub fn def_id(&self) -> DefId {
850 self.cfg.def_id
851 }
852
853 pub fn tcx(&self) -> TyCtxt<'tcx> {
854 self.cfg.tcx
855 }
856
857 pub fn cfg_block(&self, index: usize) -> &CfgBlock {
858 self.cfg.block(index)
859 }
860
861 pub fn cfg_block_mut(&mut self, index: usize) -> &mut CfgBlock {
862 self.cfg.block_mut(index)
863 }
864
865 pub fn terminator(&self, index: usize) -> Option<&Terminator<'tcx>> {
867 self.cfg.terminator(index)
868 }
869
870 pub fn is_cleanup_block(&self, index: usize) -> bool {
871 self.cfg
872 .blocks
873 .get(index)
874 .map(|b| b.is_cleanup)
875 .unwrap_or(false)
876 }
877
878 fn local_base_of(&self, def_id: DefId) -> usize {
881 self.local_bases.get(&def_id).copied().unwrap_or(0)
882 }
883
884 fn remap_local(&self, def_id: DefId, local: usize) -> usize {
886 self.local_base_of(def_id) + local
887 }
888
889 fn assign_local_base(&mut self, def_id: DefId) -> usize {
892 if let Some(&base) = self.local_bases.get(&def_id) {
893 return base;
894 }
895 let base = self.next_local_base;
896 let local_count = self.tcx().optimized_mir(def_id).local_decls.len();
897 self.local_bases.insert(def_id, base);
898 self.next_local_base = base + local_count;
899 base
900 }
901
902 fn get_variant_count(&self, local: usize, def_id: DefId) -> Option<usize> {
908 if let Some(&count) = self.disc_info.variant_count_of.get(&local) {
909 return Some(count);
910 }
911 let body = self.cfg.tcx.optimized_mir(def_id);
912 let original = local - self.local_base_of(def_id);
913 let mut ty = body.local_decls[Local::from_usize(original)].ty;
914 while let TyKind::Ref(_, inner_ty, _) | TyKind::RawPtr(inner_ty, _) = ty.kind() {
915 ty = *inner_ty;
916 }
917 match ty.kind() {
918 TyKind::Adt(adt_def, _) if adt_def.is_enum() => Some(adt_def.variants().len()),
919 _ => None,
920 }
921 }
922
923 pub fn check_transition(
927 &self,
928 cur: usize,
929 next: usize,
930 constraints: &mut FxHashMap<usize, usize>,
931 ) -> bool {
932 if cur >= self.cfg.blocks.len() || next >= self.cfg.blocks.len() {
933 return false;
934 }
935
936 if let Some(info) = self.block_info.get(cur) {
937 for local in &info.assigned_locals {
938 if let Some(&src) = info.constraint_copies.get(local) {
939 if let Some(val) = self.resolve_local_value(src, constraints) {
940 constraints.insert(*local, val);
941 continue;
942 }
943 if let Some(&dst_val) = constraints.get(local) {
944 constraints.insert(src, dst_val);
945 constraints.insert(*local, dst_val);
946 continue;
947 }
948 }
949 if let Some(&val) = info.constants.get(local) {
950 constraints.insert(*local, val);
951 continue;
952 }
953 constraints.remove(local);
954 }
955 for local in &info.known_nonnull_locals {
956 constraints.insert(*local, usize::MAX);
957 }
958 }
959
960 if let Some(terminator) = self.terminator(cur) {
965 let local_base = self.local_base_of(self.cfg.block(cur).def_id);
966 let assigned = match &terminator.kind {
967 TerminatorKind::Call { destination, .. } => {
968 Some(local_base + destination.local.as_usize())
969 }
970 TerminatorKind::Yield { resume_arg, .. } => {
971 Some(local_base + resume_arg.local.as_usize())
972 }
973 _ => None,
974 };
975 if let Some(local) = assigned {
976 if let Some(block_info) = self.block_info.get(cur) {
978 if let Some(&val) = block_info.constants.get(&local) {
979 constraints.insert(local, val);
980 } else if block_info.known_nonnull_locals.contains(&local) {
981 constraints.insert(local, usize::MAX);
982 } else {
983 constraints.remove(&local);
984 }
985 } else {
986 constraints.remove(&local);
987 }
988 }
989 }
990
991 let successors = &self.cfg.block(cur).next;
992 if !successors.contains(&next) {
993 if !self.is_unwind_target(cur, next) {
994 return false;
995 }
996 }
997
998 if !self.check_switch_transition(cur, next, constraints) {
999 return false;
1000 }
1001
1002 if !self.check_assert_transition(cur, next, constraints) {
1003 return false;
1004 }
1005
1006 true
1007 }
1008
1009 fn check_assert_transition(
1010 &self,
1011 cur: usize,
1012 next: usize,
1013 constraints: &FxHashMap<usize, usize>,
1014 ) -> bool {
1015 let Some(terminator) = self.cfg.terminator(cur) else {
1016 return true;
1017 };
1018 let TerminatorKind::Assert { cond, target, .. } = &terminator.kind else {
1019 return true;
1020 };
1021 let base = cur - self.cfg.block(cur).local_index;
1022 if next != base + target.as_usize() {
1023 return true;
1024 }
1025 let cond_local = match cond {
1026 Operand::Copy(p) | Operand::Move(p) => {
1027 self.remap_local(self.cfg.block(cur).def_id, p.local.as_usize())
1028 }
1029 Operand::Constant(c) => {
1030 let typing_env =
1031 rustc_middle::ty::TypingEnv::post_analysis(self.cfg.tcx, self.cfg.def_id);
1032 return c
1033 .const_
1034 .try_eval_bool(self.cfg.tcx, typing_env)
1035 .unwrap_or(true);
1036 }
1037 #[cfg(rapx_ge_95)]
1038 Operand::RuntimeChecks(_) => return true,
1039 };
1040 self.resolve_bool_local(cond_local, constraints)
1041 .is_none_or(|v| v == 1)
1042 }
1043
1044 fn resolve_simple_bool(
1045 &self,
1046 local: usize,
1047 constraints: &FxHashMap<usize, usize>,
1048 ) -> Option<usize> {
1049 if let Some(&val) = constraints.get(&local)
1050 && val <= 1
1051 {
1052 return Some(val);
1053 }
1054 for info in &self.block_info {
1055 if let Some(&val) = info.constants.get(&local)
1056 && val <= 1
1057 {
1058 return Some(val);
1059 }
1060 if let Some(cmp) = info.comparison_sources.get(&local) {
1061 if matches!(cmp.op, BinOp::Eq | BinOp::Ne) {
1062 let is_eq = matches!(cmp.op, BinOp::Eq);
1063 if cmp.rhs_is_constant {
1064 if let Some(lhs_val) = self.resolve_local_value(cmp.lhs_local, constraints)
1065 {
1066 return Some(if is_eq {
1067 if lhs_val == cmp.rhs_local { 1 } else { 0 }
1068 } else {
1069 if lhs_val != cmp.rhs_local { 1 } else { 0 }
1070 });
1071 }
1072 } else {
1073 let lhs_val = self.resolve_local_value(cmp.lhs_local, constraints);
1074 let rhs_val = self.resolve_local_value(cmp.rhs_local, constraints);
1075 if let Some(lhs_val) = lhs_val {
1076 if let Some(rhs_val) = rhs_val {
1077 return Some(if is_eq {
1078 if lhs_val == rhs_val { 1 } else { 0 }
1079 } else {
1080 if lhs_val != rhs_val { 1 } else { 0 }
1081 });
1082 }
1083 } else if let Some(rhs_val) = rhs_val {
1084 return Some(if is_eq {
1085 if cmp.lhs_local == rhs_val { 1 } else { 0 }
1086 } else {
1087 if cmp.lhs_local != rhs_val { 1 } else { 0 }
1088 });
1089 }
1090 }
1091 }
1092 }
1093 }
1094 None
1095 }
1096
1097 fn resolve_bool_local(
1098 &self,
1099 local: usize,
1100 constraints: &FxHashMap<usize, usize>,
1101 ) -> Option<usize> {
1102 let mut stack = vec![(local, false)];
1103 let mut seen = FxHashSet::default();
1104 while let Some((cur, negated)) = stack.pop() {
1105 let key = if negated { cur | (1 << 31) } else { cur };
1106 if !seen.insert(key) {
1107 continue;
1108 }
1109 if let Some(v) = self.resolve_simple_bool(cur, constraints) {
1110 return Some(if negated { 1 - v } else { v });
1111 }
1112 for info in &self.block_info {
1113 if let Some(&src) = info.constraint_copies.get(&cur) {
1114 stack.push((src, negated));
1115 }
1116 if let Some(&src) = info.negation_sources.get(&cur) {
1117 stack.push((src, !negated));
1118 }
1119 if let Some(&(lhs, rhs)) = info.and_sources.get(&cur) {
1120 let a = self.resolve_simple_bool(lhs, constraints);
1121 let b = self.resolve_simple_bool(rhs, constraints);
1122 if let (Some(a), Some(b)) = (a, b) {
1123 let r = if a == 1 && b == 1 { 1 } else { 0 };
1124 return Some(if negated { 1 - r } else { r });
1125 }
1126 }
1127 }
1128 if let Some(&src) = self.cast_chains.get(&cur) {
1129 stack.push((src, negated));
1130 }
1131 }
1132 None
1133 }
1134
1135 fn check_switch_transition(
1140 &self,
1141 cur: usize,
1142 next: usize,
1143 constraints: &mut FxHashMap<usize, usize>,
1144 ) -> bool {
1145 let Some(terminator) = self.cfg.terminator(cur) else {
1146 return true;
1147 };
1148 let base = cur - self.cfg.block(cur).local_index;
1152
1153 match &terminator.kind {
1154 TerminatorKind::SwitchInt { discr, targets } => {
1155 let discr_local = discr
1158 .place()
1159 .map(|p| self.remap_local(self.cfg.block(cur).def_id, p.local.as_usize()));
1160 let constraint_local = discr_local
1161 .and_then(|l| self.disc_info.source_of.get(&l).copied())
1162 .or(discr_local);
1163
1164 let all_targets: FxHashSet<usize> = targets
1166 .iter()
1167 .map(|(_, bb)| base + bb.as_usize())
1168 .chain(std::iter::once(base + targets.otherwise().as_usize()))
1169 .collect();
1170
1171 if !all_targets.contains(&next) {
1172 return false;
1173 }
1174
1175 let const_val = match discr {
1177 Operand::Constant(c) => c
1178 .const_
1179 .try_eval_target_usize(
1180 self.cfg.tcx,
1181 TypingEnv::post_analysis(self.cfg.tcx, self.cfg.def_id),
1182 )
1183 .map(|v| v as usize),
1184 _ => None,
1185 };
1186
1187 if let Some(val) = const_val {
1188 let expected = resolve_switch_target(targets, val as u128);
1191 if next != base + expected {
1192 return false;
1193 }
1194 if let Some(local) = constraint_local {
1195 constraints.insert(local, val);
1196 }
1197 return true;
1198 }
1199
1200 if let Some(local) = constraint_local {
1201 if let Some(&known_val) = constraints.get(&local) {
1202 let expected = resolve_switch_target(targets, known_val as u128);
1203 if next != base + expected {
1204 return false;
1205 }
1206 return true;
1207 }
1208 }
1209
1210 if let Some(discr_local) = discr_local
1213 && let Some(info) = self.block_info.get(cur)
1214 && let Some(cmp) = info.comparison_sources.get(&discr_local)
1215 && matches!(cmp.op, BinOp::Ne | BinOp::Eq)
1216 {
1217 let is_ne = matches!(cmp.op, BinOp::Ne);
1218 let pointer_is_nonnull =
1219 self.local_is_known_nonnull(constraints, cmp.lhs_local);
1220 let pointer_is_null = self.local_is_known_null(constraints, cmp.lhs_local);
1221 if pointer_is_nonnull {
1222 let expected_val = if is_ne { 1 } else { 0 };
1223 let expected = resolve_switch_target(targets, expected_val);
1224 let val = expected_val as usize;
1225 constraints.insert(discr_local, val);
1226 if let Some(local) = constraint_local {
1227 constraints.insert(local, val);
1228 }
1229 if next != base + expected {
1230 return false;
1231 }
1232 return true;
1233 }
1234 if pointer_is_null {
1235 let expected_val = if is_ne { 0 } else { 1 };
1236 let expected = resolve_switch_target(targets, expected_val);
1237 let val = expected_val as usize;
1238 constraints.insert(discr_local, val);
1239 if let Some(local) = constraint_local {
1240 constraints.insert(local, val);
1241 }
1242 if next != base + expected {
1243 return false;
1244 }
1245 return true;
1246 }
1247
1248 let is_eq = matches!(cmp.op, BinOp::Eq);
1251 let lhs_val = self.resolve_local_value(cmp.lhs_local, constraints);
1252 let rhs_val = if cmp.rhs_is_constant {
1253 Some(cmp.rhs_local)
1254 } else {
1255 self.resolve_local_value(cmp.rhs_local, constraints)
1256 };
1257 if let (Some(lhs), Some(rhs)) = (lhs_val, rhs_val) {
1258 let val = if is_eq {
1259 if lhs == rhs { 1 } else { 0 }
1260 } else {
1261 if lhs != rhs { 1 } else { 0 }
1262 };
1263 let expected = resolve_switch_target(targets, val as u128);
1264 if let Some(local) = constraint_local {
1265 constraints.insert(local, val);
1266 }
1267 if next != base + expected {
1268 return false;
1269 }
1270 return true;
1271 }
1272 }
1273
1274 if let Some(discr_local) = discr_local
1281 && let Some(info) = self.block_info.get(cur)
1282 && let Some(cmp) = info.comparison_sources.get(&discr_local)
1283 && matches!(cmp.op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge)
1284 {
1285 let lhs_val = self.resolve_local_value_direct(cmp.lhs_local, constraints);
1287 let rhs_val = if cmp.rhs_is_constant {
1288 Some(cmp.rhs_local)
1289 } else {
1290 self.resolve_local_value_direct(cmp.rhs_local, constraints)
1291 };
1292 if let (Some(lhs), Some(rhs)) = (lhs_val, rhs_val) {
1293 let val = match cmp.op {
1294 BinOp::Lt => {
1295 if lhs < rhs {
1296 1
1297 } else {
1298 0
1299 }
1300 }
1301 BinOp::Le => {
1302 if lhs <= rhs {
1303 1
1304 } else {
1305 0
1306 }
1307 }
1308 BinOp::Gt => {
1309 if lhs > rhs {
1310 1
1311 } else {
1312 0
1313 }
1314 }
1315 BinOp::Ge => {
1316 if lhs >= rhs {
1317 1
1318 } else {
1319 0
1320 }
1321 }
1322 _ => unreachable!(),
1323 };
1324 let expected = resolve_switch_target(targets, val as u128);
1325 if let Some(local) = constraint_local {
1326 constraints.insert(local, val);
1327 }
1328 if next != base + expected {
1329 return false;
1330 }
1331 return true;
1332 }
1333 }
1334
1335 if next == targets.otherwise().as_usize() {
1338 if let Some(local) = constraint_local {
1339 if let Some(num_variants) =
1340 self.get_variant_count(local, self.cfg.block(cur).def_id)
1341 {
1342 let all_covered = (0..num_variants)
1343 .all(|v| targets.iter().any(|(tv, _)| tv == v as u128));
1344 if all_covered {
1345 return false;
1346 }
1347 }
1348 }
1349 }
1350
1351 self.learn_constraint_with_backprop(
1352 cur,
1353 constraint_local,
1354 targets,
1355 next,
1356 constraints,
1357 );
1358
1359 true
1360 }
1361 _ => true,
1362 }
1363 }
1364
1365 fn learn_constraint_with_backprop(
1370 &self,
1371 cur: usize,
1372 constraint_local: Option<usize>,
1373 targets: &SwitchTargets,
1374 next: usize,
1375 constraints: &mut FxHashMap<usize, usize>,
1376 ) {
1377 let Some(local) = constraint_local else {
1378 return;
1379 };
1380 let Some((val, _)) = targets.iter().find(|(_, bb)| bb.as_usize() == next) else {
1381 if let Some(inferred) = self.infer_otherwise_value(cur, targets, local) {
1382 constraints.insert(local, inferred);
1383 self.backprop_constraint(cur, local, inferred, constraints);
1384 }
1385 return;
1386 };
1387 let val = val as usize;
1388 constraints.insert(local, val);
1389 self.backprop_constraint(cur, local, val, constraints);
1390 }
1391
1392 fn backprop_constraint(
1393 &self,
1394 cur: usize,
1395 local: usize,
1396 val: usize,
1397 constraints: &mut FxHashMap<usize, usize>,
1398 ) {
1399 let Some(info) = self.block_info.get(cur) else {
1400 return;
1401 };
1402 let mut current = local;
1403 while let Some(&src) = info.constraint_copies.get(¤t) {
1404 if current == src {
1405 break;
1406 }
1407 constraints.insert(src, val);
1408 current = src;
1409 }
1410 }
1411
1412 fn resolve_local_value_direct(
1417 &self,
1418 local: usize,
1419 constraints: &FxHashMap<usize, usize>,
1420 ) -> Option<usize> {
1421 let mut stack = vec![local];
1422 let mut seen = FxHashSet::default();
1423 while let Some(cur) = stack.pop() {
1424 if !seen.insert(cur) {
1425 continue;
1426 }
1427 if let Some(&val) = constraints.get(&cur) {
1428 if val != usize::MAX {
1429 return Some(val);
1430 }
1431 }
1432 for info in &self.block_info {
1433 if let Some(&src) = info.constraint_copies.get(&cur) {
1434 stack.push(src);
1435 }
1436 if let Some(&val) = info.constants.get(&cur) {
1437 return Some(val);
1438 }
1439 }
1440 if let Some(&cast_src) = self.cast_chains.get(&cur) {
1441 stack.push(cast_src);
1442 }
1443 }
1444 None
1445 }
1446
1447 fn resolve_local_value(
1448 &self,
1449 local: usize,
1450 constraints: &FxHashMap<usize, usize>,
1451 ) -> Option<usize> {
1452 let mut stack = vec![(local, 0isize)];
1453 let mut seen = FxHashSet::default();
1454 while let Some((cur, offset)) = stack.pop() {
1455 if !seen.insert(cur) {
1456 continue;
1457 }
1458 if let Some(&val) = constraints.get(&cur) {
1459 if val == usize::MAX {
1460 } else if offset >= 0 {
1462 return Some(val + offset as usize);
1463 } else {
1464 return val.checked_sub((-offset) as usize);
1465 }
1466 }
1467 for info in &self.block_info {
1469 if let Some(&src) = info.constraint_copies.get(&cur) {
1470 stack.push((src, offset));
1471 }
1472 if let Some(&val) = info.constants.get(&cur) {
1473 if offset >= 0 {
1474 return Some(val + offset as usize);
1475 } else {
1476 return val.checked_sub((-offset) as usize);
1477 }
1478 }
1479 if let Some(&(incr_src, incr_amt)) = info.increments.get(&cur) {
1482 stack.push((incr_src, offset + incr_amt as isize));
1483 }
1484 if let Some(&(rem_src, rem_div)) = info.remainders.get(&cur) {
1487 if let Some(src_val) = self.resolve_local_value(rem_src, constraints) {
1488 let rem = src_val % rem_div;
1489 let result = if offset >= 0 {
1490 Some(rem + offset as usize)
1491 } else {
1492 rem.checked_sub((-offset) as usize)
1493 };
1494 if let Some(v) = result {
1495 return Some(v);
1496 }
1497 }
1498 }
1499 }
1500 if let Some(&cast_src) = self.cast_chains.get(&cur) {
1502 stack.push((cast_src, offset));
1503 }
1504 if let Some(&encoded) = self.field_projection_source.get(&cur) {
1506 if let Some((agg_local, field_idx)) = decode_aggregate_field(encoded) {
1507 let key = encode_aggregate_field(agg_local, field_idx);
1508 if let Some(&source) = self.aggregate_field_sources.get(&key) {
1509 stack.push((source, offset));
1510 }
1511 }
1512 }
1513 }
1514 None
1515 }
1516
1517 fn infer_otherwise_value(
1521 &self,
1522 cur: usize,
1523 targets: &SwitchTargets,
1524 discr_local: usize,
1525 ) -> Option<usize> {
1526 let body = self.cfg.tcx.optimized_mir(self.cfg.block(cur).def_id);
1527 let def_id = self.cfg.block(cur).def_id;
1528 let original = discr_local - self.local_base_of(def_id);
1529 let mut discr_ty = body.local_decls[Local::from_usize(original)].ty;
1530 while let TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) = discr_ty.kind() {
1531 discr_ty = *inner;
1532 }
1533
1534 let possible_values: Vec<usize> = match discr_ty.kind() {
1535 TyKind::Bool => vec![0, 1],
1536 TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(),
1537 _ => return None,
1538 };
1539
1540 let explicit_values: FxHashSet<usize> = targets.iter().map(|(v, _)| v as usize).collect();
1541 let remaining: Vec<usize> = possible_values
1542 .into_iter()
1543 .filter(|v| !explicit_values.contains(v))
1544 .collect();
1545
1546 if remaining.len() == 1 {
1547 Some(remaining[0])
1548 } else {
1549 None
1550 }
1551 }
1552
1553 fn is_unwind_target(&self, cur: usize, next: usize) -> bool {
1556 let Some(terminator) = self.cfg.terminator(cur) else {
1557 return false;
1558 };
1559
1560 let unwind = match &terminator.kind {
1561 TerminatorKind::Call { unwind, .. }
1562 | TerminatorKind::Drop { unwind, .. }
1563 | TerminatorKind::Assert { unwind, .. } => unwind,
1564 _ => return false,
1565 };
1566
1567 if let UnwindAction::Cleanup(target) = unwind {
1568 let base = cur - self.cfg.block(cur).local_index;
1569 return base + target.as_usize() == next;
1570 }
1571 false
1572 }
1573
1574 fn local_is_known_nonnull(&self, constraints: &FxHashMap<usize, usize>, local: usize) -> bool {
1576 let Some(&val) = constraints.get(&local) else {
1577 return false;
1578 };
1579 val > 0
1580 }
1581
1582 fn local_is_known_null(&self, constraints: &FxHashMap<usize, usize>, local: usize) -> bool {
1584 let Some(&val) = constraints.get(&local) else {
1585 return false;
1586 };
1587 val == 0
1588 }
1589
1590 fn populate_child_sccs(&mut self, enter: usize) {
1594 let nodes: Vec<usize> = self.cfg.block(enter).scc.nodes.iter().cloned().collect();
1595 let mut child_enters = Vec::new();
1596 let mut seen = FxHashSet::default();
1597
1598 for node in nodes {
1599 if let Some(block) = self.cfg.blocks.get(node) {
1600 let node_enter = block.scc.enter;
1601 let non_trivial = !block.scc.nodes.is_empty();
1602 if node_enter != enter && non_trivial && seen.insert(node_enter) {
1603 child_enters.push(node_enter);
1604 }
1605 }
1606 }
1607
1608 self.cfg.block_mut(enter).scc.child_sccs = child_enters;
1609
1610 let child_count = self.cfg.block(enter).scc.child_sccs.len();
1611 for i in 0..child_count {
1612 let child_enter = self.cfg.block(enter).scc.child_sccs[i];
1613 self.populate_child_sccs(child_enter);
1614 }
1615 }
1616
1617 fn populate_all_child_sccs(&mut self) {
1618 let mut visited = FxHashSet::default();
1619 let block_count = self.cfg.blocks.len();
1620 for i in 0..block_count {
1621 let scc = &self.cfg.block(i).scc;
1622 let enter = scc.enter;
1623 if scc.nodes.is_empty() || !visited.insert(enter) {
1624 continue;
1625 }
1626 self.populate_child_sccs(enter);
1627 }
1628 }
1629}
1630
1631#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
1638pub struct ConstraintHash(u64);
1639
1640impl ConstraintHash {
1641 fn from_path(path: &[usize], graph: &PathGraph<'_>) -> Self {
1642 let mut hasher = DefaultHasher::new();
1643 let mut constraints: FxHashMap<usize, usize> = FxHashMap::default();
1644
1645 for &block in path.iter() {
1646 if let Some(info) = graph.block_info.get(block) {
1647 for local in &info.assigned_locals {
1648 if let Some(&src) = info.constraint_copies.get(local) {
1649 if let Some(&src_val) = constraints.get(&src) {
1650 constraints.insert(*local, src_val);
1651 continue;
1652 }
1653 if let Some(&dst_val) = constraints.get(local) {
1654 constraints.insert(src, dst_val);
1655 constraints.insert(*local, dst_val);
1656 continue;
1657 }
1658 }
1659 if let Some(&val) = info.constants.get(local) {
1660 constraints.insert(*local, val);
1661 continue;
1662 }
1663 constraints.remove(local);
1664 }
1665 }
1666 }
1667
1668 let mut entries: Vec<(usize, usize)> = constraints.into_iter().collect();
1669 entries.sort();
1670 entries.hash(&mut hasher);
1671 ConstraintHash(hasher.finish())
1672 }
1673}
1674
1675#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1683pub struct SccKey {
1684 pub entry: usize,
1685 pub repeat: usize,
1686 pub constraint: ConstraintHash,
1687}
1688
1689pub struct PathEnumerator<'g, 'tcx> {
1714 graph: &'g PathGraph<'tcx>,
1715 scc_paths: FxHashMap<SccKey, Vec<SccPath>>,
1716 visited_sccs: FxHashSet<SccKey>,
1717}
1718
1719impl<'g, 'tcx> PathEnumerator<'g, 'tcx> {
1720 pub fn new(graph: &'g PathGraph<'tcx>) -> Self {
1721 PathEnumerator {
1722 graph,
1723 scc_paths: FxHashMap::default(),
1724 visited_sccs: FxHashSet::default(),
1725 }
1726 }
1727
1728 pub fn enumerate_paths(&mut self) -> PathTree {
1733 self.enumerate_paths_repeat(0)
1734 }
1735
1736 pub fn enumerate_paths_repeat(&mut self, postfix_repeat: usize) -> PathTree {
1743 let mut tree = PathTree::new();
1744
1745 if self.graph.cfg.blocks.is_empty() {
1746 return tree;
1747 }
1748
1749 self.collect_whole_cfg_paths(
1750 0,
1751 &mut vec![0],
1752 &mut tree,
1753 0,
1754 postfix_repeat,
1755 &FxHashMap::default(),
1756 );
1757
1758 tree
1759 }
1760
1761 pub fn find_scc_paths_repeat(
1767 &mut self,
1768 start: usize,
1769 scc: &SccInfo,
1770 postfix_repeat: usize,
1771 ) -> Vec<SccPath> {
1772 let cache_key = SccKey {
1773 entry: scc.enter,
1774 repeat: postfix_repeat,
1775 constraint: ConstraintHash::default(),
1776 };
1777 if let Some(cached) = self.scc_paths.get(&cache_key) {
1778 return cached.clone();
1779 }
1780
1781 let mut out = Vec::new();
1782 let mut seen: FxHashSet<Vec<usize>> = FxHashSet::default();
1783 let mut path = vec![start];
1784 let mut segment_counts = FxHashMap::default();
1785
1786 self.dfs_scc_tree(
1787 scc,
1788 start,
1789 &mut path,
1790 &mut segment_counts,
1791 postfix_repeat,
1792 &mut out,
1793 &mut seen,
1794 0,
1795 );
1796
1797 if self.scc_paths.len() >= SCC_PATH_CACHE_LIMIT {
1798 self.scc_paths.clear();
1799 }
1800 self.scc_paths.insert(cache_key, out.clone());
1801
1802 out
1803 }
1804
1805 #[allow(clippy::too_many_arguments)]
1817 fn dfs_scc_tree(
1818 &mut self,
1819 scc: &SccInfo,
1820 cur: usize,
1821 path: &mut Vec<usize>,
1822 segment_counts: &mut FxHashMap<Vec<usize>, usize>,
1823 postfix_repeat: usize,
1824 out: &mut Vec<SccPath>,
1825 seen_paths: &mut FxHashSet<Vec<usize>>,
1826 depth: usize,
1827 ) {
1828 if depth > SCC_MAX_DEPTH {
1829 return;
1830 }
1831 if out.len() >= SCC_MAX_SEEN_PATHS {
1832 return;
1833 }
1834 if path.len() > SCC_MAX_PATH_LEN {
1835 return;
1836 }
1837 if cur != scc.enter && !scc.nodes.contains(&cur) {
1838 return;
1839 }
1840
1841 if cur == scc.enter && path.len() > 1 {
1842 if !check_postfix_segment(path, scc.enter, segment_counts, postfix_repeat) {
1843 if (postfix_repeat > 0 || segment_counts.len() > 1)
1844 && scc.exits.iter().any(|e| e.exit == cur)
1845 {
1846 self.record_unique_path(path, scc, out, seen_paths);
1847 }
1848 return;
1849 }
1850 }
1851
1852 if scc.exits.iter().any(|e| e.exit == cur) {
1853 self.record_unique_path(path, scc, out, seen_paths);
1854 }
1855
1856 let is_child = scc.child_sccs.contains(&cur);
1857
1858 if is_child {
1859 let ctx = self.constraint_context(path);
1860 if !self.visited_sccs.insert(SccKey {
1861 entry: cur,
1862 repeat: 0,
1863 constraint: ctx,
1864 }) {
1865 return;
1866 }
1867
1868 let child_scc = self.graph.cfg_block(cur).scc.clone();
1869 let child_paths = self.find_scc_paths_repeat(cur, &child_scc, postfix_repeat);
1870
1871 for child_path in &child_paths {
1872 let orig_len = path.len();
1873 if child_path.blocks.len() > 1 {
1874 path.extend(&child_path.blocks[1..]);
1875 }
1876
1877 let mut branch_counts = segment_counts.clone();
1878 for &next in &child_path.exit_successors {
1879 path.push(next);
1880 self.dfs_scc_tree(
1881 scc,
1882 next,
1883 path,
1884 &mut branch_counts,
1885 postfix_repeat,
1886 out,
1887 seen_paths,
1888 depth + 1,
1889 );
1890 path.pop();
1891 }
1892 path.truncate(orig_len);
1893 }
1894 return;
1895 }
1896
1897 let successors: Vec<usize> = self.graph.cfg.block(cur).next.iter().copied().collect();
1898 let saved_counts = segment_counts.clone();
1899 for next in successors {
1900 if next != scc.enter && !scc.nodes.contains(&next) {
1901 self.record_unique_path(path, scc, out, seen_paths);
1902 continue;
1903 }
1904 let mut branch_counts = saved_counts.clone();
1905 path.push(next);
1906 self.dfs_scc_tree(
1907 scc,
1908 next,
1909 path,
1910 &mut branch_counts,
1911 postfix_repeat,
1912 out,
1913 seen_paths,
1914 depth + 1,
1915 );
1916 path.pop();
1917 }
1918 }
1919
1920 fn constraint_context(&self, path: &[usize]) -> ConstraintHash {
1922 ConstraintHash::from_path(path, self.graph)
1923 }
1924
1925 fn collect_whole_cfg_paths(
1933 &mut self,
1934 current: usize,
1935 path: &mut Vec<usize>,
1936 tree: &mut PathTree,
1937 depth: usize,
1938 postfix_repeat: usize,
1939 constraints: &FxHashMap<usize, usize>,
1940 ) {
1941 if current >= self.graph.cfg.blocks.len() {
1942 return;
1943 }
1944 if depth > WHOLE_CFG_PATH_DEPTH_LIMIT
1945 || tree.len() >= crate::limit::path_limit()
1946 {
1947 tree.mark_truncated();
1948 return;
1949 }
1950
1951 let scc_info = self.graph.cfg_block(current).scc.clone();
1952 let is_scc = current == scc_info.enter && !scc_info.nodes.is_empty();
1953 if is_scc {
1954 let scc = self.sort_scc_tree(&scc_info);
1955 let segments = self.find_scc_paths_repeat(current, &scc, postfix_repeat);
1956
1957 if segments.is_empty() {
1958 tree.insert(path);
1959 return;
1960 }
1961
1962 for seg in segments {
1963 if tree.len() >= crate::limit::path_limit() {
1964 tree.mark_truncated();
1965 break;
1966 }
1967
1968 let orig_len = path.len();
1969 let mut seg_constraints = constraints.clone();
1970 let mut reachable = true;
1971
1972 if seg.blocks.len() > 1 {
1973 for i in 0..seg.blocks.len() - 1 {
1974 if !self.graph.check_transition(
1975 seg.blocks[i],
1976 seg.blocks[i + 1],
1977 &mut seg_constraints,
1978 ) {
1979 reachable = false;
1980 break;
1981 }
1982 }
1983 if reachable {
1984 path.extend_from_slice(&seg.blocks[1..]);
1985 }
1986 }
1987
1988 if reachable {
1989 if seg.exit_successors.is_empty() {
1990 tree.insert(path);
1991 } else {
1992 for &next in &seg.exit_successors {
1993 let mut next_constraints = seg_constraints.clone();
1994 let last = *path.last().unwrap();
1995 if self
1996 .graph
1997 .check_transition(last, next, &mut next_constraints)
1998 {
1999 path.push(next);
2000 self.collect_whole_cfg_paths(
2001 next,
2002 path,
2003 tree,
2004 depth + 1,
2005 postfix_repeat,
2006 &next_constraints,
2007 );
2008 path.pop();
2009 }
2010 }
2011 }
2012 }
2013
2014 path.truncate(orig_len);
2015 }
2016 return;
2017 }
2018
2019 let successors: Vec<usize> = self.graph.cfg_block(current).next.iter().copied().collect();
2021 if successors.is_empty() {
2022 tree.insert(path);
2023 return;
2024 }
2025
2026 for next in successors {
2027 let mut next_constraints = constraints.clone();
2028 if self
2029 .graph
2030 .check_transition(current, next, &mut next_constraints)
2031 {
2032 path.push(next);
2033 self.collect_whole_cfg_paths(
2034 next,
2035 path,
2036 tree,
2037 depth + 1,
2038 postfix_repeat,
2039 &next_constraints,
2040 );
2041 path.pop();
2042 }
2043 }
2044 }
2045
2046 fn sort_scc_tree(&self, scc: &SccInfo) -> SccInfo {
2047 self.graph.cfg_block(scc.enter).scc.clone()
2048 }
2049
2050 fn record_unique_path(
2051 &self,
2052 path: &[usize],
2053 scc: &SccInfo,
2054 out: &mut Vec<SccPath>,
2055 seen_paths: &mut FxHashSet<Vec<usize>>,
2056 ) {
2057 if !seen_paths.insert(path.to_vec()) {
2058 return;
2059 }
2060 let exit_successors = self.compute_exit_successors(path, scc);
2061 out.push(SccPath {
2062 blocks: path.to_vec(),
2063 exit_successors,
2064 });
2065 }
2066
2067 fn compute_exit_successors(&self, path: &[usize], scc: &SccInfo) -> Vec<usize> {
2068 let Some(&last) = path.last() else {
2069 return vec![];
2070 };
2071 scc.exits
2072 .iter()
2073 .filter(|e| e.exit == last)
2074 .map(|e| e.to)
2075 .filter(|&n| {
2076 !scc.child_sccs
2077 .contains(&self.graph.cfg.block(n).scc.enter())
2078 })
2079 .collect()
2080 }
2081}
2082
2083fn resolve_switch_target(targets: &SwitchTargets, val: u128) -> usize {
2086 targets
2087 .iter()
2088 .find(|(v, _)| *v == val)
2089 .map(|(_, bb)| bb.as_usize())
2090 .unwrap_or_else(|| targets.otherwise().as_usize())
2091}