1use std::sync::LazyLock;
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4use parking_lot::RwLock;
5use siphasher::sip128::{Hasher128, SipHasher13};
6
7use crate::accelerate;
8use crate::constraint::Constraint;
9use crate::input::Input;
10use crate::track::Call;
11use crate::tree::{CallTree, InsertError};
12
13static EVICTORS: RwLock<Vec<fn(usize)>> = RwLock::new(Vec::new());
15
16#[allow(clippy::type_complexity)]
18pub fn memoize<'a, In, Out, F>(
19 cache: &Cache<In::Call, Out>,
20 mut input: In,
21 (storage, constraint): &'a mut (
25 In::Storage<&'a Constraint<In::Call>>,
26 Constraint<In::Call>,
27 ),
28 enabled: bool,
29 func: F,
30) -> Out
31where
32 In: Input<'a>,
33 Out: Clone + 'static,
34 F: FnOnce(In) -> Out,
35{
36 if !enabled {
38 let output = func(input);
39
40 #[cfg(feature = "testing")]
42 crate::testing::register_miss();
43
44 return output;
45 }
46
47 let key = {
49 let mut state = SipHasher13::new();
50 input.key(&mut state);
51 state.finish128().as_u128()
52 };
53
54 if let Some(entry) = cache.0.read().lookup(key, &input) {
56 for call in &entry.mutable {
58 input.call_mut(call);
59 }
60
61 #[cfg(feature = "testing")]
62 crate::testing::register_hit();
63
64 return entry.output.clone();
65 }
66
67 input.attach(storage, constraint);
69
70 let output = func(input);
72
73 match cache.0.write().insert(key, constraint, output.clone()) {
75 Ok(()) => {}
76 Err(InsertError::AlreadyExists) => {
77 }
80 Err(InsertError::MissingCall) => {
81 #[cfg(debug_assertions)]
84 panic!("comemo: memoized function is non-deterministic");
85 }
86 }
87
88 #[cfg(feature = "testing")]
89 crate::testing::register_miss();
90
91 output
92}
93
94pub fn evict(max_age: usize) {
101 for subevict in EVICTORS.read().iter() {
102 subevict(max_age);
103 }
104
105 accelerate::evict();
106}
107
108pub fn register_evictor(evict: fn(usize)) {
110 EVICTORS.write().push(evict);
111}
112
113pub struct Cache<C, Out>(LazyLock<RwLock<CacheData<C, Out>>>);
115
116impl<C: 'static, Out: 'static> Cache<C, Out> {
117 pub const fn new(init: fn() -> RwLock<CacheData<C, Out>>) -> Self {
123 Self(LazyLock::new(init))
124 }
125
126 pub fn evict(&self, max_age: usize) {
128 self.0.write().evict(max_age);
129 }
130}
131
132pub struct CacheData<C, Out> {
134 tree: CallTree<C, CacheEntry<C, Out>>,
136}
137
138struct CacheEntry<C, Out> {
140 output: Out,
142 mutable: Vec<C>,
144 age: AtomicUsize,
146}
147
148impl<C, Out: 'static> CacheData<C, Out> {
149 fn evict(&mut self, max_age: usize) {
151 self.tree.retain(|entry| {
152 let age = entry.age.get_mut();
153 *age += 1;
154 *age <= max_age
155 });
156 }
157
158 fn lookup<'a, In>(&self, key: u128, input: &In) -> Option<&CacheEntry<C, Out>>
160 where
161 C: Call,
162 In: Input<'a, Call = C>,
163 {
164 self.tree
165 .get(key, |c| input.call(c))
166 .inspect(|entry| entry.age.store(0, Ordering::SeqCst))
167 }
168
169 fn insert(
171 &mut self,
172 key: u128,
173 constraint: &Constraint<C>,
174 output: Out,
175 ) -> Result<(), InsertError>
176 where
177 C: Call,
178 {
179 let (immutable, mutable) = constraint.take();
180 self.tree.insert(
181 key,
182 immutable,
183 CacheEntry { output, mutable, age: AtomicUsize::new(0) },
184 )
185 }
186}
187
188impl<C, Out> Default for CacheData<C, Out> {
189 fn default() -> Self {
190 Self { tree: CallTree::new() }
191 }
192}