use std::mem;
use brk_types::{AddrHash, OutputType, TypeIndex};
const ASSOCIATIVITY: usize = 4;
const SET_COUNT: usize = 1 << 19;
const SET_MASK: usize = SET_COUNT - 1;
const TYPE_MIX: u64 = 0x9e37_79b9_7f4a_7c15;
const REFERENCE_SHIFT: usize = 2;
#[derive(Clone, Copy)]
#[repr(C)]
struct Entry {
hash: AddrHash,
type_index: TypeIndex,
output_type: OutputType,
}
impl Entry {
const EMPTY: Self = Self {
hash: AddrHash::new(0),
type_index: TypeIndex::COINBASE,
output_type: OutputType::Unknown,
};
#[inline]
fn matches(&self, output_type: OutputType, hash: AddrHash) -> bool {
self.output_type == output_type && self.hash == hash
}
#[inline]
fn is_empty(&self) -> bool {
self.output_type == OutputType::Unknown
}
}
#[derive(Clone, Copy)]
#[repr(C, align(64))]
struct Set([Entry; ASSOCIATIVITY]);
impl Set {
const EMPTY: Self = Self([Entry::EMPTY; ASSOCIATIVITY]);
}
#[derive(Clone, Copy, Default)]
struct SetState(u8);
impl SetState {
#[inline]
fn mark_referenced(&mut self, entry_index: usize) {
self.0 |= 1 << (REFERENCE_SHIFT + entry_index);
}
#[inline]
fn victim(&mut self) -> usize {
loop {
let entry_index = usize::from(self.0 & (ASSOCIATIVITY as u8 - 1));
self.0 = (self.0 & !(ASSOCIATIVITY as u8 - 1))
| ((entry_index + 1) & (ASSOCIATIVITY - 1)) as u8;
let reference = 1 << (REFERENCE_SHIFT + entry_index);
if self.0 & reference == 0 {
return entry_index;
}
self.0 &= !reference;
}
}
}
const _: () = assert!(mem::size_of::<Entry>() == 16);
const _: () = assert!(mem::size_of::<Set>() == 64);
const _: () = assert!(mem::size_of::<SetState>() == 1);
struct Storage {
sets: Box<[Set]>,
states: Box<[SetState]>,
}
impl Storage {
fn new() -> Self {
Self {
sets: vec![Set::EMPTY; SET_COUNT].into_boxed_slice(),
states: vec![SetState::default(); SET_COUNT].into_boxed_slice(),
}
}
}
#[derive(Default)]
pub(super) struct AddressCache {
storage: Option<Storage>,
}
impl AddressCache {
#[inline]
pub(super) fn get(&mut self, output_type: OutputType, hash: AddrHash) -> Option<TypeIndex> {
let storage = self.storage.as_mut()?;
let set_index = Self::set_index(output_type, hash);
let entry_index = storage.sets[set_index]
.0
.iter()
.position(|entry| entry.matches(output_type, hash))?;
storage.states[set_index].mark_referenced(entry_index);
Some(storage.sets[set_index].0[entry_index].type_index)
}
#[inline]
pub(super) fn insert(
&mut self,
output_type: OutputType,
hash: AddrHash,
type_index: TypeIndex,
) {
let set_index = Self::set_index(output_type, hash);
let storage = self.storage.get_or_insert_with(Storage::new);
let set = &mut storage.sets[set_index].0;
let state = &mut storage.states[set_index];
let entry_index = set
.iter()
.position(Entry::is_empty)
.unwrap_or_else(|| state.victim());
set[entry_index] = Entry {
hash,
type_index,
output_type,
};
state.mark_referenced(entry_index);
}
pub(super) fn clear(&mut self) {
self.storage = None;
}
#[inline]
fn set_index(output_type: OutputType, hash: AddrHash) -> usize {
let hash = *hash ^ (output_type as u64).wrapping_mul(TYPE_MIX);
hash as usize & SET_MASK
}
}