use alloc::vec;
use alloc::vec::Vec;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct LdmEntry {
pub(crate) offset: u32,
pub(crate) checksum: u32,
}
const REBASE_GUARD_BAND: u32 = 1u32 << 30;
#[derive(Clone)]
pub(crate) struct LdmHashTable {
entries: Vec<LdmEntry>,
bucket_offsets: Vec<u8>,
effective_bucket_log: u32,
bucket_mask: u32,
position_base: usize,
}
impl LdmHashTable {
pub(crate) fn new(hash_log: u32, bucket_size_log: u32) -> Self {
assert!(hash_log > 0, "hash_log must be > 0");
assert!(
hash_log <= 30,
"hash_log {hash_log} exceeds upstream zstd ZSTD_LDM_HASHLOG_MAX (30)"
);
let effective_bucket_log = bucket_size_log.min(hash_log);
assert!(
effective_bucket_log <= 8,
"effective bucket_size_log {effective_bucket_log} exceeds upstream zstd \
ZSTD_LDM_BUCKETSIZELOG_MAX (8); a u8 cursor would silently \
truncate at 256 slots — widen the cursor or clamp the request"
);
let bucket_count = 1u32 << (hash_log - effective_bucket_log);
let total_entries = 1usize << hash_log;
Self {
entries: vec![LdmEntry::default(); total_entries],
bucket_offsets: vec![0u8; bucket_count as usize],
effective_bucket_log,
bucket_mask: bucket_count - 1,
position_base: 0,
}
}
pub(crate) fn heap_size(&self) -> usize {
self.entries.capacity() * core::mem::size_of::<LdmEntry>() + self.bucket_offsets.capacity()
}
pub(crate) fn clear(&mut self) {
self.entries.fill(LdmEntry::default());
self.bucket_offsets.fill(0);
self.position_base = 0;
}
pub(crate) const fn bucket_count(&self) -> usize {
self.bucket_mask as usize + 1
}
pub(crate) const fn bucket_slots(&self) -> usize {
1usize << self.effective_bucket_log
}
pub(crate) fn bucket(&self, hash_id: u32) -> &[LdmEntry] {
let start = (hash_id as usize) << self.effective_bucket_log;
let len = self.bucket_slots();
&self.entries[start..start + len]
}
pub(crate) fn insert(&mut self, hash_id: u32, entry: LdmEntry) {
assert!(
entry.offset != 0,
"offset 0 is reserved for the empty-slot sentinel; \
use `insert_absolute` (which applies the +1 bias) or \
store a +1-biased relative offset before calling insert"
);
debug_assert!(
hash_id <= self.bucket_mask,
"hash_id {hash_id} out of range (bucket_count = {})",
self.bucket_count()
);
let slot_mask = self.bucket_slots() - 1;
let bucket_start = (hash_id as usize) << self.effective_bucket_log;
let offset = self.bucket_offsets[hash_id as usize] as usize;
self.entries[bucket_start + offset] = entry;
let next = (offset + 1) & slot_mask;
self.bucket_offsets[hash_id as usize] = next as u8;
}
pub(crate) const fn bucket_mask(&self) -> u32 {
self.bucket_mask
}
pub(crate) const fn position_base(&self) -> usize {
self.position_base
}
pub(crate) fn resolve(&self, entry: &LdmEntry) -> Option<usize> {
match entry.offset {
0 => None,
rel => Some(self.position_base + (rel as usize) - 1),
}
}
pub(crate) fn insert_absolute(&mut self, hash_id: u32, abs_pos: usize, checksum: u32) {
let rel = abs_pos.checked_sub(self.position_base).unwrap_or_else(|| {
panic!(
"insert position {abs_pos} is below position_base {}; \
callers must rebase via `ensure_room_for` or clear before \
reaching this state",
self.position_base
)
});
assert!(
rel < u32::MAX as usize,
"insert position {abs_pos} (rel {rel}) exceeds u32 window; \
producer must call `ensure_room_for` before insert"
);
let stored = (rel as u32) + 1;
self.insert(
hash_id,
LdmEntry {
offset: stored,
checksum,
},
);
}
pub(crate) fn ensure_room_for(&mut self, abs_pos: usize) {
if abs_pos < self.position_base {
return;
}
let max_rel = u32::MAX as usize - REBASE_GUARD_BAND as usize;
while abs_pos - self.position_base > max_rel {
self.reduce(REBASE_GUARD_BAND);
}
}
pub(crate) fn reduce(&mut self, reducer: u32) {
for entry in &mut self.entries {
if entry.offset <= reducer {
entry.offset = 0;
} else {
entry.offset -= reducer;
}
}
self.position_base = self.position_base.saturating_add(reducer as usize);
}
}
#[cfg(test)]
mod tests;