cljrs_eval/ir_cache.rs
1//! Thread-safe IR cache for compiled function arities.
2//!
3//! Each `CljxFnArity` is assigned a unique `ir_arity_id` at creation time.
4//! When a function is called, the cache is consulted:
5//! - `NotAttempted` → try lowering via the Clojure compiler
6//! - `Cached(ir)` → execute via IR interpreter
7//! - `Unsupported` → fall back to tree-walking (don't retry)
8//!
9//! The hot path (`get_cached`) uses `RwLock` so concurrent reads don't
10//! contend. Writes (store) are infrequent (only during lowering).
11
12use std::collections::HashMap;
13use std::sync::{Arc, RwLock};
14
15use cljrs_ir::IrFunction;
16
17// ── Cache entries ────────────────────────────────────────────────────────────
18
19/// State of an IR cache entry for one function arity.
20pub enum IrCacheEntry {
21 /// Lowering has not been attempted yet.
22 NotAttempted,
23 /// Lowering was attempted but failed (unsupported form); don't retry.
24 Unsupported,
25 /// Successfully lowered IR function.
26 Cached(Arc<IrFunction>),
27}
28
29// ── Global cache ─────────────────────────────────────────────────────────────
30
31static IR_CACHE: RwLock<Option<HashMap<u64, IrCacheEntry>>> = RwLock::new(None);
32
33/// Look up a cached IR function by arity ID.
34/// Returns `None` if not cached or if lowering previously failed.
35/// Returns `Some(ir)` if cached.
36///
37/// This is the hot path — uses a read lock so concurrent callers don't block.
38pub fn get_cached(id: u64) -> Option<Arc<IrFunction>> {
39 let guard = IR_CACHE.read().unwrap();
40 let cache = guard.as_ref()?;
41 match cache.get(&id) {
42 Some(IrCacheEntry::Cached(ir)) => Some(ir.clone()),
43 _ => None,
44 }
45}
46
47/// Check if lowering should be attempted for this arity.
48/// Returns `true` if the entry is `NotAttempted` (or absent).
49pub fn should_attempt(id: u64) -> bool {
50 let guard = IR_CACHE.read().unwrap();
51 match guard.as_ref() {
52 Some(cache) => !cache.contains_key(&id),
53 None => true,
54 }
55}
56
57/// Store a successful IR compilation result.
58pub fn store_cached(id: u64, ir: Arc<IrFunction>) {
59 let mut guard = IR_CACHE.write().unwrap();
60 let cache = guard.get_or_insert_with(HashMap::new);
61 cache.insert(id, IrCacheEntry::Cached(ir));
62}
63
64/// Mark an arity as unsupported (lowering failed; don't retry).
65pub fn store_unsupported(id: u64) {
66 let mut guard = IR_CACHE.write().unwrap();
67 let cache = guard.get_or_insert_with(HashMap::new);
68 cache.insert(id, IrCacheEntry::Unsupported);
69}