use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use parking_lot::RwLock;
use siphasher::sip128::{Hasher128, SipHasher13};
use crate::accelerate;
use crate::constraint::Constraint;
use crate::input::Input;
use crate::track::Call;
use crate::tree::{CallTree, InsertError};
static EVICTORS: RwLock<Vec<fn(usize)>> = RwLock::new(Vec::new());
#[allow(clippy::type_complexity)]
pub fn memoize<'a, In, Out, F>(
cache: &Cache<In::Call, Out>,
mut input: In,
(storage, constraint): &'a mut (
In::Storage<&'a Constraint<In::Call>>,
Constraint<In::Call>,
),
enabled: bool,
func: F,
) -> Out
where
In: Input<'a>,
Out: Clone + 'static,
F: FnOnce(In) -> Out,
{
if !enabled {
let output = func(input);
#[cfg(feature = "testing")]
crate::testing::register_miss();
return output;
}
let key = {
let mut state = SipHasher13::new();
input.key(&mut state);
state.finish128().as_u128()
};
if let Some(entry) = cache.0.read().lookup(key, &input) {
for call in &entry.mutable {
input.call_mut(call);
}
#[cfg(feature = "testing")]
crate::testing::register_hit();
return entry.output.clone();
}
input.attach(storage, constraint);
let output = func(input);
match cache.0.write().insert(key, constraint, output.clone()) {
Ok(()) => {}
Err(InsertError::AlreadyExists) => {
}
Err(InsertError::MissingCall) => {
#[cfg(debug_assertions)]
panic!("comemo: memoized function is non-deterministic");
}
}
#[cfg(feature = "testing")]
crate::testing::register_miss();
output
}
pub fn evict(max_age: usize) {
for subevict in EVICTORS.read().iter() {
subevict(max_age);
}
accelerate::evict();
}
pub fn register_evictor(evict: fn(usize)) {
EVICTORS.write().push(evict);
}
pub struct Cache<C, Out>(LazyLock<RwLock<CacheData<C, Out>>>);
impl<C: 'static, Out: 'static> Cache<C, Out> {
pub const fn new(init: fn() -> RwLock<CacheData<C, Out>>) -> Self {
Self(LazyLock::new(init))
}
pub fn evict(&self, max_age: usize) {
self.0.write().evict(max_age);
}
}
pub struct CacheData<C, Out> {
tree: CallTree<C, CacheEntry<C, Out>>,
}
struct CacheEntry<C, Out> {
output: Out,
mutable: Vec<C>,
age: AtomicUsize,
}
impl<C, Out: 'static> CacheData<C, Out> {
fn evict(&mut self, max_age: usize) {
self.tree.retain(|entry| {
let age = entry.age.get_mut();
*age += 1;
*age <= max_age
});
}
fn lookup<'a, In>(&self, key: u128, input: &In) -> Option<&CacheEntry<C, Out>>
where
C: Call,
In: Input<'a, Call = C>,
{
self.tree
.get(key, |c| input.call(c))
.inspect(|entry| entry.age.store(0, Ordering::SeqCst))
}
fn insert(
&mut self,
key: u128,
constraint: &Constraint<C>,
output: Out,
) -> Result<(), InsertError>
where
C: Call,
{
let (immutable, mutable) = constraint.take();
self.tree.insert(
key,
immutable,
CacheEntry { output, mutable, age: AtomicUsize::new(0) },
)
}
}
impl<C, Out> Default for CacheData<C, Out> {
fn default() -> Self {
Self { tree: CallTree::new() }
}
}