Skip to main content

torsh_jit/
speculative_optimization.rs

1//! Speculative Optimization for ToRSh JIT
2//!
3//! This module implements speculative optimization techniques that make optimistic
4//! assumptions about runtime behavior and provide deoptimization mechanisms when
5//! those assumptions are violated.
6
7use crate::{ComputationGraph, JitError, JitResult, Node, NodeId};
8use petgraph::graph::NodeIndex;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::sync::{
12    atomic::{AtomicBool, AtomicU64, Ordering},
13    Arc, RwLock,
14};
15
16/// Speculative optimization manager
17pub struct SpeculativeOptimizer {
18    config: SpeculativeConfig,
19    assumptions: Arc<RwLock<HashMap<AssumptionId, Assumption>>>,
20    guards: Arc<RwLock<HashMap<NodeId, Vec<Guard>>>>,
21    deopt_counter: AtomicU64,
22    enabled: AtomicBool,
23}
24
25/// Configuration for speculative optimization
26#[derive(Debug, Clone)]
27pub struct SpeculativeConfig {
28    /// Maximum number of active assumptions
29    pub max_assumptions: usize,
30
31    /// Deoptimization threshold - disable after this many failures
32    pub deopt_threshold: u64,
33
34    /// Confidence threshold for applying speculation
35    pub confidence_threshold: f64,
36
37    /// Enable type speculation
38    pub enable_type_speculation: bool,
39
40    /// Enable shape speculation
41    pub enable_shape_speculation: bool,
42
43    /// Enable value speculation
44    pub enable_value_speculation: bool,
45
46    /// Enable nullability speculation
47    pub enable_nullability_speculation: bool,
48
49    /// Enable branch speculation
50    pub enable_branch_speculation: bool,
51
52    /// Enable loop iteration speculation
53    pub enable_loop_speculation: bool,
54
55    /// Speculation aggressiveness (0.0 to 1.0)
56    pub aggressiveness: f64,
57}
58
59impl Default for SpeculativeConfig {
60    fn default() -> Self {
61        Self {
62            max_assumptions: 1000,
63            deopt_threshold: 100,
64            confidence_threshold: 0.8,
65            enable_type_speculation: true,
66            enable_shape_speculation: true,
67            enable_value_speculation: false, // More risky
68            enable_nullability_speculation: true,
69            enable_branch_speculation: true,
70            enable_loop_speculation: true,
71            aggressiveness: 0.7,
72        }
73    }
74}
75
76/// Unique identifier for assumptions
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
78pub struct AssumptionId(pub u64);
79
80/// Speculation assumption
81#[derive(Debug, Clone)]
82pub struct Assumption {
83    pub id: AssumptionId,
84    pub assumption_type: AssumptionType,
85    pub node_id: NodeId,
86    pub confidence: f64,
87    pub success_count: u64,
88    pub failure_count: u64,
89    pub created_at: std::time::SystemTime,
90    pub metadata: HashMap<String, String>,
91}
92
93/// Types of speculative assumptions
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub enum AssumptionType {
96    /// Assume tensor has specific data type
97    TypeSpeculation { expected_type: String },
98
99    /// Assume tensor has specific shape
100    ShapeSpeculation { expected_shape: Vec<usize> },
101
102    /// Assume value is constant
103    ValueSpeculation { expected_value: f64, tolerance: f64 },
104
105    /// Assume value is not null/NaN
106    NullabilitySpeculation,
107
108    /// Assume branch is usually taken/not taken
109    BranchSpeculation {
110        usually_taken: bool,
111        probability: f64,
112    },
113
114    /// Assume loop iterates specific number of times
115    LoopSpeculation {
116        expected_iterations: u64,
117        tolerance: u64,
118    },
119
120    /// Assume memory access pattern
121    MemorySpeculation { access_pattern: MemoryAccessPattern },
122}
123
124/// Memory access patterns for speculation
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub enum MemoryAccessPattern {
127    Sequential,
128    Random,
129    Strided { stride: usize },
130    Clustered { cluster_size: usize },
131}
132
133/// Runtime guard for checking assumptions
134#[derive(Debug, Clone)]
135pub struct Guard {
136    pub assumption_id: AssumptionId,
137    pub guard_type: GuardType,
138    pub check_frequency: GuardFrequency,
139}
140
141/// Types of runtime guards
142#[derive(Debug, Clone, PartialEq)]
143pub enum GuardType {
144    /// Check tensor data type
145    TypeCheck,
146
147    /// Check tensor shape
148    ShapeCheck,
149
150    /// Check value against expected
151    ValueCheck,
152
153    /// Check for null/NaN values
154    NullabilityCheck,
155
156    /// Check branch outcome
157    BranchCheck,
158
159    /// Check loop iteration count
160    LoopCheck,
161
162    /// Check memory access pattern
163    MemoryCheck,
164}
165
166/// Guard check frequency
167#[derive(Debug, Clone, PartialEq)]
168pub enum GuardFrequency {
169    /// Check every execution
170    Always,
171
172    /// Check probabilistically
173    Probabilistic(f64),
174
175    /// Check after N executions
176    Periodic(u64),
177
178    /// Check only first N executions
179    InitialOnly(u64),
180}
181
182/// Result of a speculation attempt
183#[derive(Debug, Clone)]
184pub struct SpeculationResult {
185    pub assumptions_made: Vec<AssumptionId>,
186    pub optimizations_applied: Vec<SpeculativeOptimization>,
187    pub guards_installed: Vec<Guard>,
188    pub estimated_speedup: f64,
189}
190
191/// Speculative optimizations that can be applied
192#[derive(Debug, Clone)]
193pub struct SpeculativeOptimization {
194    pub optimization_type: SpeculativeOptimizationType,
195    pub node_id: NodeId,
196    pub description: String,
197    pub estimated_benefit: f64,
198}
199
200/// Types of speculative optimizations
201#[derive(Debug, Clone, PartialEq)]
202pub enum SpeculativeOptimizationType {
203    /// Remove type checks
204    TypeCheckElimination,
205
206    /// Remove bounds checks
207    BoundsCheckElimination,
208
209    /// Optimize for specific shape
210    ShapeSpecialization,
211
212    /// Constant propagation
213    ConstantPropagation,
214
215    /// Dead code elimination
216    DeadCodeElimination,
217
218    /// Loop unrolling
219    LoopUnrolling,
220
221    /// Branch elimination
222    BranchElimination,
223
224    /// Memory prefetching
225    MemoryPrefetching,
226
227    /// Vectorization
228    VectorizationOptimization,
229}
230
231/// Deoptimization event
232#[derive(Debug, Clone)]
233pub struct DeoptimizationEvent {
234    pub assumption_id: AssumptionId,
235    pub node_id: NodeId,
236    pub reason: String,
237    pub timestamp: std::time::SystemTime,
238    pub execution_count: u64,
239}
240
241impl SpeculativeOptimizer {
242    /// Create a new speculative optimizer
243    pub fn new(config: SpeculativeConfig) -> Self {
244        Self {
245            config,
246            assumptions: Arc::new(RwLock::new(HashMap::new())),
247            guards: Arc::new(RwLock::new(HashMap::new())),
248            deopt_counter: AtomicU64::new(0),
249            enabled: AtomicBool::new(true),
250        }
251    }
252
253    /// Analyze graph and generate speculative optimizations
254    pub fn analyze_and_speculate(
255        &self,
256        graph: &ComputationGraph,
257        execution_history: &ExecutionHistory,
258    ) -> JitResult<SpeculationResult> {
259        if !self.enabled.load(Ordering::Relaxed) {
260            return Ok(SpeculationResult {
261                assumptions_made: Vec::new(),
262                optimizations_applied: Vec::new(),
263                guards_installed: Vec::new(),
264                estimated_speedup: 1.0,
265            });
266        }
267
268        let mut assumptions_made = Vec::new();
269        let mut optimizations = Vec::new();
270        let mut guards = Vec::new();
271        let mut total_speedup = 1.0;
272
273        for (node_id, node) in graph.nodes() {
274            // Analyze node history for speculation opportunities
275            if let Some(node_history) = execution_history.get_node_history(node_id) {
276                // Type speculation
277                if self.config.enable_type_speculation {
278                    if let Some(spec_result) =
279                        self.analyze_type_speculation(node_id, node, node_history)?
280                    {
281                        assumptions_made.extend(spec_result.assumptions_made);
282                        optimizations.extend(spec_result.optimizations_applied);
283                        guards.extend(spec_result.guards_installed);
284                        total_speedup *= spec_result.estimated_speedup;
285                    }
286                }
287
288                // Shape speculation
289                if self.config.enable_shape_speculation {
290                    if let Some(spec_result) =
291                        self.analyze_shape_speculation(node_id, node, node_history)?
292                    {
293                        assumptions_made.extend(spec_result.assumptions_made);
294                        optimizations.extend(spec_result.optimizations_applied);
295                        guards.extend(spec_result.guards_installed);
296                        total_speedup *= spec_result.estimated_speedup;
297                    }
298                }
299
300                // Value speculation
301                if self.config.enable_value_speculation {
302                    if let Some(spec_result) =
303                        self.analyze_value_speculation(node_id, node, node_history)?
304                    {
305                        assumptions_made.extend(spec_result.assumptions_made);
306                        optimizations.extend(spec_result.optimizations_applied);
307                        guards.extend(spec_result.guards_installed);
308                        total_speedup *= spec_result.estimated_speedup;
309                    }
310                }
311
312                // Branch speculation
313                if self.config.enable_branch_speculation {
314                    if let Some(spec_result) =
315                        self.analyze_branch_speculation(node_id, node, node_history)?
316                    {
317                        assumptions_made.extend(spec_result.assumptions_made);
318                        optimizations.extend(spec_result.optimizations_applied);
319                        guards.extend(spec_result.guards_installed);
320                        total_speedup *= spec_result.estimated_speedup;
321                    }
322                }
323            }
324        }
325
326        // Install guards
327        if let Ok(mut guard_map) = self.guards.write() {
328            for guard in &guards {
329                guard_map
330                    .entry(NodeIndex::new(guard.assumption_id.0 as usize))
331                    .or_insert_with(Vec::new)
332                    .push(guard.clone());
333            }
334        }
335
336        // Record assumptions — pair each AssumptionId with the optimization that produced
337        // it.  The two vecs are grown in lock-step (one optimization per assumption), so
338        // zip gives correct context for every entry.
339        if let Ok(mut assumption_map) = self.assumptions.write() {
340            for (assumption_id, optimization) in assumptions_made.iter().zip(optimizations.iter()) {
341                let assumption = self.build_assumption(*assumption_id, optimization);
342                assumption_map.insert(*assumption_id, assumption);
343            }
344        }
345
346        Ok(SpeculationResult {
347            assumptions_made,
348            optimizations_applied: optimizations,
349            guards_installed: guards,
350            estimated_speedup: total_speedup,
351        })
352    }
353
354    /// Apply speculative optimizations to the graph
355    pub fn apply_speculative_optimizations(
356        &self,
357        graph: &mut ComputationGraph,
358        result: &SpeculationResult,
359    ) -> JitResult<usize> {
360        let mut applied_count = 0;
361
362        for optimization in &result.optimizations_applied {
363            match optimization.optimization_type {
364                SpeculativeOptimizationType::TypeCheckElimination => {
365                    if self.apply_type_check_elimination(graph, optimization)? {
366                        applied_count += 1;
367                    }
368                }
369                SpeculativeOptimizationType::ShapeSpecialization => {
370                    if self.apply_shape_specialization(graph, optimization)? {
371                        applied_count += 1;
372                    }
373                }
374                SpeculativeOptimizationType::ConstantPropagation => {
375                    if self.apply_constant_propagation(graph, optimization)? {
376                        applied_count += 1;
377                    }
378                }
379                SpeculativeOptimizationType::BranchElimination => {
380                    if self.apply_branch_elimination(graph, optimization)? {
381                        applied_count += 1;
382                    }
383                }
384                _ => {
385                    // Other optimizations can be implemented as needed
386                }
387            }
388        }
389
390        Ok(applied_count)
391    }
392
393    /// Check guards during execution and handle deoptimization
394    pub fn check_guards(&self, node_id: NodeId, runtime_info: &RuntimeInfo) -> JitResult<bool> {
395        let guard_map = self
396            .guards
397            .read()
398            .map_err(|_| JitError::RuntimeError("Failed to read guards".to_string()))?;
399
400        if let Some(node_guards) = guard_map.get(&node_id) {
401            for guard in node_guards {
402                if self.should_check_guard(guard, runtime_info.execution_count) {
403                    let check_passed = self.execute_guard_check(guard, runtime_info)?;
404
405                    if !check_passed {
406                        self.handle_deoptimization(
407                            guard.assumption_id,
408                            node_id,
409                            "Guard check failed",
410                        )?;
411                        return Ok(false);
412                    }
413                }
414            }
415        }
416
417        Ok(true)
418    }
419
420    /// Record successful execution (reinforces assumptions)
421    pub fn record_success(&self, assumption_id: AssumptionId) {
422        if let Ok(mut assumptions) = self.assumptions.write() {
423            if let Some(assumption) = assumptions.get_mut(&assumption_id) {
424                assumption.success_count += 1;
425                assumption.confidence = self.calculate_confidence(assumption);
426            }
427        }
428    }
429
430    /// Handle deoptimization when assumptions fail
431    pub fn handle_deoptimization(
432        &self,
433        assumption_id: AssumptionId,
434        node_id: NodeId,
435        reason: &str,
436    ) -> JitResult<()> {
437        let deopt_count = self.deopt_counter.fetch_add(1, Ordering::Relaxed);
438
439        // Update assumption failure count
440        if let Ok(mut assumptions) = self.assumptions.write() {
441            if let Some(assumption) = assumptions.get_mut(&assumption_id) {
442                assumption.failure_count += 1;
443                assumption.confidence = self.calculate_confidence(assumption);
444
445                // Remove assumption if confidence drops too low
446                if assumption.confidence < 0.3 {
447                    assumptions.remove(&assumption_id);
448                }
449            }
450        }
451
452        // Disable speculative optimization if too many failures
453        if deopt_count > self.config.deopt_threshold {
454            self.enabled.store(false, Ordering::Relaxed);
455        }
456
457        // Log deoptimization event
458        let event = DeoptimizationEvent {
459            assumption_id,
460            node_id,
461            reason: reason.to_string(),
462            timestamp: std::time::SystemTime::now(),
463            execution_count: deopt_count,
464        };
465
466        self.log_deoptimization_event(&event);
467
468        Ok(())
469    }
470
471    /// Get speculation statistics
472    pub fn get_statistics(&self) -> JitResult<SpeculationStatistics> {
473        let assumptions = self
474            .assumptions
475            .read()
476            .map_err(|_| JitError::RuntimeError("Failed to read assumptions".to_string()))?;
477
478        let active_assumptions = assumptions.len();
479        let total_successes = assumptions.values().map(|a| a.success_count).sum();
480        let total_failures = assumptions.values().map(|a| a.failure_count).sum();
481        let avg_confidence = if !assumptions.is_empty() {
482            assumptions.values().map(|a| a.confidence).sum::<f64>() / assumptions.len() as f64
483        } else {
484            0.0
485        };
486
487        let deopt_count = self.deopt_counter.load(Ordering::Relaxed);
488        let enabled = self.enabled.load(Ordering::Relaxed);
489
490        Ok(SpeculationStatistics {
491            active_assumptions,
492            total_successes,
493            total_failures,
494            avg_confidence,
495            deoptimization_count: deopt_count,
496            enabled,
497        })
498    }
499
500    // Helper methods
501    fn analyze_type_speculation(
502        &self,
503        node_id: NodeId,
504        _node: &Node,
505        history: &NodeExecutionHistory,
506    ) -> JitResult<Option<SpeculationResult>> {
507        // Analyze type patterns in execution history
508        if let Some(dominant_type) = history.get_dominant_type(self.config.confidence_threshold) {
509            let assumption_id = self.generate_assumption_id();
510
511            let optimization = SpeculativeOptimization {
512                optimization_type: SpeculativeOptimizationType::TypeCheckElimination,
513                node_id,
514                description: format!("Assume type is always {}", dominant_type),
515                estimated_benefit: 0.05, // 5% speedup from eliminating type checks
516            };
517
518            let guard = Guard {
519                assumption_id,
520                guard_type: GuardType::TypeCheck,
521                check_frequency: GuardFrequency::Probabilistic(0.1), // Check 10% of the time
522            };
523
524            return Ok(Some(SpeculationResult {
525                assumptions_made: vec![assumption_id],
526                optimizations_applied: vec![optimization],
527                guards_installed: vec![guard],
528                estimated_speedup: 1.05,
529            }));
530        }
531
532        Ok(None)
533    }
534
535    fn analyze_shape_speculation(
536        &self,
537        node_id: NodeId,
538        _node: &Node,
539        history: &NodeExecutionHistory,
540    ) -> JitResult<Option<SpeculationResult>> {
541        // Analyze shape patterns in execution history
542        if let Some(dominant_shape) = history.get_dominant_shape(self.config.confidence_threshold) {
543            let assumption_id = self.generate_assumption_id();
544
545            let optimization = SpeculativeOptimization {
546                optimization_type: SpeculativeOptimizationType::ShapeSpecialization,
547                node_id,
548                description: format!("Specialize for shape {:?}", dominant_shape),
549                estimated_benefit: 0.15, // 15% speedup from shape specialization
550            };
551
552            let guard = Guard {
553                assumption_id,
554                guard_type: GuardType::ShapeCheck,
555                check_frequency: GuardFrequency::Always, // Shape changes are critical
556            };
557
558            return Ok(Some(SpeculationResult {
559                assumptions_made: vec![assumption_id],
560                optimizations_applied: vec![optimization],
561                guards_installed: vec![guard],
562                estimated_speedup: 1.15,
563            }));
564        }
565
566        Ok(None)
567    }
568
569    fn analyze_value_speculation(
570        &self,
571        node_id: NodeId,
572        _node: &Node,
573        history: &NodeExecutionHistory,
574    ) -> JitResult<Option<SpeculationResult>> {
575        // Analyze value patterns - look for constants
576        if let Some(constant_value) = history.get_constant_value(self.config.confidence_threshold) {
577            let assumption_id = self.generate_assumption_id();
578
579            let optimization = SpeculativeOptimization {
580                optimization_type: SpeculativeOptimizationType::ConstantPropagation,
581                node_id,
582                description: format!("Assume constant value {}", constant_value),
583                estimated_benefit: 0.20, // 20% speedup from constant propagation
584            };
585
586            let guard = Guard {
587                assumption_id,
588                guard_type: GuardType::ValueCheck,
589                check_frequency: GuardFrequency::Periodic(100), // Check every 100 executions
590            };
591
592            return Ok(Some(SpeculationResult {
593                assumptions_made: vec![assumption_id],
594                optimizations_applied: vec![optimization],
595                guards_installed: vec![guard],
596                estimated_speedup: 1.20,
597            }));
598        }
599
600        Ok(None)
601    }
602
603    fn analyze_branch_speculation(
604        &self,
605        node_id: NodeId,
606        _node: &Node,
607        history: &NodeExecutionHistory,
608    ) -> JitResult<Option<SpeculationResult>> {
609        // Analyze branch patterns
610        if let Some(branch_bias) = history.get_branch_bias(self.config.confidence_threshold) {
611            let assumption_id = self.generate_assumption_id();
612
613            let optimization = SpeculativeOptimization {
614                optimization_type: SpeculativeOptimizationType::BranchElimination,
615                node_id,
616                description: format!(
617                    "Assume branch is usually {}",
618                    if branch_bias > 0.5 {
619                        "taken"
620                    } else {
621                        "not taken"
622                    }
623                ),
624                estimated_benefit: 0.10, // 10% speedup from branch elimination
625            };
626
627            let guard = Guard {
628                assumption_id,
629                guard_type: GuardType::BranchCheck,
630                check_frequency: GuardFrequency::Probabilistic(0.05), // Check 5% of the time
631            };
632
633            return Ok(Some(SpeculationResult {
634                assumptions_made: vec![assumption_id],
635                optimizations_applied: vec![optimization],
636                guards_installed: vec![guard],
637                estimated_speedup: 1.10,
638            }));
639        }
640
641        Ok(None)
642    }
643
644    fn apply_type_check_elimination(
645        &self,
646        graph: &mut ComputationGraph,
647        optimization: &SpeculativeOptimization,
648    ) -> JitResult<bool> {
649        let node_id = optimization.node_id;
650
651        if let Some(node) = graph.node_mut(node_id) {
652            // Remove redundant type checks for nodes with stable types
653            node.set_optimization_hint("eliminate_type_checks", "true")?;
654            node.set_optimization_hint("assumed_type_stable", "true")?;
655
656            // Add guard to verify type assumption at runtime
657            node.set_optimization_hint("add_type_guard", "true")?;
658            node.set_optimization_hint("guard_frequency", "low")?;
659
660            return Ok(true);
661        }
662
663        Ok(false)
664    }
665
666    fn apply_shape_specialization(
667        &self,
668        graph: &mut ComputationGraph,
669        optimization: &SpeculativeOptimization,
670    ) -> JitResult<bool> {
671        let node_id = optimization.node_id;
672
673        if let Some(node) = graph.node_mut(node_id) {
674            // Specialize operations for the most common shape
675            node.set_optimization_hint("shape_specialized", "true")?;
676            node.set_optimization_hint("eliminate_shape_checks", "true")?;
677
678            // Extract assumed shape from optimization description
679            if optimization.description.contains("shape") {
680                node.set_optimization_hint("specialized_shape_source", "speculation")?;
681                node.set_optimization_hint("add_shape_guard", "true")?;
682            }
683
684            return Ok(true);
685        }
686
687        Ok(false)
688    }
689
690    fn apply_constant_propagation(
691        &self,
692        graph: &mut ComputationGraph,
693        optimization: &SpeculativeOptimization,
694    ) -> JitResult<bool> {
695        let node_id = optimization.node_id;
696
697        if let Some(node) = graph.node_mut(node_id) {
698            // Mark node for constant propagation optimization
699            node.set_optimization_hint("constant_propagation", "true")?;
700            node.set_optimization_hint("assumed_constant", "true")?;
701
702            // Extract assumed constant value from description
703            if let Some(start) = optimization.description.find("value ") {
704                if let Some(end) = optimization.description[start + 6..].find(' ') {
705                    let value_str = &optimization.description[start + 6..start + 6 + end];
706                    node.set_optimization_hint("assumed_constant_value", value_str)?;
707                }
708            }
709
710            // Add value guard for verification
711            node.set_optimization_hint("add_value_guard", "true")?;
712            node.set_optimization_hint("guard_tolerance", "1e-10")?;
713
714            return Ok(true);
715        }
716
717        Ok(false)
718    }
719
720    fn apply_branch_elimination(
721        &self,
722        graph: &mut ComputationGraph,
723        optimization: &SpeculativeOptimization,
724    ) -> JitResult<bool> {
725        let node_id = optimization.node_id;
726
727        if let Some(node) = graph.node_mut(node_id) {
728            // Determine branch bias from description
729            let usually_taken = optimization.description.contains("usually taken");
730
731            if usually_taken {
732                node.set_optimization_hint("branch_likely", "true")?;
733                node.set_optimization_hint("optimize_taken_path", "true")?;
734            } else {
735                node.set_optimization_hint("branch_unlikely", "true")?;
736                node.set_optimization_hint("optimize_not_taken_path", "true")?;
737            }
738
739            // For highly predictable branches, consider elimination
740            if optimization.estimated_benefit > 0.08 {
741                // 8% or higher benefit
742                node.set_optimization_hint("branch_elimination_candidate", "true")?;
743                node.set_optimization_hint("speculative_branch_elimination", "true")?;
744            }
745
746            // Add branch guard
747            node.set_optimization_hint("add_branch_guard", "true")?;
748
749            return Ok(true);
750        }
751
752        Ok(false)
753    }
754
755    fn should_check_guard(&self, guard: &Guard, execution_count: u64) -> bool {
756        match guard.check_frequency {
757            GuardFrequency::Always => true,
758            GuardFrequency::Probabilistic(probability) => {
759                use std::collections::hash_map::DefaultHasher;
760                use std::hash::{Hash, Hasher};
761
762                let mut hasher = DefaultHasher::new();
763                execution_count.hash(&mut hasher);
764                let hash = hasher.finish();
765                (hash as f64 / u64::MAX as f64) < probability
766            }
767            GuardFrequency::Periodic(period) => execution_count % period == 0,
768            GuardFrequency::InitialOnly(limit) => execution_count < limit,
769        }
770    }
771
772    fn execute_guard_check(&self, guard: &Guard, runtime_info: &RuntimeInfo) -> JitResult<bool> {
773        match guard.guard_type {
774            GuardType::TypeCheck => {
775                // Check if actual type matches expected type
776                Ok(runtime_info.actual_type == runtime_info.expected_type)
777            }
778            GuardType::ShapeCheck => {
779                // Check if actual shape matches expected shape
780                Ok(runtime_info.actual_shape == runtime_info.expected_shape)
781            }
782            GuardType::ValueCheck => {
783                // Check if actual value matches expected value within tolerance
784                Ok(
785                    (runtime_info.actual_value - runtime_info.expected_value).abs()
786                        < runtime_info.tolerance,
787                )
788            }
789            GuardType::NullabilityCheck => {
790                // Check if value is not null/NaN
791                Ok(!runtime_info.actual_value.is_nan() && runtime_info.actual_value.is_finite())
792            }
793            GuardType::BranchCheck => {
794                // Check if branch outcome matches prediction
795                Ok(runtime_info.branch_taken == runtime_info.expected_branch_taken)
796            }
797            GuardType::LoopCheck => {
798                // Check if loop iterations match expectation
799                let diff = (runtime_info.actual_iterations as i64
800                    - runtime_info.expected_iterations as i64)
801                    .abs();
802                Ok(diff <= runtime_info.iteration_tolerance as i64)
803            }
804            GuardType::MemoryCheck => {
805                // Check if memory access pattern matches expectation
806                Ok(runtime_info.memory_pattern == runtime_info.expected_memory_pattern)
807            }
808        }
809    }
810
811    fn generate_assumption_id(&self) -> AssumptionId {
812        use std::sync::atomic::{AtomicU64, Ordering};
813        static COUNTER: AtomicU64 = AtomicU64::new(0);
814        AssumptionId(COUNTER.fetch_add(1, Ordering::Relaxed))
815    }
816
817    /// Build an `Assumption` from a known `(id, optimization)` pair so that
818    /// every field reflects the actual speculation context rather than a
819    /// hardcoded placeholder.
820    fn build_assumption(
821        &self,
822        id: AssumptionId,
823        optimization: &SpeculativeOptimization,
824    ) -> Assumption {
825        let assumption_type = match &optimization.optimization_type {
826            SpeculativeOptimizationType::TypeCheckElimination => {
827                // Extract the expected type from the optimization description.
828                // Description format: "Assume type is always <type_name>"
829                let expected_type = optimization
830                    .description
831                    .strip_prefix("Assume type is always ")
832                    .unwrap_or("unknown")
833                    .to_string();
834                AssumptionType::TypeSpeculation { expected_type }
835            }
836            SpeculativeOptimizationType::ShapeSpecialization => {
837                // Description format: "Specialize for shape [d0, d1, ...]"
838                // Parse the shape from the description; fall back to empty on parse error.
839                let expected_shape = optimization
840                    .description
841                    .strip_prefix("Specialize for shape ")
842                    .and_then(|s| {
843                        // Remove brackets and split on ", "
844                        let inner = s.trim_start_matches('[').trim_end_matches(']');
845                        if inner.is_empty() {
846                            Some(Vec::new())
847                        } else {
848                            inner
849                                .split(", ")
850                                .map(|tok| tok.trim().parse::<usize>().ok())
851                                .collect::<Option<Vec<usize>>>()
852                        }
853                    })
854                    .unwrap_or_default();
855                AssumptionType::ShapeSpeculation { expected_shape }
856            }
857            SpeculativeOptimizationType::ConstantPropagation => {
858                // Description format: "Assume constant value <f64>"
859                let (expected_value, tolerance) = optimization
860                    .description
861                    .strip_prefix("Assume constant value ")
862                    .and_then(|s| s.split_whitespace().next())
863                    .and_then(|tok| tok.parse::<f64>().ok())
864                    .map(|v| (v, 1e-10))
865                    .unwrap_or((0.0, 1e-10));
866                AssumptionType::ValueSpeculation {
867                    expected_value,
868                    tolerance,
869                }
870            }
871            SpeculativeOptimizationType::BranchElimination => {
872                let usually_taken = optimization.description.contains("usually taken");
873                // Use the optimization's estimated benefit as a proxy for confidence.
874                let probability = (optimization.estimated_benefit * 10.0).clamp(0.5, 0.99);
875                AssumptionType::BranchSpeculation {
876                    usually_taken,
877                    probability,
878                }
879            }
880            // For other optimization types the most conservative assumption is
881            // NullabilitySpeculation (guards against NaN/null inputs).
882            _ => AssumptionType::NullabilitySpeculation,
883        };
884
885        let initial_confidence = self.config.confidence_threshold;
886        let mut metadata = HashMap::new();
887        metadata.insert(
888            "optimization_type".to_string(),
889            format!("{:?}", optimization.optimization_type),
890        );
891        metadata.insert(
892            "estimated_benefit".to_string(),
893            optimization.estimated_benefit.to_string(),
894        );
895
896        Assumption {
897            id,
898            assumption_type,
899            node_id: optimization.node_id,
900            confidence: initial_confidence,
901            success_count: 0,
902            failure_count: 0,
903            created_at: std::time::SystemTime::now(),
904            metadata,
905        }
906    }
907
908    fn calculate_confidence(&self, assumption: &Assumption) -> f64 {
909        let total = assumption.success_count + assumption.failure_count;
910        if total == 0 {
911            return 0.5; // No data, neutral confidence
912        }
913
914        assumption.success_count as f64 / total as f64
915    }
916
917    fn log_deoptimization_event(&self, event: &DeoptimizationEvent) {
918        // Log the deoptimization event for debugging and analysis
919        eprintln!("Deoptimization: {:?}", event);
920    }
921}
922
923/// Runtime information for guard checks
924#[derive(Debug, Clone)]
925pub struct RuntimeInfo {
926    pub execution_count: u64,
927    pub actual_type: String,
928    pub expected_type: String,
929    pub actual_shape: Vec<usize>,
930    pub expected_shape: Vec<usize>,
931    pub actual_value: f64,
932    pub expected_value: f64,
933    pub tolerance: f64,
934    pub branch_taken: bool,
935    pub expected_branch_taken: bool,
936    pub actual_iterations: u64,
937    pub expected_iterations: u64,
938    pub iteration_tolerance: u64,
939    pub memory_pattern: MemoryAccessPattern,
940    pub expected_memory_pattern: MemoryAccessPattern,
941}
942
943/// Execution history for a node
944#[derive(Debug, Clone)]
945pub struct NodeExecutionHistory {
946    types: Vec<String>,
947    shapes: Vec<Vec<usize>>,
948    values: Vec<f64>,
949    branch_outcomes: Vec<bool>,
950    loop_iterations: Vec<u64>,
951}
952
953impl NodeExecutionHistory {
954    pub fn get_dominant_type(&self, threshold: f64) -> Option<String> {
955        let mut type_counts = HashMap::new();
956        for type_name in &self.types {
957            *type_counts.entry(type_name.clone()).or_insert(0) += 1;
958        }
959
960        if let Some((dominant_type, count)) = type_counts.iter().max_by_key(|(_, &count)| count) {
961            if *count as f64 / self.types.len() as f64 >= threshold {
962                return Some(dominant_type.clone());
963            }
964        }
965
966        None
967    }
968
969    pub fn get_dominant_shape(&self, threshold: f64) -> Option<Vec<usize>> {
970        let mut shape_counts = HashMap::new();
971        for shape in &self.shapes {
972            *shape_counts.entry(shape.clone()).or_insert(0) += 1;
973        }
974
975        if let Some((dominant_shape, count)) = shape_counts.iter().max_by_key(|(_, &count)| count) {
976            if *count as f64 / self.shapes.len() as f64 >= threshold {
977                return Some(dominant_shape.clone());
978            }
979        }
980
981        None
982    }
983
984    pub fn get_constant_value(&self, threshold: f64) -> Option<f64> {
985        if self.values.is_empty() {
986            return None;
987        }
988
989        // Check if all values are approximately the same
990        let first_value = self.values[0];
991        let tolerance = 1e-10;
992        let constant_count = self
993            .values
994            .iter()
995            .filter(|&&v| (v - first_value).abs() < tolerance)
996            .count();
997
998        if constant_count as f64 / self.values.len() as f64 >= threshold {
999            Some(first_value)
1000        } else {
1001            None
1002        }
1003    }
1004
1005    pub fn get_branch_bias(&self, threshold: f64) -> Option<f64> {
1006        if self.branch_outcomes.is_empty() {
1007            return None;
1008        }
1009
1010        let taken_count = self.branch_outcomes.iter().filter(|&&taken| taken).count();
1011        let bias = taken_count as f64 / self.branch_outcomes.len() as f64;
1012
1013        // Return bias if it's significantly different from 50/50
1014        if (bias - 0.5).abs() >= (threshold - 0.5) {
1015            Some(bias)
1016        } else {
1017            None
1018        }
1019    }
1020}
1021
1022/// Execution history for the entire graph
1023#[derive(Debug, Clone)]
1024pub struct ExecutionHistory {
1025    node_histories: HashMap<NodeId, NodeExecutionHistory>,
1026}
1027
1028impl ExecutionHistory {
1029    pub fn new() -> Self {
1030        Self {
1031            node_histories: HashMap::new(),
1032        }
1033    }
1034
1035    pub fn get_node_history(&self, node_id: NodeId) -> Option<&NodeExecutionHistory> {
1036        self.node_histories.get(&node_id)
1037    }
1038
1039    pub fn record_execution(&mut self, node_id: NodeId, info: NodeExecutionInfo) {
1040        let history = self
1041            .node_histories
1042            .entry(node_id)
1043            .or_insert_with(|| NodeExecutionHistory {
1044                types: Vec::new(),
1045                shapes: Vec::new(),
1046                values: Vec::new(),
1047                branch_outcomes: Vec::new(),
1048                loop_iterations: Vec::new(),
1049            });
1050
1051        if let Some(type_name) = info.type_name {
1052            history.types.push(type_name);
1053        }
1054        if let Some(shape) = info.shape {
1055            history.shapes.push(shape);
1056        }
1057        if let Some(value) = info.value {
1058            history.values.push(value);
1059        }
1060        if let Some(branch_taken) = info.branch_taken {
1061            history.branch_outcomes.push(branch_taken);
1062        }
1063        if let Some(iterations) = info.loop_iterations {
1064            history.loop_iterations.push(iterations);
1065        }
1066    }
1067}
1068
1069/// Information about a single node execution
1070#[derive(Debug, Clone)]
1071pub struct NodeExecutionInfo {
1072    pub type_name: Option<String>,
1073    pub shape: Option<Vec<usize>>,
1074    pub value: Option<f64>,
1075    pub branch_taken: Option<bool>,
1076    pub loop_iterations: Option<u64>,
1077}
1078
1079/// Statistics about speculative optimization
1080#[derive(Debug, Clone)]
1081pub struct SpeculationStatistics {
1082    pub active_assumptions: usize,
1083    pub total_successes: u64,
1084    pub total_failures: u64,
1085    pub avg_confidence: f64,
1086    pub deoptimization_count: u64,
1087    pub enabled: bool,
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092    use super::*;
1093
1094    #[test]
1095    fn test_speculative_optimizer_creation() {
1096        let config = SpeculativeConfig::default();
1097        let optimizer = SpeculativeOptimizer::new(config);
1098        assert!(optimizer.enabled.load(Ordering::Relaxed));
1099        assert_eq!(optimizer.deopt_counter.load(Ordering::Relaxed), 0);
1100    }
1101
1102    #[test]
1103    fn test_assumption_id_generation() {
1104        let optimizer = SpeculativeOptimizer::new(SpeculativeConfig::default());
1105        let id1 = optimizer.generate_assumption_id();
1106        let id2 = optimizer.generate_assumption_id();
1107        assert_ne!(id1, id2);
1108    }
1109
1110    #[test]
1111    fn test_guard_frequency_checking() {
1112        let optimizer = SpeculativeOptimizer::new(SpeculativeConfig::default());
1113
1114        let always_guard = Guard {
1115            assumption_id: AssumptionId(1),
1116            guard_type: GuardType::TypeCheck,
1117            check_frequency: GuardFrequency::Always,
1118        };
1119        assert!(optimizer.should_check_guard(&always_guard, 100));
1120
1121        let periodic_guard = Guard {
1122            assumption_id: AssumptionId(2),
1123            guard_type: GuardType::TypeCheck,
1124            check_frequency: GuardFrequency::Periodic(10),
1125        };
1126        assert!(optimizer.should_check_guard(&periodic_guard, 100));
1127        assert!(!optimizer.should_check_guard(&periodic_guard, 101));
1128    }
1129
1130    #[test]
1131    fn test_execution_history() {
1132        let mut history = ExecutionHistory::new();
1133        let node_id = NodeId::new(1);
1134
1135        // Record some executions
1136        history.record_execution(
1137            node_id,
1138            NodeExecutionInfo {
1139                type_name: Some("f32".to_string()),
1140                shape: Some(vec![10, 20]),
1141                value: Some(1.0),
1142                branch_taken: Some(true),
1143                loop_iterations: Some(5),
1144            },
1145        );
1146
1147        history.record_execution(
1148            node_id,
1149            NodeExecutionInfo {
1150                type_name: Some("f32".to_string()),
1151                shape: Some(vec![10, 20]),
1152                value: Some(1.0),
1153                branch_taken: Some(true),
1154                loop_iterations: Some(5),
1155            },
1156        );
1157
1158        let node_history = history.get_node_history(node_id).unwrap();
1159        assert_eq!(node_history.get_dominant_type(0.8), Some("f32".to_string()));
1160        assert_eq!(node_history.get_dominant_shape(0.8), Some(vec![10, 20]));
1161        assert_eq!(node_history.get_constant_value(0.8), Some(1.0));
1162        assert_eq!(node_history.get_branch_bias(0.8), Some(1.0));
1163    }
1164}