use fearless_simd::{Level, Simd, SimdBase, SimdMask, u8x16, u8x32};
use super::params::{BucketShape, ChainShape, HasherPlan};
use crate::compressor::core::shared::constants::HASH_MUL32;
use crate::compressor::core::shared::dictionary::{self, DictionaryStats};
use crate::compressor::core::shared::match_len::{current_window, match_len_at, match_len_windows};
use crate::compressor::core::shared::score::{
SearchResult, backward_reference_penalty_using_last_distance, backward_reference_score,
backward_reference_score_using_last_distance,
};
const HASH_MUL64: u64 = 0x1FE3_5A7B_D357_9BD3;
const NUM_DISTANCE_SHORT_CODES: usize = 16;
pub(crate) type DistanceCache = [i32; NUM_DISTANCE_SHORT_CODES];
pub(crate) const NUM_REMEMBERED_DISTANCES: usize = 4;
pub(crate) const INITIAL_DISTANCE_CACHE: DistanceCache =
[4, 11, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
#[inline]
pub(crate) fn prepare_distance_cache(cache: &mut DistanceCache, num_distances: usize) {
if num_distances <= NUM_REMEMBERED_DISTANCES {
return;
}
let last = cache[0];
cache[4] = last - 1;
cache[5] = last + 1;
cache[6] = last - 2;
cache[7] = last + 2;
cache[8] = last - 3;
cache[9] = last + 3;
if num_distances > 10 {
let next_last = cache[1];
cache[10] = next_last - 1;
cache[11] = next_last + 1;
cache[12] = next_last - 2;
cache[13] = next_last + 2;
cache[14] = next_last - 3;
cache[15] = next_last + 3;
}
}
#[inline(always)]
fn read_u64(data: &[u8], offset: usize) -> u64 {
debug_assert!(offset <= u32::MAX as usize);
let offset = offset & u32::MAX as usize;
match data.get(offset..offset + 8) {
Some(chunk) => u64::from_le_bytes(chunk.try_into().unwrap_or([0; 8])),
None => 0,
}
}
#[inline(always)]
fn read_u32(data: &[u8], offset: usize) -> u32 {
debug_assert!(offset <= u32::MAX as usize);
let offset = offset & u32::MAX as usize;
match data.get(offset..offset + 4) {
Some(chunk) => u32::from_le_bytes(chunk.try_into().unwrap_or([0; 4])),
None => 0,
}
}
#[inline(always)]
fn read_u8(data: &[u8], offset: usize) -> u8 {
match data.get(offset) {
Some(&byte) => byte,
None => 0,
}
}
#[derive(Copy, Clone)]
pub(crate) struct MatchQuery<'a> {
#[cfg(feature = "experimental")]
pub(crate) custom:
Option<&'a crate::compressor::core::rfc9841::static_index::StaticCombination>,
pub(crate) data: &'a [u8],
pub(crate) window: &'a [u8],
pub(crate) mask: usize,
pub(crate) cache: &'a DistanceCache,
pub(crate) cur_ix: usize,
pub(crate) max_length: usize,
pub(crate) max_backward: usize,
pub(crate) position_offset: usize,
pub(crate) dictionary_limit: usize,
pub(crate) gap: usize,
pub(crate) max_distance: usize,
}
impl MatchQuery<'_> {
#[inline(always)]
pub(crate) fn dictionary_start(&self) -> usize {
(self.cur_ix + self.position_offset).min(self.dictionary_limit)
}
#[inline(always)]
fn dictionary_distance(&self) -> usize {
self.dictionary_start() + self.gap
}
fn search_dictionary(self, stats: &mut DictionaryStats, out: &mut SearchResult, shallow: bool) {
let data = self.data.get(self.cur_ix & self.mask..).unwrap_or_default();
#[cfg(feature = "experimental")]
if let Some(custom) = self.custom {
dictionary::search_custom(
custom,
stats,
data,
self.max_length,
self.dictionary_distance(),
self.max_distance,
out,
shallow,
);
return;
}
dictionary::search(
stats,
data,
self.max_length,
self.dictionary_distance(),
self.max_distance,
out,
shallow,
);
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum Sweep {
Partial,
Full,
SelfCleaning,
}
pub(crate) trait MatchRun {
const HASH_TYPE_LENGTH: usize;
const STORE_LOOKAHEAD: usize;
fn store(&mut self, data: &[u8], mask: usize, ix: usize);
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize);
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
);
}
impl<M: Matcher> MatchRun for &mut M {
const HASH_TYPE_LENGTH: usize = M::HASH_TYPE_LENGTH;
const STORE_LOOKAHEAD: usize = M::STORE_LOOKAHEAD;
#[inline(always)]
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
M::store(self, data, mask, ix);
}
#[inline(always)]
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
M::store_range(self, data, mask, start, end);
}
#[inline(always)]
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
M::find_longest_match(self, simd, stats, query, out);
}
}
pub(crate) trait RunVisitor {
type Output;
fn visit<R: MatchRun>(self, run: R) -> Self::Output;
}
pub(crate) trait Matcher {
const HASH_TYPE_LENGTH: usize;
const STORE_LOOKAHEAD: usize;
fn visit_run<V: RunVisitor>(&mut self, visitor: V) -> V::Output;
fn last_distances_to_check(&self) -> usize {
NUM_REMEMBERED_DISTANCES
}
fn prepare(&mut self, one_shot: bool, input_size: usize, data: &[u8], clear: bool) -> Sweep;
fn store(&mut self, data: &[u8], mask: usize, ix: usize);
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
for ix in start..end {
self.store(data, mask, ix);
}
}
fn stitch_to_previous_block(
&mut self,
num_bytes: usize,
position: usize,
data: &[u8],
mask: usize,
) {
if num_bytes >= Self::HASH_TYPE_LENGTH - 1 && position >= 3 {
self.store(data, mask, position - 3);
self.store(data, mask, position - 2);
self.store(data, mask, position - 1);
}
}
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
);
}
#[derive(Default)]
struct SmallSlots {
entries: Vec<u64>,
count: usize,
}
impl SmallSlots {
const EMPTY: u64 = u64::MAX;
#[inline(always)]
fn find(&self, key: usize) -> usize {
let mask = self.entries.len().wrapping_sub(1);
let mut slot = key & mask;
loop {
let entry = self.entries[slot & mask];
if entry == Self::EMPTY || (entry >> 32) as usize == key {
return slot & mask;
}
slot = slot.wrapping_add(1);
}
}
#[inline(always)]
fn read(&self, key: usize) -> u32 {
if self.entries.is_empty() {
return 0;
}
let entry = self.entries[self.find(key) & (self.entries.len() - 1)];
if entry == Self::EMPTY {
0
} else {
entry as u32
}
}
#[inline(always)]
fn write(&mut self, key: usize, value: u32) {
if 2 * (self.count + 1) > self.entries.len() {
self.grow();
}
let slot = self.find(key) & (self.entries.len() - 1);
let entry = &mut self.entries[slot];
self.count += usize::from(*entry == Self::EMPTY);
*entry = ((key as u64) << 32) | u64::from(value);
}
fn grow(&mut self) {
let size = (self.entries.len() * 2).max(32);
let previous = std::mem::replace(&mut self.entries, vec![Self::EMPTY; size]);
self.count = 0;
for entry in previous {
if entry != Self::EMPTY {
self.write((entry >> 32) as usize, entry as u32);
}
}
}
fn reset(&mut self, input_size: usize) {
let size = (2 * input_size).next_power_of_two().max(32);
if self.entries.len() < size {
self.entries = vec![Self::EMPTY; size];
} else {
self.entries.fill(Self::EMPTY);
}
self.count = 0;
}
}
pub(crate) trait QuickSlots {
fn read(&self, slot: usize) -> u32;
fn write(&mut self, slot: usize, value: u32);
}
impl<const N: usize> QuickSlots for &mut [u32; N] {
#[inline(always)]
fn read(&self, slot: usize) -> u32 {
self[slot & (N - 1)]
}
#[inline(always)]
fn write(&mut self, slot: usize, value: u32) {
self[slot & (N - 1)] = value;
}
}
impl QuickSlots for &mut SmallSlots {
#[inline(always)]
fn read(&self, slot: usize) -> u32 {
SmallSlots::read(self, slot)
}
#[inline(always)]
fn write(&mut self, slot: usize, value: u32) {
SmallSlots::write(self, slot, value);
}
}
pub(crate) struct QuickMatcher<
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
const COMPACT: bool = false,
> {
buckets: Option<Box<[u32; BUCKETS]>>,
compact: SmallSlots,
}
impl<
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
const COMPACT: bool,
> QuickMatcher<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY, COMPACT>
{
const BUCKET_BITS: u32 = BUCKETS.trailing_zeros();
const BUCKET_MASK: usize = BUCKETS - 1;
const SWEEP: usize = 1usize << SWEEP_BITS;
const SWEEP_MASK: usize = (Self::SWEEP - 1) << 3;
pub(crate) fn retained_bytes(&self) -> usize {
self.buckets.as_ref().map_or(0, |table| table.len()) * size_of::<u32>()
+ self.compact.entries.capacity() * size_of::<u64>()
}
pub(crate) fn new() -> Self {
Self {
buckets: if COMPACT { None } else { Self::table() },
compact: SmallSlots::default(),
}
}
fn table() -> Option<Box<[u32; BUCKETS]>> {
vec![0u32; BUCKETS].into_boxed_slice().try_into().ok()
}
#[inline(always)]
fn hash(data: &[u8], offset: usize) -> usize {
let value = read_u64(data, offset) << (64 - 8 * HASH_LEN as u64);
(value.wrapping_mul(HASH_MUL64) >> (64 - Self::BUCKET_BITS)) as usize
}
}
type QuickShape<
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
> = QuickMatcher<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>;
pub(crate) struct QuickRun<
T: QuickSlots,
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
> {
slots: T,
}
impl<
T: QuickSlots,
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
> QuickRun<T, BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>
{
#[inline(always)]
const fn slot_of(key: usize, ix: usize) -> usize {
if QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP == 1 {
key
} else {
(key + (ix & QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP_MASK))
& QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::BUCKET_MASK
}
}
}
impl<
T: QuickSlots,
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
> MatchRun for QuickRun<T, BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>
{
const HASH_TYPE_LENGTH: usize = 8;
const STORE_LOOKAHEAD: usize = 8;
#[inline(always)]
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
let key =
QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::hash(data, ix & mask);
self.slots.write(Self::slot_of(key, ix), ix as u32);
}
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
for ix in start..end {
self.store(data, mask, ix);
}
}
#[inline(always)]
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
let data = query.data;
let slots = &mut self.slots;
let cur_ix_masked = query.cur_ix & query.mask;
let cur = || current_window(data, cur_ix_masked, query.max_length);
let best_len_in = out.len;
let mut compare_char = read_u8(data, cur_ix_masked + best_len_in);
let key =
QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::hash(data, cur_ix_masked);
let min_score = out.score;
let mut best_score = out.score;
let mut best_len = best_len_in;
out.len_code_delta = 0;
let cached_backward = query.cache[0] as usize;
let prev_ix = query.cur_ix.wrapping_sub(cached_backward);
if prev_ix < query.cur_ix {
let prev_ix = prev_ix & query.mask;
if compare_char == read_u8(data, prev_ix + best_len) {
let len = match_len_at(simd, data, prev_ix, cur());
if len >= 4 {
let score = backward_reference_score_using_last_distance(len);
if best_score < score {
out.len = len;
out.distance = cached_backward;
out.score = score;
if QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP == 1 {
slots.write(key, query.cur_ix as u32);
return;
}
best_len = len;
best_score = score;
compare_char = read_u8(data, cur_ix_masked + len);
}
}
}
}
if QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP == 1 {
let prev_ix = slots.read(key) as usize;
slots.write(key, query.cur_ix as u32);
let backward = query.cur_ix.wrapping_sub(prev_ix);
let prev_ix = prev_ix & query.mask;
if compare_char != read_u8(data, prev_ix + best_len_in) {
return;
}
if backward == 0 || backward > query.max_backward {
return;
}
let len = match_len_at(simd, data, prev_ix, cur());
if len >= 4 {
let score = backward_reference_score(len, backward);
if best_score < score {
out.len = len;
out.distance = backward;
out.score = score;
return;
}
}
} else {
for sweep in 0..QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP {
let slot = (key + (sweep << 3))
& QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::BUCKET_MASK;
let prev_ix = slots.read(slot) as usize;
let backward = query.cur_ix.wrapping_sub(prev_ix);
let prev_ix = prev_ix & query.mask;
if compare_char != read_u8(data, prev_ix + best_len) {
continue;
}
if backward == 0 || backward > query.max_backward {
continue;
}
let len = match_len_at(simd, data, prev_ix, cur());
if len >= 4 {
let score = backward_reference_score(len, backward);
if best_score < score {
best_len = len;
out.len = len;
compare_char = read_u8(data, cur_ix_masked + len);
best_score = score;
out.score = score;
out.distance = backward;
}
}
}
}
if USE_DICTIONARY && min_score == out.score {
query.search_dictionary(stats, out, true);
}
if QuickShape::<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY>::SWEEP != 1 {
slots.write(Self::slot_of(key, query.cur_ix), query.cur_ix as u32);
}
}
}
impl<
const BUCKETS: usize,
const SWEEP_BITS: u32,
const HASH_LEN: u32,
const USE_DICTIONARY: bool,
const COMPACT: bool,
> Matcher for QuickMatcher<BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY, COMPACT>
{
const HASH_TYPE_LENGTH: usize = 8;
const STORE_LOOKAHEAD: usize = 8;
fn visit_run<V: RunVisitor>(&mut self, visitor: V) -> V::Output {
match &mut self.buckets {
Some(table) => {
visitor.visit(
QuickRun::<_, BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY> {
slots: &mut **table,
},
)
}
None => visitor.visit(
QuickRun::<_, BUCKETS, SWEEP_BITS, HASH_LEN, USE_DICTIONARY> {
slots: &mut self.compact,
},
),
}
}
fn prepare(&mut self, one_shot: bool, input_size: usize, data: &[u8], clear: bool) -> Sweep {
let partial_prepare_threshold = BUCKETS >> 5;
let partial = if one_shot && input_size <= partial_prepare_threshold {
Sweep::Partial
} else {
Sweep::Full
};
let Some(table) = &mut self.buckets else {
if clear {
self.buckets = Self::table();
if self.buckets.is_some() {
self.compact = SmallSlots::default();
return partial;
}
}
if clear || self.compact.entries.is_empty() {
self.compact.reset(input_size);
}
return partial;
};
if !clear {
return partial;
}
if partial == Sweep::Partial {
for offset in 0..input_size {
let key = Self::hash(data, offset);
if Self::SWEEP == 1 {
table[key & Self::BUCKET_MASK] = 0;
} else {
for sweep in 0..Self::SWEEP {
table[(key + (sweep << 3)) & Self::BUCKET_MASK] = 0;
}
}
}
} else {
table.fill(0);
}
partial
}
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
self.visit_run(Store {
data,
mask,
start: ix,
end: ix + 1,
});
}
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
self.visit_run(Store {
data,
mask,
start,
end,
});
}
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
self.visit_run(Search {
simd,
stats,
query,
out,
});
}
}
const COMPACT_INPUT_LIMIT: usize = 1024;
const STARTER_SLOTS: usize = 4;
const COUNT_BITS: u32 = 16;
const OFFSET_BITS: u32 = 25;
const STARTER_FLAG: u32 = 1 << (OFFSET_BITS - 1);
const OFFSET_MASK: u64 = (1 << OFFSET_BITS) - 1;
const GENERATION_SHIFT: u32 = COUNT_BITS + OFFSET_BITS;
const MAX_GENERATION: u64 = (1 << (u64::BITS - GENERATION_SHIFT)) - 1;
#[derive(Default)]
struct KeyMap {
entries: Vec<u64>,
count: usize,
}
impl KeyMap {
const EMPTY: u64 = u64::MAX;
#[inline(always)]
fn find(&self, key: usize) -> (usize, u64) {
let mask = self.entries.len().wrapping_sub(1);
let mut slot = key & mask;
loop {
let entry = self.entries[slot & mask];
if entry == Self::EMPTY || (entry >> 48) as usize == key {
return (slot & mask, entry);
}
slot = slot.wrapping_add(1);
}
}
#[inline(always)]
const fn decode(entry: u64) -> (u16, u32) {
if entry == Self::EMPTY {
(0, 0)
} else {
((entry >> 32) as u16, entry as u32)
}
}
#[inline(always)]
fn slot_for_write(&mut self, key: usize) -> (usize, u16, u32) {
if 2 * (self.count + 1) > self.entries.len() {
self.grow();
}
let (slot, entry) = self.find(key);
let (count, offset) = Self::decode(entry);
(slot, count, offset)
}
#[inline(always)]
fn write_slot(&mut self, slot: usize, key: usize, count: u16, offset: u32) {
let mask = self.entries.len().wrapping_sub(1);
let entry = &mut self.entries[slot & mask];
self.count += usize::from(*entry == Self::EMPTY);
*entry = ((key as u64) << 48) | (u64::from(count) << 32) | u64::from(offset);
}
#[inline(always)]
fn get(&self, key: usize) -> (u16, u32) {
if self.entries.is_empty() {
return (0, 0);
}
Self::decode(self.find(key).1)
}
#[inline(always)]
fn set(&mut self, key: usize, count: u16, offset: u32) {
let (slot, _, _) = self.slot_for_write(key);
self.write_slot(slot, key, count, offset);
}
fn grow(&mut self) {
let size = (self.entries.len() * 2).max(64);
let previous = std::mem::replace(&mut self.entries, vec![Self::EMPTY; size]);
self.count = 0;
for entry in previous {
if entry != Self::EMPTY {
self.set((entry >> 48) as usize, (entry >> 32) as u16, entry as u32);
}
}
}
fn reset(&mut self, input_size: usize) {
let size = (2 * input_size).next_power_of_two().max(64);
if self.entries.len() < size {
self.entries = vec![Self::EMPTY; size];
} else {
self.entries.fill(Self::EMPTY);
}
self.count = 0;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Layout {
Compact,
Sparse,
Dense,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum BlockRef {
Starter(usize),
Full(usize),
}
const fn last_distances_for(block: usize) -> usize {
if block <= 32 {
4
} else if block <= 128 {
10
} else {
16
}
}
pub(crate) struct BucketMatcher<const HASH64: bool, const BUCKETS: usize, const BLOCK: usize> {
layout: Layout,
num: Vec<u16>,
dense: Vec<u32>,
dense_tags: Vec<u8>,
entries: Option<Box<[u64; BUCKETS]>>,
generation: u64,
compact: KeyMap,
blocks: Vec<[u32; BLOCK]>,
block_tags: Vec<[u8; BLOCK]>,
starters: Vec<[u32; STARTER_SLOTS]>,
starter_tags: Vec<[u8; STARTER_SLOTS]>,
size_hint: usize,
}
impl<const HASH64: bool, const BUCKETS: usize, const BLOCK: usize>
BucketMatcher<HASH64, BUCKETS, BLOCK>
{
const BUCKET_BITS: u32 = BUCKETS.trailing_zeros();
const TAGGED: bool = BLOCK <= 32;
const LAST_DISTANCES: usize = last_distances_for(BLOCK);
pub(crate) fn retained_bytes(&self) -> usize {
self.num.capacity() * size_of::<u16>()
+ self.dense.capacity() * size_of::<u32>()
+ self.blocks.capacity() * size_of::<[u32; BLOCK]>()
+ (self.entries.as_ref().map_or(0, |entries| entries.len())
+ self.compact.entries.capacity())
* size_of::<u64>()
+ self.dense_tags.capacity()
+ self.block_tags.capacity() * size_of::<[u8; BLOCK]>()
+ self.starters.capacity() * size_of::<[u32; STARTER_SLOTS]>()
+ self.starter_tags.capacity() * size_of::<[u8; STARTER_SLOTS]>()
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn new(size_hint: usize) -> Self {
Self {
layout: Layout::Sparse,
num: Vec::new(),
dense: Vec::new(),
dense_tags: Vec::new(),
entries: None,
generation: 1,
compact: KeyMap::default(),
blocks: Vec::new(),
block_tags: Vec::new(),
starters: Vec::new(),
starter_tags: Vec::new(),
size_hint,
}
}
const fn dense_limit() -> usize {
let table_bytes = BUCKETS * BLOCK * size_of::<u32>();
if Self::TAGGED {
table_bytes / 16
} else {
table_bytes / 2
}
}
#[inline(always)]
fn hash_with_tag(data: &[u8], offset: usize) -> usize {
if HASH64 {
let hash_mul = HASH_MUL64 << (64 - 5 * 8);
(read_u64(data, offset).wrapping_mul(hash_mul) >> (64 - 15 - 8)) as usize
} else {
(read_u32(data, offset).wrapping_mul(HASH_MUL32) >> (32 - Self::BUCKET_BITS - 8))
as usize
}
}
#[inline(always)]
const fn decode_entry(&self, entry: u64) -> (u16, u32) {
let offset = ((entry >> COUNT_BITS) & OFFSET_MASK) as u32;
let count = if entry >> GENERATION_SHIFT == self.generation {
entry as u16
} else {
0
};
(count, offset)
}
#[inline(always)]
const fn encode_entry(&self, count: u16, offset: u32) -> u64 {
(self.generation << GENERATION_SHIFT) | ((offset as u64) << COUNT_BITS) | count as u64
}
#[inline(always)]
fn entry<const COMPACT: bool>(&self, key: usize) -> (u16, u32) {
if COMPACT {
return self.compact.get(key);
}
match &self.entries {
Some(entries) => self.decode_entry(entries[key & (BUCKETS - 1)]),
None => (0, 0),
}
}
#[inline(always)]
const fn block(offset: u32) -> Option<BlockRef> {
if offset == 0 {
return None;
}
let index = ((offset & !STARTER_FLAG) - 1) as usize;
if offset & STARTER_FLAG != 0 {
Some(BlockRef::Starter(index))
} else {
Some(BlockRef::Full(index))
}
}
#[inline(always)]
fn block_for_store(&mut self, count: u16, offset: u32) -> (BlockRef, u32) {
match Self::block(offset) {
None => {
let index = self.starters.len();
self.starters.push([0; STARTER_SLOTS]);
if Self::TAGGED {
self.starter_tags.push([0; STARTER_SLOTS]);
}
(BlockRef::Starter(index), (index as u32 + 1) | STARTER_FLAG)
}
Some(BlockRef::Starter(index)) if usize::from(count) < STARTER_SLOTS => {
(BlockRef::Starter(index), offset)
}
Some(BlockRef::Starter(index)) => {
let mut block = [0u32; BLOCK];
if let Some(starter) = self.starters.get(index)
&& let Some(top) = block.last_chunk_mut::<STARTER_SLOTS>()
{
*top = *starter;
}
let full = self.blocks.len();
self.blocks.push(block);
if Self::TAGGED {
let mut tags = [0u8; BLOCK];
if let Some(starter) = self.starter_tags.get(index)
&& let Some(top) = tags.last_chunk_mut::<STARTER_SLOTS>()
{
*top = *starter;
}
self.block_tags.push(tags);
}
(BlockRef::Full(full), full as u32 + 1)
}
Some(BlockRef::Full(index)) => (BlockRef::Full(index), offset),
}
}
#[inline(always)]
fn push<const COMPACT: bool>(&mut self, key: usize, ix: u32, tag: u8) {
if COMPACT {
let (slot, count, offset) = self.compact.slot_for_write(key);
self.push_found::<COMPACT>(key, slot, count, offset, ix, tag);
return;
}
let key = key & (BUCKETS - 1);
let Some(entries) = &self.entries else {
return;
};
let (count, offset) = self.decode_entry(entries[key]);
self.push_found::<COMPACT>(key, key, count, offset, ix, tag);
}
#[inline(always)]
fn push_found<const COMPACT: bool>(
&mut self,
key: usize,
slot: usize,
count: u16,
offset: u32,
ix: u32,
tag: u8,
) {
let offset = self.push_block(count, offset, ix, tag);
if COMPACT {
self.compact
.write_slot(slot, key, count.wrapping_add(1), offset);
return;
}
let entry = self.encode_entry(count.wrapping_add(1), offset);
if let Some(entries) = &mut self.entries {
entries[key & (BUCKETS - 1)] = entry;
}
}
#[inline(always)]
fn push_block(&mut self, count: u16, offset: u32, ix: u32, tag: u8) -> u32 {
let (block, offset) = self.block_for_store(count, offset);
match block {
BlockRef::Starter(index) => {
let slot = !usize::from(count) & (STARTER_SLOTS - 1);
if let Some(block) = self.starters.get_mut(index) {
block[slot] = ix;
}
if Self::TAGGED
&& let Some(tags) = self.starter_tags.get_mut(index)
{
tags[slot] = tag;
}
}
BlockRef::Full(index) => {
let slot = !usize::from(count) & (BLOCK - 1);
if let Some(block) = self.blocks.get_mut(index) {
block[slot] = ix;
}
if Self::TAGGED
&& let Some(tags) = self.block_tags.get_mut(index)
{
tags[slot] = tag;
}
}
}
offset
}
fn select_layout(&mut self, one_shot: bool, input_size: usize) {
if one_shot && input_size <= COMPACT_INPUT_LIMIT && self.entries.is_none() {
self.layout = Layout::Compact;
self.compact.reset(input_size);
self.clear_blocks();
self.starters.reserve(input_size);
self.blocks.reserve(input_size / STARTER_SLOTS / 4);
if Self::TAGGED {
self.starter_tags.reserve(input_size);
self.block_tags.reserve(input_size / STARTER_SLOTS / 4);
}
} else if self.size_hint < Self::dense_limit() && self.dense.is_empty() {
match &mut self.entries {
None => {
self.entries = vec![0u64; BUCKETS].into_boxed_slice().try_into().ok();
self.generation = 1;
self.clear_blocks();
}
Some(entries)
if self.layout != Layout::Sparse || self.generation == MAX_GENERATION =>
{
entries.fill(0);
self.generation = 1;
self.clear_blocks();
}
Some(_) => self.generation += 1,
}
self.layout = Layout::Sparse;
} else {
if self.dense.is_empty() {
self.num = vec![0; BUCKETS];
self.dense = vec![0; BUCKETS * BLOCK];
if Self::TAGGED {
self.dense_tags = vec![0; BUCKETS * BLOCK];
}
} else {
self.num.fill(0);
}
self.layout = Layout::Dense;
}
}
fn clear_blocks(&mut self) {
self.blocks.clear();
self.block_tags.clear();
self.starters.clear();
self.starter_tags.clear();
}
fn dense_run(&mut self) -> Option<DenseRun<'_, HASH64, BUCKETS, BLOCK>> {
if self.layout != Layout::Dense {
return None;
}
let num = self.num.first_chunk_mut::<BUCKETS>()?;
let dense = self
.dense
.as_chunks_mut::<BLOCK>()
.0
.first_chunk_mut::<BUCKETS>()?;
let tags = if Self::TAGGED {
Some(
self.dense_tags
.as_chunks_mut::<BLOCK>()
.0
.first_chunk_mut::<BUCKETS>()?,
)
} else {
None
};
Some(DenseRun { num, dense, tags })
}
#[inline(always)]
fn search_on_demand<S: Simd, const COMPACT: bool>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
let cur_ix_masked = query.cur_ix & query.mask;
let min_score = out.score;
let hash = Self::hash_with_tag(query.data, cur_ix_masked);
let key = hash >> 8;
let tag = hash as u8;
let (slot, count, offset) = if COMPACT {
self.compact.slot_for_write(key)
} else {
let (count, offset) = self.entry::<COMPACT>(key);
(key, count, offset)
};
match Self::block(offset) {
Some(BlockRef::Full(index)) => {
let bucket = self.blocks.get(index).unwrap_or(&[0; BLOCK]);
let tags = if Self::TAGGED {
self.block_tags.get(index)
} else {
None
};
search_bucket::<S, HASH64, BLOCK, BLOCK>(
simd, &query, out, tag, count, bucket, tags,
);
}
Some(BlockRef::Starter(index)) => {
let bucket = self.starters.get(index).unwrap_or(&[0; STARTER_SLOTS]);
search_bucket::<S, HASH64, STARTER_SLOTS, BLOCK>(
simd, &query, out, tag, count, bucket, None,
);
}
None => {
search_bucket::<S, HASH64, STARTER_SLOTS, BLOCK>(
simd,
&query,
out,
tag,
0,
&[0; STARTER_SLOTS],
None,
);
}
}
self.push_found::<COMPACT>(key, slot, count, offset, query.cur_ix as u32, tag);
if min_score == out.score {
query.search_dictionary(stats, out, false);
}
}
}
#[inline(always)]
fn dense_store<const BUCKETS: usize, const BLOCK: usize>(
num: &mut [u16; BUCKETS],
dense: &mut [[u32; BLOCK]; BUCKETS],
tags: Option<&mut [[u8; BLOCK]; BUCKETS]>,
key: usize,
ix: u32,
tag: u8,
) {
let key = key & (BUCKETS - 1);
let count = &mut num[key];
let current = *count;
*count = current.wrapping_add(1);
let slot = !usize::from(current) & (BLOCK - 1);
dense[key][slot] = ix;
if let Some(tags) = tags {
tags[key][slot] = tag;
}
}
#[inline(always)]
fn split_candidates(equal: u32, bits: u32, newest: u32, available: u32) -> (u32, u32) {
let lanes = u32::MAX.checked_shr(32 - bits).unwrap_or(0);
let filled = u32::MAX.checked_shr(32 - available).unwrap_or(0);
let allowed = ((filled << newest) | filled.checked_shr(bits - newest).unwrap_or(0)) & lanes;
let candidates = equal & allowed;
let above = u32::MAX << newest;
(candidates & above, candidates & !above)
}
#[inline(always)]
fn tag_equality<S: Simd, const N: usize>(simd: S, tags: &[u8; N], tag: u8) -> u32 {
if matches!(simd.level(), Level::Fallback(_)) {
return u32::MAX;
}
if N == 32
&& let Some(bytes) = tags.first_chunk::<32>()
{
return u8x32::load_array_ref(simd, bytes)
.simd_eq(u8x32::splat(simd, tag))
.to_bitmask() as u32;
}
if N == 16
&& let Some(bytes) = tags.first_chunk::<16>()
{
return u32::from(
u8x16::load_array_ref(simd, bytes)
.simd_eq(u8x16::splat(simd, tag))
.to_bitmask() as u16,
);
}
u32::MAX
}
#[derive(Copy, Clone)]
struct Best {
len: u32,
word: u32,
}
#[derive(Copy, Clone)]
struct Found {
len: usize,
distance: usize,
score: usize,
}
#[inline(never)]
fn accept_cached<S: Simd>(
simd: S,
data: &[u8],
cur_ix_masked: usize,
max_length: usize,
prev_ix: usize,
index: usize,
best_score: usize,
) -> Option<(usize, usize)> {
let len = match_len_at(
simd,
data,
prev_ix,
current_window(data, cur_ix_masked, max_length),
);
if len + usize::from(index < 2) >= 3 {
let mut score = backward_reference_score_using_last_distance(len);
if best_score < score {
if index != 0 {
score -= backward_reference_penalty_using_last_distance(index);
}
if best_score < score {
return Some((len, score));
}
}
}
None
}
#[inline(never)]
fn accept_candidate<S: Simd, const HASH64: bool>(
simd: S,
data: &[u8],
cur_ix_masked: usize,
max_length: usize,
prev_ix: usize,
backward: usize,
best_score: usize,
) -> Option<(Best, usize)> {
let cur = current_window(data, cur_ix_masked, max_length);
let left = data.get(prev_ix..prev_ix + cur.len())?;
let len = if HASH64 {
if left.first_chunk::<4>() != cur.first_chunk::<4>() {
return None;
}
match (left.get(4..), cur.get(4..)) {
(Some(left), Some(cur)) => match_len_windows(simd, left, cur) + 4,
_ => return None,
}
} else {
let len = match_len_windows(simd, left, cur);
if len < 4 {
return None;
}
len
};
let score = backward_reference_score(len, backward);
if best_score < score {
let best = Best {
len: len as u32,
word: read_u32(data, cur_ix_masked + len - 3),
};
return Some((best, score));
}
None
}
#[derive(Copy, Clone)]
struct BucketScan<'a, S> {
simd: S,
data: &'a [u8],
window: &'a [u8],
cur_ix: usize,
mask: usize,
max_backward: usize,
max_length: usize,
}
#[inline(always)]
fn consider<S: Simd, const HASH64: bool>(
scan: BucketScan<'_, S>,
prev_ix: u32,
best: &mut Best,
found: &mut Found,
) -> bool {
let backward = scan.cur_ix.wrapping_sub(prev_ix as usize);
if backward > scan.max_backward {
return false;
}
let prev_ix = prev_ix as usize & scan.mask;
let start = prev_ix + best.len.wrapping_sub(3) as usize;
let Some(word) = scan.window.get(start..start + 4) else {
return true;
};
if best.word != u32::from_le_bytes(word.try_into().unwrap_or([0; 4])) {
return true;
}
if let Some((accepted, score)) = accept_candidate::<S, HASH64>(
scan.simd,
scan.data,
scan.cur_ix & scan.mask,
scan.max_length,
prev_ix,
backward,
found.score,
) {
*best = accepted;
*found = Found {
len: accepted.len as usize,
distance: backward,
score,
};
}
true
}
#[inline(always)]
fn scan_slots<S: Simd, const HASH64: bool, const N: usize>(
scan: BucketScan<'_, S>,
bucket: &[u32; N],
mut slots: u32,
best: &mut Best,
found: &mut Found,
) -> bool {
while slots != 0 {
let slot = slots.trailing_zeros() as usize;
slots &= slots - 1;
let Some(&prev_ix) = bucket.get(slot) else {
return false;
};
if !consider::<S, HASH64>(scan, prev_ix, best, found) {
return false;
}
}
true
}
#[inline(always)]
fn search_bucket<S: Simd, const HASH64: bool, const N: usize, const BLOCK: usize>(
simd: S,
query: &MatchQuery<'_>,
out: &mut SearchResult,
tag: u8,
count: u16,
bucket: &[u32; N],
tags: Option<&[u8; N]>,
) {
let data = query.data;
let window = query.window;
let mask = query.mask;
let cur_ix = query.cur_ix;
let cur_ix_masked = cur_ix & mask;
let max_backward = query.max_backward;
let max_length = query.max_length;
let mut best_len = out.len;
let mut found = Found {
len: 0,
distance: 0,
score: out.score,
};
let available = usize::from(count).min(N);
let newest = !usize::from(count.wrapping_sub(1)) & (N - 1);
let equal = match tags {
Some(tags) if available != 0 => tag_equality::<S, N>(simd, tags, tag),
_ => u32::MAX,
};
for index in 0..last_distances_for(BLOCK).min(NUM_DISTANCE_SHORT_CODES) {
let backward = query.cache[index] as usize;
let prev_ix = cur_ix.wrapping_sub(backward);
if prev_ix >= cur_ix || backward > max_backward {
continue;
}
let prev_ix = prev_ix & mask;
let cur_at = cur_ix_masked + best_len;
if cur_at >= window.len() {
break;
}
let prev_at = prev_ix + best_len;
if prev_at >= window.len() || window[cur_at] != window[prev_at] {
continue;
}
if let Some((len, score)) = accept_cached(
simd,
data,
cur_ix_masked,
max_length,
prev_ix,
index,
found.score,
) {
best_len = len;
found = Found {
len,
distance: backward,
score,
};
}
}
if best_len < 3 {
best_len = 3;
}
if available != 0 {
scan_bucket::<S, HASH64, N>(
BucketScan {
simd,
data,
window,
cur_ix,
mask,
max_backward,
max_length,
},
bucket,
equal,
newest,
available,
best_len,
&mut found,
);
}
out.len = found.len;
out.len_code_delta = 0;
if found.len != 0 {
out.distance = found.distance;
out.score = found.score;
}
}
#[inline(always)]
fn scan_bucket<S: Simd, const HASH64: bool, const N: usize>(
scan: BucketScan<'_, S>,
bucket: &[u32; N],
equal: u32,
newest: usize,
available: usize,
best_len: usize,
found: &mut Found,
) {
let data = scan.data;
let cur_ix_masked = scan.cur_ix & scan.mask;
let mut best = Best {
len: best_len as u32,
word: read_u32(data, cur_ix_masked + best_len - 3),
};
if N <= 32 {
let (above, below) = split_candidates(equal, N as u32, newest as u32, available as u32);
if scan_slots::<S, HASH64, N>(scan, bucket, above, &mut best, found) {
scan_slots::<S, HASH64, N>(scan, bucket, below, &mut best, found);
}
} else {
let mut slot = newest;
let mut remaining = available;
while remaining != 0 {
let prev_ix = bucket[slot & (N - 1)];
if !consider::<S, HASH64>(scan, prev_ix, &mut best, found) {
break;
}
slot = (slot + 1) & (N - 1);
remaining -= 1;
}
}
}
pub(crate) struct DenseRun<'a, const HASH64: bool, const BUCKETS: usize, const BLOCK: usize> {
num: &'a mut [u16; BUCKETS],
dense: &'a mut [[u32; BLOCK]; BUCKETS],
tags: Option<&'a mut [[u8; BLOCK]; BUCKETS]>,
}
pub(crate) struct OnDemandRun<
'a,
const HASH64: bool,
const BUCKETS: usize,
const BLOCK: usize,
const COMPACT: bool,
>(&'a mut BucketMatcher<HASH64, BUCKETS, BLOCK>);
impl<const HASH64: bool, const BUCKETS: usize, const BLOCK: usize, const COMPACT: bool> MatchRun
for OnDemandRun<'_, HASH64, BUCKETS, BLOCK, COMPACT>
{
const HASH_TYPE_LENGTH: usize = if HASH64 { 8 } else { 4 };
const STORE_LOOKAHEAD: usize = Self::HASH_TYPE_LENGTH;
#[inline(always)]
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
let hash = BucketMatcher::<HASH64, BUCKETS, BLOCK>::hash_with_tag(data, ix & mask);
self.0.push::<COMPACT>(hash >> 8, ix as u32, hash as u8);
}
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
for ix in start..end {
self.store(data, mask, ix);
}
}
#[inline(always)]
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
self.0
.search_on_demand::<S, COMPACT>(simd, stats, query, out);
}
}
impl<const HASH64: bool, const BUCKETS: usize, const BLOCK: usize> MatchRun
for DenseRun<'_, HASH64, BUCKETS, BLOCK>
{
const HASH_TYPE_LENGTH: usize = if HASH64 { 8 } else { 4 };
const STORE_LOOKAHEAD: usize = Self::HASH_TYPE_LENGTH;
#[inline(always)]
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
let hash = BucketMatcher::<HASH64, BUCKETS, BLOCK>::hash_with_tag(data, ix & mask);
dense_store(
self.num,
self.dense,
self.tags.as_deref_mut(),
hash >> 8,
ix as u32,
hash as u8,
);
}
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
for ix in start..end {
self.store(data, mask, ix);
}
}
#[inline(always)]
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
let cur_ix_masked = query.cur_ix & query.mask;
let min_score = out.score;
let hash =
BucketMatcher::<HASH64, BUCKETS, BLOCK>::hash_with_tag(query.data, cur_ix_masked);
let key = (hash >> 8) & (BUCKETS - 1);
let tag = hash as u8;
let count = self.num[key];
let bucket = &self.dense[key];
let tags = self.tags.as_deref().map(|tags| &tags[key]);
search_bucket::<S, HASH64, BLOCK, BLOCK>(simd, &query, out, tag, count, bucket, tags);
dense_store(
self.num,
self.dense,
self.tags.as_deref_mut(),
key,
query.cur_ix as u32,
tag,
);
if min_score == out.score {
query.search_dictionary(stats, out, false);
}
}
}
impl<const HASH64: bool, const BUCKETS: usize, const BLOCK: usize> Matcher
for BucketMatcher<HASH64, BUCKETS, BLOCK>
{
const HASH_TYPE_LENGTH: usize = if HASH64 { 8 } else { 4 };
const STORE_LOOKAHEAD: usize = Self::HASH_TYPE_LENGTH;
fn visit_run<V: RunVisitor>(&mut self, visitor: V) -> V::Output {
if self.layout == Layout::Dense
&& let Some(run) = self.dense_run()
{
return visitor.visit(run);
}
if self.layout == Layout::Compact {
visitor.visit(OnDemandRun::<HASH64, BUCKETS, BLOCK, true>(self))
} else {
visitor.visit(OnDemandRun::<HASH64, BUCKETS, BLOCK, false>(self))
}
}
fn last_distances_to_check(&self) -> usize {
Self::LAST_DISTANCES
}
fn prepare(&mut self, one_shot: bool, input_size: usize, _data: &[u8], _clear: bool) -> Sweep {
self.select_layout(one_shot, input_size);
Sweep::SelfCleaning
}
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
self.visit_run(Store {
data,
mask,
start: ix,
end: ix + 1,
});
}
fn store_range(&mut self, data: &[u8], mask: usize, start: usize, end: usize) {
self.visit_run(Store {
data,
mask,
start,
end,
});
}
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
self.visit_run(Search {
simd,
stats,
query,
out,
});
}
}
struct Store<'a> {
data: &'a [u8],
mask: usize,
start: usize,
end: usize,
}
impl RunVisitor for Store<'_> {
type Output = ();
fn visit<R: MatchRun>(self, mut run: R) {
run.store_range(self.data, self.mask, self.start, self.end);
}
}
struct Search<'a, S> {
simd: S,
stats: &'a mut DictionaryStats,
query: MatchQuery<'a>,
out: &'a mut SearchResult,
}
impl<S: Simd> RunVisitor for Search<'_, S> {
type Output = ();
fn visit<R: MatchRun>(self, mut run: R) {
run.find_longest_match(self.simd, self.stats, self.query, self.out);
}
}
const CHAIN_BUCKET_BITS: u32 = 15;
const CHAIN_BUCKET_SIZE: usize = 1 << CHAIN_BUCKET_BITS;
const CHAIN_EMPTY_ADDR: u32 = 0xCCCC_CCCC;
const CHAIN_EMPTY_HEAD: u16 = 0xCCCC;
#[derive(Copy, Clone, Debug, Default)]
struct ChainSlot {
delta: u16,
next: u16,
}
pub(crate) struct ChainMatcher<const NUM_BANKS: usize, const BANK_BITS: u32> {
addr: Vec<u32>,
head: Vec<u16>,
tiny_hash: Vec<u8>,
slots: Vec<ChainSlot>,
bank_offsets: Vec<u32>,
free_slot_idx: Vec<u16>,
last_distances: usize,
max_hops: usize,
}
impl<const NUM_BANKS: usize, const BANK_BITS: u32> ChainMatcher<NUM_BANKS, BANK_BITS> {
const BANK_SIZE: usize = 1usize << BANK_BITS;
const BANK_MASK: usize = Self::BANK_SIZE - 1;
const BANK_SELECT: usize = NUM_BANKS - 1;
pub(crate) fn retained_bytes(&self) -> usize {
self.addr.capacity() * size_of::<u32>()
+ self.head.capacity() * size_of::<u16>()
+ self.tiny_hash.capacity()
+ self.slots.capacity() * size_of::<ChainSlot>()
+ self.bank_offsets.capacity() * size_of::<u32>()
+ self.free_slot_idx.capacity() * size_of::<u16>()
}
pub(crate) fn new(shape: ChainShape) -> Self {
debug_assert_eq!(shape.num_banks, NUM_BANKS);
debug_assert_eq!(shape.bank_bits, BANK_BITS);
Self {
addr: vec![CHAIN_EMPTY_ADDR; CHAIN_BUCKET_SIZE],
head: vec![0u16; CHAIN_BUCKET_SIZE],
tiny_hash: vec![0u8; 1 << 16],
slots: Vec::new(),
bank_offsets: vec![0; NUM_BANKS],
free_slot_idx: vec![0u16; NUM_BANKS],
last_distances: shape.last_distances,
max_hops: shape.max_hops,
}
}
#[inline(always)]
fn hash(data: &[u8], offset: usize) -> usize {
(read_u32(data, offset).wrapping_mul(HASH_MUL32) >> (32 - CHAIN_BUCKET_BITS)) as usize
}
#[inline(always)]
fn activate_bank(&mut self, bank: usize) -> usize {
let offset = self.bank_offsets[bank];
if offset != 0 {
return (offset - 1) as usize;
}
let start = self.slots.len();
self.slots
.resize(start + Self::BANK_SIZE, ChainSlot::default());
self.bank_offsets[bank] = start as u32 + 1;
start
}
}
impl<const NUM_BANKS: usize, const BANK_BITS: u32> Matcher for ChainMatcher<NUM_BANKS, BANK_BITS> {
const HASH_TYPE_LENGTH: usize = 4;
const STORE_LOOKAHEAD: usize = 4;
fn visit_run<V: RunVisitor>(&mut self, visitor: V) -> V::Output {
visitor.visit(self)
}
fn last_distances_to_check(&self) -> usize {
self.last_distances
}
fn prepare(&mut self, one_shot: bool, input_size: usize, data: &[u8], clear: bool) -> Sweep {
let partial_prepare_threshold = CHAIN_BUCKET_SIZE >> 6;
let partial = if one_shot && input_size <= partial_prepare_threshold {
Sweep::Partial
} else {
Sweep::Full
};
if !clear {
return partial;
}
if partial == Sweep::Partial {
for offset in 0..input_size {
let bucket = Self::hash(data, offset);
if let Some(slot) = self.addr.get_mut(bucket) {
*slot = CHAIN_EMPTY_ADDR;
}
if let Some(slot) = self.head.get_mut(bucket) {
*slot = CHAIN_EMPTY_HEAD;
}
}
} else {
self.addr.fill(CHAIN_EMPTY_ADDR);
self.head.fill(0);
}
self.tiny_hash.fill(0);
self.free_slot_idx.fill(0);
partial
}
#[inline(always)]
fn store(&mut self, data: &[u8], mask: usize, ix: usize) {
let key = Self::hash(data, ix & mask);
let bank = key & Self::BANK_SELECT;
let bank_base = self.activate_bank(bank);
let free = self.free_slot_idx.get_mut(bank).map_or(0u16, |slot| {
let current = *slot;
*slot = current.wrapping_add(1);
current
});
let idx = usize::from(free) & Self::BANK_MASK;
let previous = self.addr.get(key).copied().unwrap_or(CHAIN_EMPTY_ADDR);
let delta = ix.wrapping_sub(previous as usize);
if let Some(slot) = self.tiny_hash.get_mut(ix as u16 as usize) {
*slot = key as u8;
}
let delta = if delta > 0xFFFF { 0xFFFF } else { delta as u16 };
let head = self.head.get(key).copied().unwrap_or(0);
if let Some(slot) = self.slots.get_mut(bank_base + idx) {
slot.delta = delta;
slot.next = head;
}
if let Some(slot) = self.addr.get_mut(key) {
*slot = ix as u32;
}
if let Some(slot) = self.head.get_mut(key) {
*slot = idx as u16;
}
}
#[inline(always)]
fn find_longest_match<S: Simd>(
&mut self,
simd: S,
stats: &mut DictionaryStats,
query: MatchQuery<'_>,
out: &mut SearchResult,
) {
let data = query.data;
let mask = query.mask;
let cur_ix_masked = query.cur_ix & mask;
let cur = || current_window(data, cur_ix_masked, query.max_length);
let min_score = out.score;
let mut best_score = out.score;
let mut best_len = out.len;
let key = Self::hash(data, cur_ix_masked);
let tiny_hash = key as u8;
out.len = 0;
out.len_code_delta = 0;
for index in 0..self.last_distances {
let backward = query.cache[index] as usize;
let prev_ix = query.cur_ix.wrapping_sub(backward);
if index > 0
&& self
.tiny_hash
.get(prev_ix as u16 as usize)
.copied()
.unwrap_or(0)
!= tiny_hash
{
continue;
}
if prev_ix >= query.cur_ix || backward > query.max_backward {
continue;
}
let prev_ix = prev_ix & mask;
let len = match_len_at(simd, data, prev_ix, cur());
if len >= 2 {
let mut score = backward_reference_score_using_last_distance(len);
if best_score < score {
if index != 0 {
score -= backward_reference_penalty_using_last_distance(index);
}
if best_score < score {
best_score = score;
best_len = len;
out.len = best_len;
out.distance = backward;
out.score = best_score;
}
}
}
}
if best_len < 3 {
best_len = 3;
}
let bank = key & Self::BANK_SELECT;
let bank_base = self.activate_bank(bank);
let mut backward = 0usize;
let mut delta = query
.cur_ix
.wrapping_sub(self.addr.get(key).copied().unwrap_or(CHAIN_EMPTY_ADDR) as usize);
let mut slot = usize::from(self.head.get(key).copied().unwrap_or(0));
for _ in 0..self.max_hops {
let last = slot;
backward = backward.wrapping_add(delta);
if backward > query.max_backward {
break;
}
let prev_ix = (query.cur_ix.wrapping_sub(backward)) & mask;
let node = self
.slots
.get(bank_base + (last & Self::BANK_MASK))
.copied()
.unwrap_or_default();
slot = usize::from(node.next);
delta = usize::from(node.delta);
if cur_ix_masked + best_len > mask
|| prev_ix + best_len > mask
|| read_u32(data, cur_ix_masked + best_len - 3)
!= read_u32(data, prev_ix + best_len - 3)
{
continue;
}
let len = match_len_at(simd, data, prev_ix, cur());
if len >= 4 {
let score = backward_reference_score(len, backward);
if best_score < score {
best_score = score;
best_len = len;
out.len = best_len;
out.distance = backward;
out.score = best_score;
}
}
}
self.store(data, mask, query.cur_ix);
if out.score == min_score {
query.search_dictionary(stats, out, false);
}
}
}
pub(crate) enum MatchFinder {
H2Small(QuickMatcher<{ 1 << 16 }, 0, 5, true, true>),
H3Small(QuickMatcher<{ 1 << 16 }, 1, 5, false, true>),
H4Small(QuickMatcher<{ 1 << 17 }, 2, 5, true, true>),
H2(QuickMatcher<{ 1 << 16 }, 0, 5, true>),
H3(QuickMatcher<{ 1 << 16 }, 1, 5, false>),
H4(QuickMatcher<{ 1 << 17 }, 2, 5, true>),
H54(QuickMatcher<{ 1 << 20 }, 2, 7, false>),
H40(ChainMatcher<1, 16>),
H42(ChainMatcher<512, 9>),
H5Q5(BucketMatcher<false, { 1 << 14 }, 16>),
H5Q6(BucketMatcher<false, { 1 << 14 }, 32>),
H5Q7(BucketMatcher<false, { 1 << 15 }, 64>),
H5Q8(BucketMatcher<false, { 1 << 15 }, 128>),
H5Q9(BucketMatcher<false, { 1 << 15 }, 256>),
H6Q5(BucketMatcher<true, { 1 << 15 }, 16>),
H6Q6(BucketMatcher<true, { 1 << 15 }, 32>),
H6Q7(BucketMatcher<true, { 1 << 15 }, 64>),
H6Q8(BucketMatcher<true, { 1 << 15 }, 128>),
H6Q9(BucketMatcher<true, { 1 << 15 }, 256>),
}
impl MatchFinder {
fn bucket(hash64: bool, shape: BucketShape, size_hint: usize) -> Self {
match (hash64, shape.block_bits) {
(false, ..=4) => Self::H5Q5(BucketMatcher::new(size_hint)),
(false, 5) => Self::H5Q6(BucketMatcher::new(size_hint)),
(false, 6) => Self::H5Q7(BucketMatcher::new(size_hint)),
(false, 7) => Self::H5Q8(BucketMatcher::new(size_hint)),
(false, 8..) => Self::H5Q9(BucketMatcher::new(size_hint)),
(true, ..=4) => Self::H6Q5(BucketMatcher::new(size_hint)),
(true, 5) => Self::H6Q6(BucketMatcher::new(size_hint)),
(true, 6) => Self::H6Q7(BucketMatcher::new(size_hint)),
(true, 7) => Self::H6Q8(BucketMatcher::new(size_hint)),
(true, 8..) => Self::H6Q9(BucketMatcher::new(size_hint)),
}
}
}
impl From<HasherPlan> for MatchFinder {
fn from(plan: HasherPlan) -> Self {
match plan {
HasherPlan::H2 => Self::H2(QuickMatcher::new()),
HasherPlan::H3 => Self::H3(QuickMatcher::new()),
HasherPlan::H4 => Self::H4(QuickMatcher::new()),
HasherPlan::H54 => Self::H54(QuickMatcher::new()),
HasherPlan::Chain(shape) => {
if shape.num_banks == 1 {
Self::H40(ChainMatcher::new(shape))
} else {
Self::H42(ChainMatcher::new(shape))
}
}
HasherPlan::H5(shape) => Self::bucket(false, shape, 0),
HasherPlan::H6(shape) => Self::bucket(true, shape, 0),
}
}
}
macro_rules! with_matcher {
($finder:expr, |$matcher:ident| $body:expr) => {
match $finder {
MatchFinder::H2Small($matcher) => $body,
MatchFinder::H3Small($matcher) => $body,
MatchFinder::H4Small($matcher) => $body,
MatchFinder::H2($matcher) => $body,
MatchFinder::H3($matcher) => $body,
MatchFinder::H4($matcher) => $body,
MatchFinder::H54($matcher) => $body,
MatchFinder::H40($matcher) => $body,
MatchFinder::H42($matcher) => $body,
MatchFinder::H5Q5($matcher) => $body,
MatchFinder::H5Q6($matcher) => $body,
MatchFinder::H5Q7($matcher) => $body,
MatchFinder::H5Q8($matcher) => $body,
MatchFinder::H5Q9($matcher) => $body,
MatchFinder::H6Q5($matcher) => $body,
MatchFinder::H6Q6($matcher) => $body,
MatchFinder::H6Q7($matcher) => $body,
MatchFinder::H6Q8($matcher) => $body,
MatchFinder::H6Q9($matcher) => $body,
}
};
}
pub(crate) use with_matcher;
impl MatchFinder {
pub(crate) fn for_input(plan: HasherPlan, size_hint: usize) -> Self {
if size_hint > 0 && size_hint <= 2048 {
match plan {
HasherPlan::H2 => return Self::H2Small(QuickMatcher::new()),
HasherPlan::H3 => return Self::H3Small(QuickMatcher::new()),
HasherPlan::H4 => return Self::H4Small(QuickMatcher::new()),
_ => {}
}
}
match plan {
HasherPlan::H5(shape) => Self::bucket(false, shape, size_hint),
HasherPlan::H6(shape) => Self::bucket(true, shape, size_hint),
_ => Self::from(plan),
}
}
pub(crate) fn prepare(
&mut self,
one_shot: bool,
input_size: usize,
data: &[u8],
clear: bool,
) -> Sweep {
with_matcher!(self, |matcher| matcher
.prepare(one_shot, input_size, data, clear))
}
pub(crate) fn retained_bytes(&self) -> usize {
with_matcher!(self, |matcher| matcher.retained_bytes())
}
pub(crate) fn stitch_to_previous_block(
&mut self,
num_bytes: usize,
position: usize,
data: &[u8],
mask: usize,
) {
with_matcher!(self, |matcher| matcher
.stitch_to_previous_block(num_bytes, position, data, mask));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_slots_preserve_colliding_keys_through_growth_overwrites_and_reset() {
let mut slots = SmallSlots::default();
assert_eq!(slots.read(3), 0);
for key in 0..1024 {
slots.write(key * 32 + 3, key as u32 + 1);
}
for key in 0..1024 {
assert_eq!(slots.read(key * 32 + 3), key as u32 + 1);
slots.write(key * 32 + 3, u32::MAX);
assert_eq!(slots.read(key * 32 + 3), u32::MAX);
}
assert_eq!(slots.count, 1024);
assert_eq!(slots.read(4), 0);
let capacity = slots.entries.capacity();
slots.reset(16);
assert_eq!(slots.entries.capacity(), capacity);
assert_eq!(slots.read(3), 0);
slots.write(3, 7);
assert_eq!(slots.read(3), 7);
}
#[test]
fn compact_quick_matchers_preserve_the_full_tables_results_on_every_backend() {
let data = repeated();
for plan in [HasherPlan::H2, HasherPlan::H3, HasherPlan::H4] {
for backend in crate::compressor::Backend::available() {
let expected = with_matcher!(MatchFinder::from(plan), |matcher| {
let mut matcher = primed(matcher, &data);
search_with(backend.0, &mut matcher, &data, REPEAT_AT)
});
let actual = with_matcher!(MatchFinder::for_input(plan, 16), |matcher| {
let mut matcher = primed(matcher, &data);
search_with(backend.0, &mut matcher, &data, REPEAT_AT)
});
assert_eq!(actual, expected, "{backend:?}, {plan:?}");
}
}
}
#[test]
fn a_cold_q9_chain_allocates_only_the_bank_it_uses() {
let mut matcher = ChainMatcher::<512, 9>::new(Q9_CHAIN);
assert!(matcher.slots.is_empty());
let data = [b'a'; 64];
matcher.store(&data, usize::MAX, 0);
assert_eq!(matcher.slots.len(), 512);
matcher.store(&data, usize::MAX, 1);
assert_eq!(matcher.slots.len(), 512);
let retained = matcher.retained_bytes();
matcher.prepare(false, data.len(), &data, true);
matcher.store(&data, usize::MAX, 0);
assert_eq!(matcher.retained_bytes(), retained);
}
#[test]
fn a_short_input_activates_starter_blocks_only_as_it_stores() {
let mut matcher = BucketMatcher::<false, { 1 << 15 }, 256>::new(0);
let data = [b'a'; 64];
matcher.prepare(true, data.len(), &data, true);
assert_eq!(matcher.layout, Layout::Compact);
assert!(matcher.retained_bytes() < 16 * 1024);
matcher.store(&data, usize::MAX, 0);
assert_eq!(matcher.starters.len(), 1);
assert!(matcher.blocks.is_empty());
matcher.store(&data, usize::MAX, 1);
assert_eq!(matcher.starters.len(), 1);
assert!(matcher.blocks.is_empty());
}
#[test]
fn a_starter_block_grows_into_the_top_of_a_full_block() {
let mut matcher = BucketMatcher::<false, { 1 << 15 }, 256>::new(0);
let data = [b'a'; 64];
matcher.prepare(true, data.len(), &data, true);
for position in 0..5 {
matcher.store(&data, usize::MAX, position);
}
assert_eq!(matcher.starters.len(), 1);
assert_eq!(matcher.blocks.len(), 1);
assert_eq!(&matcher.blocks[0][256 - 5..], &[4, 3, 2, 1, 0]);
let key = BucketMatcher::<false, { 1 << 15 }, 256>::hash_with_tag(&data, 0) >> 8;
let (count, offset) = matcher.entry::<true>(key);
assert_eq!(count, 5);
assert_eq!(
BucketMatcher::<false, { 1 << 15 }, 256>::block(offset),
Some(BlockRef::Full(0))
);
assert_eq!(BucketMatcher::<false, { 1 << 15 }, 256>::block(0), None);
}
#[test]
fn every_layout_finds_the_same_match_and_forgets_it_on_prepare() {
let data = repeated();
for backend in crate::compressor::Backend::available() {
let mut expected = None;
for (one_shot, input_size, size_hint, layout) in [
(true, data.len(), data.len(), Layout::Compact),
(true, COMPACT_INPUT_LIMIT + 1, 0, Layout::Sparse),
(false, data.len(), 1 << 20, Layout::Dense),
] {
let mut matcher = BucketMatcher::<false, { 1 << 14 }, 16>::new(size_hint);
matcher.prepare(one_shot, input_size, &data, true);
assert_eq!(matcher.layout, layout, "{backend:?}");
matcher.store_range(&data, usize::MAX, 0, REPEAT_AT);
let found = search_with(backend.0, &mut matcher, &data, REPEAT_AT);
assert_eq!(
(found.distance, found.len),
(64, 64),
"{backend:?} {layout:?}"
);
let found = (found.distance, found.len, found.score);
assert_eq!(
*expected.get_or_insert(found),
found,
"{backend:?} {layout:?}"
);
for (next_one_shot, next_size) in [
(one_shot, input_size),
(true, 16),
(true, COMPACT_INPUT_LIMIT + 1),
(false, 0),
] {
matcher.prepare(next_one_shot, next_size, &data, true);
assert!(
!search_with(backend.0, &mut matcher, &data, REPEAT_AT).is_match(),
"{backend:?} {layout:?} -> {next_one_shot} {next_size}"
);
}
}
}
}
#[test]
fn a_sparse_table_wipes_itself_when_its_generations_run_out() {
let data = repeated();
let mut matcher = BucketMatcher::<false, { 1 << 14 }, 16>::new(0);
matcher.prepare(true, COMPACT_INPUT_LIMIT + 1, &data, true);
matcher.store_range(&data, usize::MAX, 0, REPEAT_AT);
matcher.generation = MAX_GENERATION;
matcher.prepare(true, COMPACT_INPUT_LIMIT + 1, &data, true);
assert_eq!(matcher.generation, 1);
assert!(matcher.blocks.is_empty());
assert!(matcher.starters.is_empty());
assert!(!search_at(&mut matcher, &data, REPEAT_AT).is_match());
matcher.prepare(true, COMPACT_INPUT_LIMIT + 1, &data, true);
assert_eq!(matcher.generation, 2);
}
#[test]
fn a_dense_table_keeps_its_blocks_and_clears_only_its_counters() {
let data = repeated();
let mut matcher = BucketMatcher::<false, { 1 << 14 }, 16>::new(1 << 20);
matcher.prepare(false, data.len(), &data, true);
matcher.store_range(&data, usize::MAX, 0, REPEAT_AT);
let retained = matcher.retained_bytes();
matcher.prepare(false, data.len(), &data, true);
assert_eq!(matcher.retained_bytes(), retained);
assert!(matcher.num.iter().all(|&count| count == 0));
assert!(!search_at(&mut matcher, &data, REPEAT_AT).is_match());
}
#[test]
fn the_key_map_keeps_colliding_keys_through_growth_and_reset() {
let mut map = KeyMap::default();
assert_eq!(map.get(3), (0, 0));
map.reset(4);
for key in 0..1024 {
map.set(key * 64 + 3, key as u16 + 1, key as u32 + 7);
}
for key in 0..1024 {
assert_eq!(map.get(key * 64 + 3), (key as u16 + 1, key as u32 + 7));
}
assert_eq!(map.get(5), (0, 0));
map.set(67, 9, 9);
assert_eq!(map.get(67), (9, 9));
map.reset(4);
assert_eq!(map.get(3), (0, 0));
assert_eq!(map.count, 0);
}
#[test]
fn candidate_masks_walk_a_block_newest_to_oldest() {
let (above, below) = split_candidates(u32::MAX, 8, 5, 3);
assert_eq!((above, below), (0b1110_0000, 0));
let (above, below) = split_candidates(u32::MAX, 8, 5, 6);
assert_eq!((above, below), (0b1110_0000, 0b0000_0111));
let (above, below) = split_candidates(0b1010_1010, 8, 2, 8);
assert_eq!((above, below), (0b1010_1000, 0b0000_0010));
let (above, below) = split_candidates(u32::MAX, 32, 31, 32);
assert_eq!((above, below), (1 << 31, u32::MAX >> 1));
}
#[test]
fn tag_equality_matches_scalar_comparison_on_every_backend() {
fn check<const N: usize>(backend: crate::compressor::Backend) {
let tags: [u8; N] = std::array::from_fn(|i| (i % 5) as u8);
for tag in 0..5u8 {
let expected: u32 = tags
.iter()
.enumerate()
.filter(|&(_, &value)| value == tag)
.fold(0, |mask, (slot, _)| mask | 1 << slot);
let actual = dispatch!(backend.0, simd => tag_equality(simd, &tags, tag));
if backend == crate::compressor::Backend::SCALAR {
assert_eq!(actual, u32::MAX);
} else {
assert_eq!(actual, expected, "{backend:?} {N} {tag}");
}
}
}
for backend in crate::compressor::Backend::available() {
check::<16>(backend);
check::<32>(backend);
assert_eq!(
dispatch!(backend.0, simd => tag_equality(simd, &[1, 2, 3, 4], 2)),
u32::MAX
);
}
}
use fearless_simd::{Level, dispatch};
fn repeated() -> Vec<u8> {
let mut data: Vec<u8> = (0..64u32).map(|i| (i % 97) as u8 + 128).collect();
let body: Vec<u8> = (0..64u32).map(|i| (i * 7 % 251) as u8 + 1).collect();
data.extend_from_slice(&body);
data.extend_from_slice(&body);
data.extend_from_slice(&[0u8; 8]);
data
}
const REPEAT_AT: usize = 128;
fn query<'a>(data: &'a [u8], cache: &'a DistanceCache, cur_ix: usize) -> MatchQuery<'a> {
MatchQuery {
#[cfg(feature = "experimental")]
custom: None,
data,
window: data,
mask: usize::MAX,
cache,
cur_ix,
max_length: data.len() - cur_ix,
max_backward: cur_ix,
position_offset: 0,
dictionary_limit: cur_ix,
gap: 0,
max_distance: u32::MAX as usize,
}
}
fn search_at<M: Matcher>(matcher: &mut M, data: &[u8], cur_ix: usize) -> SearchResult {
search_with(Level::new(), matcher, data, cur_ix)
}
fn search_with<M: Matcher>(
level: Level,
matcher: &mut M,
data: &[u8],
cur_ix: usize,
) -> SearchResult {
let cache = INITIAL_DISTANCE_CACHE;
let mut out = SearchResult::empty();
let mut stats = DictionaryStats::default();
let query = query(data, &cache, cur_ix);
dispatch!(level, simd => matcher.find_longest_match(simd, &mut stats, query, &mut out));
out
}
fn primed<M: Matcher>(mut matcher: M, data: &[u8]) -> M {
matcher.prepare(true, data.len(), data, true);
matcher.store_range(data, usize::MAX, 0, REPEAT_AT);
matcher
}
const Q5_BUCKET: BucketShape = BucketShape {
bucket_bits: 14,
block_bits: 4,
last_distances: 4,
};
const Q9_BUCKET: BucketShape = BucketShape {
bucket_bits: 15,
block_bits: 8,
last_distances: 16,
};
const Q5_CHAIN: ChainShape = ChainShape {
num_banks: 1,
bank_bits: 16,
last_distances: 4,
max_hops: 16,
};
const Q9_CHAIN: ChainShape = ChainShape {
num_banks: 512,
bank_bits: 9,
last_distances: 16,
max_hops: 224,
};
#[test]
fn the_quick_matcher_finds_a_repeat_it_has_stored() {
let data = repeated();
let mut matcher = primed(QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new(), &data);
let found = search_at(&mut matcher, &data, REPEAT_AT);
assert!(found.is_match());
assert_eq!((found.distance, found.len), (64, 64));
}
#[test]
fn every_quick_shape_finds_the_same_repeat() {
let data = repeated();
let mut h4 = primed(QuickMatcher::<{ 1 << 17 }, 2, 5, true>::new(), &data);
let found = search_at(&mut h4, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
let mut h54 = primed(QuickMatcher::<{ 1 << 20 }, 2, 7, false>::new(), &data);
let found = search_at(&mut h54, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
}
#[test]
fn the_bucket_matchers_find_a_repeat_they_have_stored() {
let data = repeated();
let mut h5 = primed(BucketMatcher::<false, { 1 << 14 }, 16>::new(0), &data);
let found = search_at(&mut h5, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
let mut h6 = primed(BucketMatcher::<true, { 1 << 15 }, 16>::new(0), &data);
let found = search_at(&mut h6, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
let mut deep = primed(BucketMatcher::<false, { 1 << 15 }, 256>::new(0), &data);
let found = search_at(&mut deep, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
}
#[test]
fn the_chain_matchers_find_a_repeat_they_have_stored() {
let data = repeated();
let mut h40 = primed(ChainMatcher::<1, 16>::new(Q5_CHAIN), &data);
let found = search_at(&mut h40, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
let mut h42 = primed(ChainMatcher::<512, 9>::new(Q9_CHAIN), &data);
let found = search_at(&mut h42, &data, REPEAT_AT);
assert_eq!((found.distance, found.len), (64, 64));
}
#[test]
fn the_derived_distance_cache_brackets_the_two_freshest_entries() {
let mut cache = INITIAL_DISTANCE_CACHE;
prepare_distance_cache(&mut cache, 4);
assert_eq!(cache[4..], [0; 12]);
prepare_distance_cache(&mut cache, 10);
assert_eq!(cache[4..10], [3, 5, 2, 6, 1, 7]);
assert_eq!(cache[10..], [0; 6]);
prepare_distance_cache(&mut cache, 16);
assert_eq!(cache[10..], [10, 12, 9, 13, 8, 14]);
}
#[test]
fn a_deep_bucket_remembers_more_positions_than_a_shallow_one() {
let data = vec![b'a'; 1024];
fn reach<const BLOCK: usize>(data: &[u8]) -> usize {
let mut matcher = BucketMatcher::<false, { 1 << 15 }, BLOCK>::new(0);
matcher.prepare(true, data.len(), data, true);
matcher.store_range(data, usize::MAX, 0, 512);
let found = search_at(&mut matcher, data, 512);
assert!(found.is_match());
found.distance
}
assert!(reach::<16>(&data) <= 16);
assert!(reach::<256>(&data) <= 256);
}
#[test]
fn a_bucket_forgets_all_but_its_newest_sixteen_positions() {
let data = vec![b'a'; 256];
let mut matcher = BucketMatcher::<false, { 1 << 14 }, 16>::new(0);
matcher.prepare(true, data.len(), &data, true);
matcher.store_range(&data, usize::MAX, 0, 100);
let found = search_at(&mut matcher, &data, 100);
assert!(found.is_match());
assert!(found.distance <= 16);
}
#[test]
fn nothing_is_found_when_the_table_holds_no_candidate() {
let data = repeated();
let mut matcher = QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new();
matcher.prepare(true, data.len(), &data, true);
assert!(!search_at(&mut matcher, &data, REPEAT_AT).is_match());
let mut chain = ChainMatcher::<1, 16>::new(Q5_CHAIN);
chain.prepare(true, data.len(), &data, true);
assert!(!search_at(&mut chain, &data, REPEAT_AT).is_match());
let mut bucket = BucketMatcher::<false, { 1 << 14 }, 16>::new(0);
bucket.prepare(true, data.len(), &data, true);
assert!(!search_at(&mut bucket, &data, REPEAT_AT).is_match());
}
#[test]
fn a_full_preparation_clears_what_a_previous_stream_stored() {
let data = repeated();
let mut matcher = primed(QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new(), &data);
assert!(search_at(&mut matcher, &data, REPEAT_AT).is_match());
matcher.prepare(false, 0, &data, true);
assert!(!search_at(&mut matcher, &data, REPEAT_AT).is_match());
let mut chain = primed(ChainMatcher::<1, 16>::new(Q5_CHAIN), &data);
assert!(search_at(&mut chain, &data, REPEAT_AT).is_match());
chain.prepare(false, 0, &data, true);
assert!(!search_at(&mut chain, &data, REPEAT_AT).is_match());
let mut bucket = primed(BucketMatcher::<false, { 1 << 14 }, 16>::new(0), &data);
assert!(search_at(&mut bucket, &data, REPEAT_AT).is_match());
bucket.prepare(false, 0, &data, true);
assert!(!search_at(&mut bucket, &data, REPEAT_AT).is_match());
}
#[test]
fn every_backend_agrees_on_the_match_it_finds() {
let data = repeated();
for block_bits in 4..=8 {
let shape = BucketShape {
bucket_bits: if block_bits <= 5 { 14 } else { 15 },
block_bits,
last_distances: 4,
};
for plan in [
HasherPlan::H5(shape),
HasherPlan::H6(BucketShape {
bucket_bits: 15,
..shape
}),
] {
let mut results = Vec::new();
for backend in crate::compressor::Backend::available() {
let finder = MatchFinder::from(plan);
let found = with_matcher!(finder, |matcher| {
let mut matcher = primed(matcher, &data);
search_with(backend.0, &mut matcher, &data, REPEAT_AT)
});
assert_eq!((found.distance, found.len), (64, 64));
results.push(found);
}
assert!(results.windows(2).all(|pair| pair[0] == pair[1]));
}
}
}
fn thrice_repeated() -> Vec<u8> {
let mut data = repeated();
data.truncate(REPEAT_AT + 64);
let body: Vec<u8> = data[64..REPEAT_AT].to_vec();
data.extend_from_slice(&body);
data.extend_from_slice(&[0u8; 8]);
data
}
#[test]
fn stitching_stores_the_three_positions_before_the_boundary() {
let data = thrice_repeated();
let mut matcher = QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new();
matcher.prepare(true, data.len(), &data, true);
matcher.stitch_to_previous_block(64, REPEAT_AT, &data, usize::MAX);
let found = search_at(&mut matcher, &data, REPEAT_AT + 61);
assert!(found.is_match());
assert_eq!(found.distance, 64);
}
#[test]
fn stitching_does_nothing_at_the_start_of_a_stream() {
let data = thrice_repeated();
let mut matcher = QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new();
matcher.prepare(true, data.len(), &data, true);
matcher.stitch_to_previous_block(64, 2, &data, usize::MAX);
matcher.stitch_to_previous_block(1, REPEAT_AT, &data, usize::MAX);
assert!(!search_at(&mut matcher, &data, REPEAT_AT + 61).is_match());
}
#[test]
fn the_plan_selects_the_matching_finder() {
assert!(matches!(
MatchFinder::from(HasherPlan::H3),
MatchFinder::H3(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::H4),
MatchFinder::H4(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::H54),
MatchFinder::H54(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::Chain(Q5_CHAIN)),
MatchFinder::H40(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::Chain(Q9_CHAIN)),
MatchFinder::H42(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::H5(Q5_BUCKET)),
MatchFinder::H5Q5(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::H5(Q9_BUCKET)),
MatchFinder::H5Q9(_)
));
assert!(matches!(
MatchFinder::from(HasherPlan::H6(Q9_BUCKET)),
MatchFinder::H6Q9(_)
));
}
#[test]
fn a_matcher_reports_the_cached_distance_count_its_shape_asked_for() {
assert_eq!(
BucketMatcher::<false, { 1 << 15 }, 256>::new(0).last_distances_to_check(),
16
);
assert_eq!(
ChainMatcher::<1, 16>::new(Q5_CHAIN).last_distances_to_check(),
4
);
assert_eq!(
ChainMatcher::<512, 9>::new(Q9_CHAIN).last_distances_to_check(),
16
);
assert_eq!(
QuickMatcher::<{ 1 << 16 }, 1, 5, false>::new().last_distances_to_check(),
NUM_REMEMBERED_DISTANCES
);
}
}