use super::rolling::{HASH_CKOFFSET, HashCfg};
pub struct SmallTable {
table: Vec<u32>,
cfg: HashCfg,
prev: Option<Vec<u32>>,
prev_mask: usize,
}
impl SmallTable {
pub fn new(slots: usize, sprevsz: usize) -> Self {
let cfg = HashCfg::new(slots);
let table = vec![0u32; cfg.size];
let (prev, prev_mask) = if sprevsz > 0 {
debug_assert!(sprevsz.is_power_of_two());
(Some(vec![0u32; sprevsz]), sprevsz - 1)
} else {
(None, 0)
};
Self {
table,
cfg,
prev,
prev_mask,
}
}
pub fn reset(&mut self) {
self.table.fill(0);
if let Some(ref mut prev) = self.prev {
prev.fill(0);
}
}
#[inline(always)]
pub fn lookup(&self, cksum: u64) -> Option<u64> {
let bucket = self.cfg.bucket(cksum);
let val = unsafe { *self.table.get_unchecked(bucket) };
if val != 0 {
Some(val as u64 - HASH_CKOFFSET)
} else {
None
}
}
#[inline(always)]
pub fn insert(&mut self, cksum: u64, pos: u64) {
let stored = match u32::try_from(pos + HASH_CKOFFSET) {
Ok(v) => v,
Err(_) => return,
};
let bucket = self.cfg.bucket(cksum);
unsafe {
if let Some(ref mut prev) = self.prev {
let old_head = *self.table.get_unchecked(bucket);
let prev_idx = pos as usize & self.prev_mask;
*prev.get_unchecked_mut(prev_idx) = old_head;
}
*self.table.get_unchecked_mut(bucket) = stored;
}
}
#[inline]
pub fn chain_prev(&self, pos: u64, current_input_pos: u64) -> Option<u64> {
let prev = self.prev.as_ref()?;
let prev_idx = pos as usize & self.prev_mask;
let prev_val = unsafe { *prev.get_unchecked(prev_idx) };
if prev_val == 0 {
return None;
}
let prev_pos = prev_val as u64 - HASH_CKOFFSET;
if prev_pos > pos {
return None;
}
let diff = current_input_pos - prev_pos;
if diff > self.prev_mask as u64 {
return None;
}
Some(prev_pos)
}
pub fn size(&self) -> usize {
self.cfg.size
}
pub fn cfg(&self) -> &HashCfg {
&self.cfg
}
#[inline(always)]
pub fn prefetch_bucket(&self, cksum: u64) {
let bucket = self.cfg.bucket(cksum);
let addr = unsafe { self.table.as_ptr().add(bucket) as *const u8 };
super::rolling::prefetch_read(addr);
}
}
pub struct LargeTable {
table: Vec<u64>,
cfg: HashCfg,
}
impl LargeTable {
pub fn new(slots: usize) -> Self {
let cfg = HashCfg::new(slots.max(8));
let table = vec![0u64; cfg.size];
Self { table, cfg }
}
#[inline(always)]
pub fn lookup(&self, cksum: u64) -> Option<u64> {
let bucket = self.cfg.bucket(cksum);
let val = unsafe { *self.table.get_unchecked(bucket) };
if val != 0 {
Some(val - HASH_CKOFFSET)
} else {
None
}
}
#[inline(always)]
pub fn insert(&mut self, cksum: u64, pos: u64) {
let bucket = self.cfg.bucket(cksum);
unsafe { *self.table.get_unchecked_mut(bucket) = pos + HASH_CKOFFSET };
}
pub fn size(&self) -> usize {
self.cfg.size
}
pub fn cfg(&self) -> &HashCfg {
&self.cfg
}
#[inline(always)]
pub fn prefetch_bucket(&self, cksum: u64) {
let bucket = self.cfg.bucket(cksum);
let addr = unsafe { self.table.as_ptr().add(bucket) as *const u8 };
super::rolling::prefetch_read(addr);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_table_insert_lookup() {
let mut t = SmallTable::new(1024, 0);
assert!(t.lookup(42).is_none());
t.insert(42, 100);
assert_eq!(t.lookup(42), Some(100));
}
#[test]
fn small_table_overwrite() {
let mut t = SmallTable::new(1024, 0);
t.insert(42, 100);
t.insert(42, 200);
assert_eq!(t.lookup(42), Some(200));
}
#[test]
fn small_table_reset() {
let mut t = SmallTable::new(1024, 0);
t.insert(42, 100);
t.reset();
assert!(t.lookup(42).is_none());
}
#[test]
fn small_table_chaining() {
let sprevsz = 256;
let mut t = SmallTable::new(1024, sprevsz);
t.insert(42, 10);
t.insert(42, 50);
assert_eq!(t.lookup(42), Some(50));
let prev = t.chain_prev(50, 50);
assert_eq!(prev, Some(10));
let prev2 = t.chain_prev(10, 50);
assert!(prev2.is_none());
}
#[test]
fn small_table_chain_stale_rejection() {
let sprevsz = 16;
let mut t = SmallTable::new(1024, sprevsz);
t.insert(42, 0);
t.insert(42, 100); let prev = t.chain_prev(100, 100);
assert!(prev.is_none());
}
#[test]
fn small_table_chain_boundary_is_stale() {
let sprevsz = 16;
let mut t = SmallTable::new(1024, sprevsz);
t.insert(42, 0);
t.insert(42, 16);
let prev = t.chain_prev(16, 16);
assert!(prev.is_none());
}
#[test]
fn large_table_insert_lookup() {
let mut t = LargeTable::new(1024);
assert!(t.lookup(99).is_none());
t.insert(99, 5000);
assert_eq!(t.lookup(99), Some(5000));
}
#[test]
fn large_table_overwrite() {
let mut t = LargeTable::new(1024);
t.insert(99, 5000);
t.insert(99, 6000);
assert_eq!(t.lookup(99), Some(6000));
}
}