#![allow(unsafe_code)]
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use super::compiler::CompiledExprFn;
#[derive(Debug)]
#[repr(align(128))]
pub struct CacheStats {
pub hits: AtomicU64,
pub misses: AtomicU64,
pub inserts: AtomicU64,
_pad: [u8; 128 - 24],
}
impl Default for CacheStats {
fn default() -> Self {
Self {
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
inserts: AtomicU64::new(0),
_pad: [0; 128 - 24],
}
}
}
#[derive(Debug, Clone, Default)]
pub struct JitCache {
cache: Arc<RwLock<HashMap<String, usize>>>,
stats: Arc<CacheStats>,
}
impl JitCache {
#[must_use]
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(HashMap::new())),
stats: Arc::new(CacheStats::default()),
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<CompiledExprFn> {
let read_guard = self.cache.read().ok()?;
if let Some(&addr) = read_guard.get(key) {
self.stats.hits.fetch_add(1, Ordering::Release);
let func: CompiledExprFn = unsafe { std::mem::transmute(addr) };
Some(func)
} else {
self.stats.misses.fetch_add(1, Ordering::Release);
None
}
}
pub fn insert(&self, key: String, func: CompiledExprFn) {
let addr = func as usize;
{
let mut write_guard = self
.cache
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
write_guard.insert(key, addr);
}
self.stats.inserts.fetch_add(1, Ordering::Release);
}
pub fn clear(&self) {
let mut write_guard = self
.cache
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
write_guard.clear();
}
#[must_use]
pub fn stats(&self) -> (u64, u64, u64) {
(
self.stats.hits.load(Ordering::Acquire),
self.stats.misses.load(Ordering::Acquire),
self.stats.inserts.load(Ordering::Acquire),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
extern "C" fn dummy_fn(_p: *const f64) -> f64 {
0.0
}
#[test]
fn stats_counters_track_lookups() {
let cache = JitCache::new();
assert_eq!(cache.stats(), (0, 0, 0));
let _ = cache.get("missing");
assert_eq!(cache.stats(), (0, 1, 0));
cache.insert("hot".into(), dummy_fn);
assert_eq!(cache.stats(), (0, 1, 1));
let _ = cache.get("hot");
assert_eq!(cache.stats(), (1, 1, 1));
}
#[test]
fn stats_struct_is_cache_line_aligned() {
assert_eq!(core::mem::align_of::<CacheStats>(), 128);
assert!(core::mem::size_of::<CacheStats>() >= 128);
}
}