use crate::ast::Expr;
use crate::diagnostics::{Result, Error};
use crate::jit::code_generator::NativeCode;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, RwLock, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct CacheConfig {
pub max_memory_bytes: usize,
pub max_entries: usize,
pub memory_pressure_threshold: f64,
pub enable_lru_eviction: bool,
pub enable_compaction: bool,
pub compaction_interval: Duration,
pub execution_based_retention: bool,
pub min_execution_count_for_retention: u64,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
max_memory_bytes: 64 * 1024 * 1024, max_entries: 10000,
memory_pressure_threshold: 0.8,
enable_lru_eviction: true,
enable_compaction: true,
compaction_interval: Duration::from_secs(300), execution_based_retention: true,
min_execution_count_for_retention: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub code: NativeCode,
pub key: String,
pub created_at: Instant,
pub last_accessed: Instant,
pub access_count: u64,
pub execution_count: u64,
pub total_execution_time: Duration,
pub avg_execution_time: Duration,
pub priority: CachePriority,
pub memory_usage: usize,
}
impl CacheEntry {
pub fn new(key: String, code: NativeCode) -> Self {
let now = Instant::now();
let memory_usage = code.code_size() + std::mem::size_of::<CacheEntry>();
Self {
code,
key,
created_at: now,
last_accessed: now,
access_count: 0,
execution_count: 0,
total_execution_time: Duration::ZERO,
avg_execution_time: Duration::ZERO,
priority: CachePriority::Normal,
memory_usage,
}
}
pub fn record_access(&mut self) {
self.last_accessed = Instant::now();
self.access_count += 1;
self.update_priority();
}
pub fn record_execution(&mut self, execution_time: Duration) {
self.execution_count += 1;
self.total_execution_time += execution_time;
self.avg_execution_time = self.total_execution_time / self.execution_count as u32;
self.update_priority();
}
fn update_priority(&mut self) {
let access_score = (self.access_count as f64).ln().max(1.0);
let execution_score = (self.execution_count as f64).ln().max(1.0);
let recency_score = {
let age = self.last_accessed.elapsed().as_secs() as f64;
(-age / 3600.0).exp() };
let combined_score = access_score * 0.3 + execution_score * 0.5 + recency_score * 0.2;
self.priority = if combined_score > 10.0 {
CachePriority::High
} else if combined_score > 5.0 {
CachePriority::Normal
} else {
CachePriority::Low
};
}
pub fn cache_score(&self) -> f64 {
let access_weight = 0.3;
let execution_weight = 0.5;
let recency_weight = 0.2;
let access_score = (self.access_count as f64).ln().max(1.0);
let execution_score = (self.execution_count as f64).ln().max(1.0);
let recency_score = {
let age = self.last_accessed.elapsed().as_secs() as f64;
(-age / 3600.0).exp()
};
access_score * access_weight +
execution_score * execution_weight +
recency_score * recency_weight
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CachePriority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
pub struct CodeCache {
config: CacheConfig,
entries: Arc<RwLock<HashMap<String, CacheEntry>>>,
lru_order: Arc<Mutex<VecDeque<String>>>,
current_memory_usage: Arc<Mutex<usize>>,
memory_manager: MemoryManager,
stats: Arc<Mutex<CacheStats>>,
last_compaction: Arc<Mutex<Instant>>,
}
impl CodeCache {
pub fn new(config: CacheConfig) -> Result<Self> {
Ok(Self {
config: config.clone(),
entries: Arc::new(RwLock::new(HashMap::new())),
lru_order: Arc::new(Mutex::new(VecDeque::new())),
current_memory_usage: Arc::new(Mutex::new(0)),
memory_manager: MemoryManager::new(config.max_memory_bytes)?,
stats: Arc::new(Mutex::new(CacheStats::default())),
last_compaction: Arc::new(Mutex::new(Instant::now())),
})
}
pub fn store(&self, expr: Expr, code: NativeCode) -> Result<()> {
let key = self.expression_key(&expr);
let mut entry = CacheEntry::new(key.clone(), code);
{
let mut memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
*memory_usage += entry.memory_usage;
}
self.handle_memory_pressure()?;
{
let mut entries = self.entries.write()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
entries.insert(key.clone(), entry);
}
if self.config.enable_lru_eviction {
let mut lru_order = self.lru_order.lock()
.map_err(|_| Error::runtime_error("Failed to acquire LRU lock".to_string(), None))?;
lru_order.push_back(key.clone());
}
{
let mut stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
stats.entries_stored += 1;
stats.current_entries += 1;
}
Ok(())
}
pub fn get(&self, expr: &Expr) -> Result<Option<NativeCode>> {
let key = self.expression_key(expr);
let code = {
let mut entries = self.entries.write()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
if let Some(entry) = entries.get_mut(&key) {
entry.record_access();
if self.config.enable_lru_eviction {
self.update_lru_order(&key)?;
}
Some(entry.code.clone())
} else {
None
}
};
{
let mut stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
if code.is_some() {
stats.cache_hits += 1;
} else {
stats.cache_misses += 1;
}
}
Ok(code)
}
pub fn record_execution(&self, expr: &Expr, execution_time: Duration) -> Result<()> {
let key = self.expression_key(expr);
let mut entries = self.entries.write()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
if let Some(entry) = entries.get_mut(&key) {
entry.record_execution(execution_time);
}
Ok(())
}
pub fn invalidate(&self, expr: &Expr) -> Result<()> {
let key = self.expression_key(expr);
let memory_freed = {
let mut entries = self.entries.write()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
if let Some(entry) = entries.remove(&key) {
entry.memory_usage
} else {
0
}
};
if memory_freed > 0 {
let mut memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
*memory_usage = memory_usage.saturating_sub(memory_freed);
}
if self.config.enable_lru_eviction {
let mut lru_order = self.lru_order.lock()
.map_err(|_| Error::runtime_error("Failed to acquire LRU lock".to_string(), None))?;
lru_order.retain(|k| k != &key);
}
{
let mut stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
stats.entries_evicted += 1;
stats.current_entries = stats.current_entries.saturating_sub(1);
}
Ok(())
}
pub fn clear(&self) -> Result<()> {
{
let mut entries = self.entries.write()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
entries.clear();
}
if self.config.enable_lru_eviction {
let mut lru_order = self.lru_order.lock()
.map_err(|_| Error::runtime_error("Failed to acquire LRU lock".to_string(), None))?;
lru_order.clear();
}
{
let mut memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
*memory_usage = 0;
}
{
let mut stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
stats.current_entries = 0;
}
Ok(())
}
fn handle_memory_pressure(&self) -> Result<()> {
let current_usage = {
let memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
*memory_usage
};
let memory_pressure = current_usage as f64 / self.config.max_memory_bytes as f64;
if memory_pressure > self.config.memory_pressure_threshold {
self.evict_entries()?;
}
if self.config.enable_compaction {
let should_compact = {
let last_compaction = self.last_compaction.lock()
.map_err(|_| Error::runtime_error("Failed to acquire compaction lock".to_string(), None))?;
last_compaction.elapsed() > self.config.compaction_interval
};
if should_compact {
self.compact()?;
}
}
Ok(())
}
fn evict_entries(&self) -> Result<()> {
let target_memory = (self.config.max_memory_bytes as f64 * 0.7) as usize;
if !self.config.enable_lru_eviction {
return Ok(());
}
let keys_to_evict = {
let entries = self.entries.read()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
let lru_order = self.lru_order.lock()
.map_err(|_| Error::runtime_error("Failed to acquire LRU lock".to_string(), None))?;
let mut keys_to_evict = Vec::new();
let mut memory_to_free = {
let memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
memory_usage.saturating_sub(target_memory)
};
for key in lru_order.iter() {
if memory_to_free == 0 {
break;
}
if let Some(entry) = entries.get(key) {
if entry.priority >= CachePriority::High {
continue;
}
if self.config.execution_based_retention &&
entry.execution_count >= self.config.min_execution_count_for_retention {
continue;
}
keys_to_evict.push(key.clone());
memory_to_free = memory_to_free.saturating_sub(entry.memory_usage);
}
}
keys_to_evict
};
for key in keys_to_evict {
let expr_placeholder = self.key_to_expr(&key); self.invalidate(&expr_placeholder)?;
}
Ok(())
}
fn compact(&self) -> Result<()> {
{
let mut last_compaction = self.last_compaction.lock()
.map_err(|_| Error::runtime_error("Failed to acquire compaction lock".to_string(), None))?;
*last_compaction = Instant::now();
}
{
let mut stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
stats.compactions_performed += 1;
}
Ok(())
}
fn update_lru_order(&self, key: &str) -> Result<()> {
let mut lru_order = self.lru_order.lock()
.map_err(|_| Error::runtime_error("Failed to acquire LRU lock".to_string(), None))?;
lru_order.retain(|k| k != key);
lru_order.push_back(key.to_string());
Ok(())
}
fn expression_key(&self, expr: &Expr) -> String {
format!("{expr:?}")
}
fn key_to_expr(&self, _key: &str) -> Expr {
Expr::Symbol("placeholder".to_string())
}
pub fn stats(&self) -> Result<CacheStats> {
let stats = self.stats.lock()
.map_err(|_| Error::runtime_error("Failed to acquire stats lock".to_string(), None))?;
Ok(stats.clone())
}
pub fn memory_usage(&self) -> Result<usize> {
let memory_usage = self.current_memory_usage.lock()
.map_err(|_| Error::runtime_error("Failed to acquire memory lock".to_string(), None))?;
Ok(*memory_usage)
}
pub fn size(&self) -> Result<usize> {
let entries = self.entries.read()
.map_err(|_| Error::runtime_error("Failed to acquire entries lock".to_string(), None))?;
Ok(entries.len())
}
}
pub struct MemoryManager {
max_memory: usize,
regions: Vec<MemoryRegion>,
}
impl MemoryManager {
fn new(max_memory: usize) -> Result<Self> {
Ok(Self {
max_memory,
regions: Vec::new(),
})
}
}
#[derive(Debug)]
struct MemoryRegion {
start: usize,
size: usize,
executable: bool,
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub entries_stored: u64,
pub current_entries: usize,
pub cache_hits: u64,
pub cache_misses: u64,
pub entries_evicted: u64,
pub compactions_performed: u64,
pub total_memory_allocated: usize,
pub current_memory_usage: usize,
pub peak_memory_usage: usize,
}
impl CacheStats {
pub fn hit_rate(&self) -> f64 {
let total_requests = self.cache_hits + self.cache_misses;
if total_requests == 0 {
0.0
} else {
self.cache_hits as f64 / total_requests as f64
}
}
pub fn memory_utilization(&self) -> f64 {
if self.total_memory_allocated == 0 {
0.0
} else {
self.current_memory_usage as f64 / self.total_memory_allocated as f64
}
}
pub fn avg_entries_per_compaction(&self) -> f64 {
if self.compactions_performed == 0 {
0.0
} else {
self.entries_stored as f64 / self.compactions_performed as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Literal;
use crate::jit::CompilationTier;
use crate::jit::code_generator::{CodeMetadata, FunctionSignature, MemoryLayout, SchemeType, MemoryRequirements};
#[test]
fn test_cache_config_default() {
let config = CacheConfig::default();
assert_eq!(config.max_memory_bytes, 64 * 1024 * 1024);
assert_eq!(config.max_entries, 10000);
assert!(config.enable_lru_eviction);
}
#[test]
fn test_cache_entry_creation() {
let code = create_test_native_code();
let entry = CacheEntry::new("test".to_string(), code);
assert_eq!(entry.key, "test");
assert_eq!(entry.access_count, 0);
assert_eq!(entry.execution_count, 0);
}
#[test]
fn test_cache_entry_access() {
let code = create_test_native_code();
let mut entry = CacheEntry::new("test".to_string(), code);
entry.record_access();
assert_eq!(entry.access_count, 1);
entry.record_execution(Duration::from_micros(100));
assert_eq!(entry.execution_count, 1);
assert_eq!(entry.avg_execution_time, Duration::from_micros(100));
}
#[test]
fn test_cache_stats() {
let mut stats = CacheStats::default();
stats.cache_hits = 80;
stats.cache_misses = 20;
assert_eq!(stats.hit_rate(), 0.8);
stats.total_memory_allocated = 1000;
stats.current_memory_usage = 600;
assert_eq!(stats.memory_utilization(), 0.6);
}
fn create_test_native_code() -> NativeCode {
NativeCode {
machine_code: vec![0x90; 16], entry_point: 0,
metadata: CodeMetadata {
source_expr: "test".to_string(),
compilation_tier: CompilationTier::JitBasic,
safe_points: Vec::new(),
variable_locations: std::collections::HashMap::new(),
inlined_functions: Vec::new(),
},
signature: FunctionSignature {
parameter_count: 0,
is_variadic: false,
return_type: crate::jit::SchemeType::Any,
parameter_types: Vec::new(),
},
memory_layout: MemoryLayout {
stack_frame_size: 64,
gc_roots: Vec::new(),
memory_requirements: MemoryRequirements {
stack_bytes: 64,
heap_bytes: 0,
temp_bytes: 32,
},
},
}
}
}