use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, LazyLock, RwLock};
use std::time::Instant;
use cljrs_ir::IrFunction;
pub enum IrCacheEntry {
NotAttempted,
Unsupported,
Cached {
ir: Arc<IrFunction>,
last_access: AtomicU64,
},
}
static PROCESS_EPOCH: LazyLock<Instant> = LazyLock::new(Instant::now);
pub fn now_secs() -> u64 {
PROCESS_EPOCH.elapsed().as_secs()
}
pub fn ir_cache_ttl_secs() -> u64 {
std::env::var("CLJRS_IR_CACHE_TTL")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(600)
}
static IR_CACHE: RwLock<Option<HashMap<u64, IrCacheEntry>>> = RwLock::new(None);
#[cfg(test)]
pub(crate) static SWEEP_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
pub fn get_cached(id: u64) -> Option<Arc<IrFunction>> {
let guard = IR_CACHE.read().unwrap();
let cache = guard.as_ref()?;
match cache.get(&id) {
Some(IrCacheEntry::Cached { ir, last_access }) => {
last_access.store(now_secs(), Ordering::Relaxed);
Some(ir.clone())
}
_ => None,
}
}
pub fn should_attempt(id: u64) -> bool {
let guard = IR_CACHE.read().unwrap();
match guard.as_ref() {
Some(cache) => !cache.contains_key(&id),
None => true,
}
}
pub fn store_cached(id: u64, ir: Arc<IrFunction>) {
let mut guard = IR_CACHE.write().unwrap();
let cache = guard.get_or_insert_with(HashMap::new);
cache.insert(
id,
IrCacheEntry::Cached {
ir,
last_access: AtomicU64::new(now_secs()),
},
);
}
pub fn store_unsupported(id: u64) {
let mut guard = IR_CACHE.write().unwrap();
let cache = guard.get_or_insert_with(HashMap::new);
cache.insert(id, IrCacheEntry::Unsupported);
}
pub fn invalidate(id: u64) {
let mut guard = IR_CACHE.write().unwrap();
if let Some(cache) = guard.as_mut() {
cache.remove(&id);
}
}
pub fn sweep_idle(now: u64, ttl_secs: u64) -> Vec<u64> {
let mut evicted = Vec::new();
let mut guard = IR_CACHE.write().unwrap();
let Some(cache) = guard.as_mut() else {
return evicted;
};
cache.retain(|&id, entry| {
let IrCacheEntry::Cached { last_access, .. } = entry else {
return true;
};
let idle = now.saturating_sub(last_access.load(Ordering::Relaxed));
if idle <= ttl_secs {
return true;
}
if crate::tiered::jit_state::get_native_fn(id).is_some()
|| crate::tiered::jit_state::compile_queued(id)
{
return true;
}
evicted.push(id);
false
});
drop(guard);
for &id in &evicted {
crate::tiered::jit_state::evict_entry_if_cold(id);
crate::tiered::jit_state::stale_osr_code(id);
cljrs_logging::feat_debug!("ir", "evicted idle IR arity_id={}", id);
}
evicted
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_ir() -> Arc<IrFunction> {
Arc::new(IrFunction::new(None, None))
}
fn sweep_guard() -> std::sync::MutexGuard<'static, ()> {
SWEEP_TEST_LOCK.lock().unwrap_or_else(|p| p.into_inner())
}
#[test]
fn sweep_evicts_idle_entry_and_drops_jit_entry() {
let _g = sweep_guard();
let id = 0xE500_0001;
store_cached(id, dummy_ir());
crate::tiered::jit_state::mark_lower_queued(id);
let stored_at = now_secs();
assert!(sweep_idle(stored_at, 600).is_empty() || !should_attempt(id));
assert!(get_cached(id).is_some());
let evicted = sweep_idle(stored_at + 601, 600);
assert!(evicted.contains(&id));
assert!(get_cached(id).is_none());
assert!(should_attempt(id));
assert!(!crate::tiered::jit_state::lower_queued(id));
}
#[test]
fn sweep_skips_native_published_arity() {
let _g = sweep_guard();
let id = 0xE500_0002;
store_cached(id, dummy_ir());
crate::tiered::jit_state::store_native_fn(id, 0x1234usize as *const (), 31337);
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(get_cached(id).is_some());
crate::tiered::jit_state::take_native_epoch(id);
invalidate(id);
}
#[test]
fn sweep_skips_queued_compile() {
let _g = sweep_guard();
let id = 0xE500_0003;
let ir = dummy_ir();
store_cached(id, ir.clone());
for _ in 0..crate::tiered::jit_state::jit_threshold() {
crate::tiered::jit_state::record_call(id, ir.clone(), &[]);
}
assert!(crate::tiered::jit_state::compile_queued(id));
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(get_cached(id).is_some());
invalidate(id);
}
#[test]
fn sweep_never_touches_unsupported() {
let _g = sweep_guard();
let id = 0xE500_0004;
store_unsupported(id);
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(!should_attempt(id));
}
#[test]
fn get_cached_refreshes_last_access() {
let _g = sweep_guard();
let id = 0xE500_0005;
store_cached(id, dummy_ir());
let _ = get_cached(id);
let touched_at = now_secs();
let evicted = sweep_idle(touched_at + 599, 600);
assert!(!evicted.contains(&id));
assert!(get_cached(id).is_some());
invalidate(id);
}
}