use alloc::collections::VecDeque;
use alloc::vec::Vec;
use super::Sequence;
use super::blocks::encode_offset_with_history;
use super::dict_attach::DictAttach;
use super::levels::config::{RowConfig, RowDictPlan};
use super::match_generator::{
ROW_EMPTY_SLOT, ROW_HASH_BITS, ROW_HASH_KEY_LEN, ROW_LOG, ROW_MIN_MATCH_LEN, ROW_SEARCH_DEPTH,
ROW_TAG_BITS, ROW_TARGET_LEN,
};
const LAZY_ROW_ILIMIT_MARGIN: usize = 16;
const LAZY_HC_ILIMIT_MARGIN: usize = 8;
const LAZY_SEARCH_STRENGTH: u32 = 8;
const LAZY_SKIPPING_STEP: usize = 8;
const ROW_HASH_CACHE_SIZE: usize = 8;
const ROW_UPDATE_SKIP_THRESHOLD: usize = 384;
const ROW_UPDATE_MAX_START: usize = 96;
const ROW_UPDATE_MAX_END: usize = 32;
const ROW_CACHE_NONE: u32 = u32::MAX;
pub(crate) const ROW_HASH_PRIME4: u32 = 2_654_435_761;
pub(crate) const ROW_HASH_PRIME5: u64 = 889_523_592_379;
pub(crate) const ROW_HASH_PRIME6: u64 = 227_718_039_650_203;
const fn zstd_bitmix(mut val: u64, len: u64) -> u64 {
val ^= val.rotate_right(49) ^ val.rotate_right(24);
val = val.wrapping_mul(0x9FB2_1C65_1E98_DF25);
val ^= (val >> 35).wrapping_add(len);
val = val.wrapping_mul(0x9FB2_1C65_1E98_DF25);
val ^ (val >> 28)
}
pub(crate) const ROW_HASH_SALT: u64 = zstd_bitmix(0, 8) ^ zstd_bitmix(0, 4);
const BT_IDX_BASE: usize = 2;
const BT_UNSORTED_MARK: u32 = 1;
const DUBT_SORT_COMPARE_CAP: usize = 8 * 1024;
const BT_DISCARD: usize = usize::MAX;
const FINDER_ROWS: u8 = 0;
const FINDER_CHAIN: u8 = 1;
const FINDER_TREE: u8 = 2;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LazyFinder {
Rows,
Chain,
Tree,
}
#[derive(Clone, Copy)]
struct RowScan {
base: *const u8,
len: usize,
hist_start: usize,
salt: u64,
search_window: usize,
low_limit: usize,
prefix_start: usize,
dict_frame: bool,
attached: bool,
}
impl RowScan {
#[inline(always)]
fn window_low(&self, pos: usize) -> usize {
if self.dict_frame {
self.low_limit
} else {
self.low_limit.max(pos.saturating_sub(self.search_window))
}
}
}
#[inline(always)]
fn highbit32(val: u32) -> i64 {
debug_assert!(val != 0);
i64::from(31 - val.leading_zeros())
}
#[inline(always)]
fn bt_best_offbase(best_len: usize, best_off: usize) -> u32 {
if best_len == 0 {
999_999_999
} else {
(best_off + 3) as u32
}
}
#[inline(always)]
fn offbase_highbit(off: usize) -> i64 {
if off == 0 {
0
} else {
i64::from(31 - ((off + 3) as u32).leading_zeros())
}
}
#[inline(always)]
unsafe fn rd32(p: *const u8) -> u32 {
u32::from_le_bytes(unsafe { p.cast::<[u8; 4]>().read_unaligned() })
}
#[derive(Debug, Default, Clone)]
pub(crate) struct RowDictTables {
pub(crate) heads: Vec<u8>,
pub(crate) positions: Vec<u32>,
pub(crate) tags: Vec<u8>,
pub(crate) hc_hash: Vec<u32>,
pub(crate) hc_chain: Vec<u32>,
pub(crate) hash_log: usize,
pub(crate) chain_log: usize,
pub(crate) row_log: usize,
pub(crate) use_row: bool,
pub(crate) use_bt: bool,
}
use super::match_table::helpers::{
INCOMPRESSIBLE_SKIP_STEP, best_len_offset_candidate, extend_backwards_shared,
repcode_candidate_shared,
};
use super::match_table::storage::REBASE_RESET_FLOOR_CEILING;
use super::opt::types::MatchCandidate;
use super::fastpath::FastpathKernel;
pub(crate) trait RowTags: Copy {
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate>;
}
#[cfg_attr(
all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
),
allow(dead_code)
)]
#[derive(Copy, Clone)]
struct ScalarTags;
impl RowTags for ScalarTags {
#[inline]
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
unsafe { matcher.row_probe_scalar::<ROW_LOG>(abs_pos, lit_len, hash) }
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
#[derive(Copy, Clone)]
struct Sse2Tags;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
impl RowTags for Sse2Tags {
#[inline]
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
unsafe { matcher.row_probe_sse2::<ROW_LOG>(abs_pos, lit_len, hash) }
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
#[derive(Copy, Clone)]
struct Avx2Bmi2Tags;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
impl RowTags for Avx2Bmi2Tags {
#[inline]
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
unsafe { matcher.row_probe_avx2bmi2::<ROW_LOG>(abs_pos, lit_len, hash) }
}
}
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
#[derive(Copy, Clone)]
struct NeonTags;
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
impl RowTags for NeonTags {
#[inline]
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
unsafe { matcher.row_probe_neon::<ROW_LOG>(abs_pos, lit_len, hash) }
}
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
#[derive(Copy, Clone)]
struct Simd128Tags;
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
impl RowTags for Simd128Tags {
#[inline]
unsafe fn probe<const ROW_LOG: usize>(
matcher: &RowMatchGenerator,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
unsafe { matcher.row_probe_simd128::<ROW_LOG>(abs_pos, lit_len, hash) }
}
}
macro_rules! dispatch_tag_kernel {
($self:ident . $k_method:ident ( $($arg:expr),* )) => {{
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
{
$self.$k_method::<Simd128Tags>($($arg),*)
}
#[cfg(not(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
)))]
{
match $self.tag_kernel {
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
FastpathKernel::Avx2Bmi2 => $self.$k_method::<Avx2Bmi2Tags>($($arg),*),
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
FastpathKernel::Sse2 | FastpathKernel::Sse42 => {
$self.$k_method::<Sse2Tags>($($arg),*)
}
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
FastpathKernel::Neon => $self.$k_method::<NeonTags>($($arg),*),
FastpathKernel::Scalar => $self.$k_method::<ScalarTags>($($arg),*),
}
}
}};
}
macro_rules! row_find_best_match {
($m:expr, $ctx:ident, $abs_pos:expr, $row:expr, $tag:expr, $rl:expr, $use_mask:literal, $maskmac:ident, $cpl:path) => {{
let hist_start = $ctx.hist_start;
let cur_idx = $abs_pos - hist_start;
let cur_ptr = unsafe { $ctx.base.add(cur_idx) };
let limit = $ctx.len - cur_idx;
let row_entries = 1usize << $rl;
let row_mask = row_entries - 1;
let row_base = $row << $rl;
debug_assert_eq!($rl, $m.row_log);
debug_assert!(row_base + row_entries <= $m.row_positions().len());
let head = unsafe { *$m.row_heads().get_unchecked($row) } as usize;
let window_low = $ctx.window_low($abs_pos);
let budget = $m.search_depth.min(row_entries);
let mut attempts = budget;
let mut ml = 3usize;
let mut best_off = 0usize;
let entries_bits: u64 = if row_entries >= 64 {
u64::MAX
} else {
(1u64 << row_entries) - 1
};
let mut buf = [0u32; 64];
let mut n = 0usize;
{
#[allow(unused_mut)]
let mut pending: u64 = if $use_mask {
let tags = unsafe {
core::slice::from_raw_parts($m.row_tags().as_ptr().add(row_base), row_entries)
};
let m = $maskmac!(tags, $tag) & entries_bits;
if head == 0 {
m
} else {
((m >> head) | (m << (row_entries - head))) & entries_bits
}
} else {
0
};
#[allow(unused_mut)]
let mut scan = 0usize;
while attempts > 0 {
let slot_opt = if $use_mask {
if pending == 0 {
None
} else {
let i = pending.trailing_zeros() as usize;
pending &= pending - 1;
Some((head + i) & row_mask)
}
} else {
let mut found = None;
while scan < row_entries {
let s = (head + scan) & row_mask;
scan += 1;
if $m.row_tags()[row_base + s] == $tag {
found = Some(s);
break;
}
}
found
};
let Some(slot) = slot_opt else { break };
if slot == 0 {
continue;
}
let raw = unsafe { *$m.row_positions().get_unchecked(row_base + slot) };
if raw == ROW_EMPTY_SLOT || (raw as usize) < window_low {
break;
}
if (raw as usize) >= $abs_pos {
continue;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(target_arch = "x86")]
use core::arch::x86::{_MM_HINT_T0, _mm_prefetch};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
unsafe {
_mm_prefetch($ctx.base.add(raw as usize - hist_start).cast(), _MM_HINT_T0);
}
}
unsafe { *buf.get_unchecked_mut(n) = raw };
n += 1;
attempts -= 1;
}
}
let prefix_start = $ctx.prefix_start;
for &raw in &buf[..n] {
let cand_idx = raw as usize - hist_start;
unsafe {
let cand_ptr = $ctx.base.add(cand_idx);
let gate = if (raw as usize) >= prefix_start {
rd32(cand_ptr.add(ml - 3)) == rd32(cur_ptr.add(ml - 3))
} else {
rd32(cand_ptr) == rd32(cur_ptr)
};
if gate {
let cml = $cpl(cand_ptr, cur_ptr, limit);
if cml > ml {
ml = cml;
best_off = $abs_pos - raw as usize;
if cml == limit {
break;
}
}
}
}
}
if ml < limit
&& attempts > 0
&& $ctx.attached
&& let Some(dict) = $m.dict.table()
&& dict.use_row
{
debug_assert_eq!(dict.row_log, $rl);
let mut dattempts = attempts;
let dict_end = $m.dict.region_len();
let dict_row_hash_log = dict.hash_log - $rl;
let dcombined = unsafe {
RowMatchGenerator::key_hash_raw(
$ctx.base,
$ctx.len,
cur_idx,
$m.row_hash_mls,
dict_row_hash_log + ROW_TAG_BITS,
0,
)
};
let drow = ((dcombined >> ROW_TAG_BITS) as usize) & ((1usize << dict_row_hash_log) - 1);
let dtag = dcombined as u8;
let drow_base = drow << $rl;
let dhead = dict.heads[drow] as usize;
#[allow(unused_mut)]
let mut dpending: u64 = if $use_mask {
let m =
$maskmac!(&dict.tags[drow_base..drow_base + row_entries], dtag) & entries_bits;
if dhead == 0 {
m
} else {
((m >> dhead) | (m << (row_entries - dhead))) & entries_bits
}
} else {
0
};
#[allow(unused_mut)]
let mut dscan = 0usize;
while dattempts > 0 {
let slot_opt = if $use_mask {
if dpending == 0 {
None
} else {
let i = dpending.trailing_zeros() as usize;
dpending &= dpending - 1;
Some((dhead + i) & row_mask)
}
} else {
let mut found = None;
while dscan < row_entries {
let s = (dhead + dscan) & row_mask;
dscan += 1;
if dict.tags[drow_base + s] == dtag {
found = Some(s);
break;
}
}
found
};
let Some(slot) = slot_opt else { break };
if slot == 0 {
continue;
}
let dp = dict.positions[drow_base + slot];
if dp == ROW_EMPTY_SLOT {
break;
}
dattempts -= 1;
let dp = dp as usize;
debug_assert!(dp + 8 <= dict_end);
unsafe {
let dptr = $ctx.base.add(dp);
if rd32(dptr) == rd32(cur_ptr) {
let cml = $cpl(dptr, cur_ptr, limit);
if cml > ml {
ml = cml;
best_off = $abs_pos - (hist_start + dp);
if cml == limit {
break;
}
}
}
}
}
}
(ml, best_off)
}};
}
macro_rules! lazy_search_at {
($m:expr, $ctx:ident, $p:expr, $ntu:ident, $skip:ident, $cache:ident, $rl:expr, $finder:expr, $use_mask:literal, $maskmac:ident, $cpl:path) => {{
let p = $p;
if $finder == FINDER_TREE {
dubt_find_best_match!($m, $ctx, p, $ntu, $cpl)
} else if $finder == FINDER_CHAIN {
hc_find_best_match!($m, $ctx, p, $ntu, $skip, $cpl)
} else {
let (row, tag) = if $skip {
row_cache_hash!($m, $ctx, $rl, p)
} else {
if p - $ntu > ROW_UPDATE_SKIP_THRESHOLD {
row_cache_insert_range!(
$m,
$ctx,
$cache,
$rl,
$ntu,
$ntu + ROW_UPDATE_MAX_START
);
row_cache_fill!($m, $ctx, $cache, $rl, p - ROW_UPDATE_MAX_END);
row_cache_insert_range!($m, $ctx, $cache, $rl, p - ROW_UPDATE_MAX_END, p);
} else {
row_cache_insert_range!($m, $ctx, $cache, $rl, $ntu, p);
}
row_cache_next!($m, $ctx, $cache, $rl, p)
};
let r = if row != ROW_CACHE_NONE {
let row = row as usize;
let r = row_find_best_match!($m, $ctx, p, row, tag, $rl, $use_mask, $maskmac, $cpl);
$m.insert_at::<$rl>(p, row, tag);
r
} else {
(3usize, 0usize)
};
$ntu = p + 1;
r
}
}};
}
macro_rules! hc_find_best_match {
($m:expr, $ctx:ident, $p:expr, $ntu:ident, $skip:ident, $cpl:path) => {{
let p = $p;
let hist_start = $ctx.hist_start;
let chain_mask = (1usize << $m.hc_chain_log) - 1;
let cur_idx = p - hist_start;
let cur_ptr = unsafe { $ctx.base.add(cur_idx) };
let limit = $ctx.len - cur_idx;
{
let mut idx = $ntu;
while idx < p {
let h = $m.hc_hash_at($ctx, idx);
let (hash, chain) = $m.hc_tables_mut();
chain[idx & chain_mask] = hash[h];
hash[h] = idx as u32;
idx += 1;
if $skip {
break;
}
}
$ntu = p;
}
let window_low = $ctx.window_low(p);
let min_chain = p.saturating_sub(chain_mask + 1);
let mut attempts = $m.search_depth;
let mut ml = 3usize;
let mut best_off = 0usize;
let prefix_start = $ctx.prefix_start;
let hc_hash = $m.hc_hash();
let hc_chain = $m.hc_chain();
let mut match_index = hc_hash[$m.hc_hash_at($ctx, p)];
while match_index != ROW_EMPTY_SLOT && (match_index as usize) >= window_low && attempts > 0
{
let cand = match_index as usize;
debug_assert!(cand < p);
unsafe {
let cand_ptr = $ctx.base.add(cand - hist_start);
let gate = if cand >= prefix_start {
rd32(cand_ptr.add(ml - 3)) == rd32(cur_ptr.add(ml - 3))
} else {
rd32(cand_ptr) == rd32(cur_ptr)
};
if gate {
let cml = $cpl(cand_ptr, cur_ptr, limit);
if cml > ml {
ml = cml;
best_off = p - cand;
if cml == limit {
break;
}
}
}
}
if cand <= min_chain {
break;
}
match_index = hc_chain[cand & chain_mask];
attempts -= 1;
}
if ml < limit
&& attempts > 0
&& $ctx.attached
&& let Some(dict) = $m.dict.table()
&& !dict.use_row
&& !dict.use_bt
{
let dict_end = $m.dict.region_len();
let dchain_mask = (1usize << dict.chain_log) - 1;
let dmin_chain = dict_end.saturating_sub(dchain_mask + 1);
let dh = unsafe {
RowMatchGenerator::key_hash_raw(
$ctx.base,
$ctx.len,
cur_idx,
$m.row_hash_mls,
dict.hash_log,
0,
)
} as usize;
let mut dmi = dict.hc_hash[dh];
while dmi != ROW_EMPTY_SLOT && attempts > 0 {
let dp = dmi as usize;
debug_assert!(dp + 8 <= dict_end);
unsafe {
let dptr = $ctx.base.add(dp);
if rd32(dptr) == rd32(cur_ptr) {
let cml = $cpl(dptr, cur_ptr, limit);
if cml > ml {
ml = cml;
best_off = p - (hist_start + dp);
if cml == limit {
break;
}
}
}
}
if dp <= dmin_chain {
break;
}
dmi = dict.hc_chain[dp & dchain_mask];
attempts -= 1;
}
}
(ml, best_off)
}};
}
macro_rules! dubt_update {
($m:expr, $ctx:ident, $from:expr, $to:expr) => {{
let bt_mask = (1usize << ($m.hc_chain_log - 1)) - 1;
let mut idx = $from;
while idx < $to {
let h = $m.hc_hash_at($ctx, idx);
let ci = idx + BT_IDX_BASE;
let node = 2 * (ci & bt_mask);
let (hash, chain) = $m.hc_tables_mut();
chain[node] = hash[h];
chain[node + 1] = BT_UNSORTED_MARK;
hash[h] = ci as u32;
idx += 1;
}
}};
}
macro_rules! dubt_insert1 {
($bt_mask:expr, $hc_chain:ident, $ctx:ident, $curr:expr, $nb_compares:expr, $bt_low:expr, $cpl:path) => {{
let bt_mask = $bt_mask;
let hc_chain = &mut *$hc_chain;
let hist_start = $ctx.hist_start;
let curr: usize = $curr;
let cur_abs = curr - BT_IDX_BASE;
debug_assert!(cur_abs >= $ctx.low_limit);
debug_assert!(
cur_abs >= hist_start && cur_abs - hist_start <= $ctx.len,
"sorting a node whose bytes were evicted (abs {cur_abs}, history starts {hist_start})",
);
let cur_idx = cur_abs - hist_start;
let ip = unsafe { $ctx.base.add(cur_idx) };
let iend_rem = $ctx.len - cur_idx;
let sort_end_cap = iend_rem.min(DUBT_SORT_COMPARE_CAP);
let window_low = $ctx
.low_limit
.max(cur_abs.saturating_sub($ctx.search_window))
+ BT_IDX_BASE;
let mut smaller_ptr = 2 * (curr & bt_mask);
let mut larger_ptr = smaller_ptr + 1;
let mut match_index = hc_chain[smaller_ptr] as usize;
let mut common_smaller = 0usize;
let mut common_larger = 0usize;
let mut nb = $nb_compares;
while nb > 0 && match_index > window_low {
debug_assert!(match_index < curr, "sort walk reached a future node");
let next_ptr = 2 * (match_index & bt_mask);
let mut ml = common_smaller.min(common_larger);
let m_idx = match_index - BT_IDX_BASE - hist_start;
let (smaller, at_end) = unsafe {
let mptr = $ctx.base.add(m_idx);
let seed = ml;
let sort_end = if seed < sort_end_cap {
sort_end_cap
} else {
(seed + 1).min(iend_rem)
};
ml += $cpl(mptr.add(ml), ip.add(ml), sort_end - ml);
if ml == sort_end {
(false, true)
} else {
(*mptr.add(ml) < *ip.add(ml), false)
}
};
if at_end {
break;
}
if smaller {
hc_chain[smaller_ptr] = match_index as u32;
common_smaller = ml;
if match_index <= $bt_low {
smaller_ptr = BT_DISCARD;
break;
}
smaller_ptr = next_ptr + 1;
match_index = hc_chain[next_ptr + 1] as usize;
} else {
hc_chain[larger_ptr] = match_index as u32;
common_larger = ml;
if match_index <= $bt_low {
larger_ptr = BT_DISCARD;
break;
}
larger_ptr = next_ptr;
match_index = hc_chain[next_ptr] as usize;
}
nb -= 1;
}
if smaller_ptr != BT_DISCARD {
hc_chain[smaller_ptr] = 0;
}
if larger_ptr != BT_DISCARD {
hc_chain[larger_ptr] = 0;
}
}};
}
macro_rules! dubt_find_best_match {
($m:expr, $ctx:ident, $p:expr, $ntu:ident, $cpl:path) => {{
let p = $p;
if p < $ntu {
(0usize, 0usize)
} else {
dubt_update!($m, $ctx, $ntu, p);
let hist_start = $ctx.hist_start;
let bt_mask = (1usize << ($m.hc_chain_log - 1)) - 1;
let cur_idx = p - hist_start;
let ip = unsafe { $ctx.base.add(cur_idx) };
let limit = $ctx.len - cur_idx;
let h = $m.hc_hash_at($ctx, p);
let curr = p + BT_IDX_BASE;
let window_low = $ctx.window_low(p) + BT_IDX_BASE;
let bt_low = curr.saturating_sub(bt_mask);
let unsort_limit = bt_low.max(window_low);
let search_depth = $m.search_depth;
let (hc_hash, hc_chain) = $m.hc_tables_mut();
let mut match_index = hc_hash[h] as usize;
let mut next_candidate = 2 * (match_index & bt_mask);
let mut unsorted_mark = next_candidate + 1;
let mut nb_compares = search_depth;
let mut nb_candidates = nb_compares;
let mut previous_candidate = 0usize;
while match_index > unsort_limit
&& hc_chain[unsorted_mark] == BT_UNSORTED_MARK
&& nb_candidates > 1
{
hc_chain[unsorted_mark] = previous_candidate as u32;
previous_candidate = match_index;
match_index = hc_chain[next_candidate] as usize;
next_candidate = 2 * (match_index & bt_mask);
unsorted_mark = next_candidate + 1;
nb_candidates -= 1;
}
if match_index > unsort_limit && hc_chain[unsorted_mark] == BT_UNSORTED_MARK {
hc_chain[next_candidate] = 0;
hc_chain[unsorted_mark] = 0;
}
match_index = previous_candidate;
while match_index != 0 {
let next_idx = hc_chain[2 * (match_index & bt_mask) + 1] as usize;
dubt_insert1!(
bt_mask,
hc_chain,
$ctx,
match_index,
nb_candidates,
unsort_limit,
$cpl
);
match_index = next_idx;
nb_candidates += 1;
}
let mut common_smaller = 0usize;
let mut common_larger = 0usize;
let mut smaller_ptr = 2 * (curr & bt_mask);
let mut larger_ptr = smaller_ptr + 1;
let mut match_end_idx = curr + 8 + 1;
let mut best_len = 0usize;
let mut best_off = 0usize;
match_index = hc_hash[h] as usize;
hc_hash[h] = curr as u32;
while nb_compares > 0 && match_index > window_low {
debug_assert!(match_index < curr, "tree walk reached a future node");
let next_ptr = 2 * (match_index & bt_mask);
let mut ml = common_smaller.min(common_larger);
let m_idx = match_index - BT_IDX_BASE - hist_start;
let (smaller, at_end) = unsafe {
let mptr = $ctx.base.add(m_idx);
ml += $cpl(mptr.add(ml), ip.add(ml), limit - ml);
if ml == limit {
(false, true)
} else {
(*mptr.add(ml) < *ip.add(ml), false)
}
};
if ml > best_len {
if ml > match_end_idx - match_index {
match_end_idx = match_index + ml;
}
if 4 * (ml as i64 - best_len as i64)
> highbit32((curr - match_index + 1) as u32)
- highbit32(bt_best_offbase(best_len, best_off))
{
best_len = ml;
best_off = curr - match_index;
}
if at_end {
nb_compares = 0;
break;
}
}
if at_end {
break;
}
if smaller {
hc_chain[smaller_ptr] = match_index as u32;
common_smaller = ml;
if match_index <= bt_low {
smaller_ptr = BT_DISCARD;
break;
}
smaller_ptr = next_ptr + 1;
match_index = hc_chain[next_ptr + 1] as usize;
} else {
hc_chain[larger_ptr] = match_index as u32;
common_larger = ml;
if match_index <= bt_low {
larger_ptr = BT_DISCARD;
break;
}
larger_ptr = next_ptr;
match_index = hc_chain[next_ptr] as usize;
}
nb_compares -= 1;
}
if smaller_ptr != BT_DISCARD {
hc_chain[smaller_ptr] = 0;
}
if larger_ptr != BT_DISCARD {
hc_chain[larger_ptr] = 0;
}
if $ctx.attached
&& nb_compares > 0
&& let Some(dict) = $m.dict.table()
&& dict.use_bt
{
let dict_end = $m.dict.region_len();
let dbt_mask = (1usize << (dict.chain_log - 1)) - 1;
let dhigh = dict_end + BT_IDX_BASE;
let dlow = BT_IDX_BASE;
let dbt_low = if dbt_mask >= dhigh - dlow {
dlow
} else {
dhigh - dbt_mask
};
let dh = unsafe {
RowMatchGenerator::key_hash_raw(
$ctx.base,
$ctx.len,
cur_idx,
$m.row_hash_mls,
dict.hash_log,
0,
)
} as usize;
let mut dmi = dict.hc_hash[dh] as usize;
let mut common_smaller = 0usize;
let mut common_larger = 0usize;
while nb_compares > 0 && dmi > dlow {
let next_ptr = 2 * (dmi & dbt_mask);
let mut ml = common_smaller.min(common_larger);
let d_concat = dmi - BT_IDX_BASE;
debug_assert!(d_concat < dict_end);
let (smaller, at_end) = unsafe {
let mptr = $ctx.base.add(d_concat);
ml += $cpl(mptr.add(ml), ip.add(ml), limit - ml);
if ml == limit {
(false, true)
} else {
(*mptr.add(ml) < *ip.add(ml), false)
}
};
if ml > best_len {
let dist = p - (hist_start + d_concat);
if 4 * (ml as i64 - best_len as i64)
> highbit32((dist + 1) as u32)
- highbit32(bt_best_offbase(best_len, best_off) + 1)
{
best_len = ml;
best_off = dist;
}
if at_end {
break;
}
}
if at_end {
break;
}
if dmi <= dbt_low {
break;
}
if smaller {
common_smaller = ml;
dmi = dict.hc_chain[next_ptr + 1] as usize;
} else {
common_larger = ml;
dmi = dict.hc_chain[next_ptr] as usize;
}
nb_compares -= 1;
}
}
debug_assert!(match_end_idx >= 8 + BT_IDX_BASE);
$ntu = match_end_idx - 8 - BT_IDX_BASE;
(best_len, best_off)
}
}};
}
macro_rules! row_cache_hash {
($m:expr, $ctx:ident, $rl:expr, $pos:expr) => {{
match $m.row_hash_at($ctx, $pos) {
Some((row, tag)) => {
$m.prefetch_row::<$rl>(row);
(row as u32, tag)
}
None => (ROW_CACHE_NONE, 0u8),
}
}};
}
macro_rules! row_cache_fill {
($m:expr, $ctx:ident, $cache:ident, $rl:expr, $from:expr) => {{
let from = $from;
for pos in from..from + ROW_HASH_CACHE_SIZE {
$cache[pos & (ROW_HASH_CACHE_SIZE - 1)] = row_cache_hash!($m, $ctx, $rl, pos);
}
}};
}
macro_rules! row_cache_next {
($m:expr, $ctx:ident, $cache:ident, $rl:expr, $pos:expr) => {{
let pos = $pos;
let slot = pos & (ROW_HASH_CACHE_SIZE - 1);
let cur = $cache[slot];
$cache[slot] = row_cache_hash!($m, $ctx, $rl, pos + ROW_HASH_CACHE_SIZE);
cur
}};
}
macro_rules! row_cache_insert_range {
($m:expr, $ctx:ident, $cache:ident, $rl:expr, $from:expr, $to:expr) => {{
for pos in $from..$to {
let (row, tag) = row_cache_next!($m, $ctx, $cache, $rl, pos);
if row != ROW_CACHE_NONE {
$m.insert_at::<$rl>(pos, row as usize, tag);
}
}
}};
}
macro_rules! lazy_parse_body {
($m:expr, $handle:expr, $rl:expr, $finder:expr, $use_mask:literal, $maskmac:ident, $cpl:path) => {{
#[allow(unused_labels)]
'parse: {
debug_assert!($finder != FINDER_ROWS || $rl == $m.row_log);
debug_assert_eq!(
$finder,
match $m.finder {
LazyFinder::Rows => FINDER_ROWS,
LazyFinder::Chain => FINDER_CHAIN,
LazyFinder::Tree => FINDER_TREE,
}
);
$m.ensure_tables();
let (current_abs_start, current_len) = $m.current_block_range();
if current_len == 0 {
break 'parse;
}
let block_end = current_abs_start + current_len;
$m.enter_block(current_abs_start, block_end);
let scan = $m.scan_ctx();
let hist_start = scan.hist_start;
let depth = $m.lazy_depth;
let ilimit = block_end.saturating_sub(if $finder == FINDER_ROWS {
LAZY_ROW_ILIMIT_MARGIN
} else {
LAZY_HC_ILIMIT_MARGIN
});
let mut anchor = current_abs_start;
let first_byte = if scan.dict_frame && !scan.attached {
scan.prefix_start
} else {
hist_start
};
let mut ip = current_abs_start + usize::from(current_abs_start == first_byte);
let mut next_to_update = $m.lazy_next_to_update.max(scan.low_limit);
if current_abs_start > next_to_update + 384 {
next_to_update =
current_abs_start - (current_abs_start - next_to_update - 384).min(192);
}
let mut lazy_skipping = false;
let mut hash_cache = [(ROW_CACHE_NONE, 0u8); ROW_HASH_CACHE_SIZE];
if $finder == FINDER_ROWS {
row_cache_fill!($m, scan, hash_cache, $rl, next_to_update);
}
let [rep_in_1, rep_in_2] = $m
.lazy_reps
.unwrap_or([$m.offset_hist[0] as usize, $m.offset_hist[1] as usize]);
let dict_frame = scan.dict_frame;
let attached = scan.attached;
let prefix_start = scan.prefix_start;
let search_window = scan.search_window;
let rep_ok = |pos: usize, off: usize| -> bool {
if !dict_frame {
return off != 0;
}
if off == 0 || off > pos {
return false;
}
let cand = pos - off;
let floor = if attached {
hist_start
} else {
scan.window_low(pos)
};
cand >= floor && !(cand < prefix_start && cand + 3 >= prefix_start)
};
let mut offset_1 = rep_in_1;
let mut offset_2 = rep_in_2;
let mut offset_saved_1 = 0usize;
let mut offset_saved_2 = 0usize;
if !dict_frame {
let max_rep = ip - prefix_start.max(ip.saturating_sub(search_window));
if offset_2 > max_rep {
offset_saved_2 = offset_2;
offset_2 = 0;
}
if offset_1 > max_rep {
offset_saved_1 = offset_1;
offset_1 = 0;
}
}
while ip < ilimit {
let mut match_length = 0usize;
let mut off = 0usize;
let mut start = ip + 1;
{
let base = scan.base;
unsafe {
let cur = base.add(ip + 1 - hist_start);
if rep_ok(ip + 1, offset_1) && rd32(cur.sub(offset_1)) == rd32(cur) {
match_length =
$cpl(cur.add(4).sub(offset_1), cur.add(4), block_end - (ip + 5))
+ 4;
}
}
}
if !(match_length >= 4 && depth == 0) {
let (ml2, off2) = lazy_search_at!(
$m,
scan,
ip,
next_to_update,
lazy_skipping,
hash_cache,
$rl,
$finder,
$use_mask,
$maskmac,
$cpl
);
if ml2 > match_length {
match_length = ml2;
start = ip;
off = off2;
}
if match_length < 4 {
let gap = (ip - anchor) >> LAZY_SEARCH_STRENGTH;
ip += gap + 1;
lazy_skipping = if dict_frame && !attached {
gap > LAZY_SKIPPING_STEP
} else {
gap + 1 > LAZY_SKIPPING_STEP
};
continue;
}
if depth >= 1 {
while ip < ilimit {
ip += 1;
{
let base = scan.base;
unsafe {
let cur = base.add(ip - hist_start);
if rep_ok(ip, offset_1) && rd32(cur) == rd32(cur.sub(offset_1))
{
let ml_rep = $cpl(
cur.add(4).sub(offset_1),
cur.add(4),
block_end - (ip + 4),
) + 4;
let gain2 = (ml_rep * 3) as i64;
let gain1 =
(match_length * 3) as i64 - offbase_highbit(off) + 1;
if ml_rep >= 4 && gain2 > gain1 {
match_length = ml_rep;
off = 0;
start = ip;
}
}
}
}
let (ml2, off2) = lazy_search_at!(
$m,
scan,
ip,
next_to_update,
lazy_skipping,
hash_cache,
$rl,
$finder,
$use_mask,
$maskmac,
$cpl
);
let gain2 = (ml2 * 4) as i64 - offbase_highbit(off2);
let gain1 = (match_length * 4) as i64 - offbase_highbit(off) + 4;
if ml2 >= 4 && gain2 > gain1 {
match_length = ml2;
off = off2;
start = ip;
continue;
}
if depth == 2 && ip < ilimit {
ip += 1;
{
let base = scan.base;
unsafe {
let cur = base.add(ip - hist_start);
if rep_ok(ip, offset_1)
&& rd32(cur) == rd32(cur.sub(offset_1))
{
let ml_rep = $cpl(
cur.add(4).sub(offset_1),
cur.add(4),
block_end - (ip + 4),
) + 4;
let gain2 = (ml_rep * 4) as i64;
let gain1 = (match_length * 4) as i64
- offbase_highbit(off)
+ 1;
if ml_rep >= 4 && gain2 > gain1 {
match_length = ml_rep;
off = 0;
start = ip;
}
}
}
}
let (ml2, off2) = lazy_search_at!(
$m,
scan,
ip,
next_to_update,
lazy_skipping,
hash_cache,
$rl,
$finder,
$use_mask,
$maskmac,
$cpl
);
let gain2 = (ml2 * 4) as i64 - offbase_highbit(off2);
let gain1 = (match_length * 4) as i64 - offbase_highbit(off) + 7;
if ml2 >= 4 && gain2 > gain1 {
match_length = ml2;
off = off2;
start = ip;
continue;
}
}
break;
}
}
}
if off != 0 {
let concat = $m.live_history();
let m_start = if start - off >= prefix_start {
prefix_start
} else {
hist_start
};
while start > anchor
&& start - off > m_start
&& concat[start - 1 - hist_start] == concat[start - 1 - off - hist_start]
{
start -= 1;
match_length += 1;
}
offset_2 = offset_1;
offset_1 = off;
}
{
let concat = $m.live_history();
let literals = &concat[anchor - hist_start..start - hist_start];
debug_assert!(
offset_1 <= $m.max_window_size || $m.loaded_dict_end != 0,
"offset {} exceeds the advertised window {} with no live dictionary",
offset_1,
$m.max_window_size,
);
$handle(Sequence::Triple {
literals,
offset: offset_1,
match_len: match_length,
});
let _ = encode_offset_with_history(
offset_1 as u32,
(start - anchor) as u32,
&mut $m.offset_hist,
);
}
anchor = start + match_length;
ip = anchor;
if lazy_skipping {
if $finder == FINDER_ROWS {
row_cache_fill!($m, scan, hash_cache, $rl, next_to_update);
}
lazy_skipping = false;
}
while ip <= ilimit && rep_ok(ip, offset_2) {
let base = scan.base;
let rep_len = unsafe {
let cur = base.add(ip - hist_start);
if rd32(cur) != rd32(cur.sub(offset_2)) {
break;
}
$cpl(cur.add(4).sub(offset_2), cur.add(4), block_end - (ip + 4)) + 4
};
core::mem::swap(&mut offset_1, &mut offset_2);
{
let concat = $m.live_history();
$handle(Sequence::Triple {
literals: &concat[ip - hist_start..ip - hist_start],
offset: offset_1,
match_len: rep_len,
});
let _ = encode_offset_with_history(offset_1 as u32, 0, &mut $m.offset_hist);
}
ip += rep_len;
anchor = ip;
}
}
$m.lazy_next_to_update = next_to_update;
let offset_saved_2 = if offset_saved_1 != 0 && offset_1 != 0 {
offset_saved_1
} else {
offset_saved_2
};
$m.lazy_reps = Some([
if offset_1 != 0 {
offset_1
} else {
offset_saved_1
},
if offset_2 != 0 {
offset_2
} else {
offset_saved_2
},
]);
if anchor < block_end {
let concat = $m.live_history();
$handle(Sequence::Literals {
literals: &concat[anchor - hist_start..],
});
}
}
}};
}
macro_rules! gen_lazy_monolith {
($name:ident, $use_mask:literal, $maskmac:ident, $cpl:path $(, $tf:literal)?) => {
$(#[target_feature(enable = $tf)])?
#[cfg_attr(
all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
),
allow(dead_code)
)]
#[allow(unused_unsafe)]
unsafe fn $name<K: RowTags, const ROW_LOG: usize, const FINDER: u8>(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
lazy_parse_body!(self, handle_sequence, ROW_LOG, FINDER, $use_mask, $maskmac, $cpl)
}
};
}
macro_rules! dispatch_lazy {
($self:ident . $m:ident :: <$k:ty> ( $($arg:expr),* )) => {
match $self.finder {
LazyFinder::Rows => match $self.row_log {
4 => $self.$m::<$k, 4, FINDER_ROWS>($($arg),*),
5 => $self.$m::<$k, 5, FINDER_ROWS>($($arg),*),
6 => $self.$m::<$k, 6, FINDER_ROWS>($($arg),*),
_ => unreachable!("row_log is clamped to 4..=6 in configure()"),
},
LazyFinder::Chain => $self.$m::<$k, 4, FINDER_CHAIN>($($arg),*),
LazyFinder::Tree => $self.$m::<$k, 4, FINDER_TREE>($($arg),*),
}
};
}
macro_rules! dispatch_row_log {
($self:ident . $rl_method:ident :: <$k:ty> ( $($arg:expr),* )) => {
match $self.row_log {
4 => $self.$rl_method::<$k, 4>($($arg),*),
5 => $self.$rl_method::<$k, 5>($($arg),*),
6 => $self.$rl_method::<$k, 6>($($arg),*),
_ => unreachable!("row_log is clamped to 4..=6 in configure()"),
}
};
}
macro_rules! row_tag_mask_scalar {
($tags:expr, $tag:expr) => {{
let tags: &[u8] = $tags;
let tag: u8 = $tag;
let mut mask = 0u64;
for (j, &t) in tags.iter().enumerate() {
if t == tag {
mask |= 1u64 << j;
}
}
mask
}};
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
macro_rules! row_tag_mask_sse2 {
($tags:expr, $tag:expr) => {{
#[cfg(target_arch = "x86")]
use core::arch::x86::{_mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
_mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8,
};
let tags: &[u8] = $tags;
let needle = _mm_set1_epi8($tag as i8);
let mut mask = 0u64;
let mut off = 0;
while off + 16 <= tags.len() {
let v = unsafe { _mm_loadu_si128(tags.as_ptr().add(off) as *const _) };
let eq = _mm_cmpeq_epi8(v, needle);
mask |= (_mm_movemask_epi8(eq) as u16 as u64) << off;
off += 16;
}
mask
}};
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
macro_rules! row_tag_mask_avx2 {
($tags:expr, $tag:expr) => {{
#[cfg(target_arch = "x86")]
use core::arch::x86::{
_mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8, _mm256_cmpeq_epi8,
_mm256_loadu_si256, _mm256_movemask_epi8, _mm256_set1_epi8,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
_mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8, _mm256_cmpeq_epi8,
_mm256_loadu_si256, _mm256_movemask_epi8, _mm256_set1_epi8,
};
let tags: &[u8] = $tags;
let tag = $tag;
let needle = _mm256_set1_epi8(tag as i8);
let mut mask = 0u64;
let mut off = 0;
while off + 32 <= tags.len() {
let v = unsafe { _mm256_loadu_si256(tags.as_ptr().add(off) as *const _) };
let eq = _mm256_cmpeq_epi8(v, needle);
mask |= (_mm256_movemask_epi8(eq) as u32 as u64) << off;
off += 32;
}
if off + 16 <= tags.len() {
let needle16 = _mm_set1_epi8(tag as i8);
let v = unsafe { _mm_loadu_si128(tags.as_ptr().add(off) as *const _) };
let eq = _mm_cmpeq_epi8(v, needle16);
mask |= (_mm_movemask_epi8(eq) as u16 as u64) << off;
}
mask
}};
}
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
macro_rules! row_tag_mask_neon {
($tags:expr, $tag:expr) => {{
use core::arch::aarch64::{
vceqq_u8, vdupq_n_u8, vgetq_lane_u8, vld1q_u8, vreinterpretq_u8_u64,
vreinterpretq_u16_u8, vreinterpretq_u32_u16, vreinterpretq_u64_u32, vshrq_n_u8,
vsraq_n_u16, vsraq_n_u32, vsraq_n_u64,
};
let tags: &[u8] = $tags;
let needle = vdupq_n_u8($tag);
let mut mask = 0u64;
let mut off = 0;
while off + 16 <= tags.len() {
let v = unsafe { vld1q_u8(tags.as_ptr().add(off)) };
let eq = vceqq_u8(v, needle);
let high = vshrq_n_u8(eq, 7);
let paired16 = vreinterpretq_u32_u16(vsraq_n_u16(
vreinterpretq_u16_u8(high),
vreinterpretq_u16_u8(high),
7,
));
let paired32 = vreinterpretq_u64_u32(vsraq_n_u32(paired16, paired16, 14));
let paired64 = vreinterpretq_u8_u64(vsraq_n_u64(paired32, paired32, 28));
let bits =
(vgetq_lane_u8(paired64, 0) as u64) | ((vgetq_lane_u8(paired64, 8) as u64) << 8);
mask |= bits << off;
off += 16;
}
mask
}};
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
macro_rules! row_tag_mask_simd128 {
($tags:expr, $tag:expr) => {{
use core::arch::wasm32::{i8x16_bitmask, i8x16_eq, i8x16_splat, v128_load};
let tags: &[u8] = $tags;
let needle = i8x16_splat($tag as i8);
let mut mask = 0u64;
let mut off = 0;
while off + 16 <= tags.len() {
let v = unsafe { v128_load(tags.as_ptr().add(off) as *const _) };
let eq = i8x16_eq(v, needle);
mask |= (i8x16_bitmask(eq) as u64) << off;
off += 16;
}
mask
}};
}
macro_rules! row_probe_body {
($m:expr, $abs_pos:expr, $lit_len:expr, $hash:expr, $seed:expr, $rl:expr, $use_mask:literal, $maskmac:ident, $cpl:path) => {{
#[allow(unused_labels)]
'probe: {
debug_assert_eq!($rl, $m.row_log);
let mls = $m.mls;
let concat = $m.live_history();
let current_idx = $abs_pos - $m.history_abs_start;
if current_idx + mls > concat.len() {
break 'probe None;
}
let (row, tag) = match $hash.or_else(|| $m.hash_and_row($abs_pos)) {
Some(rt) => rt,
None => break 'probe None,
};
let row_entries = 1usize << $rl;
let row_mask = row_entries - 1;
let row_base = row << $rl;
let head = $m.row_heads()[row] as usize;
let max_walk = $m.search_depth.min(row_entries);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if let Some(dict) = $m.dict.table() {
#[cfg(target_arch = "x86")]
use core::arch::x86::{_MM_HINT_T0, _mm_prefetch};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
let drow_base = row << $rl;
unsafe {
_mm_prefetch(dict.tags.as_ptr().add(drow_base).cast(), _MM_HINT_T0);
_mm_prefetch(dict.positions.as_ptr().add(drow_base).cast(), _MM_HINT_T0);
}
}
let tag_match = if $use_mask {
$maskmac!(&$m.row_tags()[row_base..row_base + row_entries], tag)
} else {
0
};
let mut best: Option<MatchCandidate> = $seed;
let entries_bits: u64 = if row_entries >= 64 {
u64::MAX
} else {
(1u64 << row_entries) - 1
};
#[allow(unused_mut)]
let mut pending: u64 = if $use_mask {
let m = tag_match & entries_bits;
if head == 0 {
m
} else {
((m >> head) | (m << (row_entries - head))) & entries_bits
}
} else {
0
};
#[allow(unused_mut)]
let mut scan = 0usize;
let mut attempts = 0usize;
while attempts < max_walk {
let slot_opt = if $use_mask {
if pending == 0 {
None
} else {
let i = pending.trailing_zeros() as usize;
pending &= pending - 1;
Some((head + i) & row_mask)
}
} else {
let mut found = None;
while scan < row_entries {
let s = (head + scan) & row_mask;
scan += 1;
if $m.row_tags()[row_base + s] == tag {
found = Some(s);
break;
}
}
found
};
let Some(slot) = slot_opt else { break };
attempts += 1;
let idx = row_base + slot;
let raw_pos = $m.row_positions()[idx];
if raw_pos == ROW_EMPTY_SLOT {
continue;
}
let candidate_pos = raw_pos as usize;
let window_low = $m
.history_abs_start
.max($abs_pos.saturating_sub($m.max_window_size));
if candidate_pos < window_low || candidate_pos >= $abs_pos {
continue;
}
let candidate_idx = candidate_pos - $m.history_abs_start;
if let Some(b) = best {
let new_offset = $abs_pos - candidate_pos;
if new_offset >= b.offset
&& let Some(tail_off) = b.match_len.checked_sub($lit_len + 3)
{
let m_end = candidate_idx + tail_off + 4;
let i_end = current_idx + tail_off + 4;
if i_end > concat.len()
|| m_end > concat.len()
|| concat[candidate_idx + tail_off..m_end]
!= concat[current_idx + tail_off..i_end]
{
continue;
}
}
}
let match_len = unsafe {
$cpl(
concat.as_ptr().add(candidate_idx),
concat.as_ptr().add(current_idx),
concat.len() - current_idx,
)
};
if match_len >= mls {
let candidate =
$m.extend_backwards(candidate_pos, $abs_pos, match_len, $lit_len);
best = best_len_offset_candidate(best, Some(candidate));
if best.is_some_and(|b| current_idx + b.match_len >= concat.len()) {
break 'probe best;
}
}
}
let dict_budget = max_walk.max(16);
if attempts < dict_budget
&& let Some(dict) = $m.dict.table()
{
let dict_walk = dict_budget - attempts;
let dict_end = $m.dict.region_len();
let drow_base = row << $rl;
let dhead = dict.heads[row] as usize;
let dtag_match = if $use_mask {
$maskmac!(&dict.tags[drow_base..drow_base + row_entries], tag)
} else {
0
};
#[allow(unused_mut)]
let mut dpending: u64 = if $use_mask {
let m = dtag_match & entries_bits;
if dhead == 0 {
m
} else {
((m >> dhead) | (m << (row_entries - dhead))) & entries_bits
}
} else {
0
};
#[allow(unused_mut)]
let mut dscan = 0usize;
let mut dattempts = 0usize;
while dattempts < dict_walk {
let slot_opt = if $use_mask {
if dpending == 0 {
None
} else {
let i = dpending.trailing_zeros() as usize;
dpending &= dpending - 1;
Some((dhead + i) & row_mask)
}
} else {
let mut found = None;
while dscan < row_entries {
let s = (dhead + dscan) & row_mask;
dscan += 1;
if dict.tags[drow_base + s] == tag {
found = Some(s);
break;
}
}
found
};
let Some(slot) = slot_opt else { break };
dattempts += 1;
let didx = drow_base + slot;
let dp = dict.positions[didx];
if dp == ROW_EMPTY_SLOT {
continue;
}
let dp = dp as usize;
if dp >= dict_end || dp + mls > concat.len() {
continue;
}
let cand_abs = $m.history_abs_start + dp;
if let Some(b) = best {
let new_offset = $abs_pos - cand_abs;
if new_offset >= b.offset
&& let Some(tail_off) = b.match_len.checked_sub($lit_len + 3)
{
let m_end = dp + tail_off + 4;
let i_end = current_idx + tail_off + 4;
if i_end > concat.len()
|| m_end > concat.len()
|| concat[dp + tail_off..m_end]
!= concat[current_idx + tail_off..i_end]
{
continue;
}
}
}
let match_len = unsafe {
$cpl(
concat.as_ptr().add(dp),
concat.as_ptr().add(current_idx),
concat.len() - current_idx,
)
};
if match_len >= mls {
let candidate =
$m.extend_backwards(cand_abs, $abs_pos, match_len, $lit_len);
best = best_len_offset_candidate(best, Some(candidate));
if best.is_some_and(|b| current_idx + b.match_len >= concat.len()) {
break 'probe best;
}
}
}
}
best
}
}};
}
macro_rules! gen_row_probe {
($name:ident, $use_mask:literal, $maskmac:ident, $cpl:path $(, $tf:literal)?) => {
$(#[target_feature(enable = $tf)])?
#[inline]
#[allow(unused_unsafe)]
#[cfg_attr(
all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
),
allow(dead_code)
)]
unsafe fn $name<const ROW_LOG: usize>(
&self,
abs_pos: usize,
lit_len: usize,
hash: Option<(usize, u8)>,
) -> Option<MatchCandidate> {
row_probe_body!(self, abs_pos, lit_len, hash, None, ROW_LOG, $use_mask, $maskmac, $cpl)
}
};
}
#[cfg(all(
test,
any(
all(
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
),
all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
)
)
))]
fn row_tag_match_mask_scalar(tags: &[u8], tag: u8) -> u64 {
row_tag_mask_scalar!(tags, tag)
}
#[cfg(all(
test,
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
#[target_feature(enable = "sse2")]
unsafe fn row_tag_match_mask_sse2(tags: &[u8], tag: u8) -> u64 {
row_tag_mask_sse2!(tags, tag)
}
#[cfg(all(
test,
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
#[target_feature(enable = "avx2")]
unsafe fn row_tag_match_mask_avx2(tags: &[u8], tag: u8) -> u64 {
row_tag_mask_avx2!(tags, tag)
}
#[cfg(all(
test,
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
#[target_feature(enable = "neon")]
unsafe fn row_tag_match_mask_neon(tags: &[u8], tag: u8) -> u64 {
row_tag_mask_neon!(tags, tag)
}
#[derive(Clone)]
pub(crate) struct RowMatchGenerator {
pub(crate) max_window_size: usize,
pub(crate) chunk_lens: VecDeque<usize>,
pub(crate) window_size: usize,
pub(crate) uncommitted_len: usize,
pub(crate) history: Vec<u8>,
pub(crate) history_start: usize,
pub(crate) history_abs_start: usize,
pub(crate) offset_hist: [u32; 3],
finder: LazyFinder,
hc_chain_log: usize,
hc_layout: LazyFinder,
loaded_dict_end: usize,
low_limit: usize,
prefix_low: usize,
hash_salt: u64,
dict_plan: Option<RowDictPlan>,
search_window: usize,
pub(crate) row_hash_log: usize,
pub(crate) row_log: usize,
pub(crate) search_depth: usize,
pub(crate) target_len: usize,
pub(crate) mls: usize,
row_hash_mls: u32,
lazy_next_to_update: usize,
lazy_reps: Option<[usize; 2]>,
pub(crate) lazy_depth: u8,
pub(crate) cpl_kernel: crate::encoding::fastpath::FastpathKernel,
pub(crate) tables: Vec<u32>,
hc_split: usize,
rows_len: usize,
heads_len: usize,
tags_len: usize,
#[cfg_attr(
all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
),
allow(dead_code)
)]
tag_kernel: FastpathKernel,
pub(crate) dict: DictAttach<RowDictTables>,
pub(crate) borrowed_input: Option<(*const u8, usize)>,
pub(crate) borrowed_block: Option<(usize, usize)>,
borrowed_extent: usize,
dict_resident: bool,
}
impl RowMatchGenerator {
pub(crate) fn new(max_window_size: usize) -> Self {
Self {
max_window_size,
chunk_lens: VecDeque::new(),
window_size: 0,
uncommitted_len: 0,
history: Vec::new(),
history_start: 0,
history_abs_start: 0,
offset_hist: [1, 4, 8],
finder: LazyFinder::Rows,
hc_chain_log: ROW_HASH_BITS,
tables: Vec::new(),
hc_split: 0,
hc_layout: LazyFinder::Chain,
loaded_dict_end: 0,
low_limit: 0,
prefix_low: 0,
dict_resident: false,
hash_salt: ROW_HASH_SALT,
dict_plan: None,
search_window: 0,
row_hash_log: ROW_HASH_BITS - ROW_LOG,
row_log: ROW_LOG,
search_depth: ROW_SEARCH_DEPTH,
target_len: ROW_TARGET_LEN,
mls: ROW_MIN_MATCH_LEN,
row_hash_mls: ROW_MIN_MATCH_LEN.clamp(4, 6) as u32,
lazy_next_to_update: 0,
lazy_reps: None,
lazy_depth: 1,
cpl_kernel: crate::encoding::fastpath::select_kernel(),
rows_len: 0,
heads_len: 0,
tags_len: 0,
tag_kernel: crate::encoding::fastpath::select_kernel(),
dict: DictAttach::new(),
borrowed_input: None,
borrowed_block: None,
borrowed_extent: 0,
}
}
pub(crate) fn heap_size(&self) -> usize {
let u32_sz = core::mem::size_of::<u32>();
self.chunk_lens.capacity() * core::mem::size_of::<usize>()
+ self.history.capacity()
+ self.tables.capacity() * u32_sz
+ self.dict.table().map_or(0, |t| {
t.heads.capacity()
+ t.positions.capacity() * u32_sz
+ t.tags.capacity()
+ (t.hc_hash.capacity() + t.hc_chain.capacity()) * u32_sz
})
}
pub(crate) fn hash_bits(&self) -> usize {
self.row_hash_log + self.row_log
}
#[cfg(test)]
pub(crate) fn uses_hash_chain(&self) -> bool {
self.finder == LazyFinder::Chain
}
#[cfg(test)]
pub(crate) fn uses_binary_tree(&self) -> bool {
self.finder == LazyFinder::Tree
}
#[cfg(test)]
pub(crate) fn hc_chain_log(&self) -> usize {
self.hc_chain_log
}
fn enter_block(&mut self, block_start: usize, block_end: usize) {
if self.loaded_dict_end != 0 && block_end > self.loaded_dict_end + self.search_window {
self.loaded_dict_end = 0;
}
if block_start > self.search_window + self.loaded_dict_end {
let new_low = block_start - self.search_window;
if self.low_limit < new_low {
self.low_limit = new_low;
}
if self.prefix_low < self.low_limit {
self.prefix_low = self.low_limit;
}
self.loaded_dict_end = 0;
}
}
pub(crate) fn set_hash_bits(&mut self, bits: usize) {
let clamped = bits.clamp(self.row_log + 1, ROW_HASH_BITS);
let row_hash_log = clamped.saturating_sub(self.row_log);
if self.row_hash_log != row_hash_log {
self.row_hash_log = row_hash_log;
self.tables.clear();
self.hc_split = 0;
self.rows_len = 0;
self.heads_len = 0;
self.tags_len = 0;
}
}
pub(crate) fn configure(&mut self, config: RowConfig) {
self.row_log = config.row_log.clamp(4, 6);
self.search_depth = config.search_depth;
self.target_len = config.target_len;
self.mls = config.mls.clamp(ROW_HASH_KEY_LEN, 7);
self.row_hash_mls = self.mls.clamp(4, 6) as u32;
self.finder = if config.bt {
LazyFinder::Tree
} else {
let rows = match self.dict_plan {
Some(plan) => plan.use_row,
None => self.max_window_size > (1usize << 14),
};
if rows {
LazyFinder::Rows
} else {
LazyFinder::Chain
}
};
self.hash_salt = match self.dict_plan {
Some(plan) if !plan.attach => 0,
_ => ROW_HASH_SALT,
};
self.search_window = self.max_window_size;
self.hc_chain_log = config.chain_log.max(1);
self.set_hash_bits(config.hash_bits.max(self.row_log + 1));
}
pub(crate) fn set_offset_hist(&mut self, offset_hist: [u32; 3]) {
self.offset_hist = offset_hist;
self.lazy_reps = None;
}
pub(crate) fn set_dict_plan(&mut self, plan: Option<RowDictPlan>) {
if self.dict_plan != plan {
self.dict.invalidate();
}
self.dict_plan = plan;
}
pub(crate) fn reset(&mut self) {
self.history
.truncate(self.history.len() - self.uncommitted_len);
self.uncommitted_len = 0;
let next_floor = self.history_abs_start
+ (self.history.len() - self.history_start).max(self.borrowed_extent);
self.borrowed_extent = 0;
self.offset_hist = [1, 4, 8];
let reborrow_region = if self.dict.is_primed()
&& self.history_start == 0
&& next_floor <= REBASE_RESET_FLOOR_CEILING
&& !self.tables.is_empty()
{
let r = self.dict.region_len();
(r > 0 && self.history.len() >= r).then_some(r)
} else {
None
};
if let Some(region) = reborrow_region {
self.history.truncate(region);
self.window_size = region;
} else {
self.window_size = 0;
self.history.clear();
}
self.history_start = 0;
self.dict_resident = reborrow_region.is_some();
self.borrowed_input = None;
self.borrowed_block = None;
let tables_allocated = !self.tables.is_empty();
if next_floor <= REBASE_RESET_FLOOR_CEILING && tables_allocated {
self.history_abs_start = next_floor;
} else {
self.history_abs_start = 0;
let empty = if self.hc_split == 0 {
ROW_EMPTY_SLOT
} else {
self.hc_empty_slot()
};
self.tables.fill(empty);
if self.rows_len != 0 {
self.tail_bytes_mut().fill(0);
}
}
self.lazy_reps = None;
self.chunk_lens.clear();
let Some(region) = reborrow_region else {
self.lazy_next_to_update = self.history_abs_start;
self.loaded_dict_end = 0;
self.low_limit = self.history_abs_start;
self.prefix_low = self.history_abs_start;
return;
};
self.chunk_lens.push_back(region);
let dict_end = self.history_abs_start + region;
self.loaded_dict_end = dict_end;
self.prefix_low = dict_end;
self.low_limit = match self.dict_plan {
Some(plan) if plan.attach => dict_end,
_ => self.history_abs_start,
};
self.lazy_next_to_update = dict_end;
let headroom =
crate::encoding::match_table::storage::MAX_PRIMED_WINDOW_SIZE - self.max_window_size;
self.max_window_size += region.min(headroom);
}
pub(crate) fn dict_resident(&self) -> bool {
self.dict_resident
}
pub(crate) fn get_last_space(&self) -> &[u8] {
if let (Some((ptr, _total)), Some((block_start, block_end))) =
(self.borrowed_input, self.borrowed_block)
{
return unsafe {
core::slice::from_raw_parts(ptr.add(block_start), block_end - block_start)
};
}
let last = *self.chunk_lens.back().unwrap();
&self.history[self.history.len() - self.uncommitted_len - last
..self.history.len() - self.uncommitted_len]
}
pub(crate) fn fill_uncommitted(
&mut self,
capacity: usize,
fill: impl FnOnce(&mut Vec<u8>) -> (usize, bool),
) -> (usize, bool) {
super::match_table::storage::check_stream_abs_headroom(
self.history_abs_start,
self.window_size,
capacity + self.uncommitted_len,
);
self.history.reserve(capacity);
let before = self.history.len();
let (appended, eof) = fill(&mut self.history);
debug_assert_eq!(
self.history.len(),
before + appended,
"fill_uncommitted: fill reported {appended} bytes but grew history by {}",
self.history.len() - before,
);
self.uncommitted_len += appended;
(appended, eof)
}
pub(crate) fn reserve_for_frame(&mut self, bytes: usize) {
let ceiling = self.max_window_size
+ (self.max_window_size >> 2)
+ crate::common::MAX_BLOCK_SIZE as usize;
let target = self.history.len().saturating_add(bytes).min(ceiling);
if self.history.capacity() < target {
self.history.reserve_exact(target - self.history.len());
}
}
pub(crate) fn uncommitted(&self) -> &[u8] {
&self.history[self.history.len() - self.uncommitted_len..]
}
pub(crate) fn commit_block(&mut self, len: usize) {
if len == 0 {
return;
}
assert!(len <= self.max_window_size);
assert!(
len <= self.uncommitted().len(),
"commit_block: {len} exceeds the {} uncommitted bytes",
self.uncommitted().len(),
);
if self.history_abs_start + self.window_size + len >= u32::MAX as usize - 1 - BT_IDX_BASE {
self.rebase_positions();
}
let evicted_from = self.history_start;
while self.window_size > self.max_window_size {
let removed_len = self.chunk_lens.pop_front().unwrap();
self.window_size -= removed_len;
self.history_start += removed_len;
self.history_abs_start += removed_len;
}
if self.history_start != evicted_from {
self.dict.invalidate();
let target = self.max_window_size
+ (self.max_window_size >> 2)
+ crate::common::MAX_BLOCK_SIZE as usize;
if target > self.history.len() && self.history.capacity() < target {
self.history.reserve_exact(target - self.history.len());
}
}
if self.low_limit < self.history_abs_start {
self.low_limit = self.history_abs_start;
}
if self.prefix_low < self.low_limit {
self.prefix_low = self.low_limit;
}
self.compact_history();
self.window_size += len;
self.chunk_lens.push_back(len);
self.uncommitted_len -= len;
}
pub(crate) fn add_data(&mut self, data: Vec<u8>, mut reuse_space: impl FnMut(Vec<u8>)) {
assert!(data.len() <= self.max_window_size);
super::match_table::storage::check_stream_abs_headroom(
self.history_abs_start,
self.window_size,
data.len(),
);
if self.history_abs_start + self.window_size + data.len()
>= u32::MAX as usize - 1 - BT_IDX_BASE
{
self.rebase_positions();
}
let evicted_from = self.history_start;
while self.window_size > self.max_window_size {
let removed_len = self.chunk_lens.pop_front().unwrap();
self.window_size -= removed_len;
self.history_start += removed_len;
self.history_abs_start += removed_len;
}
if self.history_start != evicted_from {
self.dict.invalidate();
let target = self.max_window_size
+ (self.max_window_size >> 2)
+ crate::common::MAX_BLOCK_SIZE as usize;
if target > self.history.len() && self.history.capacity() < target {
self.history.reserve_exact(target - self.history.len());
}
}
if self.low_limit < self.history_abs_start {
self.low_limit = self.history_abs_start;
}
if self.prefix_low < self.low_limit {
self.prefix_low = self.low_limit;
}
self.compact_history();
let added = data.len();
self.history.extend_from_slice(&data);
self.window_size += added;
self.chunk_lens.push_back(added);
reuse_space(data);
}
pub(crate) fn trim_to_window(&mut self) {
if self.window_size > self.max_window_size {
self.dict.invalidate();
}
while self.window_size > self.max_window_size {
let removed_len = self.chunk_lens.pop_front().unwrap();
self.window_size -= removed_len;
self.history_start += removed_len;
self.history_abs_start += removed_len;
}
if self.low_limit < self.history_abs_start {
self.low_limit = self.history_abs_start;
}
if self.prefix_low < self.low_limit {
self.prefix_low = self.low_limit;
}
}
fn rebase_positions(&mut self) {
let delta = self.history_abs_start;
if delta == 0 {
return;
}
let rebase_abs = |slot: &mut u32| {
if *slot == ROW_EMPTY_SLOT {
return;
}
let abs = *slot as usize;
*slot = if abs < delta {
ROW_EMPTY_SLOT
} else {
(abs - delta) as u32
};
};
let rebase_tree = |slot: &mut u32| {
let v = *slot as usize;
if v < BT_IDX_BASE {
return;
}
let abs = v - BT_IDX_BASE;
*slot = if abs < delta {
0
} else {
(abs - delta + BT_IDX_BASE) as u32
};
};
if self.hc_split != 0 && self.hc_layout == LazyFinder::Tree {
self.tables.iter_mut().for_each(rebase_tree);
} else if self.rows_len != 0 {
self.row_positions_mut().iter_mut().for_each(rebase_abs);
} else {
self.tables.iter_mut().for_each(rebase_abs);
}
self.lazy_next_to_update = self.lazy_next_to_update.saturating_sub(delta);
self.low_limit = self.low_limit.saturating_sub(delta);
self.prefix_low = self.prefix_low.saturating_sub(delta);
if self.loaded_dict_end != 0 {
self.loaded_dict_end = self.loaded_dict_end.saturating_sub(delta);
}
self.history_abs_start -= delta;
}
pub(crate) fn skip_matching_with_hint_rl<const ROW_LOG: usize>(
&mut self,
incompressible_hint: Option<bool>,
) {
debug_assert_eq!(ROW_LOG, self.row_log);
self.ensure_tables();
let (current_abs_start, current_len) = self.current_block_range();
let current_abs_end = current_abs_start + current_len;
if self.finder != LazyFinder::Rows {
if self.finder == LazyFinder::Chain
&& let Some(incompressible) = incompressible_hint
{
let step = if incompressible {
INCOMPRESSIBLE_SKIP_STEP
} else {
1
};
let ctx = self.scan_ctx();
let chain_mask = (1usize << self.hc_chain_log) - 1;
let from = current_abs_start
.max(current_abs_end.saturating_sub(self.search_window))
.max(self.low_limit);
let mut idx = from;
while idx + ROW_HASH_KEY_LEN <= current_abs_end {
let h = self.hc_hash_at(ctx, idx);
let (hash, chain) = self.hc_tables_mut();
chain[idx & chain_mask] = hash[h];
hash[h] = idx as u32;
idx += step;
}
}
self.lazy_next_to_update = current_abs_end.saturating_sub(ROW_HASH_KEY_LEN - 1);
return;
}
let backfill_start = self.backfill_start(current_abs_start);
if backfill_start < current_abs_start {
self.insert_positions::<ROW_LOG>(backfill_start, current_abs_start);
}
match incompressible_hint {
Some(true) => {
self.insert_positions_with_step::<ROW_LOG>(
current_abs_start,
current_abs_end,
INCOMPRESSIBLE_SKIP_STEP,
);
let dense_tail = ROW_MIN_MATCH_LEN + INCOMPRESSIBLE_SKIP_STEP;
let tail_start = current_abs_end
.saturating_sub(dense_tail)
.max(current_abs_start);
for pos in tail_start..current_abs_end {
if !(pos - current_abs_start).is_multiple_of(INCOMPRESSIBLE_SKIP_STEP) {
self.insert_position::<ROW_LOG>(pos);
}
}
}
Some(false) => {
self.insert_positions::<ROW_LOG>(current_abs_start, current_abs_end);
}
None => {
}
}
self.lazy_next_to_update = current_abs_end.saturating_sub(ROW_HASH_KEY_LEN - 1);
}
pub(crate) fn release_tables(&mut self) {
self.tables = Vec::new();
self.hc_split = 0;
self.rows_len = 0;
self.heads_len = 0;
self.tags_len = 0;
}
#[inline(always)]
pub(crate) fn row_positions(&self) -> &[u32] {
&self.tables[..self.rows_len]
}
#[inline(always)]
pub(crate) fn row_positions_mut(&mut self) -> &mut [u32] {
&mut self.tables[..self.rows_len]
}
#[inline(always)]
fn tail_bytes_mut(&mut self) -> &mut [u8] {
let bytes = self.heads_len + self.tags_len;
debug_assert!(self.rows_len * 4 + bytes <= self.tables.len() * 4);
unsafe {
core::slice::from_raw_parts_mut(
self.tables.as_mut_ptr().add(self.rows_len).cast::<u8>(),
bytes,
)
}
}
#[inline(always)]
pub(crate) fn row_heads(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.tables.as_ptr().add(self.rows_len).cast::<u8>(),
self.heads_len,
)
}
}
#[inline(always)]
pub(crate) fn row_heads_mut(&mut self) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(
self.tables.as_mut_ptr().add(self.rows_len).cast::<u8>(),
self.heads_len,
)
}
}
#[inline(always)]
pub(crate) fn row_tags(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.tables
.as_ptr()
.add(self.rows_len)
.cast::<u8>()
.add(self.heads_len),
self.tags_len,
)
}
}
#[inline(always)]
pub(crate) fn row_tags_mut(&mut self) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(
self.tables
.as_mut_ptr()
.add(self.rows_len)
.cast::<u8>()
.add(self.heads_len),
self.tags_len,
)
}
}
#[inline(always)]
pub(crate) fn hc_hash(&self) -> &[u32] {
&self.tables[..self.hc_split]
}
#[inline(always)]
pub(crate) fn hc_chain(&self) -> &[u32] {
if self.hc_split == 0 {
&[]
} else {
&self.tables[self.hc_split..]
}
}
#[inline(always)]
pub(crate) fn hc_tables_mut(&mut self) -> (&mut [u32], &mut [u32]) {
self.tables.split_at_mut(self.hc_split)
}
pub(crate) fn ensure_tables(&mut self) {
let row_count = 1usize << self.row_hash_log;
let row_entries = 1usize << self.row_log;
let total = if self.finder == LazyFinder::Rows {
row_count * row_entries
} else {
0
};
let tail_u32 = (row_count + total).div_ceil(4);
let want = total + tail_u32;
if total == 0 {
self.rows_len = 0;
self.heads_len = 0;
self.tags_len = 0;
} else if self.rows_len != total || self.tables.len() != want || self.hc_split != 0 {
if super::match_table::storage::capacity_is_oversized(self.tables.capacity(), want) {
self.tables = alloc::vec![ROW_EMPTY_SLOT; want];
} else {
self.tables.clear();
self.tables.reserve_exact(want);
self.tables.resize(want, ROW_EMPTY_SLOT);
}
self.hc_split = 0;
self.rows_len = total;
self.heads_len = row_count;
self.tags_len = total;
self.tail_bytes_mut().fill(0);
}
if self.finder != LazyFinder::Rows {
let hash_len = 1usize << (self.row_hash_log + self.row_log);
let chain_len = 1usize << self.hc_chain_log;
let empty = self.hc_empty_slot();
let relayout = self.hc_layout != self.finder;
if self.hc_split != hash_len || self.tables.len() != hash_len + chain_len || relayout {
let total = hash_len + chain_len;
if empty == 0
|| super::match_table::storage::capacity_is_oversized(
self.tables.capacity(),
total,
)
{
self.tables = alloc::vec![empty; total];
} else {
self.tables.clear();
self.tables.reserve_exact(total);
self.tables.resize(total, empty);
}
self.hc_split = hash_len;
}
self.hc_layout = self.finder;
} else {
self.hc_split = 0;
}
}
#[cfg(test)]
pub(crate) fn tables_capacity(&self) -> usize {
self.tables.capacity()
}
#[cfg(test)]
pub(crate) fn hc_tables_len(&self) -> usize {
self.hc_hash().len() + self.hc_chain().len()
}
#[cfg(test)]
pub(crate) fn abs_floor(&self) -> usize {
self.history_abs_start
}
#[cfg(test)]
pub(crate) fn set_abs_floor(&mut self, floor: usize) {
self.history_abs_start = floor;
self.low_limit = floor;
self.prefix_low = floor;
self.lazy_next_to_update = floor;
}
fn hc_empty_slot(&self) -> u32 {
if self.finder == LazyFinder::Tree {
0
} else {
ROW_EMPTY_SLOT
}
}
fn compact_history(&mut self) {
if self.history_start == 0 {
return;
}
if self.history_start >= (self.max_window_size >> 2)
|| self.history_start * 2 >= self.history.len() - self.uncommitted_len
{
self.history.drain(..self.history_start);
self.history_start = 0;
}
}
pub(crate) fn live_history(&self) -> &[u8] {
if let Some((_start, end)) = self.borrowed_block {
let (ptr, total) = self
.borrowed_input
.expect("borrowed_block set without a registered borrowed window");
debug_assert!(
end <= total,
"borrowed block end {end} exceeds window {total}"
);
return unsafe { core::slice::from_raw_parts(ptr, end) };
}
&self.history[self.history_start..self.history.len() - self.uncommitted_len]
}
fn history_abs_end(&self) -> usize {
self.history_abs_start + self.live_history().len()
}
pub(crate) unsafe fn set_borrowed_window(&mut self, buffer: &[u8]) {
if self.history_abs_start as u64 + buffer.len() as u64
>= u32::MAX as u64 - 1 - BT_IDX_BASE as u64
{
self.rebase_positions();
}
self.borrowed_input = Some((buffer.as_ptr(), buffer.len()));
self.borrowed_block = None;
self.borrowed_extent = 0;
self.loaded_dict_end = 0;
}
pub(crate) fn clear_borrowed_window(&mut self) {
self.borrowed_input = None;
self.borrowed_block = None;
}
pub(crate) fn stage_borrowed_block(&mut self, block_start: usize, block_end: usize) {
let (_ptr, total) = self
.borrowed_input
.expect("stage_borrowed_block requires a registered borrowed window");
assert!(
block_start <= block_end && block_end <= total,
"borrowed block bounds out of range: start={block_start} end={block_end} total={total}",
);
self.borrowed_block = Some((block_start, block_end));
self.borrowed_extent = self.borrowed_extent.max(block_end);
}
fn current_block_range(&self) -> (usize, usize) {
if let Some((start, end)) = self.borrowed_block {
(self.history_abs_start + start, end - start)
} else {
let current_len = *self.chunk_lens.back().unwrap();
(
self.history_abs_start + self.window_size - current_len,
current_len,
)
}
}
#[inline(always)]
unsafe fn key_hash_raw(
base: *const u8,
len: usize,
idx: usize,
mls: u32,
bits: usize,
salt: u64,
) -> u64 {
debug_assert!(bits <= 32);
unsafe {
let p = base.add(idx);
if mls == 4 || idx + 8 > len {
let v = rd32(p).wrapping_mul(ROW_HASH_PRIME4) ^ (salt as u32);
u64::from(v >> (32 - bits))
} else {
let v = u64::from_le_bytes(p.cast::<[u8; 8]>().read_unaligned());
let h = if mls == 5 {
(v << 24).wrapping_mul(ROW_HASH_PRIME5)
} else {
(v << 16).wrapping_mul(ROW_HASH_PRIME6)
};
(h ^ salt) >> (64 - bits)
}
}
}
#[inline(always)]
fn hc_hash_at(&self, ctx: RowScan, abs_pos: usize) -> usize {
let idx = abs_pos - ctx.hist_start;
debug_assert!(idx + ROW_HASH_KEY_LEN <= ctx.len);
let bits = self.row_hash_log + self.row_log;
unsafe { Self::key_hash_raw(ctx.base, ctx.len, idx, self.row_hash_mls, bits, 0) as usize }
}
#[inline(always)]
fn scan_ctx(&self) -> RowScan {
let history = self.live_history();
let dict_frame = self.loaded_dict_end != 0;
RowScan {
base: history.as_ptr(),
len: history.len(),
hist_start: self.history_abs_start,
salt: self.hash_salt,
search_window: self.search_window,
low_limit: self.low_limit,
prefix_start: self.prefix_low,
dict_frame,
attached: dict_frame && self.dict_plan.is_some_and(|p| p.attach),
}
}
#[inline(always)]
fn row_hash_at(&self, ctx: RowScan, abs_pos: usize) -> Option<(usize, u8)> {
let idx = abs_pos - ctx.hist_start;
if idx + ROW_HASH_KEY_LEN > ctx.len {
return None;
}
let total_bits = self.row_hash_log + ROW_TAG_BITS;
let combined = unsafe {
Self::key_hash_raw(
ctx.base,
ctx.len,
idx,
self.row_hash_mls,
total_bits,
ctx.salt,
)
};
let row_mask = (1usize << self.row_hash_log) - 1;
let row = ((combined >> ROW_TAG_BITS) as usize) & row_mask;
Some((row, combined as u8))
}
#[inline(always)]
pub(crate) fn hash_and_row(&self, abs_pos: usize) -> Option<(usize, u8)> {
self.row_hash_at(self.scan_ctx(), abs_pos)
}
fn backfill_start(&self, current_abs_start: usize) -> usize {
current_abs_start
.saturating_sub(ROW_HASH_KEY_LEN - 1)
.max(self.history_abs_start)
}
#[inline(always)]
pub(crate) fn best_match_rl<K: RowTags, const ROW_LOG: usize>(
&self,
abs_pos: usize,
lit_len: usize,
) -> Option<MatchCandidate> {
let rep = self.repcode_candidate(abs_pos, lit_len);
let row = unsafe { K::probe::<ROW_LOG>(self, abs_pos, lit_len, None) };
best_len_offset_candidate(rep, row)
}
#[inline(always)]
pub(crate) fn pick_lazy_match_rl<K: RowTags, const ROW_LOG: usize>(
&self,
abs_pos: usize,
lit_len: usize,
best: Option<MatchCandidate>,
) -> Option<MatchCandidate> {
let best = best?;
match crate::encoding::lazy_parse::lazy_decide!(
best_len = best.match_len,
best_off = best.offset,
target_len = usize::MAX,
lazy_depth = self.lazy_depth,
abs_pos = abs_pos,
lit_len = lit_len,
history_end = self.history_abs_end(),
min_match = self.mls,
search = |p, l| self.best_match_rl::<K, ROW_LOG>(p, l),
) {
::core::option::Option::None => Some(best),
::core::option::Option::Some(_) => None,
}
}
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn repcode_candidate(
&self,
abs_pos: usize,
lit_len: usize,
) -> Option<MatchCandidate> {
repcode_candidate_shared(
self.cpl_kernel,
self.live_history(),
self.history_abs_start,
self.offset_hist,
abs_pos,
lit_len,
self.mls,
)
}
pub(crate) fn start_matching(&mut self, handle_sequence: impl for<'a> FnMut(Sequence<'a>)) {
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
{
unsafe { dispatch_lazy!(self.lazy_simd128::<Simd128Tags>(handle_sequence)) }
}
#[cfg(not(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
)))]
{
match self.tag_kernel {
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
FastpathKernel::Avx2Bmi2 => unsafe {
dispatch_lazy!(self.lazy_avx2bmi2::<Avx2Bmi2Tags>(handle_sequence))
},
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
FastpathKernel::Sse2 | FastpathKernel::Sse42 => unsafe {
dispatch_lazy!(self.lazy_sse2::<Sse2Tags>(handle_sequence))
},
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
FastpathKernel::Neon => unsafe {
dispatch_lazy!(self.lazy_neon::<NeonTags>(handle_sequence))
},
FastpathKernel::Scalar => unsafe {
dispatch_lazy!(self.lazy_scalar::<ScalarTags>(handle_sequence))
},
}
}
}
gen_lazy_monolith!(
lazy_scalar,
false,
row_tag_mask_scalar,
crate::encoding::fastpath::scalar::common_prefix_len_ptr
);
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
gen_lazy_monolith!(
lazy_sse2,
true,
row_tag_mask_sse2,
crate::encoding::fastpath::sse2::common_prefix_len_ptr,
"sse2"
);
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
gen_lazy_monolith!(
lazy_avx2bmi2,
true,
row_tag_mask_avx2,
crate::encoding::fastpath::avx2_bmi2::common_prefix_len_ptr,
"avx2,bmi2"
);
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
gen_lazy_monolith!(
lazy_neon,
true,
row_tag_mask_neon,
crate::encoding::fastpath::neon::common_prefix_len_ptr,
"neon"
);
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
gen_lazy_monolith!(
lazy_simd128,
true,
row_tag_mask_simd128,
crate::encoding::fastpath::scalar::common_prefix_len_ptr
);
pub(crate) fn skip_matching_with_hint(&mut self, incompressible_hint: Option<bool>) {
match self.row_log {
4 => self.skip_matching_with_hint_rl::<4>(incompressible_hint),
5 => self.skip_matching_with_hint_rl::<5>(incompressible_hint),
6 => self.skip_matching_with_hint_rl::<6>(incompressible_hint),
_ => unreachable!("row_log is clamped to 4..=6 in configure()"),
}
}
pub(crate) fn start_matching_borrowed(
&mut self,
block_start: usize,
block_end: usize,
greedy: bool,
handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
self.stage_borrowed_block(block_start, block_end);
let _ = greedy;
self.start_matching(handle_sequence);
}
pub(crate) fn skip_matching_borrowed(
&mut self,
block_start: usize,
block_end: usize,
incompressible_hint: Option<bool>,
) {
self.stage_borrowed_block(block_start, block_end);
self.skip_matching_with_hint(incompressible_hint);
}
#[allow(dead_code)]
pub(crate) fn best_match(&self, abs_pos: usize, lit_len: usize) -> Option<MatchCandidate> {
dispatch_tag_kernel!(self.best_match_k(abs_pos, lit_len))
}
fn best_match_k<K: RowTags>(&self, abs_pos: usize, lit_len: usize) -> Option<MatchCandidate> {
dispatch_row_log!(self.best_match_rl::<K>(abs_pos, lit_len))
}
#[allow(dead_code)]
pub(crate) fn pick_lazy_match(
&self,
abs_pos: usize,
lit_len: usize,
best: Option<MatchCandidate>,
) -> Option<MatchCandidate> {
dispatch_tag_kernel!(self.pick_lazy_match_k(abs_pos, lit_len, best))
}
fn pick_lazy_match_k<K: RowTags>(
&self,
abs_pos: usize,
lit_len: usize,
best: Option<MatchCandidate>,
) -> Option<MatchCandidate> {
dispatch_row_log!(self.pick_lazy_match_rl::<K>(abs_pos, lit_len, best))
}
#[allow(dead_code)]
pub(crate) fn row_candidate(&self, abs_pos: usize, lit_len: usize) -> Option<MatchCandidate> {
dispatch_tag_kernel!(self.row_candidate_k(abs_pos, lit_len))
}
fn row_candidate_k<K: RowTags>(
&self,
abs_pos: usize,
lit_len: usize,
) -> Option<MatchCandidate> {
match self.row_log {
4 => unsafe { K::probe::<4>(self, abs_pos, lit_len, None) },
5 => unsafe { K::probe::<5>(self, abs_pos, lit_len, None) },
6 => unsafe { K::probe::<6>(self, abs_pos, lit_len, None) },
_ => unreachable!("row_log is clamped to 4..=6 in configure()"),
}
}
gen_row_probe!(
row_probe_scalar,
false,
row_tag_mask_scalar,
crate::encoding::fastpath::scalar::common_prefix_len_ptr
);
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
gen_row_probe!(
row_probe_sse2,
true,
row_tag_mask_sse2,
crate::encoding::fastpath::sse2::common_prefix_len_ptr,
"sse2"
);
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
gen_row_probe!(
row_probe_avx2bmi2,
true,
row_tag_mask_avx2,
crate::encoding::fastpath::avx2_bmi2::common_prefix_len_ptr,
"avx2,bmi2"
);
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
gen_row_probe!(
row_probe_neon,
true,
row_tag_mask_neon,
crate::encoding::fastpath::neon::common_prefix_len_ptr,
"neon"
);
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
gen_row_probe!(
row_probe_simd128,
true,
row_tag_mask_simd128,
crate::encoding::fastpath::scalar::common_prefix_len_ptr
);
fn extend_backwards(
&self,
candidate_pos: usize,
abs_pos: usize,
match_len: usize,
lit_len: usize,
) -> MatchCandidate {
extend_backwards_shared(
self.live_history(),
self.history_abs_start,
candidate_pos,
abs_pos,
match_len,
lit_len,
)
}
fn insert_positions<const ROW_LOG: usize>(&mut self, start: usize, end: usize) {
for pos in start..end {
self.insert_position::<ROW_LOG>(pos);
}
}
fn insert_positions_with_step<const ROW_LOG: usize>(
&mut self,
start: usize,
end: usize,
step: usize,
) {
if step <= 1 {
self.insert_positions::<ROW_LOG>(start, end);
return;
}
let mut pos = start;
while pos < end {
self.insert_position::<ROW_LOG>(pos);
let next = pos.saturating_add(step);
if next <= pos {
break;
}
pos = next;
}
}
#[inline(always)]
fn insert_position<const ROW_LOG: usize>(&mut self, abs_pos: usize) {
let Some((row, tag)) = self.hash_and_row(abs_pos) else {
return;
};
self.insert_at::<ROW_LOG>(abs_pos, row, tag);
}
#[inline]
fn prefetch_row<const ROW_LOG: usize>(&self, row: usize) {
let row_base = row << ROW_LOG;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(target_arch = "x86")]
use core::arch::x86::{_MM_HINT_T0, _mm_prefetch};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
unsafe {
_mm_prefetch(self.row_heads().as_ptr().add(row).cast(), _MM_HINT_T0);
_mm_prefetch(self.row_tags().as_ptr().add(row_base).cast(), _MM_HINT_T0);
_mm_prefetch(
self.row_positions().as_ptr().add(row_base).cast(),
_MM_HINT_T0,
);
if ROW_LOG >= 5 {
_mm_prefetch(
self.row_positions().as_ptr().add(row_base + 16).cast(),
_MM_HINT_T0,
);
}
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
let _ = row_base;
}
}
#[inline(always)]
fn insert_at<const ROW_LOG: usize>(&mut self, abs_pos: usize, row: usize, tag: u8) {
debug_assert_eq!(ROW_LOG, self.row_log);
let row_entries = 1usize << ROW_LOG;
let row_mask = row_entries - 1;
let row_base = row << ROW_LOG;
debug_assert!(row < self.row_heads().len());
debug_assert!(row_base + row_entries <= self.row_positions().len());
unsafe {
let head = *self.row_heads().get_unchecked(row) as usize;
let next = match head.wrapping_sub(1) & row_mask {
0 => row_mask,
n => n,
};
*self.row_heads_mut().get_unchecked_mut(row) = next as u8;
*self.row_tags_mut().get_unchecked_mut(row_base + next) = tag;
*self.row_positions_mut().get_unchecked_mut(row_base + next) = abs_pos as u32;
}
}
pub(crate) fn mark_dict_primed(&mut self) {
if let Some(plan) = self.dict_plan {
let concat_len = self.history.len() - self.history_start;
if self.finder == LazyFinder::Tree {
if plan.attach {
self.prime_dict_tree(plan, concat_len);
} else {
self.build_live_dict_tree(concat_len);
}
}
if !plan.attach {
self.lazy_next_to_update = self.history_abs_start + concat_len;
}
}
self.dict.mark_primed();
}
#[allow(clippy::too_many_arguments)]
unsafe fn insert_bt1_sorted(
hash: &mut [u32],
bt: &mut [u32],
content: *const u8,
content_len: usize,
pos: usize,
index_base: usize,
window_low: usize,
hash_log: usize,
chain_log: usize,
nb_compares: usize,
mls: u32,
) -> usize {
use crate::encoding::fastpath::scalar::common_prefix_len_ptr;
let bt_mask = (1usize << (chain_log - 1)) - 1;
let h = unsafe { Self::key_hash_raw(content, content_len, pos, mls, hash_log, 0) } as usize;
let curr = pos + index_base;
let mut match_index = hash[h] as usize;
let ip = unsafe { content.add(pos) };
let iend_rem = content_len - pos;
let bt_low = curr.saturating_sub(bt_mask);
let mut smaller_ptr = 2 * (curr & bt_mask);
let mut larger_ptr = smaller_ptr + 1;
let mut match_end_idx = curr + 8 + 1;
let mut best_len = 8usize;
let mut nb = nb_compares;
let mut common_smaller = 0usize;
let mut common_larger = 0usize;
hash[h] = curr as u32;
while nb > 0 && match_index >= window_low {
let next_ptr = 2 * (match_index & bt_mask);
let mut ml = common_smaller.min(common_larger);
let m_off = match_index - index_base;
let (smaller, at_end) = unsafe {
let mptr = content.add(m_off);
ml += common_prefix_len_ptr(mptr.add(ml), ip.add(ml), iend_rem - ml);
if ml == iend_rem {
(false, true)
} else {
(*mptr.add(ml) < *ip.add(ml), false)
}
};
if ml > best_len {
best_len = ml;
if ml > match_end_idx - match_index {
match_end_idx = match_index + ml;
}
}
if at_end {
break;
}
if smaller {
bt[smaller_ptr] = match_index as u32;
common_smaller = ml;
if match_index <= bt_low {
smaller_ptr = BT_DISCARD;
break;
}
smaller_ptr = next_ptr + 1;
match_index = bt[next_ptr + 1] as usize;
} else {
bt[larger_ptr] = match_index as u32;
common_larger = ml;
if match_index <= bt_low {
larger_ptr = BT_DISCARD;
break;
}
larger_ptr = next_ptr;
match_index = bt[next_ptr] as usize;
}
nb -= 1;
}
if smaller_ptr != BT_DISCARD {
bt[smaller_ptr] = 0;
}
if larger_ptr != BT_DISCARD {
bt[larger_ptr] = 0;
}
let positions = if best_len > 384 {
192.min(best_len - 384)
} else {
0
};
positions.max(match_end_idx - (curr + 8))
}
#[allow(clippy::too_many_arguments)]
unsafe fn update_dict_tree(
hash: &mut [u32],
bt: &mut [u32],
content: *const u8,
concat_len: usize,
index_base: usize,
hash_log: usize,
chain_log: usize,
nb_compares: usize,
mls: u32,
) {
let target = concat_len.saturating_sub(8);
let mut idx = 0usize;
while idx < target {
let forward = unsafe {
Self::insert_bt1_sorted(
hash,
bt,
content,
concat_len,
idx,
index_base,
index_base,
hash_log,
chain_log,
nb_compares,
mls,
)
};
idx += forward.max(1);
}
}
fn build_live_dict_tree(&mut self, concat_len: usize) {
self.ensure_tables();
let hash_log = self.row_hash_log + self.row_log;
let chain_log = self.hc_chain_log;
let nb_compares = self.search_depth;
let mls = self.row_hash_mls;
let index_base = self.history_abs_start + BT_IDX_BASE;
let base = self.history.as_ptr();
let history_start = self.history_start;
unsafe {
let (hc_hash, hc_chain) = self.tables.split_at_mut(self.hc_split);
Self::update_dict_tree(
hc_hash,
hc_chain,
base.add(history_start),
concat_len,
index_base,
hash_log,
chain_log,
nb_compares,
mls,
);
}
}
fn prime_dict_tree(&mut self, plan: RowDictPlan, concat_len: usize) {
let cd = plan.cdict;
let hash_log = cd.hash_log as usize;
let chain_log = cd.chain_log as usize;
let row_log = (cd.search_log as usize).clamp(4, 6);
let nb_compares = 1usize << cd.search_log;
let mls = cd.min_match.clamp(4, 6);
if self.dict.table().is_some_and(|d| {
d.hash_log != hash_log || d.chain_log != chain_log || d.row_log != row_log || !d.use_bt
}) {
self.dict.invalidate();
}
self.dict.set_region_len(concat_len);
if self.dict.is_primed() {
return;
}
let base = self.history.as_ptr();
let history_start = self.history_start;
let dict = self.dict.table_mut_or_init(|| RowDictTables {
heads: Vec::new(),
positions: Vec::new(),
tags: Vec::new(),
hc_hash: alloc::vec![0u32; 1usize << hash_log],
hc_chain: alloc::vec![0u32; 1usize << chain_log],
hash_log,
chain_log,
row_log,
use_row: false,
use_bt: true,
});
dict.hc_hash.fill(0);
dict.hc_chain.fill(0);
unsafe {
Self::update_dict_tree(
&mut dict.hc_hash,
&mut dict.hc_chain,
base.add(history_start),
concat_len,
BT_IDX_BASE,
hash_log,
chain_log,
nb_compares,
mls,
);
}
}
pub(crate) fn invalidate_dict_cache(&mut self) {
self.dict.invalidate();
}
pub(crate) fn prime_dictionary_current_block(&mut self) {
self.ensure_tables();
let Some(plan) = self.dict_plan else {
self.skip_matching_with_hint(Some(false));
return;
};
let concat_len = self.history.len() - self.history_start;
let indexable_end = concat_len.saturating_sub(8);
let hist_start = self.history_abs_start;
self.loaded_dict_end = hist_start + concat_len;
self.prefix_low = hist_start + concat_len;
self.low_limit = if plan.attach {
self.prefix_low
} else {
hist_start
};
if self.finder == LazyFinder::Tree {
self.dict.set_region_len(concat_len);
return;
}
if plan.attach {
self.prime_dict_tables(plan, concat_len, indexable_end);
} else {
self.dict.set_region_len(concat_len);
let from = self.lazy_next_to_update.max(hist_start) - hist_start;
if from < indexable_end {
let scan = self.scan_ctx();
if self.finder == LazyFinder::Chain {
let chain_mask = (1usize << self.hc_chain_log) - 1;
for idx in from..indexable_end {
let abs = hist_start + idx;
let h = self.hc_hash_at(scan, abs);
let (hash, chain) = self.hc_tables_mut();
chain[abs & chain_mask] = hash[h];
hash[h] = abs as u32;
}
} else {
match self.row_log {
4 => self.copy_dict_rows::<4>(scan, from, indexable_end),
5 => self.copy_dict_rows::<5>(scan, from, indexable_end),
_ => self.copy_dict_rows::<6>(scan, from, indexable_end),
}
}
}
self.lazy_next_to_update = hist_start + indexable_end.max(from);
}
}
fn copy_dict_rows<const ROW_LOG: usize>(&mut self, scan: RowScan, from: usize, to: usize) {
for idx in from..to {
let abs = scan.hist_start + idx;
if let Some((row, tag)) = self.row_hash_at(scan, abs) {
self.insert_at::<ROW_LOG>(abs, row, tag);
}
}
}
fn prime_dict_tables(&mut self, plan: RowDictPlan, concat_len: usize, indexable_end: usize) {
let cd = plan.cdict;
let hash_log = cd.hash_log as usize;
let chain_log = cd.chain_log as usize;
let row_log = (cd.search_log as usize).clamp(4, 6);
let use_row = plan.use_row;
let mls = cd.min_match.clamp(4, 6);
if self.dict.table().is_some_and(|d| {
d.hash_log != hash_log
|| d.chain_log != chain_log
|| d.row_log != row_log
|| d.use_row != use_row
|| d.use_bt
}) {
self.dict.invalidate();
}
self.dict.set_region_len(concat_len);
if self.dict.is_primed() {
return;
}
let from = self.dict.next_to_update();
if from >= indexable_end {
return;
}
self.dict.set_next_to_update(indexable_end);
let history_start = self.history_start;
let base = self.history.as_ptr();
let dict = self.dict.table_mut_or_init(|| {
let (row_count, total) = if use_row {
(1usize << (hash_log - row_log), 1usize << hash_log)
} else {
(0, 0)
};
RowDictTables {
heads: alloc::vec![0u8; row_count],
positions: alloc::vec![ROW_EMPTY_SLOT; total],
tags: alloc::vec![0u8; total],
hc_hash: if use_row {
Vec::new()
} else {
alloc::vec![ROW_EMPTY_SLOT; 1usize << hash_log]
},
hc_chain: if use_row {
Vec::new()
} else {
alloc::vec![ROW_EMPTY_SLOT; 1usize << chain_log]
},
hash_log,
chain_log,
row_log,
use_row,
use_bt: false,
}
});
unsafe {
let content = base.add(history_start);
if use_row {
let row_hash_log = hash_log - row_log;
let row_mask = (1usize << row_log) - 1;
let row_count_mask = (1usize << row_hash_log) - 1;
let heads = dict.heads.as_mut_ptr();
let positions = dict.positions.as_mut_ptr();
let tags = dict.tags.as_mut_ptr();
for concat in from..indexable_end {
let combined = Self::key_hash_raw(
content,
concat_len,
concat,
mls,
row_hash_log + ROW_TAG_BITS,
0,
);
let row = ((combined >> ROW_TAG_BITS) as usize) & row_count_mask;
let tag = combined as u8;
let row_base = row << row_log;
let head = *heads.add(row) as usize;
let next = match head.wrapping_sub(1) & row_mask {
0 => row_mask,
n => n,
};
*heads.add(row) = next as u8;
*tags.add(row_base + next) = tag;
*positions.add(row_base + next) = concat as u32;
}
} else {
let chain_mask = (1usize << chain_log) - 1;
let hash = dict.hc_hash.as_mut_ptr();
let chain = dict.hc_chain.as_mut_ptr();
for concat in from..indexable_end {
let h =
Self::key_hash_raw(content, concat_len, concat, mls, hash_log, 0) as usize;
*chain.add(concat & chain_mask) = *hash.add(h);
*hash.add(h) = concat as u32;
}
}
}
}
}
#[cfg(test)]
mod rebase_tests;
#[cfg(all(
test,
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
mod tag_mask_tests;
#[cfg(all(
test,
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
mod neon_tag_mask_tests;