use std::sync::{Arc, LazyLock, RwLock, Weak};
use crate::tiered::ir_cache::IrCache;
use crate::tiered::jit_state::JitState;
pub struct Tiers {
globals_id: u64,
ir_cache: IrCache,
jit: JitState,
weak_self: Weak<Tiers>,
}
impl Tiers {
pub fn new(globals_id: u64) -> Arc<Self> {
let tiers = Arc::new_cyclic(|weak_self| Tiers {
globals_id,
ir_cache: IrCache::new(),
jit: JitState::new(weak_self.clone()),
weak_self: weak_self.clone(),
});
let mut live = LIVE.write().unwrap();
live.retain(|w| w.strong_count() > 0);
live.push(Arc::downgrade(&tiers));
tiers
}
pub fn globals_id(&self) -> u64 {
self.globals_id
}
pub fn ir_cache(&self) -> &IrCache {
&self.ir_cache
}
pub fn jit(&self) -> &JitState {
&self.jit
}
pub fn handle(&self) -> Weak<Tiers> {
self.weak_self.clone()
}
pub fn sweep(&self, now: u64, ttl_secs: u64) -> Vec<u64> {
let evicted = self
.ir_cache
.sweep(now, ttl_secs, |id| self.jit.pins_ir(id));
for &id in &evicted {
self.jit.evict_entry_if_cold(id);
self.jit.stale_osr_code(id);
cljrs_logging::feat_debug!("ir", "evicted idle IR arity_id={}", id);
}
evicted
}
}
static LIVE: LazyLock<RwLock<Vec<Weak<Tiers>>>> = LazyLock::new(|| RwLock::new(Vec::new()));
pub fn live() -> Vec<Arc<Tiers>> {
LIVE.read()
.unwrap()
.iter()
.filter_map(Weak::upgrade)
.collect()
}
pub fn sweep_idle(now: u64, ttl_secs: u64) -> Vec<u64> {
let mut evicted = Vec::new();
for tiers in live() {
evicted.extend(tiers.sweep(now, ttl_secs));
}
evicted
}
#[cfg(test)]
pub(crate) static SWEEP_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(test)]
mod tests {
use super::*;
use crate::tiered::ir_cache::now_secs;
use cljrs_ir::IrFunction;
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 tier_state_is_per_runtime() {
let _g = sweep_guard();
let a = Tiers::new(0xE5AA_0001);
let b = Tiers::new(0xE5AA_0002);
let id = 0xE500_0006;
a.ir_cache().store(id, dummy_ir());
assert!(a.ir_cache().get(id).is_some());
assert!(b.ir_cache().get(id).is_none(), "b must not see a's entry");
assert!(b.ir_cache().should_attempt(id));
a.jit().mark_lower_queued(id);
assert!(a.jit().lower_queued(id));
assert!(!b.jit().lower_queued(id));
let handle = a.handle();
let a_ptr = Arc::as_ptr(&a);
assert!(handle.upgrade().is_some());
assert!(live().iter().any(|t| Arc::as_ptr(t) == a_ptr));
drop(a);
assert!(handle.upgrade().is_none());
assert!(live().iter().all(|t| Arc::as_ptr(t) != a_ptr));
drop(b);
}
#[test]
fn sweep_evicts_idle_entry_and_drops_jit_entry() {
let _g = sweep_guard();
let tiers = Tiers::new(0xE5AA_0003);
let id = 0xE500_0001;
tiers.ir_cache().store(id, dummy_ir());
tiers.jit().mark_lower_queued(id);
let stored_at = now_secs();
assert!(sweep_idle(stored_at, 600).is_empty() || !tiers.ir_cache().should_attempt(id));
assert!(tiers.ir_cache().get(id).is_some());
let evicted = sweep_idle(stored_at + 601, 600);
assert!(evicted.contains(&id));
assert!(tiers.ir_cache().get(id).is_none());
assert!(tiers.ir_cache().should_attempt(id));
assert!(!tiers.jit().lower_queued(id));
}
#[test]
fn sweep_skips_native_published_arity() {
let _g = sweep_guard();
let tiers = Tiers::new(0xE5AA_0003);
let id = 0xE500_0002;
tiers.ir_cache().store(id, dummy_ir());
tiers
.jit()
.store_native_fn(id, 0x1234usize as *const (), 31337);
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(tiers.ir_cache().get(id).is_some());
}
#[test]
fn sweep_skips_queued_compile() {
let _g = sweep_guard();
let tiers = Tiers::new(0xE5AA_0003);
let id = 0xE500_0003;
let ir = dummy_ir();
tiers.ir_cache().store(id, ir.clone());
for _ in 0..crate::tiered::jit_state::jit_threshold() {
tiers.jit().record_call(id, ir.clone(), &[]);
}
assert!(tiers.jit().compile_queued(id));
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(tiers.ir_cache().get(id).is_some());
}
#[test]
fn sweep_never_touches_unsupported() {
let _g = sweep_guard();
let tiers = Tiers::new(0xE5AA_0003);
let id = 0xE500_0004;
tiers.ir_cache().store_unsupported(id);
let evicted = sweep_idle(now_secs() + 10_000, 600);
assert!(!evicted.contains(&id));
assert!(!tiers.ir_cache().should_attempt(id));
}
#[test]
fn get_refreshes_last_access() {
let _g = sweep_guard();
let tiers = Tiers::new(0xE5AA_0003);
let id = 0xE500_0005;
tiers.ir_cache().store(id, dummy_ir());
let _ = tiers.ir_cache().get(id);
let touched_at = now_secs();
let evicted = sweep_idle(touched_at + 599, 600);
assert!(!evicted.contains(&id));
assert!(tiers.ir_cache().get(id).is_some());
}
}