1use crate::componant::arena::Arena;
2use crate::componant::cache_tier::{CacheTier, FastTier};
3use crate::componant::config::{CachePolicy, DefaultExponentialPolicy};
4use crate::componant::qsbr::Guard;
5use ahash::RandomState;
6use core::hash::Hash;
7
8#[repr(align(64))]
11pub struct DualCacheCore<
12 K,
13 V,
14 P: CachePolicy = DefaultExponentialPolicy,
15 const T0_CAP: usize = 64,
16 const T1_CAP: usize = 4096,
17 const T2_CAP: usize = 262144,
18 const TOTAL_CAP: usize = { 64 + 4096 + 262144 },
19> {
20 pub arena: Arena<K, V, TOTAL_CAP>,
21 pub t0: FastTier<T0_CAP>,
22 pub t1: FastTier<T1_CAP>,
23 pub t2: CacheTier<K, V, P::Evict, T2_CAP, 8>,
24 pub blackjack: crate::core::blackjack::PackedBlackjack,
25 hash_builder: RandomState,
26 _marker: core::marker::PhantomData<P>,
27}
28
29pub type BottomUpCache<K, V> =
31 DualCacheCore<K, V, DefaultExponentialPolicy, 64, 4096, 262144, { 64 + 4096 + 262144 }>;
32
33impl<
34 K,
35 V,
36 P: CachePolicy,
37 const T0_CAP: usize,
38 const T1_CAP: usize,
39 const T2_CAP: usize,
40 const TOTAL_CAP: usize,
41> DualCacheCore<K, V, P, T0_CAP, T1_CAP, T2_CAP, TOTAL_CAP>
42where
43 K: Clone + Eq + Hash,
44 V: Clone,
45{
46 pub const fn new(eviction: P::Evict) -> Self {
47 Self {
48 arena: Arena::new(),
49 t0: FastTier::new(),
50 t1: FastTier::new(),
51 t2: CacheTier::new(eviction),
52 blackjack: crate::core::blackjack::PackedBlackjack::new(
53 P::T0_THRESHOLD,
54 P::T1_THRESHOLD,
55 P::T2_THRESHOLD,
56 256,
57 ),
58 hash_builder: ahash::RandomState::with_seeds(
59 0x1234567890ABCDEF,
60 0xFEDCBA0987654321,
61 0x13579BDF02468ACE,
62 0xECA86420FDB97531,
63 ),
64 _marker: core::marker::PhantomData,
65 }
66 }
67
68 #[inline(always)]
69 pub fn hash_key(&self, key: &K) -> usize {
70 self.hash_builder.hash_one(key) as usize
71 }
72
73 #[inline(always)]
74 pub fn get_t0<'g>(&self, hash: usize, key: &K, _guard: &'g Guard, _op_count: u32) -> Option<&'g V> {
75 let node_idx = self.t0.get_slot_idx(hash);
76 if node_idx != crate::componant::arena::NULL_INDEX {
77 let node = unsafe { self.arena.get(node_idx as usize) };
78 if node.key == *key {
79 return Some(unsafe { &*(&node.value as *const V) });
80 }
81 }
82 None
83 }
84
85 #[inline(always)]
86 pub fn get_t1<'g>(&self, hash: usize, key: &K, _guard: &'g Guard, _op_count: u32) -> Option<&'g V> {
87 let node_idx = self.t1.get_slot_idx(hash);
88 if node_idx != crate::componant::arena::NULL_INDEX {
89 let node = unsafe { self.arena.get(node_idx as usize) };
90 if node.key == *key {
91 return Some(unsafe { &*(&node.value as *const V) });
92 }
93 }
94 None
95 }
96
97 #[inline(always)]
98 #[allow(clippy::type_complexity)]
99 pub fn get_t2<'g>(&'g self, hash: usize, key: &K, guard: &'g crate::componant::qsbr::Guard, op_count: u32) -> Option<(&'g V, u8, Option<(K, V)>)> {
100 let (_t0_thresh, t1_thresh, _, _) = self.blackjack.load_params();
101
102 if let Some(val) = self.get_t0(hash, key, guard, op_count) {
104 return Some((val, 0, None));
105 }
106
107 let t1_idx = self.t1.get_slot_idx(hash);
109 if t1_idx != crate::componant::arena::NULL_INDEX {
110 let node = unsafe { self.arena.get(t1_idx as usize) };
111 if node.key == *key {
112 return Some((unsafe { &*(&node.value as *const V) }, 1, None));
113 }
114 }
115
116 if let Some(slot) = self.t2.get_slot(&self.arena, hash, key, guard) {
118 let (old_hits, new_hits) = slot.record_hit(op_count);
119 let hint = slot.prefetch_hint.load(::core::sync::atomic::Ordering::Relaxed);
120 let hint_kv = if hint != 0 {
121 self.t2.fetch_hint(hint, &self.arena, guard)
122 } else {
123 None
124 };
125
126 let node = unsafe { self.arena.get(slot.read(guard).1 as usize) };
127 if old_hits < t1_thresh && new_hits >= t1_thresh {
128 unsafe {
130 self.t1.insert_promote(&self.arena, hash, key.clone(), node.value.clone(), guard.node());
131 }
132 }
133 return Some((unsafe { &*(&node.value as *const V) }, 2, hint_kv));
134 }
135 None
136 }
137
138 #[inline(never)]
139 #[allow(clippy::type_complexity)]
140 pub fn get<'g>(&'g self, key: &K, guard: &'g Guard, op_count: u32) -> Option<(&'g V, u8, Option<(K, V)>)> {
141 #[repr(align(64))]
142 struct CachePadded;
143 let _pad = CachePadded;
144
145 mod core {
146 pub mod intrinsics {
147 pub use crate::utils::likely;
148
149 }
150 }
151
152 let hash = self.hash_key(key);
153 let res = self.get_t2(hash, key, guard, op_count);
154
155 if core::intrinsics::likely(res.is_some()) {
156 return res;
157 }
158 None
159 }
160
161 #[allow(clippy::not_unsafe_ptr_arg_deref)]
162 pub fn put_t0(&self, key: K, value: V, node: *mut crate::componant::qsbr::ThreadStateNode) {
163 if crate::utils::likely(true) {
164 let hash = self.hash_key(&key);
165 unsafe {
166 self.t0.insert_promote(&self.arena, hash, key, value, node);
167 }
168 }
169 }
170
171 #[allow(clippy::not_unsafe_ptr_arg_deref)]
172 pub fn put_t1(&self, key: K, value: V, node: *mut crate::componant::qsbr::ThreadStateNode) {
173 if crate::utils::likely(true) {
174 let hash = self.hash_key(&key);
175 unsafe {
176 self.t1.insert_promote(&self.arena, hash, key, value, node);
177 }
178 }
179 }
180
181 pub fn put(&self, key: K, value: V, node: *mut crate::componant::qsbr::ThreadStateNode) {
182 if crate::utils::likely(true) {
183 let hash = self.hash_key(&key);
184 self.t2.insert(&self.arena, hash, key, value, node);
185 }
186 }
187
188 pub fn try_reclaim(&self, _node: *mut crate::componant::qsbr::ThreadStateNode) {
189 }
191
192 pub fn sync_reclaim(&self) {
196 crate::componant::qsbr::daemon_reclaim(|batch| {
197 if batch.is_empty() { return; }
198 unsafe {
199 for i in 0..batch.len() {
200 let idx = batch[i];
201 self.arena.drop_node(idx as usize);
202 if i < batch.len() - 1 {
203 self.arena.set_next_free(idx, batch[i + 1]);
204 }
205 }
206 self.arena.free_batch(batch[0], batch[batch.len() - 1]);
207 }
208 });
209 }
210
211 pub fn record_remote_hit(&self, hash: usize, _weight: u8) {
214 let set = self.t2.get_set(hash);
215 for i in 0..8 {
216 let slot = unsafe { set.get_unchecked(i) };
217 if slot.hash.load(::core::sync::atomic::Ordering::Relaxed) == hash {
218 let old_hits = slot.hits.load(::core::sync::atomic::Ordering::Relaxed);
219 let new_hits = old_hits.saturating_add(_weight as u16);
220 slot.hits
221 .store(new_hits, ::core::sync::atomic::Ordering::Relaxed);
222 break;
223 }
224 }
225 }
226
227 pub fn set_prefetch_hint(&self, hash: usize, next_hash: usize) {
228 let set = self.t2.get_set(hash);
229 for i in 0..8 {
230 let slot = unsafe { set.get_unchecked(i) };
231 if slot.hash.load(::core::sync::atomic::Ordering::Relaxed) == hash {
232 slot.prefetch_hint.store(next_hash, ::core::sync::atomic::Ordering::Relaxed);
233 break;
234 }
235 }
236 }
237}
238
239impl<
240 K,
241 V,
242 P: CachePolicy,
243 const T0_CAP: usize,
244 const T1_CAP: usize,
245 const T2_CAP: usize,
246 const TOTAL_CAP: usize,
247> Default for DualCacheCore<K, V, P, T0_CAP, T1_CAP, T2_CAP, TOTAL_CAP>
248where
249 K: Clone + Eq + Hash,
250 V: Clone,
251{
252 fn default() -> Self {
253 Self::new(P::Evict::default())
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::componant::config::CachePolicy;
261 use crate::componant::qsbr;
262
263 struct TestPolicy;
265 impl CachePolicy for TestPolicy {
266 type Evict = crate::componant::policy::DefaultEvictionPolicy;
267 const T2_THRESHOLD: u16 = 2;
268 const T1_THRESHOLD: u16 = 4;
269 const T0_THRESHOLD: u16 = 8;
270 }
271
272 #[test]
273 fn test_comprehensive_cache_flow() {
274 let core = DualCacheCore::<u64, u64, TestPolicy, 8, 8, 8, 24>::default();
275 static mut TEST_NODE: qsbr::ThreadStateNode = qsbr::ThreadStateNode::new();
276 let thread_node = {
277 let node = &raw mut TEST_NODE as *mut _;
278 qsbr::register_node(node, 0, ::core::ptr::null(), None);
279 node
280 };
281 let guard = qsbr::pin(thread_node);
282
283 assert_eq!(core.get(&100, &guard, 0), None);
284
285 core.put(100, 200, thread_node);
286 assert_eq!(core.get(&100, &guard, 0), Some((&200, 2, None)));
287
288 core.put_t1(300, 400, thread_node);
289 assert_eq!(core.get(&300, &guard, 0), Some((&400, 1, None)));
290
291 core.put_t0(500, 600, thread_node);
292 assert_eq!(core.get(&500, &guard, 0), Some((&600, 0, None)));
293 }
294
295 #[test]
296 fn test_t0_promotion_flow() {
297 let core = DualCacheCore::<u64, u64, TestPolicy, 8, 8, 8, 24>::default();
298 static mut TEST_NODE: qsbr::ThreadStateNode = qsbr::ThreadStateNode::new();
299 let thread_node = {
300 let node = &raw mut TEST_NODE as *mut _;
301 qsbr::register_node(node, 0, ::core::ptr::null(), None);
302 node
303 };
304 let guard = qsbr::pin(thread_node);
305
306 assert_eq!(core.get(&99, &guard, 0), None);
307
308 core.put(300, 400, thread_node);
309 core.record_remote_hit(core.hash_key(&300), 10);
310 assert_eq!(core.get(&300, &guard, 0), Some((&400, 2, None)));
311
312 core.put_t0(500, 600, thread_node);
313 assert_eq!(core.get(&500, &guard, 0), Some((&600, 0, None)));
314 }
315
316 #[test]
317 fn test_tier_fallbacks() {
318 let core = DualCacheCore::<u64, u64, TestPolicy, 8, 8, 8, 24>::default();
319 static mut TEST_NODE: qsbr::ThreadStateNode = qsbr::ThreadStateNode::new();
320 let thread_node = {
321 let node = &raw mut TEST_NODE as *mut _;
322 qsbr::register_node(node, 0, ::core::ptr::null(), None);
323 node
324 };
325 let guard = qsbr::pin(thread_node);
326
327 let hash1 = core.hash_key(&1000);
328 unsafe {
329 core.t1.insert_promote(&core.arena, hash1, 1000, 2000, thread_node);
330 }
331 assert_eq!(core.get(&1000, &guard, 0), Some((&2000, 1, None)));
332
333 let hash0 = core.hash_key(&3000);
334 unsafe {
335 core.t0.insert_promote(&core.arena, hash0, 3000, 4000, thread_node);
336 }
337 assert_eq!(core.get(&3000, &guard, 0), Some((&4000, 0, None)));
338
339 let idx = core.t1.get_slot_idx(hash1);
340 assert_ne!(idx, crate::componant::arena::NULL_INDEX);
341 }
342}