1use crate::analysis::path::graph::PathGraph;
36use crate::compat::{FxHashMap, FxHashSet};
37use rustc_hir::def_id::DefId;
38use rustc_middle::{
39 mir::{
40 BasicBlock, BinOp, Body, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
41 TerminatorKind,
42 },
43 ty::{TyCtxt, TyKind, TypingEnv},
44};
45
46use super::{
47 contract::{ContractExpr, NumericPredicate, Property, PropertyArg, PropertyKind, RelOp},
48 def_use::{RelevantPlaces, bind_callsite_roots},
49 target::FunctionTarget,
50};
51use crate::helpers::mir_scan::Checkpoint;
52
53pub(crate) const MAX_AUTO_REPEAT: usize = 16;
55
56const DEFAULT_LOOP_CARRIED_BACKEDGES: usize = 3;
62
63const DEFAULT_NUMERIC_WITNESS_ITERATION: usize = 4;
67
68const MIN_DATAFLOW_REPEAT: usize = 2;
71
72const BRANCH_SENSITIVE_BACKEDGES: usize = DEFAULT_LOOP_CARRIED_BACKEDGES;
79
80#[derive(Clone, Copy, Debug)]
87pub enum RepeatStrategy {
88 Auto,
90 Fixed(usize),
92}
93
94#[derive(Clone, Debug, Default)]
96pub(crate) struct RepeatPlan {
97 pub repeat: usize,
99}
100
101impl RepeatPlan {
102 fn from_hints(
104 dataflow_hints: Vec<DataflowDistanceHint>,
105 numeric_hints: Vec<NumericRangeHint>,
106 ) -> Self {
107 let repeat = dataflow_hints
108 .iter()
109 .map(DataflowDistanceHint::calibrated_repeat)
110 .chain(
111 numeric_hints
112 .iter()
113 .map(NumericRangeHint::calibrated_repeat),
114 )
115 .max()
116 .unwrap_or(0)
117 .min(MAX_AUTO_REPEAT);
118
119 Self { repeat }
120 }
121}
122
123#[derive(Clone, Debug)]
130pub(crate) struct DataflowDistanceHint {
131 pub needed_backedges: usize,
133}
134
135impl DataflowDistanceHint {
136 fn calibrated_repeat(&self) -> usize {
138 repeat_for_backedges(self.needed_backedges)
139 }
140}
141
142#[derive(Clone, Debug)]
149pub(crate) struct NumericRangeHint {
150 pub witness_iteration: usize,
152}
153
154impl NumericRangeHint {
155 fn calibrated_repeat(&self) -> usize {
157 repeat_for_witness_iteration(self.witness_iteration)
158 }
159}
160
161struct SafetySink<'target, 'tcx> {
168 checkpoint: &'target Checkpoint<'tcx>,
170 property: &'target Property<'tcx>,
172 roots: RelevantPlaces,
174}
175
176pub(crate) struct LoopSensitivityAnalyzer<'tcx> {
183 tcx: TyCtxt<'tcx>,
184}
185
186impl<'tcx> LoopSensitivityAnalyzer<'tcx> {
187 pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self {
189 Self { tcx }
190 }
191
192 pub(crate) fn analyze(&self, target: &FunctionTarget<'tcx>) -> RepeatPlan {
207 if !self.tcx.is_mir_available(target.def_id) {
208 return RepeatPlan::default();
209 }
210
211 let sinks = self.collect_sinks(target);
212 if sinks.is_empty() {
213 return RepeatPlan::default();
214 }
215
216 let mut graph = PathGraph::new(self.tcx, target.def_id);
217 graph.find_scc();
218 let body = self.tcx.optimized_mir(target.def_id);
219 let dependencies = LocalDependencyIndex::new(self.tcx, target.def_id);
220 let component_summaries: Vec<_> = loop_components(&graph)
221 .into_iter()
222 .map(|component| {
223 let local_summary = LoopLocalSummary::new(body, &component);
224 let numeric_summary =
225 LoopNumericSummary::new(self.tcx, target.def_id, body, &graph, &component);
226 (component, local_summary, numeric_summary)
227 })
228 .collect();
229
230 let dataflow_hints =
231 self.dataflow_distance_hints(&sinks, &graph, &dependencies, &component_summaries);
232 let numeric_hints =
233 self.numeric_range_hints(&sinks, &graph, &dependencies, &component_summaries);
234
235 RepeatPlan::from_hints(dataflow_hints, numeric_hints)
236 }
237
238 fn dataflow_distance_hints<'target>(
245 &self,
246 sinks: &[SafetySink<'target, 'tcx>],
247 graph: &PathGraph<'_>,
248 dependencies: &LocalDependencyIndex,
249 component_summaries: &[(LoopComponent, LoopLocalSummary, LoopNumericSummary)],
250 ) -> Vec<DataflowDistanceHint> {
251 let mut hints = Vec::new();
252
253 for sink in sinks {
254 if sink.property.is_or()
255 || matches!(sink.property.kind(), Some(PropertyKind::Unknown))
256 {
257 continue;
258 }
259 let root_closure = dependencies.closure_from(&sink.roots.locals);
260 if root_closure.is_empty() {
261 continue;
262 }
263
264 for (component, local_summary, _) in component_summaries {
265 if !component_reaches_checkpoint(graph, component, sink.checkpoint.block) {
266 continue;
267 }
268 if root_closure
269 .iter()
270 .any(|local| local_summary.assigned_inside.contains(local))
271 {
272 let distance_backedges = estimate_dataflow_backedges(
273 dependencies,
274 &sink.roots.locals,
275 local_summary,
276 )
277 .unwrap_or(DEFAULT_LOOP_CARRIED_BACKEDGES);
278 let branch_backedges = estimate_branch_sensitive_backedges(
279 graph,
280 component,
281 dependencies,
282 &root_closure,
283 local_summary,
284 )
285 .unwrap_or(0);
286 let needed_backedges = distance_backedges.max(branch_backedges);
287 hints.push(DataflowDistanceHint {
288 needed_backedges,
289 });
290 break;
291 }
292 }
293 }
294
295 hints
296 }
297
298 fn numeric_range_hints<'target>(
306 &self,
307 sinks: &[SafetySink<'target, 'tcx>],
308 graph: &PathGraph<'_>,
309 dependencies: &LocalDependencyIndex,
310 component_summaries: &[(LoopComponent, LoopLocalSummary, LoopNumericSummary)],
311 ) -> Vec<NumericRangeHint> {
312 let mut hints = Vec::new();
313
314 for sink in sinks {
315 if !matches!(
316 sink.property.kind(),
317 Some(PropertyKind::ValidNum | PropertyKind::InBound)
318 ) {
319 continue;
320 }
321 let root_closure = dependencies.closure_from(&sink.roots.locals);
322 if root_closure.is_empty() {
323 continue;
324 }
325
326 for (component, local_summary, numeric_summary) in component_summaries {
327 if !component_reaches_checkpoint(graph, component, sink.checkpoint.block) {
328 continue;
329 }
330 if !root_closure
331 .iter()
332 .any(|local| local_summary.assigned_inside.contains(local))
333 {
334 continue;
335 }
336
337 let witness_iteration = match sink.property.kind() {
338 Some(PropertyKind::ValidNum) => {
339 estimate_valid_num_witness(sink.property, &root_closure, numeric_summary)
340 }
341 Some(PropertyKind::InBound) => {
342 estimate_inbound_witness(&root_closure, numeric_summary)
343 }
344 _ => None,
345 };
346
347 if let Some(witness_iteration) = witness_iteration {
348 hints.push(NumericRangeHint {
349 witness_iteration,
350 });
351 break;
352 }
353 }
354 }
355
356 hints
357 }
358
359 fn collect_sinks<'target>(
366 &self,
367 target: &'target FunctionTarget<'tcx>,
368 ) -> Vec<SafetySink<'target, 'tcx>> {
369 let mut sinks = Vec::new();
370
371 for checkpoint in target.all_checkpoints() {
372 let properties = target.properties_for_callsite(checkpoint);
373 if properties.is_empty() {
374 continue;
375 }
376
377 for property in properties.iter() {
378 let mut leaves = Vec::new();
382 flatten_or_property(property, &mut leaves);
383 for leaf in leaves {
384 let mut roots = RelevantPlaces::from_property(leaf);
385 bind_callsite_roots(self.tcx, &mut roots, checkpoint);
386 if roots.locals.is_empty() {
387 continue;
388 }
389 sinks.push(SafetySink {
390 checkpoint,
391 property: leaf,
392 roots,
393 });
394 }
395 }
396 }
397
398 sinks
399 }
400}
401
402fn flatten_or_property<'a, 'tcx>(
404 property: &'a Property<'tcx>,
405 out: &mut Vec<&'a Property<'tcx>>,
406) {
407 if property.is_or() {
408 for group in property.groups() {
409 for sub in group.iter() {
410 flatten_or_property(sub, out);
411 }
412 }
413 } else {
414 out.push(property);
415 }
416}
417
418#[derive(Clone, Debug)]
423struct LoopComponent {
424 blocks: FxHashSet<usize>,
425}
426
427fn loop_components(graph: &PathGraph<'_>) -> Vec<LoopComponent> {
432 let mut components = Vec::new();
433 for block in &graph.cfg.blocks {
434 let scc = &block.scc;
435 if block.index != scc.enter || scc.nodes.is_empty() {
436 continue;
437 }
438 let mut blocks = scc.nodes.clone();
439 blocks.insert(scc.enter);
440 components.push(LoopComponent {
441 blocks,
442 });
443 }
444 components
445}
446
447fn graph_reaches_any(
450 graph: &PathGraph<'_>,
451 sources: &[usize],
452 target_pred: impl Fn(usize) -> bool,
453) -> bool {
454 if sources.iter().any(|&s| target_pred(s)) {
455 return true;
456 }
457 let mut stack: Vec<usize> = sources.to_vec();
458 let mut seen = FxHashSet::default();
459 while let Some(block) = stack.pop() {
460 if target_pred(block) {
461 return true;
462 }
463 if !seen.insert(block) || block >= graph.cfg.blocks.len() {
464 continue;
465 }
466 for next in &graph.cfg.block(block).next {
467 stack.push(*next);
468 }
469 }
470 false
471}
472
473fn component_reaches_checkpoint(
475 graph: &PathGraph<'_>,
476 component: &LoopComponent,
477 checkpoint: BasicBlock,
478) -> bool {
479 let sources: Vec<usize> = component.blocks.iter().copied().collect();
480 graph_reaches_any(graph, &sources, |b| b == checkpoint.as_usize())
481}
482
483fn block_reaches_component(graph: &PathGraph<'_>, start: usize, component: &LoopComponent) -> bool {
485 graph_reaches_any(graph, &[start], |b| component.blocks.contains(&b))
486}
487
488struct LoopLocalSummary {
496 assigned_inside: FxHashSet<Local>,
498 state_locals: FxHashSet<Local>,
500}
501
502impl LoopLocalSummary {
503 fn new(body: &Body<'_>, component: &LoopComponent) -> Self {
505 let mut assigned_inside = FxHashSet::default();
506 let mut assigned_outside = FxHashSet::default();
507
508 for (block, data) in body.basic_blocks.iter_enumerated() {
509 let assigned = collect_assigned_locals(data);
510 if component.blocks.contains(&block.as_usize()) {
511 assigned_inside.extend(assigned);
512 } else {
513 assigned_outside.extend(assigned);
514 }
515 }
516
517 let mut state_locals = FxHashSet::default();
518 for local in &assigned_inside {
519 if assigned_outside.contains(local) || local_is_argument(*local, body) {
520 state_locals.insert(*local);
521 }
522 }
523
524 Self {
525 assigned_inside,
526 state_locals,
527 }
528 }
529}
530
531#[derive(Clone, Copy, Debug)]
533enum NumericTerm {
534 Local(Local),
536 Const(i128),
538}
539
540#[derive(Clone, Copy, Debug)]
542struct ComparisonFact {
543 op: BinOp,
545 lhs: NumericTerm,
547 rhs: NumericTerm,
549}
550
551struct LoopNumericSummary {
560 initial_constants: FxHashMap<Local, i128>,
562 steps: FxHashMap<Local, i128>,
564 guard_upper_bounds: FxHashMap<Local, NumericTerm>,
566 entry_lower_bounds: FxHashMap<Local, i128>,
569}
570
571impl LoopNumericSummary {
572 fn new<'tcx>(
574 tcx: TyCtxt<'tcx>,
575 def_id: DefId,
576 body: &Body<'tcx>,
577 graph: &PathGraph<'_>,
578 component: &LoopComponent,
579 ) -> Self {
580 let mut initial_constants = FxHashMap::default();
581 let mut tuple_steps: FxHashMap<Local, (Local, i128)> = FxHashMap::default();
582 let mut steps = FxHashMap::default();
583 let mut copy_sources = FxHashMap::default();
584 let mut comparisons = FxHashMap::default();
585
586 for (block, data) in body.basic_blocks.iter_enumerated() {
587 let in_component = component.blocks.contains(&block.as_usize());
588 for statement in &data.statements {
589 let StatementKind::Assign(assign) = &statement.kind else {
590 continue;
591 };
592 let (place, rvalue) = &**assign;
593 if place_is_indirect_write(place) {
594 continue;
595 }
596
597 if let Some(source) = plain_copy_source(rvalue) {
598 copy_sources.insert(place.local, source);
599 }
600 if let Some(comparison) = comparison_fact(tcx, def_id, rvalue) {
601 comparisons.insert(place.local, comparison);
602 }
603
604 if !in_component {
605 if let Some(value) = rvalue_const_i128(tcx, def_id, rvalue) {
606 initial_constants.insert(place.local, value);
607 }
608 continue;
609 }
610
611 if let Some((source, step)) = increment_source_and_step(tcx, def_id, rvalue) {
612 if source == place.local {
613 steps.insert(place.local, step);
614 } else {
615 tuple_steps.insert(place.local, (source, step));
616 }
617 }
618 }
619 }
620
621 for block in &component.blocks {
622 let data = &body.basic_blocks[BasicBlock::from(*block)];
623 for statement in &data.statements {
624 let StatementKind::Assign(assign) = &statement.kind else {
625 continue;
626 };
627 let (place, rvalue) = &**assign;
628 if place_is_indirect_write(place) {
629 continue;
630 }
631 let Some(source_temp) = rvalue_projection_source(rvalue, 0) else {
632 continue;
633 };
634 let Some((source, step)) = tuple_steps.get(&source_temp).copied() else {
635 continue;
636 };
637 if source == place.local {
638 steps.insert(place.local, step);
639 }
640 }
641 }
642
643 let guard_upper_bounds =
644 collect_loop_guard_upper_bounds(&steps, ©_sources, &comparisons);
645 let entry_lower_bounds =
646 collect_entry_lower_bounds(graph, component, ©_sources, &comparisons);
647
648 Self {
649 initial_constants,
650 steps,
651 guard_upper_bounds,
652 entry_lower_bounds,
653 }
654 }
655}
656
657fn collect_assigned_locals(data: &rustc_middle::mir::BasicBlockData<'_>) -> FxHashSet<Local> {
663 let mut locals = FxHashSet::default();
664 for statement in &data.statements {
665 let StatementKind::Assign(assign) = &statement.kind else {
666 continue;
667 };
668 let (place, _) = &**assign;
669 if !place_is_indirect_write(place) {
670 locals.insert(place.local);
671 }
672 }
673 if let TerminatorKind::Call { destination, .. } = &data.terminator().kind {
674 locals.insert(destination.local);
675 }
676 locals
677}
678
679fn collect_loop_guard_upper_bounds(
681 steps: &FxHashMap<Local, i128>,
682 copy_sources: &FxHashMap<Local, Local>,
683 comparisons: &FxHashMap<Local, ComparisonFact>,
684) -> FxHashMap<Local, NumericTerm> {
685 let mut bounds = FxHashMap::default();
686 for comparison in comparisons.values() {
687 let lhs = resolve_numeric_term(comparison.lhs, copy_sources);
688 let rhs = resolve_numeric_term(comparison.rhs, copy_sources);
689 match (comparison.op, lhs, rhs) {
690 (BinOp::Lt | BinOp::Le, NumericTerm::Local(local), bound)
691 if steps.contains_key(&local) =>
692 {
693 bounds.insert(local, bound);
694 }
695 (BinOp::Gt | BinOp::Ge, bound, NumericTerm::Local(local))
696 if steps.contains_key(&local) =>
697 {
698 bounds.insert(local, bound);
699 }
700 _ => {}
701 }
702 }
703 bounds
704}
705
706fn collect_entry_lower_bounds(
708 graph: &PathGraph<'_>,
709 component: &LoopComponent,
710 copy_sources: &FxHashMap<Local, Local>,
711 comparisons: &FxHashMap<Local, ComparisonFact>,
712) -> FxHashMap<Local, i128> {
713 let mut bounds: FxHashMap<Local, i128> = FxHashMap::default();
714
715 for block in &graph.cfg.blocks {
716 if component.blocks.contains(&block.index) {
717 continue;
718 }
719 let Some(terminator) = graph.cfg.terminator(block.index) else {
720 continue;
721 };
722 let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind else {
723 continue;
724 };
725 let Some(discr_local) = crate::helpers::mir_utils::extract_local(discr) else {
726 continue;
727 };
728 let discr_local = resolve_local_copy(discr_local, copy_sources);
729 let Some(comparison) = comparisons.get(&discr_local).copied() else {
730 continue;
731 };
732
733 for successor in switch_successors(targets) {
734 if !block_reaches_component(graph, successor.block, component) {
735 continue;
736 }
737 let Some((local, lower_bound)) =
738 lower_bound_from_branch(comparison, successor.value, copy_sources)
739 else {
740 continue;
741 };
742 bounds
743 .entry(local)
744 .and_modify(|existing| *existing = (*existing).max(lower_bound))
745 .or_insert(lower_bound);
746 }
747 }
748
749 bounds
750}
751
752#[derive(Clone, Copy)]
754struct SwitchSuccessor {
755 block: usize,
756 value: u128,
757}
758
759fn switch_successors(targets: &rustc_middle::mir::SwitchTargets) -> Vec<SwitchSuccessor> {
761 let explicit: Vec<_> = targets.iter().collect();
762 let mut successors: Vec<_> = explicit
763 .iter()
764 .map(|(value, target)| SwitchSuccessor {
765 block: target.as_usize(),
766 value: *value,
767 })
768 .collect();
769 let otherwise_value = if explicit.iter().any(|(value, _)| *value == 0) {
770 1
771 } else {
772 0
773 };
774 successors.push(SwitchSuccessor {
775 block: targets.otherwise().as_usize(),
776 value: otherwise_value,
777 });
778 successors
779}
780
781fn estimate_branch_sensitive_backedges(
789 graph: &PathGraph<'_>,
790 component: &LoopComponent,
791 dependencies: &LocalDependencyIndex,
792 root_closure: &FxHashSet<Local>,
793 local_summary: &LoopLocalSummary,
794) -> Option<usize> {
795 if !component_has_internal_branch(graph, component) {
796 return None;
797 }
798
799 let sink_state_reassigned = root_closure
800 .iter()
801 .any(|local| local_summary.state_locals.contains(local));
802 let multi_source_assignment = root_closure.iter().any(|local| {
803 local_summary.assigned_inside.contains(local)
804 && dependencies
805 .sources_by_dest
806 .get(local)
807 .is_some_and(|sources| sources.len() > 1)
808 });
809
810 (sink_state_reassigned || multi_source_assignment).then_some(BRANCH_SENSITIVE_BACKEDGES)
811}
812
813fn component_has_internal_branch(graph: &PathGraph<'_>, component: &LoopComponent) -> bool {
820 component.blocks.iter().any(|block| {
821 graph
822 .cfg
823 .block(*block)
824 .next
825 .iter()
826 .filter(|next| component.blocks.contains(next))
827 .take(2)
828 .count()
829 >= 2
830 })
831}
832
833fn lower_bound_from_branch(
835 comparison: ComparisonFact,
836 branch_value: u128,
837 copy_sources: &FxHashMap<Local, Local>,
838) -> Option<(Local, i128)> {
839 let is_true = branch_value != 0;
840 let lhs = resolve_numeric_term(comparison.lhs, copy_sources);
841 let rhs = resolve_numeric_term(comparison.rhs, copy_sources);
842 match (is_true, comparison.op, lhs, rhs) {
843 (false, BinOp::Lt, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
844 Some((local, bound))
845 }
846 (false, BinOp::Le, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
847 Some((local, bound.checked_add(1)?))
848 }
849 (false, BinOp::Gt, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
850 Some((local, bound))
851 }
852 (false, BinOp::Ge, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
853 Some((local, bound.checked_add(1)?))
854 }
855 (true, BinOp::Ge, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
856 Some((local, bound))
857 }
858 (true, BinOp::Gt, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
859 Some((local, bound.checked_add(1)?))
860 }
861 (true, BinOp::Le, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
862 Some((local, bound))
863 }
864 (true, BinOp::Lt, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
865 Some((local, bound.checked_add(1)?))
866 }
867 _ => None,
868 }
869}
870
871fn repeat_for_backedges(needed_backedges: usize) -> usize {
877 if needed_backedges == 0 {
878 0
879 } else {
880 needed_backedges
881 .saturating_sub(1)
882 .max(MIN_DATAFLOW_REPEAT)
883 .min(MAX_AUTO_REPEAT)
884 }
885}
886
887fn repeat_for_witness_iteration(witness_iteration: usize) -> usize {
892 witness_iteration.saturating_sub(2).min(MAX_AUTO_REPEAT)
893}
894
895fn estimate_dataflow_backedges(
902 dependencies: &LocalDependencyIndex,
903 roots: &FxHashSet<Local>,
904 local_summary: &LoopLocalSummary,
905) -> Option<usize> {
906 let mut best_state_distance = 0usize;
907 for root in roots {
908 let mut visited = FxHashSet::default();
909 best_state_distance = best_state_distance.max(max_state_distance_from(
910 dependencies,
911 *root,
912 local_summary,
913 0,
914 &mut visited,
915 ));
916 }
917
918 if best_state_distance == 0 {
919 None
920 } else {
921 Some(best_state_distance)
922 }
923}
924
925fn max_state_distance_from(
927 dependencies: &LocalDependencyIndex,
928 local: Local,
929 local_summary: &LoopLocalSummary,
930 distance: usize,
931 visited: &mut FxHashSet<Local>,
932) -> usize {
933 if !visited.insert(local) {
934 return distance;
935 }
936
937 let mut best = distance;
938 if let Some(sources) = dependencies.sources_by_dest.get(&local) {
939 for source in sources {
940 let next_distance = distance + usize::from(local_summary.state_locals.contains(source));
941 let mut branch_visited = visited.clone();
942 best = best.max(max_state_distance_from(
943 dependencies,
944 *source,
945 local_summary,
946 next_distance,
947 &mut branch_visited,
948 ));
949 }
950 }
951 best
952}
953
954fn estimate_valid_num_witness(
956 property: &Property<'_>,
957 root_closure: &FxHashSet<Local>,
958 numeric_summary: &LoopNumericSummary,
959) -> Option<usize> {
960 let violation_value = valid_num_violation_value(property)?;
961 root_closure
962 .iter()
963 .filter_map(|local| {
964 let init = numeric_summary.initial_constants.get(local).copied()?;
965 let step = numeric_summary.steps.get(local).copied()?;
966 witness_iteration_for_threshold(init, step, violation_value)
967 })
968 .min()
969}
970
971fn estimate_inbound_witness(
978 root_closure: &FxHashSet<Local>,
979 numeric_summary: &LoopNumericSummary,
980) -> Option<usize> {
981 let mut fallback = false;
982 let mut best = None;
983
984 for local in root_closure {
985 let Some(init) = numeric_summary.initial_constants.get(local).copied() else {
986 continue;
987 };
988 let Some(step) = numeric_summary.steps.get(local).copied() else {
989 continue;
990 };
991 if step == 0 {
992 continue;
993 }
994 fallback = true;
995
996 let Some(guard_bound) = numeric_summary.guard_upper_bounds.get(local).copied() else {
997 continue;
998 };
999 let Some(bound_lower) = numeric_term_lower_bound(guard_bound, numeric_summary) else {
1000 continue;
1001 };
1002 let Some(witness) = witness_iteration_for_threshold(init, step, bound_lower) else {
1003 continue;
1004 };
1005 best = Some(best.map_or(witness, |current: usize| current.min(witness)));
1006 }
1007
1008 best.or_else(|| fallback.then_some(DEFAULT_NUMERIC_WITNESS_ITERATION))
1009}
1010
1011fn valid_num_violation_value(property: &Property<'_>) -> Option<i128> {
1013 if !matches!(property.kind(), Some(PropertyKind::ValidNum)) {
1014 return None;
1015 }
1016 let Some(PropertyArg::Predicates(predicates)) = property.args().first() else {
1017 return None;
1018 };
1019 predicates
1020 .iter()
1021 .filter_map(simple_upper_bound_violation_value)
1022 .min()
1023}
1024
1025fn simple_upper_bound_violation_value(predicate: &NumericPredicate<'_>) -> Option<i128> {
1027 match (&predicate.lhs, predicate.op, &predicate.rhs) {
1028 (lhs, RelOp::Lt, rhs) if expr_is_place(lhs) => expr_const_i128(rhs),
1029 (lhs, RelOp::Le, rhs) if expr_is_place(lhs) => expr_const_i128(rhs)?.checked_add(1),
1030 (lhs, RelOp::Gt, rhs) if expr_is_place(rhs) => expr_const_i128(lhs),
1031 (lhs, RelOp::Ge, rhs) if expr_is_place(rhs) => expr_const_i128(lhs)?.checked_add(1),
1032 _ => None,
1033 }
1034}
1035
1036fn expr_is_place(expr: &ContractExpr<'_>) -> bool {
1038 matches!(expr, ContractExpr::Place(_))
1039}
1040
1041fn expr_const_i128(expr: &ContractExpr<'_>) -> Option<i128> {
1043 match expr {
1044 ContractExpr::Const(value) if *value <= i128::MAX as u128 => Some(*value as i128),
1045 _ => None,
1046 }
1047}
1048
1049fn numeric_term_lower_bound(term: NumericTerm, summary: &LoopNumericSummary) -> Option<i128> {
1051 match term {
1052 NumericTerm::Const(value) => Some(value),
1053 NumericTerm::Local(local) => summary.entry_lower_bounds.get(&local).copied(),
1054 }
1055}
1056
1057fn witness_iteration_for_threshold(init: i128, step: i128, violation_value: i128) -> Option<usize> {
1060 if step <= 0 {
1061 return None;
1062 }
1063 if init >= violation_value {
1064 return Some(0);
1065 }
1066 let delta = violation_value.checked_sub(init)?;
1067 usize::try_from(ceil_div_i128(delta, step)).ok()
1068}
1069
1070fn ceil_div_i128(lhs: i128, rhs: i128) -> i128 {
1072 debug_assert!(lhs >= 0);
1073 debug_assert!(rhs > 0);
1074 (lhs + rhs - 1) / rhs
1075}
1076
1077fn local_is_argument(local: Local, body: &Body<'_>) -> bool {
1079 let index = local.as_usize();
1080 index > 0 && index <= body.arg_count
1081}
1082
1083fn rvalue_const_i128<'tcx>(
1085 tcx: TyCtxt<'tcx>,
1086 def_id: DefId,
1087 rvalue: &Rvalue<'tcx>,
1088) -> Option<i128> {
1089 match rvalue {
1090 Rvalue::Use(operand, ..) | Rvalue::Cast(_, operand, _) => {
1091 operand_const_i128(tcx, def_id, operand)
1092 }
1093 _ => None,
1094 }
1095}
1096
1097fn plain_copy_source(rvalue: &Rvalue<'_>) -> Option<Local> {
1099 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
1100 return None;
1101 };
1102 place.projection.is_empty().then_some(place.local)
1103}
1104
1105fn comparison_fact<'tcx>(
1107 tcx: TyCtxt<'tcx>,
1108 def_id: DefId,
1109 rvalue: &Rvalue<'tcx>,
1110) -> Option<ComparisonFact> {
1111 let Rvalue::BinaryOp(op, operands) = rvalue else {
1112 return None;
1113 };
1114 if !matches!(
1115 op,
1116 BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::Eq | BinOp::Ne
1117 ) {
1118 return None;
1119 }
1120 Some(ComparisonFact {
1121 op: *op,
1122 lhs: numeric_term_from_operand(tcx, def_id, &operands.0)?,
1123 rhs: numeric_term_from_operand(tcx, def_id, &operands.1)?,
1124 })
1125}
1126
1127fn numeric_term_from_operand<'tcx>(
1129 tcx: TyCtxt<'tcx>,
1130 def_id: DefId,
1131 operand: &Operand<'tcx>,
1132) -> Option<NumericTerm> {
1133 crate::helpers::mir_utils::extract_local(operand)
1134 .map(NumericTerm::Local)
1135 .or_else(|| operand_const_i128(tcx, def_id, operand).map(NumericTerm::Const))
1136}
1137
1138fn resolve_local_copy(local: Local, copy_sources: &FxHashMap<Local, Local>) -> Local {
1140 let mut current = local;
1141 let mut seen = FxHashSet::default();
1142 while seen.insert(current) {
1143 let Some(next) = copy_sources.get(¤t).copied() else {
1144 break;
1145 };
1146 current = next;
1147 }
1148 current
1149}
1150
1151fn resolve_numeric_term(term: NumericTerm, copy_sources: &FxHashMap<Local, Local>) -> NumericTerm {
1153 match term {
1154 NumericTerm::Local(local) => NumericTerm::Local(resolve_local_copy(local, copy_sources)),
1155 NumericTerm::Const(value) => NumericTerm::Const(value),
1156 }
1157}
1158
1159fn increment_source_and_step<'tcx>(
1161 tcx: TyCtxt<'tcx>,
1162 def_id: DefId,
1163 rvalue: &Rvalue<'tcx>,
1164) -> Option<(Local, i128)> {
1165 let Rvalue::BinaryOp(op, operands) = rvalue else {
1166 return None;
1167 };
1168 let lhs_local = crate::helpers::mir_utils::extract_local(&operands.0);
1169 let rhs_local = crate::helpers::mir_utils::extract_local(&operands.1);
1170 let lhs_const = operand_const_i128(tcx, def_id, &operands.0);
1171 let rhs_const = operand_const_i128(tcx, def_id, &operands.1);
1172
1173 match op {
1174 BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked => match (lhs_local, rhs_local) {
1175 (Some(local), None) => Some((local, rhs_const?)),
1176 (None, Some(local)) => Some((local, lhs_const?)),
1177 _ => None,
1178 },
1179 BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => match (lhs_local, rhs_const) {
1180 (Some(local), Some(value)) => Some((local, -value)),
1181 _ => None,
1182 },
1183 _ => None,
1184 }
1185}
1186
1187fn rvalue_projection_source(rvalue: &Rvalue<'_>, field_index: usize) -> Option<Local> {
1189 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
1190 return None;
1191 };
1192 (first_field_projection(place) == Some(field_index)).then_some(place.local)
1193}
1194
1195fn operand_const_i128<'tcx>(
1198 tcx: TyCtxt<'tcx>,
1199 def_id: DefId,
1200 operand: &Operand<'tcx>,
1201) -> Option<i128> {
1202 let Operand::Constant(constant) = operand else {
1203 return None;
1204 };
1205 let typing_env = TypingEnv::post_analysis(tcx, def_id);
1206 match constant.const_.ty().kind() {
1207 TyKind::Bool => constant
1208 .const_
1209 .try_eval_bool(tcx, typing_env)
1210 .map(|value| if value { 1 } else { 0 }),
1211 TyKind::Int(_) | TyKind::Uint(_) => constant
1212 .const_
1213 .try_eval_bits(tcx, typing_env)
1214 .and_then(|bits| {
1215 if bits <= i128::MAX as u128 {
1216 Some(bits as i128)
1217 } else {
1218 None
1219 }
1220 }),
1221 _ => None,
1222 }
1223}
1224
1225fn first_field_projection(place: &Place<'_>) -> Option<usize> {
1227 for projection in place.projection.iter() {
1228 if let ProjectionElem::Field(field, _) = projection {
1229 return Some(field.as_usize());
1230 }
1231 }
1232 None
1233}
1234
1235struct LocalDependencyIndex {
1242 sources_by_dest: FxHashMap<Local, FxHashSet<Local>>,
1243}
1244
1245impl LocalDependencyIndex {
1246 fn new(tcx: TyCtxt<'_>, def_id: DefId) -> Self {
1253 let body = tcx.optimized_mir(def_id);
1254 let mut sources_by_dest: FxHashMap<Local, FxHashSet<Local>> = FxHashMap::default();
1255
1256 for data in body.basic_blocks.iter() {
1257 for statement in &data.statements {
1258 let StatementKind::Assign(assign) = &statement.kind else {
1259 continue;
1260 };
1261 let (place, rvalue) = &**assign;
1262 if place_is_indirect_write(place) {
1263 continue;
1264 }
1265 let mut sources = FxHashSet::default();
1266 collect_rvalue_sources(rvalue, &mut sources);
1267 if !sources.is_empty() {
1268 sources_by_dest
1269 .entry(place.local)
1270 .or_default()
1271 .extend(sources);
1272 }
1273 }
1274
1275 if let TerminatorKind::Call {
1276 args, destination, ..
1277 } = &data.terminator().kind
1278 {
1279 let mut sources = FxHashSet::default();
1280 for arg in args {
1281 collect_operand_sources(&arg.node, &mut sources);
1282 }
1283 if !sources.is_empty() {
1284 sources_by_dest
1285 .entry(destination.local)
1286 .or_default()
1287 .extend(sources);
1288 }
1289 }
1290 }
1291
1292 Self { sources_by_dest }
1293 }
1294
1295 fn closure_from(&self, roots: &FxHashSet<Local>) -> FxHashSet<Local> {
1302 let mut closure = FxHashSet::default();
1303 let mut stack: Vec<Local> = roots.iter().copied().collect();
1304 while let Some(local) = stack.pop() {
1305 if !closure.insert(local) {
1306 continue;
1307 }
1308 if let Some(sources) = self.sources_by_dest.get(&local) {
1309 for source in sources {
1310 stack.push(*source);
1311 }
1312 }
1313 }
1314 closure
1315 }
1316}
1317
1318fn collect_rvalue_sources(rvalue: &Rvalue<'_>, out: &mut FxHashSet<Local>) {
1324 match rvalue {
1325 Rvalue::Use(operand, ..) => collect_operand_sources(operand, out),
1326 Rvalue::Repeat(operand, _) => collect_operand_sources(operand, out),
1327 Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) | Rvalue::Discriminant(place) => {
1328 out.insert(place.local);
1329 }
1330 Rvalue::Cast(_, operand, _) | Rvalue::UnaryOp(_, operand) => {
1331 collect_operand_sources(operand, out);
1332 }
1333 Rvalue::BinaryOp(_, operands) => {
1334 collect_operand_sources(&operands.0, out);
1335 collect_operand_sources(&operands.1, out);
1336 }
1337 Rvalue::Aggregate(_, operands) => {
1338 for operand in operands {
1339 collect_operand_sources(operand, out);
1340 }
1341 }
1342 Rvalue::CopyForDeref(place) => {
1343 out.insert(place.local);
1344 }
1345 #[cfg(not(rapx_ge_99))]
1346 Rvalue::ShallowInitBox(operand, _) => collect_operand_sources(operand, out),
1347 Rvalue::ThreadLocalRef(_) => {}
1348 #[cfg(not(rapx_ge_99))]
1349 Rvalue::NullaryOp(..) => {}
1350 _ => {}
1351 }
1352}
1353
1354fn collect_operand_sources(operand: &Operand<'_>, out: &mut FxHashSet<Local>) {
1356 match operand {
1357 Operand::Copy(place) | Operand::Move(place) => {
1358 out.insert(place.local);
1359 }
1360 Operand::Constant(_) => {}
1361 #[cfg(rapx_ge_99)]
1362 Operand::RuntimeChecks(_) => {}
1363 }
1364}
1365
1366fn place_is_indirect_write(place: &Place<'_>) -> bool {
1371 place
1372 .projection
1373 .iter()
1374 .any(|projection| matches!(projection, ProjectionElem::Deref))
1375}