1use super::domains::{
8 AbstractDomain, AbstractDomainFactory, AbstractValue, BinaryAbstractOp,
9 ConstantValue as DomainConstantValue, SignValue, UnaryAbstractOp,
10};
11use crate::graph::operations::{ConstantValue as OpConstantValue, Operation};
12use crate::{
13 ir::{BasicBlock, BlockId, IrModule, IrOpcode, IrValue},
14 ComputationGraph, JitResult, NodeId,
15};
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::time::Instant;
18
19#[path = "framework_config.rs"]
20mod framework_config;
21pub use framework_config::*;
22
23pub struct AbstractInterpreter {
25 config: AbstractInterpretationConfig,
26 domain_factory: AbstractDomainFactory,
27 fixpoint_engine: FixpointEngine,
28 invariant_detector: InvariantDetector,
29 property_checker: PropertyChecker,
30 analysis_cache: AnalysisCache,
31}
32
33impl AbstractInterpreter {
34 pub fn new(config: AbstractInterpretationConfig) -> Self {
42 Self {
43 domain_factory: AbstractDomainFactory::new(),
44 fixpoint_engine: FixpointEngine::new(config.clone()),
45 invariant_detector: InvariantDetector::new(),
46 property_checker: PropertyChecker::new(),
47 analysis_cache: AnalysisCache::new(),
48 config,
49 }
50 }
51
52 pub fn with_defaults() -> Self {
54 Self::new(AbstractInterpretationConfig::default())
55 }
56
57 pub fn analyze_graph(&mut self, graph: &ComputationGraph) -> JitResult<AbstractAnalysisResult> {
69 let start_time = Instant::now();
70
71 let domain = self.domain_factory.create_domain(&self.config.domain_type);
73
74 let abstract_graph = self.convert_to_abstract_graph(graph, domain.as_ref())?;
76
77 let forward_result = self.forward_analysis(&abstract_graph, domain.as_ref())?;
79
80 let backward_result = if self.config.enable_backward_analysis {
82 Some(self.backward_analysis(&abstract_graph, domain.as_ref())?)
83 } else {
84 None
85 };
86
87 let invariants = self
89 .invariant_detector
90 .detect_invariants(&forward_result, &backward_result)?;
91
92 let property_results = self
94 .property_checker
95 .check_properties(&forward_result, &self.config.properties)?;
96
97 let precision_analysis = self.analyze_precision(&forward_result, &abstract_graph);
99 let performance_analysis = self.analyze_performance(&forward_result, graph);
100
101 let total_time = start_time.elapsed();
102 let fixpoint_iterations =
103 forward_result.iterations + backward_result.as_ref().map(|r| r.iterations).unwrap_or(0);
104 let abstract_states_computed = forward_result.post_states.len();
105 let node_values = forward_result.post_states.clone();
106
107 Ok(AbstractAnalysisResult {
108 forward_result,
109 backward_result,
110 invariants,
111 property_results,
112 precision_analysis,
113 performance_analysis,
114 statistics: AnalysisStatistics {
115 analysis_time: total_time,
116 fixpoint_iterations,
117 abstract_states_computed,
118 cache_hits: self.analysis_cache.hit_count(),
119 cache_misses: self.analysis_cache.miss_count(),
120 },
121 node_values,
122 })
123 }
124
125 pub fn analyze_ir(&mut self, ir_module: &IrModule) -> JitResult<AbstractIrResult> {
136 let mut block_results = HashMap::new();
137 let mut module_invariants = Vec::new();
138
139 for (block_id, block) in &ir_module.blocks {
141 let block_result = self.analyze_block(block)?;
142 block_results.insert(*block_id, block_result);
143 }
144
145 let interprocedural_result = self.interprocedural_analysis_blocks(&block_results)?;
147
148 module_invariants.extend(self.detect_module_invariants_blocks(&block_results)?);
150
151 Ok(AbstractIrResult {
152 function_results: HashMap::new(), interprocedural_result,
154 module_invariants,
155 })
156 }
157
158 pub fn config(&self) -> &AbstractInterpretationConfig {
160 &self.config
161 }
162
163 pub fn set_config(&mut self, config: AbstractInterpretationConfig) {
165 self.config = config;
166 self.fixpoint_engine = FixpointEngine::new(self.config.clone());
167 }
168
169 pub fn clear_cache(&mut self) {
171 self.analysis_cache.clear();
172 }
173
174 pub fn cache_stats(&self) -> (usize, usize) {
176 (
177 self.analysis_cache.hit_count(),
178 self.analysis_cache.miss_count(),
179 )
180 }
181}
182
183pub struct FixpointEngine {
188 config: AbstractInterpretationConfig,
189}
190
191impl FixpointEngine {
192 pub fn new(config: AbstractInterpretationConfig) -> Self {
193 Self { config }
194 }
195}
196
197pub struct InvariantDetector;
205
206impl InvariantDetector {
207 pub fn new() -> Self {
208 Self
209 }
210
211 pub fn detect_invariants(
219 &self,
220 forward_result: &ForwardAnalysisResult,
221 backward_result: &Option<BackwardAnalysisResult>,
222 ) -> JitResult<Vec<Invariant>> {
223 let mut invariants = Vec::new();
224 for (node_id, value) in &forward_result.post_states {
225 if let Some(inv) = describe_invariant(*node_id, value, "forward") {
226 invariants.push(inv);
227 }
228 }
229 if let Some(bw) = backward_result {
230 for (node_id, value) in &bw.pre_states {
231 if let Some(inv) = describe_invariant(*node_id, value, "backward") {
233 invariants.push(inv);
234 }
235 }
236 }
237 Ok(invariants)
238 }
239}
240
241fn describe_invariant(node_id: NodeId, value: &AbstractValue, source: &str) -> Option<Invariant> {
244 match value {
245 AbstractValue::Interval { min, max } => {
246 if min == max && min.is_finite() {
247 Some(Invariant {
248 invariant_type: InvariantType::NumericalProperty,
249 description: format!("node {:?} = {}", node_id, min),
250 confidence: 1.0,
251 location: source.to_string(),
252 })
253 } else if min.is_finite() && max.is_finite() {
254 Some(Invariant {
255 invariant_type: InvariantType::ValueRange,
256 description: format!("node {:?} ∈ [{}, {}]", node_id, min, max),
257 confidence: 0.9,
258 location: source.to_string(),
259 })
260 } else {
261 None
262 }
263 }
264 AbstractValue::Constant(DomainConstantValue::Value(v)) => Some(Invariant {
265 invariant_type: InvariantType::NumericalProperty,
266 description: format!("node {:?} ≡ {}", node_id, v),
267 confidence: 1.0,
268 location: source.to_string(),
269 }),
270 AbstractValue::Sign(SignValue::Zero) => Some(Invariant {
271 invariant_type: InvariantType::NumericalProperty,
272 description: format!("node {:?} ≡ 0", node_id),
273 confidence: 1.0,
274 location: source.to_string(),
275 }),
276 AbstractValue::Sign(SignValue::NonNegative) => Some(Invariant {
277 invariant_type: InvariantType::MemorySafety,
278 description: format!("node {:?} ≥ 0", node_id),
279 confidence: 0.95,
280 location: source.to_string(),
281 }),
282 AbstractValue::Sign(SignValue::Positive) => Some(Invariant {
283 invariant_type: InvariantType::MemorySafety,
284 description: format!("node {:?} > 0", node_id),
285 confidence: 0.95,
286 location: source.to_string(),
287 }),
288 _ => None,
289 }
290}
291
292impl Default for InvariantDetector {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298pub struct PropertyChecker;
305
306impl PropertyChecker {
307 pub fn new() -> Self {
308 Self
309 }
310
311 pub fn check_properties(
322 &self,
323 forward_result: &ForwardAnalysisResult,
324 properties: &[Property],
325 ) -> JitResult<Vec<PropertyResult>> {
326 let mut results = Vec::with_capacity(properties.len());
327 for prop in properties {
328 let node = property_node(prop);
329 let state = forward_result.post_states.get(&node);
330 results.push(check_property(prop, state));
331 }
332 Ok(results)
333 }
334}
335
336fn property_node(prop: &Property) -> NodeId {
338 match prop {
339 Property::NonNegative(n)
340 | Property::Positive(n)
341 | Property::BoundedValue(n, _, _)
342 | Property::NoDivisionByZero(n)
343 | Property::NoOverflow(n)
344 | Property::SafetyProperty(_, n) => *n,
345 }
346}
347
348fn check_property(prop: &Property, value: Option<&AbstractValue>) -> PropertyResult {
350 let value = match value {
351 Some(v) => v,
352 None => {
353 return PropertyResult {
354 property: prop.clone(),
355 result: SafetyCheckResult::Unknown,
356 confidence: 0.0,
357 details: "no abstract value computed for node".to_string(),
358 }
359 }
360 };
361 match prop {
362 Property::NonNegative(_) => decide_non_negative(prop, value),
363 Property::Positive(_) => decide_positive(prop, value),
364 Property::BoundedValue(_, lo, hi) => decide_bounded(prop, value, *lo, *hi),
365 Property::NoDivisionByZero(_) => decide_nonzero(prop, value),
366 Property::NoOverflow(_) => decide_no_overflow(prop, value),
367 Property::SafetyProperty(_, _) => PropertyResult {
368 property: prop.clone(),
369 result: SafetyCheckResult::Unknown,
370 confidence: 0.0,
371 details: "custom safety property requires explicit verifier".to_string(),
372 },
373 }
374}
375
376fn decide_non_negative(prop: &Property, value: &AbstractValue) -> PropertyResult {
377 match value {
378 AbstractValue::Interval { min, max } => {
379 if *min >= 0.0 {
380 PropertyResult {
381 property: prop.clone(),
382 result: SafetyCheckResult::Safe,
383 confidence: 1.0,
384 details: "interval lower bound ≥ 0".to_string(),
385 }
386 } else if *max < 0.0 {
387 PropertyResult {
388 property: prop.clone(),
389 result: SafetyCheckResult::Unsafe,
390 confidence: 1.0,
391 details: "interval upper bound < 0".to_string(),
392 }
393 } else {
394 PropertyResult {
395 property: prop.clone(),
396 result: SafetyCheckResult::Unknown,
397 confidence: 0.5,
398 details: "interval straddles zero".to_string(),
399 }
400 }
401 }
402 AbstractValue::Sign(s) => match s {
403 SignValue::Zero | SignValue::Positive | SignValue::NonNegative => PropertyResult {
404 property: prop.clone(),
405 result: SafetyCheckResult::Safe,
406 confidence: 1.0,
407 details: "sign domain proves non-negativity".to_string(),
408 },
409 SignValue::Negative => PropertyResult {
410 property: prop.clone(),
411 result: SafetyCheckResult::Unsafe,
412 confidence: 1.0,
413 details: "sign domain proves negativity".to_string(),
414 },
415 _ => PropertyResult {
416 property: prop.clone(),
417 result: SafetyCheckResult::Unknown,
418 confidence: 0.5,
419 details: "sign domain too imprecise".to_string(),
420 },
421 },
422 AbstractValue::Constant(DomainConstantValue::Value(v)) => {
423 if *v >= 0.0 {
424 PropertyResult {
425 property: prop.clone(),
426 result: SafetyCheckResult::Safe,
427 confidence: 1.0,
428 details: format!("constant {} ≥ 0", v),
429 }
430 } else {
431 PropertyResult {
432 property: prop.clone(),
433 result: SafetyCheckResult::Unsafe,
434 confidence: 1.0,
435 details: format!("constant {} < 0", v),
436 }
437 }
438 }
439 _ => PropertyResult {
440 property: prop.clone(),
441 result: SafetyCheckResult::Unknown,
442 confidence: 0.0,
443 details: "abstract domain insufficient".to_string(),
444 },
445 }
446}
447
448fn decide_positive(prop: &Property, value: &AbstractValue) -> PropertyResult {
449 match value {
450 AbstractValue::Interval { min, max } => {
451 if *min > 0.0 {
452 PropertyResult {
453 property: prop.clone(),
454 result: SafetyCheckResult::Safe,
455 confidence: 1.0,
456 details: "interval strictly positive".to_string(),
457 }
458 } else if *max <= 0.0 {
459 PropertyResult {
460 property: prop.clone(),
461 result: SafetyCheckResult::Unsafe,
462 confidence: 1.0,
463 details: "interval entirely ≤ 0".to_string(),
464 }
465 } else {
466 PropertyResult {
467 property: prop.clone(),
468 result: SafetyCheckResult::Unknown,
469 confidence: 0.5,
470 details: "interval includes 0 or negatives".to_string(),
471 }
472 }
473 }
474 AbstractValue::Sign(SignValue::Positive) => PropertyResult {
475 property: prop.clone(),
476 result: SafetyCheckResult::Safe,
477 confidence: 1.0,
478 details: "sign domain proves positivity".to_string(),
479 },
480 AbstractValue::Sign(SignValue::Zero)
481 | AbstractValue::Sign(SignValue::Negative)
482 | AbstractValue::Sign(SignValue::NonPositive) => PropertyResult {
483 property: prop.clone(),
484 result: SafetyCheckResult::Unsafe,
485 confidence: 1.0,
486 details: "sign domain proves non-positivity".to_string(),
487 },
488 AbstractValue::Constant(DomainConstantValue::Value(v)) => {
489 if *v > 0.0 {
490 PropertyResult {
491 property: prop.clone(),
492 result: SafetyCheckResult::Safe,
493 confidence: 1.0,
494 details: format!("constant {} > 0", v),
495 }
496 } else {
497 PropertyResult {
498 property: prop.clone(),
499 result: SafetyCheckResult::Unsafe,
500 confidence: 1.0,
501 details: format!("constant {} ≤ 0", v),
502 }
503 }
504 }
505 _ => PropertyResult {
506 property: prop.clone(),
507 result: SafetyCheckResult::Unknown,
508 confidence: 0.0,
509 details: "abstract domain insufficient".to_string(),
510 },
511 }
512}
513
514fn decide_bounded(prop: &Property, value: &AbstractValue, lo: f64, hi: f64) -> PropertyResult {
515 match value {
516 AbstractValue::Interval { min, max } => {
517 if *min >= lo && *max <= hi {
518 PropertyResult {
519 property: prop.clone(),
520 result: SafetyCheckResult::Safe,
521 confidence: 1.0,
522 details: format!("interval [{}, {}] ⊆ [{}, {}]", min, max, lo, hi),
523 }
524 } else if *max < lo || *min > hi {
525 PropertyResult {
526 property: prop.clone(),
527 result: SafetyCheckResult::Unsafe,
528 confidence: 1.0,
529 details: format!("interval disjoint from [{}, {}]", lo, hi),
530 }
531 } else {
532 PropertyResult {
533 property: prop.clone(),
534 result: SafetyCheckResult::Unknown,
535 confidence: 0.5,
536 details: format!(
537 "interval [{}, {}] partially exceeds [{}, {}]",
538 min, max, lo, hi
539 ),
540 }
541 }
542 }
543 AbstractValue::Constant(DomainConstantValue::Value(v)) => {
544 if *v >= lo && *v <= hi {
545 PropertyResult {
546 property: prop.clone(),
547 result: SafetyCheckResult::Safe,
548 confidence: 1.0,
549 details: format!("constant {} ∈ [{}, {}]", v, lo, hi),
550 }
551 } else {
552 PropertyResult {
553 property: prop.clone(),
554 result: SafetyCheckResult::Unsafe,
555 confidence: 1.0,
556 details: format!("constant {} ∉ [{}, {}]", v, lo, hi),
557 }
558 }
559 }
560 _ => PropertyResult {
561 property: prop.clone(),
562 result: SafetyCheckResult::Unknown,
563 confidence: 0.0,
564 details: "abstract domain insufficient for bounded check".to_string(),
565 },
566 }
567}
568
569fn decide_nonzero(prop: &Property, value: &AbstractValue) -> PropertyResult {
570 match value {
571 AbstractValue::Interval { min, max } => {
572 if *min > 0.0 || *max < 0.0 {
573 PropertyResult {
574 property: prop.clone(),
575 result: SafetyCheckResult::Safe,
576 confidence: 1.0,
577 details: "interval excludes zero".to_string(),
578 }
579 } else if *min == 0.0 && *max == 0.0 {
580 PropertyResult {
581 property: prop.clone(),
582 result: SafetyCheckResult::Unsafe,
583 confidence: 1.0,
584 details: "interval is {0}".to_string(),
585 }
586 } else {
587 PropertyResult {
588 property: prop.clone(),
589 result: SafetyCheckResult::Unknown,
590 confidence: 0.5,
591 details: "interval contains zero".to_string(),
592 }
593 }
594 }
595 AbstractValue::Sign(SignValue::Zero) => PropertyResult {
596 property: prop.clone(),
597 result: SafetyCheckResult::Unsafe,
598 confidence: 1.0,
599 details: "sign domain proves value is zero".to_string(),
600 },
601 AbstractValue::Sign(SignValue::Positive) | AbstractValue::Sign(SignValue::Negative) => {
602 PropertyResult {
603 property: prop.clone(),
604 result: SafetyCheckResult::Safe,
605 confidence: 1.0,
606 details: "sign domain proves non-zero".to_string(),
607 }
608 }
609 AbstractValue::Constant(DomainConstantValue::Value(v)) => {
610 if *v != 0.0 {
611 PropertyResult {
612 property: prop.clone(),
613 result: SafetyCheckResult::Safe,
614 confidence: 1.0,
615 details: format!("constant {} ≠ 0", v),
616 }
617 } else {
618 PropertyResult {
619 property: prop.clone(),
620 result: SafetyCheckResult::Unsafe,
621 confidence: 1.0,
622 details: "constant is zero".to_string(),
623 }
624 }
625 }
626 _ => PropertyResult {
627 property: prop.clone(),
628 result: SafetyCheckResult::Unknown,
629 confidence: 0.0,
630 details: "abstract domain insufficient".to_string(),
631 },
632 }
633}
634
635fn decide_no_overflow(prop: &Property, value: &AbstractValue) -> PropertyResult {
636 match value {
637 AbstractValue::Interval { min, max } => {
638 if min.is_finite() && max.is_finite() {
639 PropertyResult {
640 property: prop.clone(),
641 result: SafetyCheckResult::Safe,
642 confidence: 1.0,
643 details: "interval is finite".to_string(),
644 }
645 } else {
646 PropertyResult {
647 property: prop.clone(),
648 result: SafetyCheckResult::Unknown,
649 confidence: 0.3,
650 details: "interval is unbounded".to_string(),
651 }
652 }
653 }
654 AbstractValue::Constant(DomainConstantValue::Value(v)) => {
655 if v.is_finite() {
656 PropertyResult {
657 property: prop.clone(),
658 result: SafetyCheckResult::Safe,
659 confidence: 1.0,
660 details: format!("constant {} is finite", v),
661 }
662 } else {
663 PropertyResult {
664 property: prop.clone(),
665 result: SafetyCheckResult::Unsafe,
666 confidence: 1.0,
667 details: "constant is not finite".to_string(),
668 }
669 }
670 }
671 _ => PropertyResult {
672 property: prop.clone(),
673 result: SafetyCheckResult::Unknown,
674 confidence: 0.3,
675 details: "abstract domain insufficient for overflow check".to_string(),
676 },
677 }
678}
679
680impl Default for PropertyChecker {
681 fn default() -> Self {
682 Self::new()
683 }
684}
685
686pub struct AnalysisCache {
688 hits: usize,
689 misses: usize,
690}
691
692impl AnalysisCache {
693 pub fn new() -> Self {
694 Self { hits: 0, misses: 0 }
695 }
696
697 pub fn hit_count(&self) -> usize {
698 self.hits
699 }
700
701 pub fn miss_count(&self) -> usize {
702 self.misses
703 }
704
705 pub fn clear(&mut self) {
706 self.hits = 0;
707 self.misses = 0;
708 }
709}
710
711impl Default for AnalysisCache {
712 fn default() -> Self {
713 Self::new()
714 }
715}
716
717impl AbstractInterpreter {
719 fn convert_to_abstract_graph(
728 &self,
729 graph: &ComputationGraph,
730 _domain: &dyn AbstractDomain,
731 ) -> JitResult<AbstractGraph> {
732 let mut abstract_graph = AbstractGraph::new();
733 for (node_id, node) in graph.nodes() {
734 let op = Self::classify_operation(&node.operation);
735 abstract_graph.node_ops.insert(node_id, op);
736 let preds = graph.get_node_inputs(node_id);
737 let succs = graph.get_node_outputs(node_id);
738 abstract_graph.predecessors.insert(node_id, preds);
739 abstract_graph.successors.insert(node_id, succs);
740 }
741 for node_id in abstract_graph.node_ops.keys().copied().collect::<Vec<_>>() {
742 let preds_empty = abstract_graph
743 .predecessors
744 .get(&node_id)
745 .map(|v| v.is_empty())
746 .unwrap_or(true);
747 let succs_empty = abstract_graph
748 .successors
749 .get(&node_id)
750 .map(|v| v.is_empty())
751 .unwrap_or(true);
752 if preds_empty {
753 abstract_graph.entry_nodes.push(node_id);
754 }
755 if succs_empty {
756 abstract_graph.exit_nodes.push(node_id);
757 }
758 }
759 Ok(abstract_graph)
760 }
761
762 fn classify_operation(op: &Operation) -> AbstractNodeOp {
768 match op {
769 Operation::Input | Operation::Parameter(_) => AbstractNodeOp::Input,
770 Operation::Constant(info) => {
771 let v = match &info.value {
772 OpConstantValue::Float(f) | OpConstantValue::Scalar(f) => Some(*f),
773 OpConstantValue::Int(i) | OpConstantValue::IntScalar(i) => Some(*i as f64),
774 OpConstantValue::UInt(u) => Some(*u as f64),
775 OpConstantValue::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
776 _ => None,
777 };
778 match v {
779 Some(value) => AbstractNodeOp::Constant(value),
780 None => AbstractNodeOp::Unknown,
781 }
782 }
783 Operation::Add => AbstractNodeOp::Binary(BinaryAbstractOp::Add),
784 Operation::Sub => AbstractNodeOp::Binary(BinaryAbstractOp::Sub),
785 Operation::Mul => AbstractNodeOp::Binary(BinaryAbstractOp::Mul),
786 Operation::Div => AbstractNodeOp::Binary(BinaryAbstractOp::Div),
787 Operation::Neg => AbstractNodeOp::Unary(UnaryAbstractOp::Neg),
788 Operation::Abs => AbstractNodeOp::Unary(UnaryAbstractOp::Abs),
789 Operation::Sqrt => AbstractNodeOp::Unary(UnaryAbstractOp::Sqrt),
790 Operation::Sin => AbstractNodeOp::Unary(UnaryAbstractOp::Sin),
791 Operation::Cos => AbstractNodeOp::Unary(UnaryAbstractOp::Cos),
792 Operation::Exp => AbstractNodeOp::Unary(UnaryAbstractOp::Exp),
793 Operation::Log => AbstractNodeOp::Unary(UnaryAbstractOp::Log),
794 _ => AbstractNodeOp::Unknown,
795 }
796 }
797
798 fn transfer_node(
806 node: NodeId,
807 graph: &AbstractGraph,
808 state: &HashMap<NodeId, AbstractValue>,
809 domain: &dyn AbstractDomain,
810 ) -> JitResult<AbstractValue> {
811 let op = match graph.node_ops.get(&node) {
812 Some(op) => op,
813 None => return Ok(domain.top()),
814 };
815 match op {
816 AbstractNodeOp::Input | AbstractNodeOp::Unknown => Ok(domain.top()),
817 AbstractNodeOp::Constant(value) => domain.lift_constant(*value),
818 AbstractNodeOp::Binary(bin_op) => {
819 let preds = graph.predecessors_of(node);
820 if preds.len() < 2 {
821 return Ok(domain.top());
822 }
823 let left = state
824 .get(&preds[0])
825 .cloned()
826 .unwrap_or_else(|| domain.bottom());
827 let right = state
828 .get(&preds[1])
829 .cloned()
830 .unwrap_or_else(|| domain.bottom());
831 domain.abstract_binary_op(*bin_op, &left, &right)
832 }
833 AbstractNodeOp::Unary(un_op) => {
834 let preds = graph.predecessors_of(node);
835 if preds.is_empty() {
836 return Ok(domain.top());
837 }
838 let operand = state
839 .get(&preds[0])
840 .cloned()
841 .unwrap_or_else(|| domain.bottom());
842 domain.abstract_unary_op(*un_op, &operand)
843 }
844 }
845 }
846
847 fn values_equal(a: &AbstractValue, b: &AbstractValue, domain: &dyn AbstractDomain) -> bool {
853 domain.less_equal(a, b) && domain.less_equal(b, a)
854 }
855
856 fn forward_analysis(
867 &mut self,
868 graph: &AbstractGraph,
869 domain: &dyn AbstractDomain,
870 ) -> JitResult<ForwardAnalysisResult> {
871 let mut result = ForwardAnalysisResult::new();
872 let mut post_states: HashMap<NodeId, AbstractValue> = HashMap::new();
873 let mut pre_states: HashMap<NodeId, AbstractValue> = HashMap::new();
874 let mut visit_count: HashMap<NodeId, usize> = HashMap::new();
875
876 for node in graph.nodes() {
877 post_states.insert(node, domain.bottom());
878 pre_states.insert(node, domain.bottom());
879 }
880
881 let mut worklist: VecDeque<NodeId> = if graph.entry_nodes.is_empty() {
882 graph.nodes().collect()
883 } else {
884 graph.entry_nodes.iter().copied().collect()
885 };
886 let mut in_worklist: HashSet<NodeId> = worklist.iter().copied().collect();
887
888 let mut iterations = 0usize;
889 let max_iterations = self.config.max_iterations.max(1);
890 let widening_delay = self.config.widening_delay;
891 let mut converged = false;
892
893 while let Some(node) = worklist.pop_front() {
894 in_worklist.remove(&node);
895 iterations += 1;
896 if iterations > max_iterations {
897 break;
898 }
899
900 let pre = Self::join_predecessor_states(node, graph, &post_states, domain)?;
902 pre_states.insert(node, pre);
903
904 let transferred = Self::transfer_node(node, graph, &post_states, domain)?;
906 let old_post = post_states
907 .get(&node)
908 .cloned()
909 .unwrap_or_else(|| domain.bottom());
910
911 let visits = visit_count.entry(node).or_insert(0);
912 *visits += 1;
913 let new_post = if widening_delay > 0 && *visits > widening_delay {
914 domain.widen(&old_post, &transferred)?
915 } else {
916 domain.join(&old_post, &transferred)?
917 };
918
919 if !Self::values_equal(&new_post, &old_post, domain) {
920 post_states.insert(node, new_post);
921 for succ in graph.successors_of(node) {
922 if in_worklist.insert(*succ) {
923 worklist.push_back(*succ);
924 }
925 }
926 }
927
928 if worklist.is_empty() {
929 converged = true;
930 break;
931 }
932 }
933
934 if self.config.enable_narrowing {
936 for node in graph.nodes() {
937 let transferred = Self::transfer_node(node, graph, &post_states, domain)?;
938 let current = post_states
939 .get(&node)
940 .cloned()
941 .unwrap_or_else(|| domain.bottom());
942 let narrowed = domain.narrow(¤t, &transferred)?;
943 post_states.insert(node, narrowed);
944 }
945 }
946
947 result.pre_states = pre_states;
948 result.post_states = post_states;
949 result.iterations = iterations;
950 result.converged = converged && iterations <= max_iterations;
951 Ok(result)
952 }
953
954 fn join_predecessor_states(
959 node: NodeId,
960 graph: &AbstractGraph,
961 post_states: &HashMap<NodeId, AbstractValue>,
962 domain: &dyn AbstractDomain,
963 ) -> JitResult<AbstractValue> {
964 let preds = graph.predecessors_of(node);
965 if preds.is_empty() {
966 return Ok(domain.bottom());
967 }
968 let mut acc = domain.bottom();
969 for pred in preds {
970 let pred_state = post_states
971 .get(pred)
972 .cloned()
973 .unwrap_or_else(|| domain.bottom());
974 acc = domain.join(&acc, &pred_state)?;
975 }
976 Ok(acc)
977 }
978
979 fn backward_analysis(
988 &mut self,
989 graph: &AbstractGraph,
990 domain: &dyn AbstractDomain,
991 ) -> JitResult<BackwardAnalysisResult> {
992 let mut result = BackwardAnalysisResult::new();
993 let mut pre_states: HashMap<NodeId, AbstractValue> = HashMap::new();
994 let mut post_states: HashMap<NodeId, AbstractValue> = HashMap::new();
995 let mut visit_count: HashMap<NodeId, usize> = HashMap::new();
996
997 for node in graph.nodes() {
998 pre_states.insert(node, domain.bottom());
999 post_states.insert(node, domain.top());
1000 }
1001
1002 let mut worklist: VecDeque<NodeId> = if graph.exit_nodes.is_empty() {
1003 graph.nodes().collect()
1004 } else {
1005 graph.exit_nodes.iter().copied().collect()
1006 };
1007 let mut in_worklist: HashSet<NodeId> = worklist.iter().copied().collect();
1008
1009 let mut iterations = 0usize;
1010 let max_iterations = self.config.max_iterations.max(1);
1011 let widening_delay = self.config.widening_delay;
1012 let mut converged = false;
1013
1014 while let Some(node) = worklist.pop_front() {
1015 in_worklist.remove(&node);
1016 iterations += 1;
1017 if iterations > max_iterations {
1018 break;
1019 }
1020
1021 let succs = graph.successors_of(node);
1023 let post = if succs.is_empty() {
1024 domain.top()
1025 } else {
1026 let mut acc = domain.bottom();
1027 for s in succs {
1028 let s_state = pre_states
1029 .get(s)
1030 .cloned()
1031 .unwrap_or_else(|| domain.bottom());
1032 acc = domain.join(&acc, &s_state)?;
1033 }
1034 acc
1035 };
1036 post_states.insert(node, post.clone());
1037
1038 let old_pre = pre_states
1040 .get(&node)
1041 .cloned()
1042 .unwrap_or_else(|| domain.bottom());
1043 let visits = visit_count.entry(node).or_insert(0);
1044 *visits += 1;
1045 let new_pre = if widening_delay > 0 && *visits > widening_delay {
1046 domain.widen(&old_pre, &post)?
1047 } else {
1048 domain.join(&old_pre, &post)?
1049 };
1050
1051 if !Self::values_equal(&new_pre, &old_pre, domain) {
1052 pre_states.insert(node, new_pre);
1053 for pred in graph.predecessors_of(node) {
1054 if in_worklist.insert(*pred) {
1055 worklist.push_back(*pred);
1056 }
1057 }
1058 }
1059
1060 if worklist.is_empty() {
1061 converged = true;
1062 break;
1063 }
1064 }
1065
1066 result.pre_states = pre_states;
1067 result.post_states = post_states;
1068 result.iterations = iterations;
1069 result.converged = converged && iterations <= max_iterations;
1070 Ok(result)
1071 }
1072
1073 fn analyze_precision(
1080 &self,
1081 forward_result: &ForwardAnalysisResult,
1082 graph: &AbstractGraph,
1083 ) -> PrecisionAnalysis {
1084 let mut node_precision = HashMap::new();
1085 let mut total = 0.0f64;
1086 let mut count = 0usize;
1087 let mut suggestions = Vec::new();
1088
1089 for node in graph.nodes() {
1090 let val = forward_result.post_states.get(&node);
1091 let p = val.map(|v| v.precision()).unwrap_or(0.0);
1092 node_precision.insert(node, p);
1093 total += p;
1094 count += 1;
1095 if p < self.config.precision_threshold {
1096 suggestions.push(format!(
1097 "node {:?}: low precision ({:.2}); consider richer abstract domain",
1098 node, p
1099 ));
1100 }
1101 }
1102 let overall_precision = if count == 0 {
1103 0.0
1104 } else {
1105 total / count as f64
1106 };
1107 PrecisionAnalysis {
1108 overall_precision,
1109 node_precision,
1110 improvement_suggestions: suggestions,
1111 }
1112 }
1113
1114 fn analyze_performance(
1123 &self,
1124 forward_result: &ForwardAnalysisResult,
1125 graph: &ComputationGraph,
1126 ) -> PerformanceAnalysis {
1127 let mut bottlenecks = Vec::new();
1128 let mut opportunities = Vec::new();
1129 let mut weight = 0.0f64;
1130 let total_nodes = graph.node_count().max(1) as f64;
1131
1132 for (node_id, node) in graph.nodes() {
1133 let complexity = node.complexity_estimate() as f64;
1134 weight += complexity;
1135 let category = node.operation_category();
1136 if matches!(
1137 category,
1138 crate::graph::core::OperationCategory::LinearAlgebra
1139 | crate::graph::core::OperationCategory::NeuralNetwork
1140 | crate::graph::core::OperationCategory::Reduction
1141 ) {
1142 bottlenecks.push(PerformanceBottleneck {
1143 bottleneck_type: BottleneckType::ComputationIntensive,
1144 location: node_id,
1145 severity: (complexity / 1000.0).min(1.0),
1146 description: format!("{:?} is computation-intensive", node.operation_type()),
1147 });
1148 }
1149
1150 if let Some(value) = forward_result.post_states.get(&node_id) {
1151 if value.is_constant() {
1152 opportunities.push(OptimizationOpportunity {
1153 optimization_type: OptimizationType::ConstantFolding,
1154 location: node_id,
1155 benefit: 0.9,
1156 description: "abstract value is a constant; fold at compile time"
1157 .to_string(),
1158 });
1159 }
1160 }
1161 }
1162
1163 let complexity_score = (weight / (total_nodes * 100.0)).min(1.0);
1164 PerformanceAnalysis {
1165 complexity_score,
1166 bottlenecks,
1167 optimization_opportunities: opportunities,
1168 }
1169 }
1170
1171 fn analyze_block(&mut self, block: &BasicBlock) -> JitResult<AbstractFunctionResult> {
1178 let domain = self.domain_factory.create_domain(&self.config.domain_type);
1179 let mut value_state: HashMap<IrValue, AbstractValue> = HashMap::new();
1180
1181 for p in &block.params {
1183 value_state.insert(*p, domain.top());
1184 }
1185
1186 let mut iterations = 0usize;
1187 for instr in &block.instructions {
1188 iterations += 1;
1189 let abstract_value = Self::transfer_instruction(instr, &value_state, domain.as_ref())?;
1190 if let Some(result) = instr.result {
1191 value_state.insert(result, abstract_value);
1192 }
1193 }
1194
1195 let entry_state = Self::join_value_state(&value_state, domain.as_ref(), true)?;
1196 let exit_state = Self::join_value_state(&value_state, domain.as_ref(), false)?;
1197
1198 let invariants = Self::block_invariants(block, &value_state);
1199
1200 let forward_result = FunctionForwardResult {
1201 converged: true,
1202 iterations,
1203 entry_state: Some(entry_state),
1204 exit_state: Some(exit_state.clone()),
1205 };
1206
1207 let backward_result = if self.config.enable_backward_analysis {
1208 Some(FunctionBackwardResult {
1209 converged: true,
1210 iterations,
1211 entry_state: Some(domain.top()),
1212 exit_state: Some(exit_state),
1213 })
1214 } else {
1215 None
1216 };
1217
1218 Ok(AbstractFunctionResult {
1219 forward_result,
1220 backward_result,
1221 invariants,
1222 })
1223 }
1224
1225 fn transfer_instruction(
1233 instr: &crate::ir::Instruction,
1234 state: &HashMap<IrValue, AbstractValue>,
1235 domain: &dyn AbstractDomain,
1236 ) -> JitResult<AbstractValue> {
1237 let lookup = |v: &IrValue| -> AbstractValue {
1238 state.get(v).cloned().unwrap_or_else(|| domain.top())
1239 };
1240
1241 match &instr.opcode {
1242 IrOpcode::Const => match instr.attrs.get("value") {
1243 Some(crate::ir::IrAttribute::Float(value)) => domain.lift_constant(*value),
1244 Some(crate::ir::IrAttribute::Int(value)) => domain.lift_constant(*value as f64),
1245 Some(crate::ir::IrAttribute::Bool(value)) => {
1246 domain.lift_constant(if *value { 1.0 } else { 0.0 })
1247 }
1248 _ => Ok(domain.top()),
1249 },
1250 IrOpcode::Add | IrOpcode::Sub | IrOpcode::Mul | IrOpcode::Div => {
1251 if instr.operands.len() < 2 {
1252 return Ok(domain.top());
1253 }
1254 let l = lookup(&instr.operands[0]);
1255 let r = lookup(&instr.operands[1]);
1256 let op = match instr.opcode {
1257 IrOpcode::Add => BinaryAbstractOp::Add,
1258 IrOpcode::Sub => BinaryAbstractOp::Sub,
1259 IrOpcode::Mul => BinaryAbstractOp::Mul,
1260 IrOpcode::Div => BinaryAbstractOp::Div,
1261 _ => unreachable!("matched outer opcode"),
1262 };
1263 domain.abstract_binary_op(op, &l, &r)
1264 }
1265 IrOpcode::Neg
1266 | IrOpcode::Abs
1267 | IrOpcode::Sqrt
1268 | IrOpcode::Sin
1269 | IrOpcode::Cos
1270 | IrOpcode::Exp
1271 | IrOpcode::Log => {
1272 if instr.operands.is_empty() {
1273 return Ok(domain.top());
1274 }
1275 let operand = lookup(&instr.operands[0]);
1276 let op = match instr.opcode {
1277 IrOpcode::Neg => UnaryAbstractOp::Neg,
1278 IrOpcode::Abs => UnaryAbstractOp::Abs,
1279 IrOpcode::Sqrt => UnaryAbstractOp::Sqrt,
1280 IrOpcode::Sin => UnaryAbstractOp::Sin,
1281 IrOpcode::Cos => UnaryAbstractOp::Cos,
1282 IrOpcode::Exp => UnaryAbstractOp::Exp,
1283 IrOpcode::Log => UnaryAbstractOp::Log,
1284 _ => unreachable!("matched outer opcode"),
1285 };
1286 domain.abstract_unary_op(op, &operand)
1287 }
1288 _ => Ok(domain.top()),
1289 }
1290 }
1291
1292 fn join_value_state(
1298 state: &HashMap<IrValue, AbstractValue>,
1299 domain: &dyn AbstractDomain,
1300 is_entry: bool,
1301 ) -> JitResult<AbstractValue> {
1302 if is_entry || state.is_empty() {
1303 return Ok(domain.top());
1304 }
1305 let mut acc = domain.bottom();
1306 for v in state.values() {
1307 acc = domain.join(&acc, v)?;
1308 }
1309 Ok(acc)
1310 }
1311
1312 fn block_invariants(
1318 block: &BasicBlock,
1319 state: &HashMap<IrValue, AbstractValue>,
1320 ) -> Vec<FunctionInvariant> {
1321 let mut invariants = Vec::new();
1322 for (value, av) in state {
1323 if av.is_constant() {
1324 invariants.push(FunctionInvariant {
1325 invariant_type: InvariantType::NumericalProperty,
1326 description: format!("value {:?} is constant", value),
1327 confidence: 1.0,
1328 location: format!("block {}", block.id),
1329 });
1330 }
1331 }
1332 invariants
1333 }
1334
1335 fn interprocedural_analysis_blocks(
1343 &self,
1344 block_results: &HashMap<BlockId, AbstractFunctionResult>,
1345 ) -> JitResult<InterproceduralAnalysisResult> {
1346 let mut call_graph: HashMap<String, Vec<String>> = HashMap::new();
1347 let mut global_invariants = Vec::new();
1348 let block_names: Vec<String> = block_results
1349 .keys()
1350 .map(|id| format!("block_{}", id))
1351 .collect();
1352 call_graph.insert("module".to_string(), block_names);
1353
1354 for (block_id, res) in block_results {
1355 for inv in &res.invariants {
1356 if inv.confidence >= self.config.precision_threshold {
1357 global_invariants.push(Invariant {
1358 invariant_type: inv.invariant_type.clone(),
1359 description: format!("block {}: {}", block_id, inv.description),
1360 confidence: inv.confidence,
1361 location: inv.location.clone(),
1362 });
1363 }
1364 }
1365 }
1366 Ok(InterproceduralAnalysisResult {
1367 call_graph,
1368 global_invariants,
1369 })
1370 }
1371
1372 fn detect_module_invariants_blocks(
1379 &self,
1380 block_results: &HashMap<BlockId, AbstractFunctionResult>,
1381 ) -> JitResult<Vec<ModuleInvariant>> {
1382 if block_results.is_empty() {
1383 return Ok(Vec::new());
1384 }
1385 let mut all_constant = true;
1386 for res in block_results.values() {
1387 let is_block_constant = res
1388 .forward_result
1389 .exit_state
1390 .as_ref()
1391 .map(AbstractValue::is_constant)
1392 .unwrap_or(false);
1393 if !is_block_constant {
1394 all_constant = false;
1395 break;
1396 }
1397 }
1398 let mut invariants = Vec::new();
1399 if all_constant {
1400 invariants.push(ModuleInvariant {
1401 invariant_type: InvariantType::NumericalProperty,
1402 description: "all blocks have constant exit state".to_string(),
1403 confidence: 1.0,
1404 scope: "module".to_string(),
1405 });
1406 }
1407 Ok(invariants)
1408 }
1409}
1410
1411#[derive(Debug, Clone)]
1420pub enum AbstractNodeOp {
1421 Input,
1424 Constant(f64),
1426 Binary(BinaryAbstractOp),
1428 Unary(UnaryAbstractOp),
1430 Unknown,
1433}
1434
1435#[derive(Debug, Clone, Default)]
1443pub struct AbstractGraph {
1444 pub node_ops: HashMap<NodeId, AbstractNodeOp>,
1446 pub predecessors: HashMap<NodeId, Vec<NodeId>>,
1448 pub successors: HashMap<NodeId, Vec<NodeId>>,
1450 pub entry_nodes: Vec<NodeId>,
1452 pub exit_nodes: Vec<NodeId>,
1454}
1455
1456impl AbstractGraph {
1457 pub fn new() -> Self {
1459 Self::default()
1460 }
1461
1462 pub fn nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1464 self.node_ops.keys().copied()
1465 }
1466
1467 pub fn node_count(&self) -> usize {
1469 self.node_ops.len()
1470 }
1471
1472 pub fn successors_of(&self, node: NodeId) -> &[NodeId] {
1474 self.successors.get(&node).map(Vec::as_slice).unwrap_or(&[])
1475 }
1476
1477 pub fn predecessors_of(&self, node: NodeId) -> &[NodeId] {
1479 self.predecessors
1480 .get(&node)
1481 .map(Vec::as_slice)
1482 .unwrap_or(&[])
1483 }
1484}