use portable_atomic::{AtomicU64, Ordering};
use crate::SlotState;
#[repr(transparent)]
pub struct MetaWord(AtomicU64);
impl MetaWord {
#[inline]
pub const fn empty() -> Self {
Self(AtomicU64::new(0))
}
#[inline]
fn load(&self) -> u64 {
self.0.load(Ordering::Relaxed)
}
#[inline]
fn store(&self, val: u64) {
self.0.store(val, Ordering::Relaxed);
}
#[inline]
pub fn get_state(&self, slot: u8) -> SlotState {
let shift = 56 + (slot as u32) * 2;
let bits = ((self.load() >> shift) & 0x03) as u8;
SlotState::from_bits(bits)
}
#[inline]
pub fn set_state(&self, slot: u8, state: SlotState) {
let shift = 56 + (slot as u32) * 2;
let mut v = self.load();
v &= !(0x03u64 << shift);
v |= (state as u64) << shift;
self.store(v);
}
#[inline]
pub fn get_h2(&self, slot: u8) -> u8 {
let shift = 28 + (slot as u32) * 7;
((self.load() >> shift) & 0x7F) as u8
}
#[inline]
pub fn set_h2(&self, slot: u8, h2: u8) {
let shift = 28 + (slot as u32) * 7;
let mut v = self.load();
v &= !(0x7Fu64 << shift);
v |= ((h2 & 0x7F) as u64) << shift;
self.store(v);
}
#[inline]
pub fn get_priority(&self, slot: u8) -> u8 {
let shift = (slot as u32) * 7;
((self.load() >> shift) & 0x7F) as u8
}
#[inline]
fn set_priority(&self, slot: u8, prio: u8) {
let shift = (slot as u32) * 7;
let mut v = self.load();
v &= !(0x7Fu64 << shift);
v |= ((prio & 0x7F) as u64) << shift;
self.store(v);
}
#[inline]
pub fn match_mask(&self, h2: u8) -> u8 {
let raw = self.load();
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
{
unsafe { crate::simd::match_mask_simd(raw, h2) }
}
#[cfg(not(all(target_arch = "x86_64", feature = "simd")))]
{
Self::match_mask_branchless_raw(raw, h2)
}
}
#[inline]
#[allow(dead_code)]
fn match_mask_branchless_raw(v: u64, h2: u8) -> u8 {
let h2 = h2 & 0x7F;
let mut mask: u8 = 0;
let s0 = ((v >> 56) & 0x03) as u8;
let s1 = ((v >> 58) & 0x03) as u8;
let s2 = ((v >> 60) & 0x03) as u8;
let s3 = ((v >> 62) & 0x03) as u8;
let h0 = ((v >> 28) & 0x7F) as u8;
let h1 = ((v >> 35) & 0x7F) as u8;
let h2_2 = ((v >> 42) & 0x7F) as u8;
let h3 = ((v >> 49) & 0x7F) as u8;
mask |= (s0 == 1) as u8 & (h0 == h2) as u8;
mask |= ((s1 == 1) as u8 & (h1 == h2) as u8) << 1;
mask |= ((s2 == 1) as u8 & (h2_2 == h2) as u8) << 2;
mask |= ((s3 == 1) as u8 & (h3 == h2) as u8) << 3;
mask
}
#[inline]
#[allow(dead_code)]
fn match_mask_branchless(&self, h2: u8) -> u8 {
Self::match_mask_branchless_raw(self.load(), h2)
}
#[inline]
pub fn find_free_slot(&self) -> Option<u8> {
let v = self.load();
for i in 0..4u8 {
let shift = 56 + (i as u32) * 2;
let bits = ((v >> shift) & 0x03) as u8;
if SlotState::from_bits(bits) != SlotState::Full {
return Some(i);
}
}
None
}
#[inline]
pub fn find_evict_target(&self) -> Option<u8> {
let v = self.load();
let mut min_prio: u8 = 0xFF;
let mut min_slot: Option<u8> = None;
for i in 0..4u8 {
let state_shift = 56 + (i as u32) * 2;
let state_bits = ((v >> state_shift) & 0x03) as u8;
if SlotState::from_bits(state_bits) == SlotState::Full {
let prio_shift = (i as u32) * 7;
let p = ((v >> prio_shift) & 0x7F) as u8;
if min_slot.is_none() || p < min_prio {
min_prio = p;
min_slot = Some(i);
}
}
}
min_slot
}
#[inline]
pub fn on_access(&self, slot: u8) {
loop {
let current = self.load();
let mut new_val = current;
let prio_shift = (slot as u32) * 7;
let prio = ((new_val >> prio_shift) & 0x7F) as u8;
let freq = (prio >> 3) & 0x0F;
let new_freq = if freq < 15 { freq + 1 } else { 15 };
let new_prio = (new_freq << 3) | 0x07; new_val &= !(0x7Fu64 << prio_shift);
new_val |= ((new_prio & 0x7F) as u64) << prio_shift;
for i in 0..4u8 {
if i != slot {
let state_shift = 56 + (i as u32) * 2;
let state_bits = ((new_val >> state_shift) & 0x03) as u8;
if SlotState::from_bits(state_bits) == SlotState::Full {
let other_shift = (i as u32) * 7;
let p = ((new_val >> other_shift) & 0x7F) as u8;
let r = p & 0x07;
if r > 0 {
let decayed = (p & 0x78) | (r - 1);
new_val &= !(0x7Fu64 << other_shift);
new_val |= ((decayed & 0x7F) as u64) << other_shift;
}
}
}
}
match self.0.compare_exchange_weak(
current,
new_val,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(_) => continue, }
}
}
#[inline]
pub fn on_insert(&self, slot: u8) {
self.set_priority(slot, 0x01); }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_roundtrip() {
let m = MetaWord::empty();
m.set_state(0, SlotState::Full);
m.set_state(2, SlotState::Tombstone);
assert_eq!(m.get_state(0), SlotState::Full);
assert_eq!(m.get_state(1), SlotState::Empty);
assert_eq!(m.get_state(2), SlotState::Tombstone);
}
#[test]
fn test_h2_roundtrip() {
let m = MetaWord::empty();
m.set_h2(0, 0x55);
m.set_h2(3, 0x7F);
assert_eq!(m.get_h2(0), 0x55);
assert_eq!(m.get_h2(3), 0x7F);
}
#[test]
fn test_find_free_slot() {
let m = MetaWord::empty();
assert_eq!(m.find_free_slot(), Some(0));
m.set_state(0, SlotState::Full);
m.set_state(1, SlotState::Full);
assert_eq!(m.find_free_slot(), Some(2));
}
#[test]
fn test_evict_target() {
let m = MetaWord::empty();
for i in 0..4u8 {
m.set_state(i, SlotState::Full);
}
m.set_priority(0, 100);
m.set_priority(1, 5); m.set_priority(2, 50);
m.set_priority(3, 80);
assert_eq!(m.find_evict_target(), Some(1));
}
}