use crate::ast::Expr;
use crate::eval::Environment;
use crate::diagnostics::{Error, Result};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct HotspotConfig {
pub min_frequency: f64,
pub min_total_time: Duration,
pub min_execution_count: u64,
pub complexity_threshold: f64,
pub stability_window: Duration,
pub max_tracked_functions: usize,
}
impl Default for HotspotConfig {
fn default() -> Self {
HotspotConfig {
min_frequency: 10.0, min_total_time: Duration::from_millis(50), min_execution_count: 100, complexity_threshold: 5.0, stability_window: Duration::from_secs(2), max_tracked_functions: 1000, }
}
}
#[derive(Debug, Clone)]
pub struct ExecutionProfile {
pub identifier: String,
pub ast: Expr,
pub execution_count: u64,
pub total_time: Duration,
pub average_time: Duration,
pub min_time: Duration,
pub max_time: Duration,
pub first_execution: Instant,
pub last_execution: Instant,
pub complexity_score: f64,
pub variance: f64,
pub is_compiled: bool,
pub compilation_attempts: u32,
}
impl ExecutionProfile {
pub fn new(identifier: String, ast: Expr) -> Self {
let now = Instant::now();
let complexity_score = Self::calculate_complexity(&ast);
ExecutionProfile {
identifier,
ast,
execution_count: 0,
total_time: Duration::ZERO,
average_time: Duration::ZERO,
min_time: Duration::MAX,
max_time: Duration::ZERO,
first_execution: now,
last_execution: now,
complexity_score,
variance: 0.0,
is_compiled: false,
compilation_attempts: 0,
}
}
pub fn record_execution(&mut self, execution_time: Duration) {
self.execution_count += 1;
self.total_time += execution_time;
self.last_execution = Instant::now();
if execution_time < self.min_time {
self.min_time = execution_time;
}
if execution_time > self.max_time {
self.max_time = execution_time;
}
self.average_time = self.total_time / self.execution_count as u32;
self.update_variance(execution_time);
}
pub fn execution_frequency(&self) -> f64 {
let elapsed = self.last_execution.duration_since(self.first_execution);
if elapsed.as_secs_f64() > 0.0 {
self.execution_count as f64 / elapsed.as_secs_f64()
} else {
0.0
}
}
pub fn time_stability(&self) -> f64 {
if self.variance > 0.0 {
1.0 / (1.0 + self.variance)
} else {
1.0
}
}
pub fn compilation_benefit_score(&self) -> f64 {
let frequency_factor = (self.execution_frequency() / 10.0).min(2.0);
let time_factor = (self.total_time.as_millis() as f64 / 100.0).min(3.0);
let complexity_factor = (self.complexity_score / 5.0).min(2.0);
let stability_factor = self.time_stability();
frequency_factor * time_factor * complexity_factor * stability_factor
}
fn update_variance(&mut self, new_time: Duration) {
if self.execution_count <= 1 {
self.variance = 0.0;
return;
}
let avg_ms = self.average_time.as_millis() as f64;
let new_ms = new_time.as_millis() as f64;
let diff = new_ms - avg_ms;
let alpha = 0.1; self.variance = (1.0 - alpha) * self.variance + alpha * (diff * diff);
}
fn calculate_complexity(ast: &Expr) -> f64 {
match ast {
Expr::Literal(_) => 0.1,
Expr::Identifier(_) => 0.2,
Expr::Lambda { formals, body, .. } => {
let param_count = match formals {
crate::ast::Formals::Fixed(params) => params.len(),
crate::ast::Formals::Variable(_) => 1,
crate::ast::Formals::Mixed { fixed, .. } => fixed.len() + 1,
crate::ast::Formals::Keyword { fixed, .. } => fixed.len(),
};
2.0 + param_count as f64 * 0.5 + body.iter().map(|e| Self::calculate_complexity(&e.inner)).sum::<f64>()
}
Expr::Application { operator, operands } => {
1.0 + Self::calculate_complexity(&operator.inner) +
operands.iter().map(|e| Self::calculate_complexity(&e.inner)).sum::<f64>()
}
Expr::If { test, consequent, alternative } => {
1.5 + Self::calculate_complexity(&test.inner) +
Self::calculate_complexity(&consequent.inner) +
alternative.as_ref().map_or(0.0, |e| Self::calculate_complexity(&e.inner))
}
Expr::Let { bindings, body } => {
1.0 + bindings.len() as f64 * 0.3 +
bindings.iter().map(|binding| Self::calculate_complexity(&binding.value.inner)).sum::<f64>() +
body.iter().map(|e| Self::calculate_complexity(&e.inner)).sum::<f64>()
}
Expr::LetRec { bindings, body } => {
1.5 + bindings.len() as f64 * 0.4 +
bindings.iter().map(|binding| Self::calculate_complexity(&binding.value.inner)).sum::<f64>() +
body.iter().map(|e| Self::calculate_complexity(&e.inner)).sum::<f64>()
}
Expr::Begin(body) => {
0.5 + body.iter().map(|e| Self::calculate_complexity(&e.inner)).sum::<f64>()
}
Expr::Quote { .. } => 0.1,
Expr::Set { .. } => 0.5,
_ => 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct CompilationCandidate {
pub identifier: String,
pub score: f64,
pub profile: ExecutionProfile,
pub recommended_tier: CompilationTier,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompilationTier {
Interpreter,
Bytecode,
JitBasic,
JitOptimized,
}
pub struct HotspotDetector {
config: HotspotConfig,
profiles: HashMap<String, ExecutionProfile>,
compiling: HashMap<String, Instant>,
compilation_history: Vec<(String, Instant, bool)>, }
impl HotspotDetector {
pub fn new(config: HotspotConfig) -> Self {
HotspotDetector {
config,
profiles: HashMap::new(),
compiling: HashMap::new(),
compilation_history: Vec::new(),
}
}
pub fn record_execution(
&mut self,
identifier: String,
ast: Expr,
execution_time: Duration,
_environment: Arc<Environment>,
) -> Result<()> {
let profile = self.profiles
.entry(identifier.clone())
.or_insert_with(|| ExecutionProfile::new(identifier.clone(), ast));
profile.record_execution(execution_time);
if self.profiles.len() > self.config.max_tracked_functions {
self.cleanup_stale_profiles();
}
Ok(())
}
pub fn should_compile(&self, identifier: &str) -> Result<bool> {
let profile = match self.profiles.get(identifier) {
Some(profile) => profile,
None => return Ok(false),
};
if profile.is_compiled || self.compiling.contains_key(identifier) {
return Ok(false);
}
let meets_frequency = profile.execution_frequency() >= self.config.min_frequency;
let meets_total_time = profile.total_time >= self.config.min_total_time;
let meets_count = profile.execution_count >= self.config.min_execution_count;
let meets_complexity = profile.complexity_score >= self.config.complexity_threshold;
let elapsed_since_first = profile.last_execution.duration_since(profile.first_execution);
let stable = elapsed_since_first >= self.config.stability_window;
if meets_complexity && profile.execution_count >= 10 {
return Ok(true);
}
Ok(meets_frequency && meets_total_time && meets_count && stable)
}
pub fn get_compilation_candidates(&self) -> Vec<CompilationCandidate> {
let mut candidates = Vec::new();
for profile in self.profiles.values() {
if !profile.is_compiled && !self.compiling.contains_key(&profile.identifier) {
let score = profile.compilation_benefit_score();
if score > 1.0 { let tier = self.recommend_tier(profile);
candidates.push(CompilationCandidate {
identifier: profile.identifier.clone(),
score,
profile: profile.clone(),
recommended_tier: tier,
});
}
}
}
candidates.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
candidates
}
pub fn mark_compiling(&mut self, identifier: &str) {
self.compiling.insert(identifier.to_string(), Instant::now());
if let Some(profile) = self.profiles.get_mut(identifier) {
profile.compilation_attempts += 1;
}
}
pub fn mark_compiled(&mut self, identifier: &str, success: bool) {
self.compiling.remove(identifier);
self.compilation_history.push((identifier.to_string(), Instant::now(), success));
if success {
if let Some(profile) = self.profiles.get_mut(identifier) {
profile.is_compiled = true;
}
}
}
fn recommend_tier(&self, profile: &ExecutionProfile) -> CompilationTier {
let score = profile.compilation_benefit_score();
let frequency = profile.execution_frequency();
let complexity = profile.complexity_score;
if score > 10.0 || frequency > 100.0 {
CompilationTier::JitOptimized
}
else if score > 5.0 || frequency > 25.0 || complexity > 8.0 {
CompilationTier::JitBasic
}
else if score > 2.0 || frequency > 5.0 {
CompilationTier::Bytecode
}
else {
CompilationTier::Interpreter
}
}
fn cleanup_stale_profiles(&mut self) {
let cutoff_time = Instant::now() - Duration::from_secs(300);
self.profiles.retain(|_, profile| {
profile.last_execution > cutoff_time || profile.is_compiled
});
}
pub fn get_statistics(&self) -> HashMap<String, f64> {
let mut stats = HashMap::new();
stats.insert("tracked_functions".to_string(), self.profiles.len() as f64);
stats.insert("compiled_functions".to_string(),
self.profiles.values().filter(|p| p.is_compiled).count() as f64);
stats.insert("compiling_functions".to_string(), self.compiling.len() as f64);
if !self.profiles.is_empty() {
let total_executions: u64 = self.profiles.values().map(|p| p.execution_count).sum();
let avg_frequency: f64 = self.profiles.values()
.map(|p| p.execution_frequency())
.sum::<f64>() / self.profiles.len() as f64;
let avg_complexity: f64 = self.profiles.values()
.map(|p| p.complexity_score)
.sum::<f64>() / self.profiles.len() as f64;
stats.insert("total_executions".to_string(), total_executions as f64);
stats.insert("average_frequency".to_string(), avg_frequency);
stats.insert("average_complexity".to_string(), avg_complexity);
}
stats
}
pub fn get_top_hotspots(&self, limit: usize) -> Vec<&ExecutionProfile> {
let mut profiles: Vec<&ExecutionProfile> = self.profiles.values()
.filter(|p| !p.is_compiled)
.collect();
profiles.sort_by(|a, b| b.compilation_benefit_score()
.partial_cmp(&a.compilation_benefit_score())
.unwrap_or(std::cmp::Ordering::Equal));
profiles.into_iter().take(limit).collect()
}
pub fn reset_compilation_status(&mut self, identifier: &str) {
if let Some(profile) = self.profiles.get_mut(identifier) {
profile.is_compiled = false;
}
self.compiling.remove(identifier);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Literal;
#[test]
fn test_execution_profile_creation() {
let ast = Expr::Literal(Literal::ExactInteger(42));
let profile = ExecutionProfile::new("test".to_string(), ast);
assert_eq!(profile.identifier, "test");
assert_eq!(profile.execution_count, 0);
assert!(profile.complexity_score > 0.0);
}
#[test]
fn test_complexity_calculation() {
let simple = Expr::Literal(Literal::ExactInteger(1));
let simple_score = ExecutionProfile::calculate_complexity(&simple);
assert!(simple_score < 1.0);
let lambda = Expr::Lambda {
params: vec!["x".to_string()],
body: Box::new(Expr::Identifier("x".to_string())),
};
let lambda_score = ExecutionProfile::calculate_complexity(&lambda);
assert!(lambda_score > simple_score);
}
#[test]
fn test_hotspot_detection() {
let config = HotspotConfig::default();
let mut detector = HotspotDetector::new(config);
let ast = Expr::Lambda {
params: vec!["x".to_string()],
body: Box::new(Expr::Identifier("x".to_string())),
};
let env = Arc::new(Environment::new(None, 0));
for _ in 0..150 {
detector.record_execution(
"test_function".to_string(),
ast.clone(),
Duration::from_micros(100),
env.clone(),
).unwrap();
}
let should_compile = detector.should_compile("test_function").unwrap();
assert!(should_compile);
}
#[test]
fn test_compilation_candidates() {
let config = HotspotConfig::default();
let mut detector = HotspotDetector::new(config);
let ast = Expr::Lambda {
params: vec!["x".to_string()],
body: Box::new(Expr::Identifier("x".to_string())),
};
let env = Arc::new(Environment::new(None, 0));
for i in 0..3 {
for _ in 0..100 {
detector.record_execution(
format!("function_{}", i),
ast.clone(),
Duration::from_micros(100 * (i + 1)),
env.clone(),
).unwrap();
}
}
let candidates = detector.get_compilation_candidates();
assert!(!candidates.is_empty());
if candidates.len() > 1 {
assert!(candidates[0].score >= candidates[1].score);
}
}
#[test]
fn test_tier_recommendation() {
let config = HotspotConfig::default();
let detector = HotspotDetector::new(config);
let mut profile = ExecutionProfile::new(
"hot_function".to_string(),
Expr::Lambda {
params: vec!["x".to_string()],
body: Box::new(Expr::Identifier("x".to_string())),
},
);
for _ in 0..1000 {
profile.record_execution(Duration::from_nanos(100));
}
let tier = detector.recommend_tier(&profile);
assert!(matches!(tier, CompilationTier::JitOptimized | CompilationTier::JitBasic));
}
}