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
53use crate::limit::{
54 DEFAULT_LOOP_CARRIED_BACKEDGES, DEFAULT_NUMERIC_WITNESS_ITERATION, MAX_AUTO_REPEAT,
55 MIN_DATAFLOW_REPEAT,
56};
57
58#[derive(Clone, Copy, Debug)]
65pub(crate) enum RepeatStrategy {
66 Auto,
68 Fixed(usize),
70}
71
72#[derive(Clone, Debug, Default)]
74pub(crate) struct RepeatPlan {
75 pub repeat: usize,
77}
78
79impl RepeatPlan {
80 fn from_hints(
82 dataflow_hints: Vec<DataflowDistanceHint>,
83 numeric_hints: Vec<NumericRangeHint>,
84 ) -> Self {
85 let repeat = dataflow_hints
86 .iter()
87 .map(DataflowDistanceHint::calibrated_repeat)
88 .chain(
89 numeric_hints
90 .iter()
91 .map(NumericRangeHint::calibrated_repeat),
92 )
93 .max()
94 .unwrap_or(0)
95 .min(MAX_AUTO_REPEAT);
96
97 Self { repeat }
98 }
99}
100
101#[derive(Clone, Debug)]
108pub(crate) struct DataflowDistanceHint {
109 pub needed_backedges: usize,
111}
112
113impl DataflowDistanceHint {
114 fn calibrated_repeat(&self) -> usize {
116 repeat_for_backedges(self.needed_backedges)
117 }
118}
119
120#[derive(Clone, Debug)]
127pub(crate) struct NumericRangeHint {
128 pub witness_iteration: usize,
130}
131
132impl NumericRangeHint {
133 fn calibrated_repeat(&self) -> usize {
135 repeat_for_witness_iteration(self.witness_iteration)
136 }
137}
138
139struct SafetySink<'target, 'tcx> {
146 checkpoint: &'target Checkpoint<'tcx>,
148 property: &'target Property<'tcx>,
150 roots: RelevantPlaces,
152}
153
154pub(crate) struct LoopSensitivityAnalyzer<'tcx> {
161 tcx: TyCtxt<'tcx>,
162}
163
164impl<'tcx> LoopSensitivityAnalyzer<'tcx> {
165 pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self {
167 Self { tcx }
168 }
169
170 pub(crate) fn analyze(&self, target: &FunctionTarget<'tcx>) -> RepeatPlan {
185 if !self.tcx.is_mir_available(target.def_id) {
186 return RepeatPlan::default();
187 }
188
189 let sinks = self.collect_sinks(target);
190 if sinks.is_empty() {
191 return RepeatPlan::default();
192 }
193
194 let mut graph = PathGraph::new(self.tcx, target.def_id);
195 graph.find_scc();
196 let body = self.tcx.optimized_mir(target.def_id);
197 let dependencies = LocalDependencyIndex::new(self.tcx, target.def_id);
198 let component_summaries: Vec<_> = loop_components(&graph)
199 .into_iter()
200 .map(|component| {
201 let local_summary = LoopLocalSummary::new(body, &component);
202 let numeric_summary =
203 LoopNumericSummary::new(self.tcx, target.def_id, body, &graph, &component);
204 (component, local_summary, numeric_summary)
205 })
206 .collect();
207
208 let dataflow_hints =
209 self.dataflow_distance_hints(&sinks, &graph, &dependencies, &component_summaries);
210 let numeric_hints =
211 self.numeric_range_hints(&sinks, &graph, &dependencies, &component_summaries);
212
213 RepeatPlan::from_hints(dataflow_hints, numeric_hints)
214 }
215
216 fn dataflow_distance_hints<'target>(
223 &self,
224 sinks: &[SafetySink<'target, 'tcx>],
225 graph: &PathGraph<'_>,
226 dependencies: &LocalDependencyIndex,
227 component_summaries: &[(LoopComponent, LoopLocalSummary, LoopNumericSummary)],
228 ) -> Vec<DataflowDistanceHint> {
229 let mut hints = Vec::new();
230
231 for sink in sinks {
232 if sink.property.is_or() || matches!(sink.property.kind(), Some(PropertyKind::Unknown))
233 {
234 continue;
235 }
236 let root_closure = dependencies.closure_from(&sink.roots.locals);
237 if root_closure.is_empty() {
238 continue;
239 }
240
241 for (component, local_summary, _) in component_summaries {
242 if !component_reaches_checkpoint(graph, component, sink.checkpoint.block) {
243 continue;
244 }
245 if root_closure
246 .iter()
247 .any(|local| local_summary.assigned_inside.contains(local))
248 {
249 let distance_backedges = estimate_dataflow_backedges(
250 dependencies,
251 &sink.roots.locals,
252 local_summary,
253 )
254 .unwrap_or(DEFAULT_LOOP_CARRIED_BACKEDGES);
255 let branch_backedges = estimate_branch_sensitive_backedges(
256 graph,
257 component,
258 dependencies,
259 &root_closure,
260 local_summary,
261 )
262 .unwrap_or(0);
263 let needed_backedges = distance_backedges.max(branch_backedges);
264 hints.push(DataflowDistanceHint { needed_backedges });
265 break;
266 }
267 }
268 }
269
270 hints
271 }
272
273 fn numeric_range_hints<'target>(
281 &self,
282 sinks: &[SafetySink<'target, 'tcx>],
283 graph: &PathGraph<'_>,
284 dependencies: &LocalDependencyIndex,
285 component_summaries: &[(LoopComponent, LoopLocalSummary, LoopNumericSummary)],
286 ) -> Vec<NumericRangeHint> {
287 let mut hints = Vec::new();
288
289 for sink in sinks {
290 if !matches!(
291 sink.property.kind(),
292 Some(PropertyKind::ValidNum | PropertyKind::InBound)
293 ) {
294 continue;
295 }
296 let root_closure = dependencies.closure_from(&sink.roots.locals);
297 if root_closure.is_empty() {
298 continue;
299 }
300
301 for (component, local_summary, numeric_summary) in component_summaries {
302 if !component_reaches_checkpoint(graph, component, sink.checkpoint.block) {
303 continue;
304 }
305 if !root_closure
306 .iter()
307 .any(|local| local_summary.assigned_inside.contains(local))
308 {
309 continue;
310 }
311
312 let witness_iteration = match sink.property.kind() {
313 Some(PropertyKind::ValidNum) => {
314 estimate_valid_num_witness(sink.property, &root_closure, numeric_summary)
315 }
316 Some(PropertyKind::InBound) => {
317 estimate_inbound_witness(&root_closure, numeric_summary)
318 }
319 _ => None,
320 };
321
322 if let Some(witness_iteration) = witness_iteration {
323 hints.push(NumericRangeHint { witness_iteration });
324 break;
325 }
326 }
327 }
328
329 hints
330 }
331
332 fn collect_sinks<'target>(
339 &self,
340 target: &'target FunctionTarget<'tcx>,
341 ) -> Vec<SafetySink<'target, 'tcx>> {
342 let mut sinks = Vec::new();
343
344 for checkpoint in target.all_checkpoints() {
345 let properties = target.properties_for_callsite(checkpoint);
346 if properties.is_empty() {
347 continue;
348 }
349
350 for property in properties.iter() {
351 let mut atoms = Vec::new();
355 collect_atoms(property, &mut atoms);
356 for atom in atoms {
357 let mut roots = RelevantPlaces::from_property(atom);
358 bind_callsite_roots(self.tcx, &mut roots, checkpoint);
359 if roots.locals.is_empty() {
360 continue;
361 }
362 sinks.push(SafetySink {
363 checkpoint,
364 property: atom,
365 roots,
366 });
367 }
368 }
369 }
370
371 sinks
372 }
373}
374
375fn collect_atoms<'a, 'tcx>(property: &'a Property<'tcx>, out: &mut Vec<&'a Property<'tcx>>) {
377 match property {
378 Property::Atom(_) => out.push(property),
379 Property::And(and) => {
380 for conjunct in &and.conjuncts {
381 collect_atoms(conjunct, out);
382 }
383 }
384 Property::Or(or) => {
385 for disjunct in &or.disjuncts {
386 collect_atoms(disjunct, out);
387 }
388 }
389 }
390}
391
392#[derive(Clone, Debug)]
397struct LoopComponent {
398 blocks: FxHashSet<usize>,
399}
400
401fn loop_components(graph: &PathGraph<'_>) -> Vec<LoopComponent> {
406 let mut components = Vec::new();
407 for block in &graph.cfg.blocks {
408 let scc = &block.scc;
409 if block.index != scc.enter || scc.nodes.is_empty() {
410 continue;
411 }
412 let mut blocks = scc.nodes.clone();
413 blocks.insert(scc.enter);
414 components.push(LoopComponent { blocks });
415 }
416 components
417}
418
419fn graph_reaches_any(
422 graph: &PathGraph<'_>,
423 sources: &[usize],
424 target_pred: impl Fn(usize) -> bool,
425) -> bool {
426 if sources.iter().any(|&s| target_pred(s)) {
427 return true;
428 }
429 let mut stack: Vec<usize> = sources.to_vec();
430 let mut seen = FxHashSet::default();
431 while let Some(block) = stack.pop() {
432 if target_pred(block) {
433 return true;
434 }
435 if !seen.insert(block) || block >= graph.cfg.blocks.len() {
436 continue;
437 }
438 for next in &graph.cfg.block(block).next {
439 stack.push(*next);
440 }
441 }
442 false
443}
444
445fn component_reaches_checkpoint(
447 graph: &PathGraph<'_>,
448 component: &LoopComponent,
449 checkpoint: BasicBlock,
450) -> bool {
451 let sources: Vec<usize> = component.blocks.iter().copied().collect();
452 graph_reaches_any(graph, &sources, |b| b == checkpoint.as_usize())
453}
454
455fn block_reaches_component(graph: &PathGraph<'_>, start: usize, component: &LoopComponent) -> bool {
457 graph_reaches_any(graph, &[start], |b| component.blocks.contains(&b))
458}
459
460struct LoopLocalSummary {
468 assigned_inside: FxHashSet<Local>,
470 state_locals: FxHashSet<Local>,
472}
473
474impl LoopLocalSummary {
475 fn new(body: &Body<'_>, component: &LoopComponent) -> Self {
477 let mut assigned_inside = FxHashSet::default();
478 let mut assigned_outside = FxHashSet::default();
479
480 for (block, data) in body.basic_blocks.iter_enumerated() {
481 let assigned = collect_assigned_locals(data);
482 if component.blocks.contains(&block.as_usize()) {
483 assigned_inside.extend(assigned);
484 } else {
485 assigned_outside.extend(assigned);
486 }
487 }
488
489 let mut state_locals = FxHashSet::default();
490 for local in &assigned_inside {
491 if assigned_outside.contains(local) || local_is_argument(*local, body) {
492 state_locals.insert(*local);
493 }
494 }
495
496 Self {
497 assigned_inside,
498 state_locals,
499 }
500 }
501}
502
503#[derive(Clone, Copy, Debug)]
505enum NumericTerm {
506 Local(Local),
508 Const(i128),
510}
511
512#[derive(Clone, Copy, Debug)]
514struct ComparisonFact {
515 op: BinOp,
517 lhs: NumericTerm,
519 rhs: NumericTerm,
521}
522
523struct LoopNumericSummary {
532 initial_constants: FxHashMap<Local, i128>,
534 steps: FxHashMap<Local, i128>,
536 guard_upper_bounds: FxHashMap<Local, NumericTerm>,
538 entry_lower_bounds: FxHashMap<Local, i128>,
541}
542
543impl LoopNumericSummary {
544 fn new<'tcx>(
546 tcx: TyCtxt<'tcx>,
547 def_id: DefId,
548 body: &Body<'tcx>,
549 graph: &PathGraph<'_>,
550 component: &LoopComponent,
551 ) -> Self {
552 let mut initial_constants = FxHashMap::default();
553 let mut tuple_steps: FxHashMap<Local, (Local, i128)> = FxHashMap::default();
554 let mut steps = FxHashMap::default();
555 let mut copy_sources = FxHashMap::default();
556 let mut comparisons = FxHashMap::default();
557
558 for (block, data) in body.basic_blocks.iter_enumerated() {
559 let in_component = component.blocks.contains(&block.as_usize());
560 for statement in &data.statements {
561 let StatementKind::Assign(assign) = &statement.kind else {
562 continue;
563 };
564 let (place, rvalue) = &**assign;
565 if place_is_indirect_write(place) {
566 continue;
567 }
568
569 if let Some(source) = plain_copy_source(rvalue) {
570 copy_sources.insert(place.local, source);
571 }
572 if let Some(comparison) = comparison_fact(tcx, def_id, rvalue) {
573 comparisons.insert(place.local, comparison);
574 }
575
576 if !in_component {
577 if let Some(value) = rvalue_const_i128(tcx, def_id, rvalue) {
578 initial_constants.insert(place.local, value);
579 }
580 continue;
581 }
582
583 if let Some((source, step)) = increment_source_and_step(tcx, def_id, rvalue) {
584 if source == place.local {
585 steps.insert(place.local, step);
586 } else {
587 tuple_steps.insert(place.local, (source, step));
588 }
589 }
590 }
591 }
592
593 for block in &component.blocks {
594 let data = &body.basic_blocks[BasicBlock::from(*block)];
595 for statement in &data.statements {
596 let StatementKind::Assign(assign) = &statement.kind else {
597 continue;
598 };
599 let (place, rvalue) = &**assign;
600 if place_is_indirect_write(place) {
601 continue;
602 }
603 let Some(source_temp) = rvalue_projection_source(rvalue, 0) else {
604 continue;
605 };
606 let Some((source, step)) = tuple_steps.get(&source_temp).copied() else {
607 continue;
608 };
609 if source == place.local {
610 steps.insert(place.local, step);
611 }
612 }
613 }
614
615 let guard_upper_bounds =
616 collect_loop_guard_upper_bounds(&steps, ©_sources, &comparisons);
617 let entry_lower_bounds =
618 collect_entry_lower_bounds(graph, component, ©_sources, &comparisons);
619
620 Self {
621 initial_constants,
622 steps,
623 guard_upper_bounds,
624 entry_lower_bounds,
625 }
626 }
627}
628
629fn collect_assigned_locals(data: &rustc_middle::mir::BasicBlockData<'_>) -> FxHashSet<Local> {
635 let mut locals = FxHashSet::default();
636 for statement in &data.statements {
637 let StatementKind::Assign(assign) = &statement.kind else {
638 continue;
639 };
640 let (place, _) = &**assign;
641 if !place_is_indirect_write(place) {
642 locals.insert(place.local);
643 }
644 }
645 if let TerminatorKind::Call { destination, .. } = &data.terminator().kind {
646 locals.insert(destination.local);
647 }
648 locals
649}
650
651fn collect_loop_guard_upper_bounds(
653 steps: &FxHashMap<Local, i128>,
654 copy_sources: &FxHashMap<Local, Local>,
655 comparisons: &FxHashMap<Local, ComparisonFact>,
656) -> FxHashMap<Local, NumericTerm> {
657 let mut bounds = FxHashMap::default();
658 for comparison in comparisons.values() {
659 let lhs = resolve_numeric_term(comparison.lhs, copy_sources);
660 let rhs = resolve_numeric_term(comparison.rhs, copy_sources);
661 match (comparison.op, lhs, rhs) {
662 (BinOp::Lt | BinOp::Le, NumericTerm::Local(local), bound)
663 if steps.contains_key(&local) =>
664 {
665 bounds.insert(local, bound);
666 }
667 (BinOp::Gt | BinOp::Ge, bound, NumericTerm::Local(local))
668 if steps.contains_key(&local) =>
669 {
670 bounds.insert(local, bound);
671 }
672 _ => {}
673 }
674 }
675 bounds
676}
677
678fn collect_entry_lower_bounds(
680 graph: &PathGraph<'_>,
681 component: &LoopComponent,
682 copy_sources: &FxHashMap<Local, Local>,
683 comparisons: &FxHashMap<Local, ComparisonFact>,
684) -> FxHashMap<Local, i128> {
685 let mut bounds: FxHashMap<Local, i128> = FxHashMap::default();
686
687 for block in &graph.cfg.blocks {
688 if component.blocks.contains(&block.index) {
689 continue;
690 }
691 let Some(terminator) = graph.cfg.terminator(block.index) else {
692 continue;
693 };
694 let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind else {
695 continue;
696 };
697 let Some(discr_local) = crate::helpers::mir_utils::extract_local(discr) else {
698 continue;
699 };
700 let discr_local = resolve_local_copy(discr_local, copy_sources);
701 let Some(comparison) = comparisons.get(&discr_local).copied() else {
702 continue;
703 };
704
705 for successor in switch_successors(targets) {
706 if !block_reaches_component(graph, successor.block, component) {
707 continue;
708 }
709 let Some((local, lower_bound)) =
710 lower_bound_from_branch(comparison, successor.value, copy_sources)
711 else {
712 continue;
713 };
714 bounds
715 .entry(local)
716 .and_modify(|existing| *existing = (*existing).max(lower_bound))
717 .or_insert(lower_bound);
718 }
719 }
720
721 bounds
722}
723
724#[derive(Clone, Copy)]
726struct SwitchSuccessor {
727 block: usize,
728 value: u128,
729}
730
731fn switch_successors(targets: &rustc_middle::mir::SwitchTargets) -> Vec<SwitchSuccessor> {
733 let explicit: Vec<_> = targets.iter().collect();
734 let mut successors: Vec<_> = explicit
735 .iter()
736 .map(|(value, target)| SwitchSuccessor {
737 block: target.as_usize(),
738 value: *value,
739 })
740 .collect();
741 let otherwise_value = if explicit.iter().any(|(value, _)| *value == 0) {
742 1
743 } else {
744 0
745 };
746 successors.push(SwitchSuccessor {
747 block: targets.otherwise().as_usize(),
748 value: otherwise_value,
749 });
750 successors
751}
752
753fn estimate_branch_sensitive_backedges(
761 graph: &PathGraph<'_>,
762 component: &LoopComponent,
763 dependencies: &LocalDependencyIndex,
764 root_closure: &FxHashSet<Local>,
765 local_summary: &LoopLocalSummary,
766) -> Option<usize> {
767 if !component_has_internal_branch(graph, component) {
768 return None;
769 }
770
771 let sink_state_reassigned = root_closure
772 .iter()
773 .any(|local| local_summary.state_locals.contains(local));
774 let multi_source_assignment = root_closure.iter().any(|local| {
775 local_summary.assigned_inside.contains(local)
776 && dependencies
777 .sources_by_dest
778 .get(local)
779 .is_some_and(|sources| sources.len() > 1)
780 });
781
782 (sink_state_reassigned || multi_source_assignment).then_some(DEFAULT_LOOP_CARRIED_BACKEDGES)
783}
784
785fn component_has_internal_branch(graph: &PathGraph<'_>, component: &LoopComponent) -> bool {
792 component.blocks.iter().any(|block| {
793 graph
794 .cfg
795 .block(*block)
796 .next
797 .iter()
798 .filter(|next| component.blocks.contains(next))
799 .take(2)
800 .count()
801 >= 2
802 })
803}
804
805fn lower_bound_from_branch(
807 comparison: ComparisonFact,
808 branch_value: u128,
809 copy_sources: &FxHashMap<Local, Local>,
810) -> Option<(Local, i128)> {
811 let is_true = branch_value != 0;
812 let lhs = resolve_numeric_term(comparison.lhs, copy_sources);
813 let rhs = resolve_numeric_term(comparison.rhs, copy_sources);
814 match (is_true, comparison.op, lhs, rhs) {
815 (false, BinOp::Lt, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
816 Some((local, bound))
817 }
818 (false, BinOp::Le, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
819 Some((local, bound.checked_add(1)?))
820 }
821 (false, BinOp::Gt, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
822 Some((local, bound))
823 }
824 (false, BinOp::Ge, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
825 Some((local, bound.checked_add(1)?))
826 }
827 (true, BinOp::Ge, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
828 Some((local, bound))
829 }
830 (true, BinOp::Gt, NumericTerm::Local(local), NumericTerm::Const(bound)) => {
831 Some((local, bound.checked_add(1)?))
832 }
833 (true, BinOp::Le, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
834 Some((local, bound))
835 }
836 (true, BinOp::Lt, NumericTerm::Const(bound), NumericTerm::Local(local)) => {
837 Some((local, bound.checked_add(1)?))
838 }
839 _ => None,
840 }
841}
842
843fn repeat_for_backedges(needed_backedges: usize) -> usize {
849 if needed_backedges == 0 {
850 0
851 } else {
852 needed_backedges
853 .saturating_sub(1)
854 .max(MIN_DATAFLOW_REPEAT)
855 .min(MAX_AUTO_REPEAT)
856 }
857}
858
859fn repeat_for_witness_iteration(witness_iteration: usize) -> usize {
864 witness_iteration.saturating_sub(2).min(MAX_AUTO_REPEAT)
865}
866
867fn estimate_dataflow_backedges(
874 dependencies: &LocalDependencyIndex,
875 roots: &FxHashSet<Local>,
876 local_summary: &LoopLocalSummary,
877) -> Option<usize> {
878 let mut best_state_distance = 0usize;
879 for root in roots {
880 let mut visited = FxHashSet::default();
881 best_state_distance = best_state_distance.max(max_state_distance_from(
882 dependencies,
883 *root,
884 local_summary,
885 0,
886 &mut visited,
887 ));
888 }
889
890 if best_state_distance == 0 {
891 None
892 } else {
893 Some(best_state_distance)
894 }
895}
896
897fn max_state_distance_from(
899 dependencies: &LocalDependencyIndex,
900 local: Local,
901 local_summary: &LoopLocalSummary,
902 distance: usize,
903 visited: &mut FxHashSet<Local>,
904) -> usize {
905 if !visited.insert(local) {
906 return distance;
907 }
908
909 let mut best = distance;
910 if let Some(sources) = dependencies.sources_by_dest.get(&local) {
911 for source in sources {
912 let next_distance = distance + usize::from(local_summary.state_locals.contains(source));
913 let mut branch_visited = visited.clone();
914 best = best.max(max_state_distance_from(
915 dependencies,
916 *source,
917 local_summary,
918 next_distance,
919 &mut branch_visited,
920 ));
921 }
922 }
923 best
924}
925
926fn estimate_valid_num_witness(
928 property: &Property<'_>,
929 root_closure: &FxHashSet<Local>,
930 numeric_summary: &LoopNumericSummary,
931) -> Option<usize> {
932 let violation_value = valid_num_violation_value(property)?;
933 root_closure
934 .iter()
935 .filter_map(|local| {
936 let init = numeric_summary.initial_constants.get(local).copied()?;
937 let step = numeric_summary.steps.get(local).copied()?;
938 witness_iteration_for_threshold(init, step, violation_value)
939 })
940 .min()
941}
942
943fn estimate_inbound_witness(
950 root_closure: &FxHashSet<Local>,
951 numeric_summary: &LoopNumericSummary,
952) -> Option<usize> {
953 let mut fallback = false;
954 let mut best = None;
955
956 for local in root_closure {
957 let Some(init) = numeric_summary.initial_constants.get(local).copied() else {
958 continue;
959 };
960 let Some(step) = numeric_summary.steps.get(local).copied() else {
961 continue;
962 };
963 if step == 0 {
964 continue;
965 }
966 fallback = true;
967
968 let Some(guard_bound) = numeric_summary.guard_upper_bounds.get(local).copied() else {
969 continue;
970 };
971 let Some(bound_lower) = numeric_term_lower_bound(guard_bound, numeric_summary) else {
972 continue;
973 };
974 let Some(witness) = witness_iteration_for_threshold(init, step, bound_lower) else {
975 continue;
976 };
977 best = Some(best.map_or(witness, |current: usize| current.min(witness)));
978 }
979
980 best.or_else(|| fallback.then_some(DEFAULT_NUMERIC_WITNESS_ITERATION))
981}
982
983fn valid_num_violation_value(property: &Property<'_>) -> Option<i128> {
985 if !matches!(property.kind(), Some(PropertyKind::ValidNum)) {
986 return None;
987 }
988 let Some(PropertyArg::Predicates(predicates)) = property.args().first() else {
989 return None;
990 };
991 predicates
992 .iter()
993 .filter_map(simple_upper_bound_violation_value)
994 .min()
995}
996
997fn simple_upper_bound_violation_value(predicate: &NumericPredicate<'_>) -> Option<i128> {
999 match (&predicate.lhs, predicate.op, &predicate.rhs) {
1000 (lhs, RelOp::Lt, rhs) if expr_is_place(lhs) => expr_const_i128(rhs),
1001 (lhs, RelOp::Le, rhs) if expr_is_place(lhs) => expr_const_i128(rhs)?.checked_add(1),
1002 (lhs, RelOp::Gt, rhs) if expr_is_place(rhs) => expr_const_i128(lhs),
1003 (lhs, RelOp::Ge, rhs) if expr_is_place(rhs) => expr_const_i128(lhs)?.checked_add(1),
1004 _ => None,
1005 }
1006}
1007
1008fn expr_is_place(expr: &ContractExpr<'_>) -> bool {
1010 matches!(expr, ContractExpr::Place(_))
1011}
1012
1013fn expr_const_i128(expr: &ContractExpr<'_>) -> Option<i128> {
1015 match expr {
1016 ContractExpr::Const(value) if *value <= i128::MAX as u128 => Some(*value as i128),
1017 _ => None,
1018 }
1019}
1020
1021fn numeric_term_lower_bound(term: NumericTerm, summary: &LoopNumericSummary) -> Option<i128> {
1023 match term {
1024 NumericTerm::Const(value) => Some(value),
1025 NumericTerm::Local(local) => summary.entry_lower_bounds.get(&local).copied(),
1026 }
1027}
1028
1029fn witness_iteration_for_threshold(init: i128, step: i128, violation_value: i128) -> Option<usize> {
1032 if step <= 0 {
1033 return None;
1034 }
1035 if init >= violation_value {
1036 return Some(0);
1037 }
1038 let delta = violation_value.checked_sub(init)?;
1039 usize::try_from(ceil_div_i128(delta, step)).ok()
1040}
1041
1042fn ceil_div_i128(lhs: i128, rhs: i128) -> i128 {
1044 debug_assert!(lhs >= 0);
1045 debug_assert!(rhs > 0);
1046 (lhs + rhs - 1) / rhs
1047}
1048
1049fn local_is_argument(local: Local, body: &Body<'_>) -> bool {
1051 let index = local.as_usize();
1052 index > 0 && index <= body.arg_count
1053}
1054
1055fn rvalue_const_i128<'tcx>(
1057 tcx: TyCtxt<'tcx>,
1058 def_id: DefId,
1059 rvalue: &Rvalue<'tcx>,
1060) -> Option<i128> {
1061 match rvalue {
1062 Rvalue::Use(operand, ..) | Rvalue::Cast(_, operand, _) => {
1063 operand_const_i128(tcx, def_id, operand)
1064 }
1065 _ => None,
1066 }
1067}
1068
1069fn plain_copy_source(rvalue: &Rvalue<'_>) -> Option<Local> {
1071 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
1072 return None;
1073 };
1074 place.projection.is_empty().then_some(place.local)
1075}
1076
1077fn comparison_fact<'tcx>(
1079 tcx: TyCtxt<'tcx>,
1080 def_id: DefId,
1081 rvalue: &Rvalue<'tcx>,
1082) -> Option<ComparisonFact> {
1083 let Rvalue::BinaryOp(op, operands) = rvalue else {
1084 return None;
1085 };
1086 if !matches!(
1087 op,
1088 BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::Eq | BinOp::Ne
1089 ) {
1090 return None;
1091 }
1092 Some(ComparisonFact {
1093 op: *op,
1094 lhs: numeric_term_from_operand(tcx, def_id, &operands.0)?,
1095 rhs: numeric_term_from_operand(tcx, def_id, &operands.1)?,
1096 })
1097}
1098
1099fn numeric_term_from_operand<'tcx>(
1101 tcx: TyCtxt<'tcx>,
1102 def_id: DefId,
1103 operand: &Operand<'tcx>,
1104) -> Option<NumericTerm> {
1105 crate::helpers::mir_utils::extract_local(operand)
1106 .map(NumericTerm::Local)
1107 .or_else(|| operand_const_i128(tcx, def_id, operand).map(NumericTerm::Const))
1108}
1109
1110fn resolve_local_copy(local: Local, copy_sources: &FxHashMap<Local, Local>) -> Local {
1112 let mut current = local;
1113 let mut seen = FxHashSet::default();
1114 while seen.insert(current) {
1115 let Some(next) = copy_sources.get(¤t).copied() else {
1116 break;
1117 };
1118 current = next;
1119 }
1120 current
1121}
1122
1123fn resolve_numeric_term(term: NumericTerm, copy_sources: &FxHashMap<Local, Local>) -> NumericTerm {
1125 match term {
1126 NumericTerm::Local(local) => NumericTerm::Local(resolve_local_copy(local, copy_sources)),
1127 NumericTerm::Const(value) => NumericTerm::Const(value),
1128 }
1129}
1130
1131fn increment_source_and_step<'tcx>(
1133 tcx: TyCtxt<'tcx>,
1134 def_id: DefId,
1135 rvalue: &Rvalue<'tcx>,
1136) -> Option<(Local, i128)> {
1137 let Rvalue::BinaryOp(op, operands) = rvalue else {
1138 return None;
1139 };
1140 let lhs_local = crate::helpers::mir_utils::extract_local(&operands.0);
1141 let rhs_local = crate::helpers::mir_utils::extract_local(&operands.1);
1142 let lhs_const = operand_const_i128(tcx, def_id, &operands.0);
1143 let rhs_const = operand_const_i128(tcx, def_id, &operands.1);
1144
1145 match op {
1146 BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked => match (lhs_local, rhs_local) {
1147 (Some(local), None) => Some((local, rhs_const?)),
1148 (None, Some(local)) => Some((local, lhs_const?)),
1149 _ => None,
1150 },
1151 BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => match (lhs_local, rhs_const) {
1152 (Some(local), Some(value)) => Some((local, -value)),
1153 _ => None,
1154 },
1155 _ => None,
1156 }
1157}
1158
1159fn rvalue_projection_source(rvalue: &Rvalue<'_>, field_index: usize) -> Option<Local> {
1161 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
1162 return None;
1163 };
1164 (first_field_projection(place) == Some(field_index)).then_some(place.local)
1165}
1166
1167fn operand_const_i128<'tcx>(
1170 tcx: TyCtxt<'tcx>,
1171 def_id: DefId,
1172 operand: &Operand<'tcx>,
1173) -> Option<i128> {
1174 let Operand::Constant(constant) = operand else {
1175 return None;
1176 };
1177 let typing_env = TypingEnv::post_analysis(tcx, def_id);
1178 match constant.const_.ty().kind() {
1179 TyKind::Bool => constant
1180 .const_
1181 .try_eval_bool(tcx, typing_env)
1182 .map(|value| if value { 1 } else { 0 }),
1183 TyKind::Int(_) | TyKind::Uint(_) => constant
1184 .const_
1185 .try_eval_bits(tcx, typing_env)
1186 .and_then(|bits| {
1187 if bits <= i128::MAX as u128 {
1188 Some(bits as i128)
1189 } else {
1190 None
1191 }
1192 }),
1193 _ => None,
1194 }
1195}
1196
1197fn first_field_projection(place: &Place<'_>) -> Option<usize> {
1199 for projection in place.projection.iter() {
1200 if let ProjectionElem::Field(field, _) = projection {
1201 return Some(field.as_usize());
1202 }
1203 }
1204 None
1205}
1206
1207struct LocalDependencyIndex {
1214 sources_by_dest: FxHashMap<Local, FxHashSet<Local>>,
1215}
1216
1217impl LocalDependencyIndex {
1218 fn new(tcx: TyCtxt<'_>, def_id: DefId) -> Self {
1225 let body = tcx.optimized_mir(def_id);
1226 let mut sources_by_dest: FxHashMap<Local, FxHashSet<Local>> = FxHashMap::default();
1227
1228 for data in body.basic_blocks.iter() {
1229 for statement in &data.statements {
1230 let StatementKind::Assign(assign) = &statement.kind else {
1231 continue;
1232 };
1233 let (place, rvalue) = &**assign;
1234 if place_is_indirect_write(place) {
1235 continue;
1236 }
1237 let mut sources = FxHashSet::default();
1238 collect_rvalue_sources(rvalue, &mut sources);
1239 if !sources.is_empty() {
1240 sources_by_dest
1241 .entry(place.local)
1242 .or_default()
1243 .extend(sources);
1244 }
1245 }
1246
1247 if let TerminatorKind::Call {
1248 args, destination, ..
1249 } = &data.terminator().kind
1250 {
1251 let mut sources = FxHashSet::default();
1252 for arg in args {
1253 collect_operand_sources(&arg.node, &mut sources);
1254 }
1255 if !sources.is_empty() {
1256 sources_by_dest
1257 .entry(destination.local)
1258 .or_default()
1259 .extend(sources);
1260 }
1261 }
1262 }
1263
1264 Self { sources_by_dest }
1265 }
1266
1267 fn closure_from(&self, roots: &FxHashSet<Local>) -> FxHashSet<Local> {
1274 let mut closure = FxHashSet::default();
1275 let mut stack: Vec<Local> = roots.iter().copied().collect();
1276 while let Some(local) = stack.pop() {
1277 if !closure.insert(local) {
1278 continue;
1279 }
1280 if let Some(sources) = self.sources_by_dest.get(&local) {
1281 for source in sources {
1282 stack.push(*source);
1283 }
1284 }
1285 }
1286 closure
1287 }
1288}
1289
1290fn collect_rvalue_sources(rvalue: &Rvalue<'_>, out: &mut FxHashSet<Local>) {
1296 match rvalue {
1297 Rvalue::Use(operand, ..) => collect_operand_sources(operand, out),
1298 Rvalue::Repeat(operand, _) => collect_operand_sources(operand, out),
1299 Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) | Rvalue::Discriminant(place) => {
1300 out.insert(place.local);
1301 }
1302 Rvalue::Cast(_, operand, _) | Rvalue::UnaryOp(_, operand) => {
1303 collect_operand_sources(operand, out);
1304 }
1305 Rvalue::BinaryOp(_, operands) => {
1306 collect_operand_sources(&operands.0, out);
1307 collect_operand_sources(&operands.1, out);
1308 }
1309 Rvalue::Aggregate(_, operands) => {
1310 for operand in operands {
1311 collect_operand_sources(operand, out);
1312 }
1313 }
1314 Rvalue::CopyForDeref(place) => {
1315 out.insert(place.local);
1316 }
1317 #[cfg(not(rapx_ge_99))]
1318 Rvalue::ShallowInitBox(operand, _) => collect_operand_sources(operand, out),
1319 Rvalue::ThreadLocalRef(_) => {}
1320 #[cfg(not(rapx_ge_95))]
1321 Rvalue::NullaryOp(..) => {}
1322 _ => {}
1323 }
1324}
1325
1326fn collect_operand_sources(operand: &Operand<'_>, out: &mut FxHashSet<Local>) {
1328 match operand {
1329 Operand::Copy(place) | Operand::Move(place) => {
1330 out.insert(place.local);
1331 }
1332 Operand::Constant(_) => {}
1333 #[cfg(rapx_ge_95)]
1334 Operand::RuntimeChecks(_) => {}
1335 }
1336}
1337
1338fn place_is_indirect_write(place: &Place<'_>) -> bool {
1343 place
1344 .projection
1345 .iter()
1346 .any(|projection| matches!(projection, ProjectionElem::Deref))
1347}