use alloc::vec::Vec;
use super::CompressionLevel;
use super::Matcher;
use super::Sequence;
use super::blocks::encode_offset_with_history;
use super::bt::BtMatcher;
#[cfg(test)]
use super::cost_model::HC_MAX_LIT;
use super::cost_model::{
HC_BITCOST_MULTIPLIER, HC_FORMAT_MINMATCH, HC_OPT_NODE_LEN, HC_OPT_NUM, HC_OPT_PRICE_ARENA_LEN,
HC_OPT_PRICE_STRIDE, HC_PREDEF_THRESHOLD, HcOptState, HcOptimalCostProfile,
};
#[cfg(test)]
use super::cost_model::{HC_BLOCKSIZE_MAX, HC_MAX_LL, HC_MAX_ML, HC_MAX_OFF, HcOptPriceType};
use super::dfast::DfastMatchGenerator;
#[cfg(test)]
use super::match_table::helpers::INCOMPRESSIBLE_SKIP_STEP;
use super::match_table::helpers::MIN_MATCH_LEN;
#[cfg(test)]
use super::match_table::helpers::common_prefix_len;
#[cfg(test)]
use super::opt::ldm::HcRawSeq;
use super::opt::ldm::{HcOptLdmState, HcRawSeqStore};
use super::opt::types::{
HcCandidateQuery, HcOptimalNode, HcOptimalPlanBuffers, HcOptimalPlanState, HcOptimalSequence,
MatchCandidate,
};
use super::row::RowMatchGenerator;
use super::simple::fast_matcher::{FAST_LEVEL_1_HASH_LOG, FAST_LEVEL_1_MLS, FastKernelMatcher};
#[cfg(all(
test,
feature = "std",
target_arch = "aarch64",
target_endian = "little"
))]
use std::arch::is_aarch64_feature_detected;
#[cfg(all(test, feature = "std", target_arch = "x86_64"))]
use std::arch::is_x86_feature_detected;
pub(crate) const DFAST_MIN_MATCH_LEN: usize = 5;
pub(crate) const DFAST_SHORT_HASH_LOOKAHEAD: usize = 5;
pub(crate) const ROW_MIN_MATCH_LEN: usize = 5;
pub(crate) const DFAST_HASH_BITS: usize = 17;
pub(crate) const DFAST_SHORT_HASH_BITS_DELTA: usize = 1;
pub(crate) const DFAST_EMPTY_SLOT: u32 = 0;
pub(crate) const DFAST_REBASE_GUARD_BAND: u32 = 1u32 << 30;
pub(crate) const DFAST_SKIP_SEARCH_STRENGTH: usize = 6;
pub(crate) const DFAST_SKIP_STEP_GROWTH_INTERVAL: usize = 1 << DFAST_SKIP_SEARCH_STRENGTH;
pub(crate) const DFAST_MAX_SKIP_STEP: usize = 8;
pub(crate) const DFAST_INCOMPRESSIBLE_SKIP_STEP: usize = 16;
pub(crate) const ROW_HASH_BITS: usize = 20;
pub(crate) const ROW_LOG: usize = 5;
pub(crate) const ROW_SEARCH_DEPTH: usize = 16;
pub(crate) const ROW_TARGET_LEN: usize = 48;
pub(crate) const ROW_TAG_BITS: usize = 8;
pub(crate) const ROW_EMPTY_SLOT: u32 = u32::MAX;
pub(crate) const ROW_HASH_KEY_LEN: usize = 4;
#[cfg(test)]
use super::match_table::storage::{HC_PRIME3BYTES, HC_PRIME4BYTES};
#[cfg(test)]
use super::match_table::storage::HC_EMPTY;
use super::match_table::storage::HC3_HASH_LOG;
#[cfg(test)]
use super::match_table::storage::{HC_CHAIN_LOG, HC_HASH_LOG};
const HC_SEARCH_DEPTH: usize = 16;
use super::hc::HC_MIN_MATCH_LEN;
const HC_OPT_MIN_MATCH_LEN: usize = HC_FORMAT_MINMATCH;
const HC_TARGET_LEN: usize = 48;
use super::hc::MAX_HC_SEARCH_DEPTH;
#[derive(Copy, Clone, PartialEq, Eq)]
struct HcConfig {
hash_log: usize,
chain_log: usize,
search_depth: usize,
target_len: usize,
search_mls: usize,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct RowConfig {
pub(crate) hash_bits: usize,
pub(crate) row_log: usize,
pub(crate) search_depth: usize,
pub(crate) target_len: usize,
pub(crate) mls: usize,
}
#[cfg(test)]
const HC_CONFIG: HcConfig = HcConfig {
hash_log: HC_HASH_LOG,
chain_log: HC_CHAIN_LOG,
search_depth: HC_SEARCH_DEPTH,
target_len: HC_TARGET_LEN,
search_mls: 4,
};
const HC_OVERRIDE_DEFAULT: HcConfig = HcConfig {
hash_log: super::match_table::storage::HC_HASH_LOG,
chain_log: super::match_table::storage::HC_CHAIN_LOG,
search_depth: HC_SEARCH_DEPTH,
target_len: HC_TARGET_LEN,
search_mls: 4,
};
const BTULTRA2_HC_CONFIG: HcConfig = HcConfig {
hash_log: 24,
chain_log: 24,
search_depth: 512,
target_len: 256,
search_mls: 4,
};
const BTULTRA2_HC_CONFIG_L22: HcConfig = HcConfig {
hash_log: 25,
chain_log: 27,
search_depth: 512,
target_len: 999,
search_mls: 4,
};
const BTULTRA2_HC_CONFIG_L22_256K: HcConfig = HcConfig {
hash_log: 19,
chain_log: 19,
search_depth: 1 << 13,
target_len: 999,
search_mls: 4,
};
const BTULTRA2_HC_CONFIG_L22_128K: HcConfig = HcConfig {
hash_log: 17,
chain_log: 18,
search_depth: 1 << 11,
target_len: 999,
search_mls: 4,
};
const BTULTRA2_HC_CONFIG_L22_16K: HcConfig = HcConfig {
hash_log: 15,
chain_log: 15,
search_depth: 1 << 10,
target_len: 999,
search_mls: 4,
};
#[cfg(test)]
const ROW_CONFIG: RowConfig = RowConfig {
hash_bits: ROW_HASH_BITS,
row_log: ROW_LOG,
search_depth: ROW_SEARCH_DEPTH,
target_len: ROW_TARGET_LEN,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L5: RowConfig = RowConfig {
hash_bits: 19,
row_log: 4,
search_depth: 8,
target_len: 2,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L6: RowConfig = RowConfig {
hash_bits: 19,
row_log: 4,
search_depth: 8,
target_len: 4,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L7: RowConfig = RowConfig {
hash_bits: 20,
row_log: 4,
search_depth: 16,
target_len: 8,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L8: RowConfig = RowConfig {
hash_bits: 20,
row_log: 4,
search_depth: 16,
target_len: 16,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L9: RowConfig = RowConfig {
hash_bits: 21,
row_log: 4,
search_depth: 16,
target_len: 16,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L10: RowConfig = RowConfig {
hash_bits: 22,
row_log: 5,
search_depth: 32,
target_len: 16,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L11: RowConfig = RowConfig {
hash_bits: 22,
row_log: 6,
search_depth: 64,
target_len: 16,
mls: ROW_MIN_MATCH_LEN,
};
const ROW_L12: RowConfig = RowConfig {
hash_bits: 23,
row_log: 6,
search_depth: 64,
target_len: 32,
mls: ROW_MIN_MATCH_LEN,
};
#[derive(Copy, Clone, PartialEq, Eq)]
struct DfastConfig {
long_hash_log: u8,
short_hash_log: u8,
}
const DFAST_L3: DfastConfig = DfastConfig {
long_hash_log: 17,
short_hash_log: 16,
};
const DFAST_L4: DfastConfig = DfastConfig {
long_hash_log: 18,
short_hash_log: 18,
};
#[derive(Copy, Clone, PartialEq, Eq)]
struct FastConfig {
hash_log: u32,
mls: u32,
step_size: usize,
}
const FAST_L1: FastConfig = FastConfig {
hash_log: 14,
mls: 7,
step_size: 2,
};
const FAST_L2: FastConfig = FastConfig {
hash_log: 16,
mls: 6,
step_size: 2,
};
#[derive(Copy, Clone, PartialEq, Eq)]
struct LevelParams {
strategy_tag: super::strategy::StrategyTag,
search: super::strategy::SearchMethod,
window_log: u8,
lazy_depth: u8,
fast: Option<FastConfig>,
dfast: Option<DfastConfig>,
hc: Option<HcConfig>,
row: Option<RowConfig>,
}
impl LevelParams {
fn backend(&self) -> super::strategy::BackendTag {
self.search.backend()
}
fn parse(&self) -> super::strategy::ParseMode {
match self.search {
super::strategy::SearchMethod::BinaryTree => super::strategy::ParseMode::Optimal,
_ => super::strategy::ParseMode::from_lazy_depth(self.lazy_depth),
}
}
fn pre_split(&self) -> Option<u8> {
match self.strategy_tag {
super::strategy::StrategyTag::Fast => Some(0),
super::strategy::StrategyTag::Dfast => Some(1),
super::strategy::StrategyTag::Greedy => Some(2),
super::strategy::StrategyTag::Lazy => {
if self.lazy_depth >= 2 {
Some(4)
} else {
Some(2)
}
}
super::strategy::StrategyTag::Btlazy2 => Some(4),
super::strategy::StrategyTag::BtOpt
| super::strategy::StrategyTag::BtUltra
| super::strategy::StrategyTag::BtUltra2 => Some(4),
}
}
}
fn apply_param_overrides(params: &mut LevelParams, ov: &super::parameters::ParamOverrides) {
use super::strategy::SearchMethod;
if let Some(strategy) = ov.strategy {
let tag = strategy.tag();
params.strategy_tag = tag;
params.search = tag.search();
params.lazy_depth = strategy.lazy_depth();
}
match params.search {
SearchMethod::Fast => {
params.fast.get_or_insert(FAST_L1);
}
SearchMethod::DoubleFast => {
params.dfast.get_or_insert(DFAST_L3);
}
SearchMethod::RowHash => {
params.row.get_or_insert(ROW_L5);
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
params.hc.get_or_insert(HcConfig {
search_mls: if matches!(params.strategy_tag, super::strategy::StrategyTag::Btlazy2)
{
5
} else {
HC_OVERRIDE_DEFAULT.search_mls
},
..HC_OVERRIDE_DEFAULT
});
}
}
if let Some(window_log) = ov.window_log {
params.window_log = window_log;
}
match params.search {
SearchMethod::Fast => {
if let Some(fast) = params.fast.as_mut() {
if let Some(hash_log) = ov.hash_log {
fast.hash_log = hash_log;
}
if let Some(min_match) = ov.min_match {
fast.mls = min_match;
}
}
}
SearchMethod::DoubleFast => {
if let Some(dfast) = params.dfast.as_mut() {
if let Some(hash_log) = ov.hash_log {
dfast.long_hash_log = hash_log as u8;
}
if let Some(chain_log) = ov.chain_log {
dfast.short_hash_log = chain_log as u8;
}
}
}
SearchMethod::RowHash => {
if let Some(row) = params.row.as_mut() {
if let Some(hash_log) = ov.hash_log {
row.hash_bits = hash_log as usize;
}
if let Some(search_log) = ov.search_log {
let row_log = (search_log as usize).clamp(4, 6);
row.row_log = row_log;
row.search_depth = 1usize << (search_log as usize).min(row_log);
}
if let Some(target_length) = ov.target_length {
row.target_len = target_length as usize;
}
if let Some(min_match) = ov.min_match {
row.mls = min_match as usize;
}
}
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
if let Some(hc) = params.hc.as_mut() {
if let Some(hash_log) = ov.hash_log {
hc.hash_log = hash_log as usize;
}
if let Some(chain_log) = ov.chain_log {
hc.chain_log = chain_log as usize;
}
if let Some(search_log) = ov.search_log {
hc.search_depth = 1usize << search_log;
}
if let Some(target_length) = ov.target_length {
hc.target_len = target_length as usize;
}
if let Some(min_match) = ov.min_match {
hc.search_mls = (min_match as usize).clamp(4, 6);
}
}
}
}
}
#[cfg(feature = "hash")]
fn ldm_strategy_ordinal(tag: super::strategy::StrategyTag, lazy_depth: u8) -> u32 {
use super::strategy::StrategyTag;
match tag {
StrategyTag::Fast => 1,
StrategyTag::Dfast => 2,
StrategyTag::Greedy => 3,
StrategyTag::Lazy => {
if lazy_depth >= 2 {
5
} else {
4
}
}
StrategyTag::Btlazy2 => 6,
StrategyTag::BtOpt => 7,
StrategyTag::BtUltra => 8,
StrategyTag::BtUltra2 => 9,
}
}
pub(crate) fn source_size_ceil_log(size: u64) -> u8 {
if size == 0 {
MIN_WINDOW_LOG
} else {
(64 - (size - 1).leading_zeros()) as u8
}
}
const FAST_ATTACH_DICT_CUTOFF_LOG: u8 = 13;
const DFAST_ATTACH_DICT_CUTOFF_LOG: u8 = 14;
const ROW_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
const HC_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
const BT_OPT_ATTACH_DICT_CUTOFF_LOG: u8 = 15;
const BT_ULTRA_ATTACH_DICT_CUTOFF_LOG: u8 = 13;
fn dfast_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
window_log.max(MIN_WINDOW_LOG as usize)
}
fn row_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
(window_log + 1).max(MIN_WINDOW_LOG as usize)
}
fn hc_hash_bits_for_window(max_window_size: usize) -> usize {
let window_log = (usize::BITS - 1 - max_window_size.leading_zeros()) as usize;
window_log.max(MIN_WINDOW_LOG as usize)
}
#[rustfmt::skip]
const LEVEL_TABLE: [LevelParams; 22] = [
LevelParams { strategy_tag: super::strategy::StrategyTag::Fast, search: super::strategy::SearchMethod::Fast, window_log: 19, lazy_depth: 0, fast: Some(FAST_L1), dfast: None, hc: None, row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Fast, search: super::strategy::SearchMethod::Fast, window_log: 20, lazy_depth: 0, fast: Some(FAST_L2), dfast: None, hc: None, row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Dfast, search: super::strategy::SearchMethod::DoubleFast, window_log: 21, lazy_depth: 1, fast: None, dfast: Some(DFAST_L3), hc: None, row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Dfast, search: super::strategy::SearchMethod::DoubleFast, window_log: 21, lazy_depth: 1, fast: None, dfast: Some(DFAST_L4), hc: None, row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Greedy, search: super::strategy::SearchMethod::RowHash, window_log: 21, lazy_depth: 0, fast: None, dfast: None, hc: None, row: Some(ROW_L5) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 21, lazy_depth: 1, fast: None, dfast: None, hc: None, row: Some(ROW_L6) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 21, lazy_depth: 1, fast: None, dfast: None, hc: None, row: Some(ROW_L7) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 21, lazy_depth: 2, fast: None, dfast: None, hc: None, row: Some(ROW_L8) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: None, row: Some(ROW_L9) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: None, row: Some(ROW_L10) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: None, row: Some(ROW_L11) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Lazy, search: super::strategy::SearchMethod::RowHash, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: None, row: Some(ROW_L12) },
LevelParams { strategy_tag: super::strategy::StrategyTag::Btlazy2, search: super::strategy::SearchMethod::BinaryTree, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 22, chain_log: 22, search_depth: 16, target_len: 32, search_mls: 5 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Btlazy2, search: super::strategy::SearchMethod::BinaryTree, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 23, chain_log: 22, search_depth: 32, target_len: 32, search_mls: 5 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::Btlazy2, search: super::strategy::SearchMethod::BinaryTree, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 23, chain_log: 23, search_depth: 64, target_len: 32, search_mls: 5 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtOpt, search: super::strategy::SearchMethod::BinaryTree, window_log: 22, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 22, chain_log: 22, search_depth: 32, target_len: 48, search_mls: 5 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtOpt, search: super::strategy::SearchMethod::BinaryTree, window_log: 23, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 22, chain_log: 23, search_depth: 32, target_len: 64, search_mls: 4 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtUltra, search: super::strategy::SearchMethod::BinaryTree, window_log: 23, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 22, chain_log: 23, search_depth: 64, target_len: 64, search_mls: 4 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtUltra2, search: super::strategy::SearchMethod::BinaryTree, window_log: 23, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 22, chain_log: 24, search_depth: 128, target_len: 256, search_mls: 4 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtUltra2, search: super::strategy::SearchMethod::BinaryTree, window_log: 25, lazy_depth: 2, fast: None, dfast: None, hc: Some(HcConfig { hash_log: 23, chain_log: 25, search_depth: 128, target_len: 256, search_mls: 4 }), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtUltra2, search: super::strategy::SearchMethod::BinaryTree, window_log: 26, lazy_depth: 2, fast: None, dfast: None, hc: Some(BTULTRA2_HC_CONFIG), row: None },
LevelParams { strategy_tag: super::strategy::StrategyTag::BtUltra2, search: super::strategy::SearchMethod::BinaryTree, window_log: 27, lazy_depth: 2, fast: None, dfast: None, hc: Some(BTULTRA2_HC_CONFIG_L22), row: None },
];
const DICT_MIN_SRC_SIZE: u64 = 513;
fn dict_and_window_log(window_log: u8, src_size: u64, dict_size: u64) -> u32 {
if dict_size == 0 {
return window_log as u32;
}
let window_size: u64 = 1u64 << window_log;
let dict_and_window = dict_size + window_size;
if window_size >= dict_size + src_size {
window_log as u32
} else {
source_size_ceil_log(dict_and_window) as u32
}
}
fn cdict_table_logs(
window_log: u8,
hash_log: usize,
chain_log: usize,
uses_bt: bool,
dict_size: usize,
) -> (usize, usize) {
let dict_size = dict_size as u64;
let src_size = DICT_MIN_SRC_SIZE;
let tsize = src_size + dict_size;
let resized_window_log = (window_log as u32)
.min(source_size_ceil_log(tsize) as u32)
.max(1);
let daw = dict_and_window_log(resized_window_log as u8, src_size, dict_size);
let cycle_log = (chain_log as u32).saturating_sub(uses_bt as u32);
let new_hash_log = if hash_log as u32 > daw + 1 {
(daw + 1) as usize
} else {
hash_log
};
let new_chain_log = if cycle_log > daw {
chain_log.saturating_sub((cycle_log - daw) as usize)
} else {
chain_log
};
(new_hash_log, new_chain_log)
}
pub(crate) const MIN_WINDOW_LOG: u8 = 10;
const MIN_HINTED_WINDOW_LOG: u8 = 14;
fn adjust_params_for_source_size(mut params: LevelParams, src_size: u64) -> LevelParams {
let raw_src_log = source_size_ceil_log(src_size);
let src_log = raw_src_log.max(MIN_WINDOW_LOG).max(MIN_HINTED_WINDOW_LOG);
if src_log < params.window_log {
params.window_log = src_log;
}
let table_log = raw_src_log.max(MIN_WINDOW_LOG);
let backend = params.backend();
if backend == super::strategy::BackendTag::HashChain {
let hc = params
.hc
.as_mut()
.expect("HashChain level row carries an HcConfig");
if (table_log + 2) < hc.hash_log as u8 {
hc.hash_log = (table_log + 2) as usize;
}
if (table_log + 1) < hc.chain_log as u8 {
hc.chain_log = (table_log + 1) as usize;
}
} else if backend == super::strategy::BackendTag::Row {
let row = params
.row
.as_mut()
.expect("Row level row carries a RowConfig");
let row_cap = (table_log + 1) as usize;
if row_cap < row.hash_bits {
row.hash_bits = row_cap;
}
} else if backend == super::strategy::BackendTag::Simple {
let fast = params
.fast
.as_mut()
.expect("Fast level row carries a FastConfig");
let fast_cap = (table_log + 1) as u32;
if fast_cap < fast.hash_log {
fast.hash_log = fast_cap;
}
}
params
}
fn level22_btultra2_params_for_source_size(source_size: Option<u64>) -> LevelParams {
let mut hc = match source_size {
Some(size) if size <= 16 * 1024 => BTULTRA2_HC_CONFIG_L22_16K,
Some(size) if size <= 128 * 1024 => BTULTRA2_HC_CONFIG_L22_128K,
Some(size) if size <= 256 * 1024 => BTULTRA2_HC_CONFIG_L22_256K,
_ => BTULTRA2_HC_CONFIG_L22,
};
let mut window_log = match source_size {
Some(size) if size <= 16 * 1024 => 14,
Some(size) if size <= 128 * 1024 => 17,
Some(size) if size <= 256 * 1024 => 18,
_ => 27,
};
if let Some(size) = source_size
&& size > 256 * 1024
{
let src_log = source_size_ceil_log(size);
window_log = window_log.min(src_log.max(MIN_WINDOW_LOG));
let adjusted_table_log = window_log as usize + 1;
hc.hash_log = hc.hash_log.min(adjusted_table_log);
hc.chain_log = hc.chain_log.min(adjusted_table_log);
}
LevelParams {
strategy_tag: super::strategy::StrategyTag::BtUltra2,
search: super::strategy::SearchMethod::BinaryTree,
window_log,
lazy_depth: 2,
fast: None,
dfast: None,
hc: Some(hc),
row: None,
}
}
pub fn estimated_compression_workspace_bytes(level: CompressionLevel) -> usize {
use super::strategy::StrategyTag;
let params = resolve_level_params(level, None);
let window = 1usize << params.window_log;
let wants_hash3 = matches!(
params.strategy_tag,
StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let uses_bt = matches!(
params.strategy_tag,
StrategyTag::Btlazy2 | StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2
);
let tables = params.fast.map(|f| 4usize << f.hash_log).unwrap_or(0)
+ params
.dfast
.map(|d| (4usize << d.long_hash_log) + (4usize << d.short_hash_log))
.unwrap_or(0)
+ params
.hc
.map(|h| {
let hash3 = if wants_hash3 {
4usize
<< super::match_table::storage::HC3_HASH_LOG.min(params.window_log as usize)
} else {
0
};
(4usize << h.hash_log) + (4usize << h.chain_log) + hash3
})
.unwrap_or(0)
+ params
.row
.map(|r| (4usize << r.hash_bits) + (2usize << r.hash_bits))
.unwrap_or(0);
let bt = if uses_bt {
super::bt::BtMatcher::estimated_workspace_bytes()
} else {
0
};
let staging = 3 * (128 * 1024);
window + tables + bt + staging
}
pub fn estimated_bt_strategy_extra_bytes(strategy_ordinal: u32, window_log: u32) -> usize {
if !(6..=9).contains(&strategy_ordinal) {
return 0;
}
let hash3 = if matches!(strategy_ordinal, 8 | 9) {
4usize << super::match_table::storage::HC3_HASH_LOG.min(window_log as usize)
} else {
0
};
super::bt::BtMatcher::estimated_workspace_bytes() + hash3
}
fn resolve_level_params(level: CompressionLevel, source_size: Option<u64>) -> LevelParams {
if matches!(level, CompressionLevel::Level(22)) {
return level22_btultra2_params_for_source_size(source_size);
}
let params = match level {
CompressionLevel::Uncompressed => LevelParams {
strategy_tag: super::strategy::StrategyTag::Fast,
search: super::strategy::SearchMethod::Fast,
window_log: 17,
lazy_depth: 0,
fast: Some(FastConfig {
hash_log: 14,
mls: 6,
step_size: 2,
}),
dfast: None,
hc: None,
row: None,
},
CompressionLevel::Fastest => {
let mut p = LEVEL_TABLE[0];
p.fast = Some(FastConfig {
hash_log: 14,
mls: 6,
step_size: 2,
});
p
}
CompressionLevel::Default => LEVEL_TABLE[2],
CompressionLevel::Better => LEVEL_TABLE[6],
CompressionLevel::Best => LEVEL_TABLE[12],
CompressionLevel::Level(n) => {
if n > 0 {
let idx = (n as usize).min(CompressionLevel::MAX_LEVEL as usize) - 1;
LEVEL_TABLE[idx]
} else if n == 0 {
LEVEL_TABLE[CompressionLevel::DEFAULT_LEVEL as usize - 1]
} else {
let clamped = n.max(CompressionLevel::MIN_LEVEL);
let target_length = (-clamped) as usize;
let step_size = target_length + 1;
LevelParams {
strategy_tag: super::strategy::StrategyTag::Fast,
search: super::strategy::SearchMethod::Fast,
window_log: 19,
lazy_depth: 0,
fast: Some(FastConfig {
hash_log: 13,
mls: 7,
step_size,
}),
dfast: None,
hc: None,
row: None,
}
}
}
};
if let Some(size) = source_size {
adjust_params_for_source_size(params, size)
} else {
params
}
}
pub(crate) fn level_pre_split(level: CompressionLevel) -> Option<usize> {
if matches!(level, CompressionLevel::Uncompressed) {
return None;
}
resolve_level_params(level, None)
.pre_split()
.map(usize::from)
}
#[derive(Clone)]
enum MatcherStorage {
Simple(FastKernelMatcher),
Dfast(DfastMatchGenerator),
Row(RowMatchGenerator),
HashChain(HcMatchGenerator),
}
impl MatcherStorage {
fn heap_size(&self) -> usize {
match self {
Self::Simple(m) => m.heap_size(),
Self::Dfast(m) => m.heap_size(),
Self::Row(m) => m.heap_size(),
Self::HashChain(m) => m.heap_size(),
}
}
fn backend(&self) -> super::strategy::BackendTag {
use super::strategy::BackendTag;
match self {
Self::Simple(_) => BackendTag::Simple,
Self::Dfast(_) => BackendTag::Dfast,
Self::Row(_) => BackendTag::Row,
Self::HashChain(_) => BackendTag::HashChain,
}
}
}
pub struct MatchGeneratorDriver {
vec_pool: Vec<Vec<u8>>,
storage: MatcherStorage,
strategy_tag: super::strategy::StrategyTag,
search: super::strategy::SearchMethod,
parse: super::strategy::ParseMode,
#[cfg(test)]
config_override: Option<(super::strategy::SearchMethod, super::strategy::ParseMode)>,
param_overrides: Option<super::parameters::ParamOverrides>,
slice_size: usize,
base_slice_size: usize,
reported_window_size: usize,
dictionary_retained_budget: usize,
source_size_hint: Option<u64>,
dictionary_size_hint: Option<usize>,
reset_size_log: Option<u8>,
reset_shape: Option<(
LevelParams,
usize,
bool,
Option<super::parameters::LdmOverride>,
)>,
borrowed_pending: Option<(usize, usize)>,
primed: Option<(MatcherStorage, usize, PrimedKey)>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct PrimedKey {
level: super::CompressionLevel,
params: LevelParams,
table_bits: usize,
fast_attach: bool,
ldm: Option<super::parameters::LdmOverride>,
}
impl MatchGeneratorDriver {
pub(crate) fn new(slice_size: usize, max_slices_in_window: usize) -> Self {
assert!(
slice_size > 0,
"MatchGeneratorDriver::new requires slice_size > 0 (got 0)",
);
assert!(
max_slices_in_window > 0,
"MatchGeneratorDriver::new requires max_slices_in_window > 0 (got 0)",
);
let max_window_size = max_slices_in_window
.checked_mul(slice_size)
.expect("MatchGeneratorDriver::new: slice_size * max_slices_in_window overflows usize");
let next_pow2 = max_window_size.checked_next_power_of_two().expect(
"MatchGeneratorDriver::new: max_window_size too large for \
next_power_of_two without overflow",
);
let window_log_init = next_pow2.trailing_zeros() as u8;
Self {
vec_pool: Vec::new(),
storage: MatcherStorage::Simple(FastKernelMatcher::with_params(
window_log_init,
FAST_LEVEL_1_HASH_LOG,
FAST_LEVEL_1_MLS,
2, )),
strategy_tag: super::strategy::StrategyTag::Fast,
search: super::strategy::SearchMethod::Fast,
parse: super::strategy::ParseMode::Greedy,
#[cfg(test)]
config_override: None,
param_overrides: None,
slice_size,
base_slice_size: slice_size,
reported_window_size: next_pow2,
reset_size_log: None,
reset_shape: None,
dictionary_retained_budget: 0,
source_size_hint: None,
dictionary_size_hint: None,
borrowed_pending: None,
primed: None,
}
}
fn level_params(level: CompressionLevel, source_size: Option<u64>) -> LevelParams {
resolve_level_params(level, source_size)
}
pub(crate) fn set_param_overrides(
&mut self,
overrides: Option<super::parameters::ParamOverrides>,
) {
self.param_overrides = overrides;
}
pub(crate) fn active_backend(&self) -> super::strategy::BackendTag {
self.storage.backend()
}
pub(crate) fn borrowed_supported(&self) -> bool {
use super::strategy::{BackendTag, SearchMethod, StrategyTag};
match self.active_backend() {
BackendTag::Simple | BackendTag::Dfast | BackendTag::Row => true,
BackendTag::HashChain => match self.search {
SearchMethod::HashChain => true,
SearchMethod::BinaryTree => matches!(self.strategy_tag, StrategyTag::Btlazy2),
_ => false,
},
}
}
pub(crate) fn borrowed_dict_supported(&self) -> bool {
matches!(
&self.storage,
MatcherStorage::Simple(m) if m.dict_is_attached()
)
}
fn simple_mut(&mut self) -> &mut FastKernelMatcher {
match &mut self.storage {
MatcherStorage::Simple(m) => m,
_ => panic!("simple backend must be initialized by reset() before use"),
}
}
fn recycle_simple_space(&mut self) {
if let Some(space) = self.simple_mut().take_recycled_space() {
self.vec_pool.push(space);
}
}
pub(crate) unsafe fn set_borrowed_window(&mut self, buffer: &[u8]) {
match self.active_backend() {
super::strategy::BackendTag::Simple => unsafe {
self.simple_mut().set_borrowed_window(buffer)
},
super::strategy::BackendTag::Dfast => unsafe {
self.dfast_matcher_mut().set_borrowed_window(buffer)
},
super::strategy::BackendTag::Row => unsafe {
self.row_matcher_mut().set_borrowed_window(buffer)
},
super::strategy::BackendTag::HashChain => unsafe {
self.hc_matcher_mut().set_borrowed_window(buffer)
},
}
}
pub(crate) fn clear_borrowed_window(&mut self) {
match self.active_backend() {
super::strategy::BackendTag::Simple => self.simple_mut().clear_borrowed_window(),
super::strategy::BackendTag::Dfast => self.dfast_matcher_mut().clear_borrowed_window(),
super::strategy::BackendTag::Row => self.row_matcher_mut().clear_borrowed_window(),
super::strategy::BackendTag::HashChain => self.hc_matcher_mut().clear_borrowed_window(),
#[allow(unreachable_patterns)]
_ => {}
}
self.borrowed_pending = None;
}
pub(crate) fn set_borrowed_block(&mut self, block_start: usize, block_end: usize) {
assert!(
self.borrowed_supported(),
"borrowed block staging is not supported for the active backend/search config",
);
assert!(
block_start <= block_end,
"borrowed block range must satisfy start <= end (start={block_start} end={block_end})",
);
self.borrowed_pending = Some((block_start, block_end));
match self.active_backend() {
super::strategy::BackendTag::Simple => self
.simple_mut()
.stage_borrowed_block(block_start, block_end),
super::strategy::BackendTag::Dfast => self
.dfast_matcher_mut()
.stage_borrowed_block(block_start, block_end),
super::strategy::BackendTag::Row => self
.row_matcher_mut()
.stage_borrowed_block(block_start, block_end),
super::strategy::BackendTag::HashChain => self
.hc_matcher_mut()
.table
.stage_borrowed_block(block_start, block_end),
}
}
#[cfg(test)]
fn dfast_matcher(&self) -> &DfastMatchGenerator {
match &self.storage {
MatcherStorage::Dfast(m) => m,
_ => panic!("dfast backend must be initialized by reset() before use"),
}
}
fn dfast_matcher_mut(&mut self) -> &mut DfastMatchGenerator {
match &mut self.storage {
MatcherStorage::Dfast(m) => m,
_ => panic!("dfast backend must be initialized by reset() before use"),
}
}
#[cfg(test)]
fn row_matcher(&self) -> &RowMatchGenerator {
match &self.storage {
MatcherStorage::Row(m) => m,
_ => panic!("row backend must be initialized by reset() before use"),
}
}
fn row_matcher_mut(&mut self) -> &mut RowMatchGenerator {
match &mut self.storage {
MatcherStorage::Row(m) => m,
_ => panic!("row backend must be initialized by reset() before use"),
}
}
#[cfg(test)]
fn hc_matcher(&self) -> &HcMatchGenerator {
match &self.storage {
MatcherStorage::HashChain(m) => m,
_ => panic!("hash chain backend must be initialized by reset() before use"),
}
}
fn hc_matcher_mut(&mut self) -> &mut HcMatchGenerator {
match &mut self.storage {
MatcherStorage::HashChain(m) => m,
_ => panic!("hash chain backend must be initialized by reset() before use"),
}
}
#[must_use]
fn retire_dictionary_budget(&mut self, evicted_bytes: usize) -> bool {
let reclaimed = evicted_bytes.min(self.dictionary_retained_budget);
if reclaimed == 0 {
return false;
}
self.dictionary_retained_budget -= reclaimed;
match self.active_backend() {
super::strategy::BackendTag::Simple => {
let matcher = self.simple_mut();
matcher.max_window_size = matcher.max_window_size.saturating_sub(reclaimed);
}
super::strategy::BackendTag::Dfast => {
let matcher = self.dfast_matcher_mut();
matcher.max_window_size = matcher.max_window_size.saturating_sub(reclaimed);
}
super::strategy::BackendTag::Row => {
let matcher = self.row_matcher_mut();
matcher.max_window_size = matcher.max_window_size.saturating_sub(reclaimed);
}
super::strategy::BackendTag::HashChain => {
let matcher = self.hc_matcher_mut();
matcher.table.max_window_size =
matcher.table.max_window_size.saturating_sub(reclaimed);
}
}
true
}
fn trim_after_budget_retire(&mut self) {
loop {
let mut evicted_bytes = 0usize;
match self.active_backend() {
super::strategy::BackendTag::Simple => {
let MatcherStorage::Simple(m) = &mut self.storage else {
unreachable!("active_backend() == Simple proven above");
};
evicted_bytes += m.trim_to_window();
}
super::strategy::BackendTag::Dfast => {
let dfast = self.dfast_matcher_mut();
let pre = dfast.window_size;
dfast.trim_to_window();
evicted_bytes += pre - dfast.window_size;
}
super::strategy::BackendTag::Row => {
let row = self.row_matcher_mut();
let pre = row.window_size;
row.trim_to_window();
evicted_bytes += pre - row.window_size;
}
super::strategy::BackendTag::HashChain => {
let table = &mut self.hc_matcher_mut().table;
let pre = table.window_size;
table.trim_to_window();
evicted_bytes += pre - table.window_size;
}
}
if evicted_bytes == 0 {
break;
}
let _ = self.retire_dictionary_budget(evicted_bytes);
}
}
fn hc_dict_attach_mode(&self) -> bool {
let MatcherStorage::HashChain(hc) = &self.storage else {
return true;
};
let cutoff = if hc.table.uses_bt {
match hc.strategy_tag {
super::strategy::StrategyTag::BtUltra | super::strategy::StrategyTag::BtUltra2 => {
BT_ULTRA_ATTACH_DICT_CUTOFF_LOG
}
_ => BT_OPT_ATTACH_DICT_CUTOFF_LOG,
}
} else {
HC_ATTACH_DICT_CUTOFF_LOG
};
self.reset_size_log.is_none_or(|log| log <= cutoff)
}
fn skip_matching_for_dictionary_priming(&mut self) {
match self.active_backend() {
super::strategy::BackendTag::Simple => {
let attach = self
.reset_size_log
.is_none_or(|log| log <= FAST_ATTACH_DICT_CUTOFF_LOG);
if attach {
self.simple_mut().skip_matching_for_dict_prime();
} else {
self.simple_mut().skip_matching_with_hint(Some(false));
}
self.recycle_simple_space();
}
super::strategy::BackendTag::Dfast => {
let attach = self
.reset_size_log
.is_none_or(|log| log <= DFAST_ATTACH_DICT_CUTOFF_LOG);
if attach {
self.dfast_matcher_mut().skip_matching_for_dict_attach();
} else {
self.dfast_matcher_mut().invalidate_dict_cache();
self.dfast_matcher_mut().skip_matching_dense();
}
}
super::strategy::BackendTag::Row => {
let attach = self
.reset_size_log
.is_none_or(|log| log <= ROW_ATTACH_DICT_CUTOFF_LOG);
if attach {
self.row_matcher_mut().prime_dict_attach_current_block();
} else {
self.row_matcher_mut().invalidate_dict_cache();
self.row_matcher_mut().skip_matching_with_hint(Some(false));
}
}
super::strategy::BackendTag::HashChain => {
if self.hc_dict_attach_mode() {
self.hc_matcher_mut().table.skip_matching_dict_bt();
} else {
self.hc_matcher_mut().skip_matching(Some(false));
}
}
}
}
}
impl Matcher for MatchGeneratorDriver {
fn supports_dictionary_priming(&self) -> bool {
true
}
fn set_source_size_hint(&mut self, size: u64) {
self.source_size_hint = Some(size);
}
fn set_dictionary_size_hint(&mut self, size: usize) {
self.dictionary_size_hint = Some(size);
}
fn heap_size(&self) -> usize {
let pool: usize = self.vec_pool.capacity() * core::mem::size_of::<Vec<u8>>()
+ self.vec_pool.iter().map(Vec::capacity).sum::<usize>();
let snapshot = self
.primed
.as_ref()
.map_or(0, |(storage, _, _)| storage.heap_size());
pool + self.storage.heap_size() + snapshot
}
fn clear_param_overrides(&mut self) {
self.param_overrides = None;
}
fn reset(&mut self, level: CompressionLevel) {
let hint = self.source_size_hint.take();
let dict_hint = self.dictionary_size_hint.take();
self.reset_size_log = hint.map(source_size_ceil_log);
let hinted = hint.is_some();
#[cfg_attr(not(test), allow(unused_mut))]
let mut params = Self::level_params(level, hint);
#[cfg(test)]
if let Some((search, parse)) = self.config_override.take() {
params.search = search;
params.lazy_depth = parse.lazy_depth();
use super::strategy::SearchMethod;
match search {
SearchMethod::Fast => {
params.fast.get_or_insert(FAST_L1);
}
SearchMethod::DoubleFast => {
params.dfast.get_or_insert(DFAST_L3);
}
SearchMethod::RowHash => {
params.row.get_or_insert(ROW_CONFIG);
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
params.hc.get_or_insert(HC_CONFIG);
}
}
}
if let Some(ov) = self.param_overrides
&& !ov.is_empty()
{
apply_param_overrides(&mut params, &ov);
if let Some(hint_size) = hint {
params = adjust_params_for_source_size(params, hint_size);
if let Some(window_log) = ov.window_log {
params.window_log = window_log;
}
}
}
if let Some(dict_size) = dict_hint.filter(|&size| size > 0) {
let mut base_params = Self::level_params(level, None);
if let Some(ov) = self.param_overrides
&& !ov.is_empty()
{
apply_param_overrides(&mut base_params, &ov);
}
if let (Some(hc), Some(base_hc)) = (params.hc.as_mut(), base_params.hc) {
let uses_bt = matches!(
params.strategy_tag,
super::strategy::StrategyTag::Btlazy2
| super::strategy::StrategyTag::BtOpt
| super::strategy::StrategyTag::BtUltra
| super::strategy::StrategyTag::BtUltra2
);
let (dict_hash_log, dict_chain_log) = cdict_table_logs(
params.window_log,
base_hc.hash_log,
base_hc.chain_log,
uses_bt,
dict_size,
);
hc.hash_log = dict_hash_log;
hc.chain_log = dict_chain_log;
}
}
if params.search == super::strategy::SearchMethod::RowHash && params.window_log <= 14 {
let row = params
.row
.expect("a RowHash level row must carry a RowConfig");
params.search = super::strategy::SearchMethod::HashChain;
let row_cdict_hash_bits = match dict_hint.filter(|&size| size > 0) {
Some(_) => {
let mut base_params = Self::level_params(level, None);
if let Some(ov) = self.param_overrides
&& !ov.is_empty()
{
apply_param_overrides(&mut base_params, &ov);
}
base_params
.row
.map_or(row.hash_bits, |base_row| base_row.hash_bits)
}
None => row.hash_bits,
};
let explicit_chain_log = self
.param_overrides
.filter(|ov| !ov.is_empty())
.and_then(|ov| ov.chain_log)
.map(|chain_log| chain_log as usize);
let row_cdict_chain_bits = explicit_chain_log.unwrap_or(row_cdict_hash_bits - 1);
let (mut hash_log, mut chain_log) = match dict_hint.filter(|&size| size > 0) {
Some(dict_size) => cdict_table_logs(
params.window_log,
row_cdict_hash_bits,
row_cdict_chain_bits,
false,
dict_size,
),
None => (
row.hash_bits,
explicit_chain_log.unwrap_or(row.hash_bits - 1),
),
};
if dict_hint.filter(|&size| size > 0).is_none() {
let wlog = params.window_log as usize;
hash_log = hash_log.min(wlog + 1);
chain_log = chain_log.min(wlog);
}
params.hc = Some(HcConfig {
hash_log,
chain_log,
search_depth: row.search_depth,
target_len: row.target_len,
search_mls: 4,
});
params.row = None;
}
let next_backend = params.backend();
let max_window_size = 1usize << params.window_log;
self.dictionary_retained_budget = 0;
self.borrowed_pending = None;
if self.active_backend() != next_backend {
match &mut self.storage {
MatcherStorage::Simple(_m) => {
}
MatcherStorage::Dfast(m) => {
m.short_hash = Vec::new();
m.long_hash = Vec::new();
m.reset();
}
MatcherStorage::Row(m) => {
m.row_heads = Vec::new();
m.row_positions = Vec::new();
m.row_tags = Vec::new();
m.reset();
}
MatcherStorage::HashChain(m) => {
m.table.hash_table = Vec::new();
m.table.chain_table = Vec::new();
m.table.hash3_table = Vec::new();
let vec_pool = &mut self.vec_pool;
m.reset(|mut data| {
data.resize(data.capacity(), 0);
vec_pool.push(data);
});
}
}
self.storage = match next_backend {
super::strategy::BackendTag::Simple => {
let fast = params.fast.expect("Fast level row carries a FastConfig");
MatcherStorage::Simple(FastKernelMatcher::with_params(
params.window_log,
fast.hash_log,
fast.mls,
fast.step_size,
))
}
super::strategy::BackendTag::Dfast => {
MatcherStorage::Dfast(DfastMatchGenerator::new(max_window_size))
}
super::strategy::BackendTag::Row => {
MatcherStorage::Row(RowMatchGenerator::new(max_window_size))
}
super::strategy::BackendTag::HashChain => {
MatcherStorage::HashChain(HcMatchGenerator::new(max_window_size))
}
};
}
self.strategy_tag = params.strategy_tag;
self.search = params.search;
self.parse = params.parse();
self.slice_size = self.base_slice_size.min(max_window_size);
self.reported_window_size = max_window_size;
let strategy_tag = self.strategy_tag;
let table_window_size = match hint {
Some(h) => {
let raw_log = source_size_ceil_log(h);
let shift = raw_log.max(MIN_WINDOW_LOG).min(usize::BITS as u8 - 1);
(1usize << shift).min(max_window_size)
}
None => max_window_size,
};
let mut resolved_table_bits: usize = 0;
match &mut self.storage {
MatcherStorage::Simple(m) => {
let fast = params.fast.expect("Fast level row carries a FastConfig");
let dict_attach_epoch = matches!(dict_hint, Some(size) if size > 0)
&& self
.reset_size_log
.is_none_or(|log| log <= FAST_ATTACH_DICT_CUTOFF_LOG);
let table_overwritten_by_restore = matches!(dict_hint, Some(size) if size > 0)
&& !dict_attach_epoch
&& self.primed.as_ref().is_some_and(|(_, _, captured)| {
*captured
== PrimedKey {
level,
params,
table_bits: 0,
fast_attach: false,
ldm: None,
}
});
m.reset(
params.window_log,
fast.hash_log,
fast.mls,
fast.step_size,
dict_attach_epoch,
table_overwritten_by_restore,
);
}
MatcherStorage::Dfast(dfast) => {
dfast.max_window_size = max_window_size;
let dcfg = params
.dfast
.expect("Dfast level row must carry a DfastConfig");
let long_bits = if hinted {
dfast_hash_bits_for_window(table_window_size).min(dcfg.long_hash_log as usize)
} else {
dcfg.long_hash_log as usize
};
let short_bits = if hinted {
dfast_hash_bits_for_window(table_window_size).min(dcfg.short_hash_log as usize)
} else {
dcfg.short_hash_log as usize
};
resolved_table_bits = long_bits;
dfast.set_hash_bits(long_bits, short_bits);
dfast.reset();
}
MatcherStorage::Row(row) => {
row.max_window_size = max_window_size;
row.lazy_depth = params.lazy_depth;
let mut row_cfg = params.row.expect("Row level row carries a RowConfig");
if hinted {
row_cfg.hash_bits = row_cfg
.hash_bits
.min(row_hash_bits_for_window(table_window_size));
}
row.configure(row_cfg);
resolved_table_bits = row.hash_bits();
row.reset();
}
MatcherStorage::HashChain(hc) => {
hc.table.max_window_size = max_window_size;
hc.hc.lazy_depth = params.lazy_depth;
let mut hc_cfg = params.hc.expect("HashChain level row carries an HcConfig");
if hinted && !matches!(dict_hint, Some(size) if size > 0) {
let wlog = hc_hash_bits_for_window(table_window_size);
let uses_bt = matches!(
strategy_tag,
super::strategy::StrategyTag::Btlazy2
| super::strategy::StrategyTag::BtOpt
| super::strategy::StrategyTag::BtUltra
| super::strategy::StrategyTag::BtUltra2
);
hc_cfg.hash_log = hc_cfg.hash_log.min(wlog + 1);
hc_cfg.chain_log = hc_cfg.chain_log.min(if uses_bt { wlog + 1 } else { wlog });
}
hc.configure(hc_cfg, strategy_tag, params.window_log);
let vec_pool = &mut self.vec_pool;
hc.reset(|mut data| {
data.resize(data.capacity(), 0);
vec_pool.push(data);
});
if let Some(src) = hint {
let src_hint = usize::try_from(src).unwrap_or(usize::MAX);
let expected = src_hint.saturating_add(dict_hint.unwrap_or(0));
hc.table.reserve_history(expected);
}
}
}
#[cfg(feature = "hash")]
if let MatcherStorage::HashChain(hc) = &mut self.storage {
let producer = self
.param_overrides
.as_ref()
.and_then(|ov| ov.ldm)
.map(|ldm_ov| {
let strategy_ord = ldm_strategy_ordinal(params.strategy_tag, params.lazy_depth);
let seed = super::ldm::params::LdmParams {
window_log: params.window_log as u32,
hash_log: ldm_ov.hash_log.unwrap_or(0),
hash_rate_log: ldm_ov.hash_rate_log.unwrap_or(0),
min_match_length: ldm_ov.min_match.unwrap_or(0),
bucket_size_log: ldm_ov.bucket_size_log.unwrap_or(0),
};
super::ldm::LdmProducer::new(seed.derive(strategy_ord))
});
hc.set_ldm_producer(producer);
}
let fast_attach = matches!(next_backend, super::strategy::BackendTag::Simple)
&& self
.reset_size_log
.is_none_or(|log| log <= FAST_ATTACH_DICT_CUTOFF_LOG);
let active_ldm = if matches!(params.search, super::strategy::SearchMethod::BinaryTree) {
self.param_overrides.and_then(|ov| ov.ldm)
} else {
None
};
self.reset_shape = Some((params, resolved_table_bits, fast_attach, active_ldm));
}
fn prime_with_dictionary(&mut self, dict_content: &[u8], offset_hist: [u32; 3]) {
match self.active_backend() {
super::strategy::BackendTag::Simple => {
self.simple_mut().prime_offset_history(offset_hist);
}
super::strategy::BackendTag::Dfast => {
self.dfast_matcher_mut().offset_hist = offset_hist
}
super::strategy::BackendTag::Row => self.row_matcher_mut().offset_hist = offset_hist,
super::strategy::BackendTag::HashChain => {
let matcher = self.hc_matcher_mut();
matcher.table.offset_hist = offset_hist;
matcher.table.mark_dictionary_primed();
}
}
if dict_content.is_empty() {
return;
}
const MAX_PRIMED_WINDOW_SIZE: usize =
(u32::MAX as usize - crate::common::MAX_BLOCK_SIZE as usize) / 2;
let requested_dict_budget = dict_content.len();
let base_max_window_size = match self.active_backend() {
super::strategy::BackendTag::Simple => self.simple_mut().max_window_size,
super::strategy::BackendTag::Dfast => self.dfast_matcher_mut().max_window_size,
super::strategy::BackendTag::Row => self.row_matcher_mut().max_window_size,
super::strategy::BackendTag::HashChain => self.hc_matcher_mut().table.max_window_size,
};
match self.active_backend() {
super::strategy::BackendTag::Simple => {
let matcher = self.simple_mut();
matcher.max_window_size = matcher
.max_window_size
.saturating_add(requested_dict_budget)
.min(MAX_PRIMED_WINDOW_SIZE);
}
super::strategy::BackendTag::Dfast => {
let matcher = self.dfast_matcher_mut();
matcher.max_window_size = matcher
.max_window_size
.saturating_add(requested_dict_budget)
.min(MAX_PRIMED_WINDOW_SIZE);
}
super::strategy::BackendTag::Row => {
let matcher = self.row_matcher_mut();
matcher.max_window_size = matcher
.max_window_size
.saturating_add(requested_dict_budget)
.min(MAX_PRIMED_WINDOW_SIZE);
}
super::strategy::BackendTag::HashChain => {
let matcher = self.hc_matcher_mut();
matcher.table.max_window_size = matcher
.table
.max_window_size
.saturating_add(requested_dict_budget)
.min(MAX_PRIMED_WINDOW_SIZE);
}
}
let mut start = 0usize;
let mut committed_dict_budget = 0usize;
let min_primed_tail = match self.active_backend() {
super::strategy::BackendTag::Simple => MIN_MATCH_LEN,
super::strategy::BackendTag::Dfast
| super::strategy::BackendTag::Row
| super::strategy::BackendTag::HashChain => 4,
};
while start < dict_content.len() {
let end = (start + self.slice_size).min(dict_content.len());
if end - start < min_primed_tail {
break;
}
let mut space = self.vec_pool.pop().unwrap_or_default();
space.clear();
space.extend_from_slice(&dict_content[start..end]);
self.commit_space(space);
self.skip_matching_for_dictionary_priming();
committed_dict_budget += end - start;
start = end;
}
let capped_retained_budget = MAX_PRIMED_WINDOW_SIZE.saturating_sub(base_max_window_size);
let granted_retained_budget = committed_dict_budget.min(capped_retained_budget);
let final_max_window_size = base_max_window_size.saturating_add(granted_retained_budget);
match self.active_backend() {
super::strategy::BackendTag::Simple => {
self.simple_mut().max_window_size = final_max_window_size;
}
super::strategy::BackendTag::Dfast => {
self.dfast_matcher_mut().max_window_size = final_max_window_size;
}
super::strategy::BackendTag::Row => {
self.row_matcher_mut().max_window_size = final_max_window_size;
}
super::strategy::BackendTag::HashChain => {
self.hc_matcher_mut().table.max_window_size = final_max_window_size;
}
}
if granted_retained_budget > 0 {
self.dictionary_retained_budget = self
.dictionary_retained_budget
.saturating_add(granted_retained_budget);
}
if self.active_backend() == super::strategy::BackendTag::HashChain {
let attach = self.hc_dict_attach_mode();
let table = &mut self.hc_matcher_mut().table;
table.set_dictionary_limit_from_primed_bytes(committed_dict_budget);
if !attach {
table.dms.invalidate();
} else if table.uses_bt {
table.prime_dms_bt(committed_dict_budget);
} else {
table.prime_dms_hc(committed_dict_budget);
}
}
match self.active_backend() {
super::strategy::BackendTag::Simple => self.simple_mut().mark_dict_primed(),
super::strategy::BackendTag::Dfast => self.dfast_matcher_mut().mark_dict_primed(),
super::strategy::BackendTag::Row => self.row_matcher_mut().mark_dict_primed(),
_ => {}
}
}
fn restore_primed_dictionary(&mut self, level: super::CompressionLevel) -> bool {
let Some((params, table_bits, fast_attach, ldm)) = self.reset_shape else {
return false;
};
let key = PrimedKey {
level,
params,
table_bits,
fast_attach,
ldm,
};
let Some((snapshot, budget, captured_key)) = &self.primed else {
return false;
};
if *captured_key != key {
return false;
}
let budget = *budget;
match (&mut self.storage, snapshot) {
(MatcherStorage::Simple(live), MatcherStorage::Simple(snap)) => {
live.clone_from(snap);
}
(MatcherStorage::HashChain(live), MatcherStorage::HashChain(snap))
if !snap.table.uses_bt =>
{
live.table.clone_from(&snap.table);
live.hc.clone_from(&snap.hc);
live.strategy_tag = snap.strategy_tag;
}
(live, snapshot_storage) => {
let mut storage = snapshot_storage.clone();
if let MatcherStorage::HashChain(hc) = &mut storage {
hc.table.ensure_tables();
}
#[cfg(feature = "hash")]
{
let fresh_ldm = if let MatcherStorage::HashChain(hc) = live {
hc.take_ldm_producer()
} else {
None
};
if let MatcherStorage::HashChain(hc) = &mut storage {
hc.set_ldm_producer(fresh_ldm);
}
}
*live = storage;
}
}
self.dictionary_retained_budget = budget;
true
}
fn capture_primed_dictionary(&mut self, level: super::CompressionLevel) {
let Some((params, table_bits, fast_attach, ldm)) = self.reset_shape else {
return;
};
let key = PrimedKey {
level,
params,
table_bits,
fast_attach,
ldm,
};
let bt_decoupled = matches!(
&self.storage,
MatcherStorage::HashChain(hc) if hc.table.uses_bt && hc.table.dms.is_primed()
);
if bt_decoupled {
let MatcherStorage::HashChain(hc) = &mut self.storage else {
unreachable!("bt_decoupled implies HashChain storage");
};
let hash_table = core::mem::take(&mut hc.table.hash_table);
let chain_table = core::mem::take(&mut hc.table.chain_table);
let hash3_table = core::mem::take(&mut hc.table.hash3_table);
#[cfg(feature = "hash")]
let ldm_producer = hc.take_ldm_producer();
let snapshot = self.storage.clone();
let MatcherStorage::HashChain(hc) = &mut self.storage else {
unreachable!("storage variant is stable across the take/put");
};
hc.table.hash_table = hash_table;
hc.table.chain_table = chain_table;
hc.table.hash3_table = hash3_table;
#[cfg(feature = "hash")]
hc.set_ldm_producer(ldm_producer);
self.primed = Some((snapshot, self.dictionary_retained_budget, key));
} else {
self.primed = Some((self.storage.clone(), self.dictionary_retained_budget, key));
}
}
fn invalidate_primed_dictionary(&mut self) {
self.primed = None;
match self.active_backend() {
super::strategy::BackendTag::Simple => self.simple_mut().invalidate_dict_cache(),
super::strategy::BackendTag::Dfast => self.dfast_matcher_mut().invalidate_dict_cache(),
super::strategy::BackendTag::Row => self.row_matcher_mut().invalidate_dict_cache(),
super::strategy::BackendTag::HashChain => {
self.hc_matcher_mut().table.dms.invalidate();
}
}
}
fn seed_dictionary_entropy(
&mut self,
huff: Option<&crate::huff0::huff0_encoder::HuffmanTable>,
ll: Option<&crate::fse::fse_encoder::FSETable>,
ml: Option<&crate::fse::fse_encoder::FSETable>,
of: Option<&crate::fse::fse_encoder::FSETable>,
) {
if self.active_backend() == super::strategy::BackendTag::HashChain {
self.hc_matcher_mut()
.seed_dictionary_entropy(huff, ll, ml, of);
}
}
fn window_size(&self) -> u64 {
self.reported_window_size as u64
}
fn get_next_space(&mut self) -> Vec<u8> {
if let Some(mut space) = self.vec_pool.pop() {
if space.len() > self.slice_size {
space.truncate(self.slice_size);
}
if space.len() < self.slice_size {
space.resize(self.slice_size, 0);
}
return space;
}
alloc::vec![0; self.slice_size]
}
fn get_last_space(&mut self) -> &[u8] {
match &self.storage {
MatcherStorage::Simple(m) => m.last_committed_space(),
MatcherStorage::Dfast(m) => m.get_last_space(),
MatcherStorage::Row(m) => m.get_last_space(),
MatcherStorage::HashChain(m) => m.table.get_last_space(),
}
}
fn commit_space(&mut self, space: Vec<u8>) {
let mut evicted_bytes = 0usize;
let vec_pool = &mut self.vec_pool;
match &mut self.storage {
MatcherStorage::Simple(m) => {
let pre = m.history_len_for_eviction_accounting();
m.accept_data(space);
let post = m.history_len_for_eviction_accounting();
evicted_bytes += pre.saturating_sub(post);
}
MatcherStorage::Dfast(m) => {
let pre = m.window_size;
let space_len = space.len();
m.add_data(space, |data| {
vec_pool.push(data);
});
evicted_bytes += (pre + space_len).saturating_sub(m.window_size);
}
MatcherStorage::Row(m) => {
let pre = m.window_size;
let space_len = space.len();
m.add_data(space, |data| {
vec_pool.push(data);
});
evicted_bytes += (pre + space_len).saturating_sub(m.window_size);
}
MatcherStorage::HashChain(m) => {
let pre = m.table.window_size;
let space_len = space.len();
m.table.add_data(space, |data| {
vec_pool.push(data);
});
evicted_bytes += (pre + space_len).saturating_sub(m.table.window_size);
}
}
if self.retire_dictionary_budget(evicted_bytes) {
self.trim_after_budget_retire();
}
}
fn start_matching(&mut self, mut handle_sequence: impl for<'a> FnMut(Sequence<'a>)) {
use super::strategy::{self, StrategyTag};
if let Some((block_start, block_end)) = self.borrowed_pending.take() {
match self.active_backend() {
super::strategy::BackendTag::Simple => {
let m = self.simple_mut();
if m.dict_is_attached() {
m.start_matching_borrowed_dict(
block_start,
block_end,
&mut handle_sequence,
);
} else {
m.start_matching_borrowed(block_start, block_end, &mut handle_sequence);
}
}
super::strategy::BackendTag::Dfast => self
.dfast_matcher_mut()
.start_matching_borrowed(block_start, block_end, &mut handle_sequence),
super::strategy::BackendTag::Row => {
let greedy = self.parse == super::strategy::ParseMode::Greedy;
self.row_matcher_mut().start_matching_borrowed(
block_start,
block_end,
greedy,
&mut handle_sequence,
);
}
super::strategy::BackendTag::HashChain => match self.search {
super::strategy::SearchMethod::HashChain => self
.hc_matcher_mut()
.start_matching_lazy_borrowed(block_start, block_end, &mut handle_sequence),
super::strategy::SearchMethod::BinaryTree => {
match self.strategy_tag {
StrategyTag::Btlazy2 => self
.hc_matcher_mut()
.start_matching_btlazy2(&mut handle_sequence),
other => unreachable!(
"borrowed BinaryTree scan is only supported for Btlazy2, got {other:?}"
),
}
}
other => {
unreachable!("HashChain backend with unexpected search {other:?}")
}
},
}
return;
}
use super::strategy::SearchMethod;
match self.search {
SearchMethod::Fast => {
self.simple_mut().start_matching(&mut handle_sequence);
self.recycle_simple_space();
}
SearchMethod::DoubleFast => {
self.dfast_matcher_mut()
.start_matching(&mut handle_sequence);
}
SearchMethod::RowHash => {
let greedy = self.parse == super::strategy::ParseMode::Greedy;
let row = self.row_matcher_mut();
if greedy {
row.start_matching_greedy(&mut handle_sequence);
} else {
row.start_matching(&mut handle_sequence);
}
}
SearchMethod::HashChain => {
self.hc_matcher_mut()
.start_matching_lazy(&mut handle_sequence);
}
SearchMethod::BinaryTree => match self.strategy_tag {
StrategyTag::Btlazy2 => self
.hc_matcher_mut()
.start_matching_btlazy2(&mut handle_sequence),
StrategyTag::BtOpt => self.compress_block::<strategy::BtOpt>(&mut handle_sequence),
StrategyTag::BtUltra => {
self.compress_block::<strategy::BtUltra>(&mut handle_sequence)
}
StrategyTag::BtUltra2 => {
self.compress_block::<strategy::BtUltra2>(&mut handle_sequence)
}
_ => unreachable!(
"SearchMethod::BinaryTree requires a BT strategy tag (Btlazy2/BtOpt/BtUltra/BtUltra2)"
),
},
}
}
fn skip_matching(&mut self) {
self.skip_matching_with_hint(None);
}
fn skip_matching_with_hint(&mut self, incompressible_hint: Option<bool>) {
if let Some((block_start, block_end)) = self.borrowed_pending.take() {
match self.active_backend() {
super::strategy::BackendTag::Simple => self.simple_mut().skip_matching_borrowed(
block_start,
block_end,
incompressible_hint,
),
super::strategy::BackendTag::Dfast => self
.dfast_matcher_mut()
.skip_matching_borrowed(block_start, block_end, incompressible_hint),
super::strategy::BackendTag::Row => self.row_matcher_mut().skip_matching_borrowed(
block_start,
block_end,
incompressible_hint,
),
super::strategy::BackendTag::HashChain => self
.hc_matcher_mut()
.skip_matching_borrowed(block_start, block_end, incompressible_hint),
}
return;
}
match self.active_backend() {
super::strategy::BackendTag::Simple => {
self.simple_mut()
.skip_matching_with_hint(incompressible_hint);
self.recycle_simple_space();
}
super::strategy::BackendTag::Dfast => {
self.dfast_matcher_mut().skip_matching(incompressible_hint)
}
super::strategy::BackendTag::Row => self
.row_matcher_mut()
.skip_matching_with_hint(incompressible_hint),
super::strategy::BackendTag::HashChain => {
self.hc_matcher_mut().skip_matching(incompressible_hint)
}
}
}
}
impl MatchGeneratorDriver {
fn compress_block<S: super::strategy::Strategy>(
&mut self,
handle_sequence: &mut impl for<'a> FnMut(Sequence<'a>),
) {
debug_assert_eq!(S::BACKEND, super::strategy::BackendTag::HashChain);
debug_assert!(
S::USE_BT,
"compress_block only handles the optimal (BT) path"
);
self.hc_matcher_mut()
.start_matching_strategy::<S>(handle_sequence);
}
}
#[derive(Clone)]
pub(crate) enum HcBackend {
Hc,
Bt(alloc::boxed::Box<super::bt::BtMatcher>),
}
impl HcBackend {
fn heap_size(&self) -> usize {
match self {
Self::Hc => 0,
Self::Bt(bt) => core::mem::size_of::<super::bt::BtMatcher>() + bt.heap_size(),
}
}
#[inline(always)]
pub(crate) fn bt_mut(&mut self) -> &mut super::bt::BtMatcher {
match self {
Self::Bt(bt) => bt,
Self::Hc => unreachable!("BT-only accessor called in HC mode"),
}
}
}
#[derive(Clone)]
struct HcMatchGenerator {
table: super::match_table::storage::MatchTable,
hc: super::hc::HcMatcher,
backend: HcBackend,
strategy_tag: super::strategy::StrategyTag,
}
macro_rules! bt_insert_step_no_rebase_body {
($table:expr, $search_depth:expr, $abs_pos:ident, $current_abs_end:ident, $target_abs:ident, $cmf:path) => {{
let idx = $abs_pos - $table.history_abs_start;
let concat: &[u8] = unsafe {
let lh = $table.live_history();
core::slice::from_raw_parts(lh.as_ptr(), lh.len())
};
if idx + 8 > concat.len() {
return 1;
}
debug_assert!(
$abs_pos <= $current_abs_end,
"BT walker called past current block end"
);
let tail_limit = $current_abs_end - $abs_pos;
let hash = $crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx,
$table.hash_log,
$table.search_mls,
);
#[cfg(all(
target_feature = "sse",
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($table.hash_table.as_ptr().add(hash).cast(), _MM_HINT_T0);
}
if idx + 1 + 8 <= concat.len() {
let hash_next =
$crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx + 1,
$table.hash_log,
$table.search_mls,
);
unsafe {
_mm_prefetch(
$table.hash_table.as_ptr().add(hash_next).cast(),
_MM_HINT_T0,
);
}
}
}
let Some(relative_pos) = $table.relative_position($abs_pos) else {
return 1;
};
let stored = relative_pos + 1;
let bt_mask = $table.bt_mask();
let bt_low = $abs_pos.saturating_sub(bt_mask);
let chain_ptr = $table.chain_table.as_mut_ptr();
debug_assert_eq!($table.chain_table.len(), 2 << $table.bt_log());
let window_low = $table.window_low_abs_for_target($target_abs);
let mut match_end_abs = $abs_pos + 9;
let mut best_len = 8usize;
let mut compares_left = $search_depth;
let mut common_length_smaller = 0usize;
let mut common_length_larger = 0usize;
let pair_idx = $table.bt_pair_index_for_abs($abs_pos);
let mut smaller_slot = pair_idx;
let mut larger_slot = pair_idx + 1;
let mut match_stored = $table.hash_table[hash];
$table.hash_table[hash] = stored;
while compares_left > 0 {
if match_stored == $crate::encoding::match_table::storage::HC_EMPTY {
break;
}
let Some(candidate_abs) = ($table.position_base + (match_stored as usize - 1))
.checked_sub($table.index_shift)
else {
break;
};
if candidate_abs < window_low || candidate_abs >= $abs_pos {
break;
}
compares_left -= 1;
let next_pair_idx = $table.bt_pair_index_for_abs(candidate_abs);
let next_smaller = unsafe { *chain_ptr.add(next_pair_idx) };
let next_larger = unsafe { *chain_ptr.add(next_pair_idx + 1) };
let seed_len = common_length_smaller.min(common_length_larger);
let candidate_idx = candidate_abs - $table.history_abs_start;
let match_len = unsafe { $cmf(concat, idx, candidate_idx, tail_limit, seed_len) };
if match_len > best_len {
best_len = match_len;
let candidate_end = candidate_abs + match_len;
if candidate_end > match_end_abs {
match_end_abs = candidate_end;
}
}
if match_len >= tail_limit {
break;
}
let candidate_next = candidate_idx + match_len;
let current_next = idx + match_len;
if unsafe {
*concat.get_unchecked(candidate_next) < *concat.get_unchecked(current_next)
} {
unsafe { *chain_ptr.add(smaller_slot) = match_stored };
common_length_smaller = match_len;
if candidate_abs <= bt_low {
smaller_slot = usize::MAX;
break;
}
smaller_slot = next_pair_idx + 1;
match_stored = next_larger;
} else {
unsafe { *chain_ptr.add(larger_slot) = match_stored };
common_length_larger = match_len;
if candidate_abs <= bt_low {
larger_slot = usize::MAX;
break;
}
larger_slot = next_pair_idx;
match_stored = next_smaller;
}
}
if smaller_slot != usize::MAX {
unsafe {
*chain_ptr.add(smaller_slot) = $crate::encoding::match_table::storage::HC_EMPTY
};
}
if larger_slot != usize::MAX {
unsafe {
*chain_ptr.add(larger_slot) = $crate::encoding::match_table::storage::HC_EMPTY
};
}
let speed_positions = if best_len > 384 {
(best_len - 384).min(192)
} else {
0
};
speed_positions.max(match_end_abs - ($abs_pos + 8))
}};
}
pub(crate) use bt_insert_step_no_rebase_body;
#[inline]
fn btlazy2_offbase(offset: usize, reps: [u32; 3], ll0: bool) -> u32 {
let o = offset as u32;
if ll0 {
if o == reps[1] {
1
} else if o == reps[2] {
2
} else if reps[0] > 1 && o == reps[0] - 1 {
3
} else {
o + 3
}
} else if o == reps[0] {
1
} else if o == reps[1] {
2
} else if o == reps[2] {
3
} else {
o + 3
}
}
#[inline]
fn btlazy2_gain(match_len: usize, offset: usize, reps: [u32; 3], ll0: bool) -> i64 {
let offbase = btlazy2_offbase(offset, reps, ll0);
(match_len as i64) * 4 - (31 - offbase.leading_zeros()) as i64
}
macro_rules! start_matching_btlazy2_body {
($self:ident, $handle_sequence:ident, $collect:ident, $cmf:path $(,)?) => {{
$self.table.ensure_tables();
let (current_abs_start, current_len) = $self.table.current_block_range();
if current_len == 0 {
return;
}
let current_ptr = $self.table.get_last_space().as_ptr();
let current: &[u8] = unsafe { core::slice::from_raw_parts(current_ptr, current_len) };
let history_abs_start = $self.table.history_abs_start;
let concat_full: &[u8] = unsafe {
let lh = $self.table.live_history();
core::slice::from_raw_parts(lh.as_ptr(), lh.len())
};
let current_abs_end = current_abs_start + current_len;
$self
.table
.apply_limited_update_after_long_match(current_abs_start);
$self
.table
.backfill_boundary_positions(current_abs_start, current_abs_end);
let profile = HcOptimalCostProfile::const_for_strategy::<super::strategy::Btlazy2>();
let mut candidates = core::mem::take(&mut $self.backend.bt_mut().opt_candidates_scratch);
let depth = $self.hc.lazy_depth as usize;
let mut pos = 0usize;
let mut literals_start = 0usize;
macro_rules! bt_select {
($p:expr) => {{
let sel_pos: usize = $p;
let ll0 = sel_pos == literals_start;
let sel_abs = current_abs_start + sel_pos;
candidates.clear();
let query = HcCandidateQuery {
reps: $self.table.offset_hist,
lit_len: sel_pos - literals_start,
ldm_candidate: None,
};
unsafe {
$self.$collect::<super::strategy::Btlazy2, true>(
sel_abs,
current_abs_end,
profile,
query,
&mut candidates,
);
}
let reps = $self.table.offset_hist;
let mut sel_ml = 0usize;
let mut sel_off = 0usize;
let mut sel_gain = i64::MIN;
for c in candidates.iter() {
let ml = c.match_len.min(current_len - sel_pos);
if ml < HC_OPT_MIN_MATCH_LEN {
continue;
}
let g = btlazy2_gain(ml, c.offset, reps, ll0);
if g > sel_gain {
sel_gain = g;
sel_ml = ml;
sel_off = c.offset;
}
}
let sel_idx = sel_abs - history_abs_start;
let probe_rep = if ll0 {
reps[1] as usize
} else {
reps[0] as usize
};
if probe_rep != 0 && sel_idx >= probe_rep {
let tail = current_len - sel_pos;
let rep_ml =
unsafe { $cmf(concat_full, sel_idx, sel_idx - probe_rep, tail, 0) };
if rep_ml >= HC_OPT_MIN_MATCH_LEN
&& btlazy2_gain(rep_ml, probe_rep, reps, ll0) > sel_gain
{
sel_ml = rep_ml;
sel_off = probe_rep;
}
}
(sel_ml, sel_off)
}};
}
while pos + HC_OPT_MIN_MATCH_LEN <= current_len {
let (mut best_ml, mut best_off) = bt_select!(pos);
if best_ml < HC_OPT_MIN_MATCH_LEN {
pos += 1;
continue;
}
let mut start = pos;
let mut d = 0usize;
while d < depth && start + 1 + HC_OPT_MIN_MATCH_LEN <= current_len {
let look = start + 1;
let (ml2, off2) = bt_select!(look);
if ml2 < HC_OPT_MIN_MATCH_LEN {
break;
}
let reps = $self.table.offset_hist;
let margin = if d == 0 { 4 } else { 7 };
let gain1 = btlazy2_gain(best_ml, best_off, reps, start == literals_start) + margin;
let gain2 = btlazy2_gain(ml2, off2, reps, false);
if gain2 > gain1 {
best_ml = ml2;
best_off = off2;
start = look;
d += 1;
} else {
break;
}
}
let lit_len = start - literals_start;
let literals = ¤t[literals_start..start];
$handle_sequence(Sequence::Triple {
literals,
offset: best_off,
match_len: best_ml,
});
let _ = encode_offset_with_history(
best_off as u32,
lit_len as u32,
&mut $self.table.offset_hist,
);
pos = start + best_ml;
literals_start = pos;
}
if literals_start < current_len {
$handle_sequence(Sequence::Literals {
literals: ¤t[literals_start..],
});
}
$self.backend.bt_mut().opt_candidates_scratch = candidates;
}};
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn priceset_improved_mask8_avx2(next_cost: &[u32; 8], node_price: &[u32]) -> u8 {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m256i, _mm256_andnot_si256, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256,
_mm256_min_epu32, _mm256_movemask_ps,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m256i, _mm256_andnot_si256, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256,
_mm256_min_epu32, _mm256_movemask_ps,
};
let nc = unsafe { _mm256_loadu_si256(next_cost.as_ptr() as *const __m256i) };
let np = unsafe { _mm256_loadu_si256(node_price.as_ptr() as *const __m256i) };
let min = _mm256_min_epu32(nc, np);
let le = _mm256_cmpeq_epi32(min, nc); let eq = _mm256_cmpeq_epi32(nc, np); let lt = _mm256_andnot_si256(eq, le); _mm256_movemask_ps(_mm256_castsi256_ps(lt)) as u8
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn priceset_next_cost(
profile: HcOptimalCostProfile,
stats: &HcOptState,
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
match_len: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
) -> u32 {
let ml_price =
BtMatcher::cached_match_length_price(profile, stats, match_len, ml_cache, ml_stamp);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, stats),
);
BtMatcher::add_prices(base_cost, seq_cost)
}
#[inline]
#[allow(clippy::too_many_arguments)]
#[cfg_attr(
any(
all(target_arch = "aarch64", target_endian = "little"),
all(target_arch = "wasm32", target_feature = "simd128")
),
allow(dead_code)
)]
fn priceset_range_nonabort_scalar(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
let mut new_last = last_pos;
for ml in start..=max {
let next_cost = priceset_next_cost(
profile, stats, ml_cache, ml_stamp, ml, ll0_price, off_price, base_cost,
);
let next = pos + ml;
if next_cost < node_prices[next] {
node_prices[next] = next_cost;
nodes[next] = HcOptimalNode {
off,
mlen: ml as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
}
new_last
}
#[cfg(test)]
#[test]
fn priceset_tier_helpers_match_scalar() {
fn scalar_deint<const W: usize>(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; W]> {
let mut out = [0u32; W];
for k in 0..W {
if cells[k][1] != stamp {
return None;
}
out[k] = cells[k][0];
}
Some(out)
}
fn scalar_mask<const W: usize>(nc: &[u32; W], np: &[u32]) -> u8 {
let mut m = 0u8;
for k in 0..W {
if nc[k] < np[k] {
m |= 1 << k;
}
}
m
}
const S: u32 = 0x55;
let warm: [[u32; 2]; 4] = [[11, S], [22, S], [33, S], [44, S]];
let mut cold = warm;
cold[2][1] = S ^ 1; let nc4: [u32; 4] = [10, 99, 30, 41];
let np4: [u32; 4] = [20, 21, 30, 99];
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
unsafe {
assert_eq!(
priceset_cached_prices4_neon(&warm, S),
scalar_deint::<4>(&warm, S)
);
assert_eq!(priceset_cached_prices4_neon(&cold, S), None);
assert_eq!(
priceset_improved_mask4_neon(&nc4, &np4),
scalar_mask::<4>(&nc4, &np4)
);
}
#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
{
if std::is_x86_feature_detected!("sse4.2") {
unsafe {
assert_eq!(
priceset_cached_prices4_sse41(&warm, S),
scalar_deint::<4>(&warm, S)
);
assert_eq!(priceset_cached_prices4_sse41(&cold, S), None);
assert_eq!(
priceset_improved_mask4_sse41(&nc4, &np4),
scalar_mask::<4>(&nc4, &np4)
);
}
}
if std::is_x86_feature_detected!("avx2") {
let warm8: [[u32; 2]; 8] = [
[11, S],
[22, S],
[33, S],
[44, S],
[55, S],
[66, S],
[77, S],
[88, S],
];
let mut cold8 = warm8;
cold8[5][1] = S ^ 1;
let nc8: [u32; 8] = [10, 99, 30, 41, 99, 60, 99, 80];
let np8: [u32; 8] = [20, 21, 30, 99, 50, 99, 70, 99];
unsafe {
assert_eq!(
priceset_cached_prices8_avx2(&warm8, S),
scalar_deint::<8>(&warm8, S)
);
assert_eq!(priceset_cached_prices8_avx2(&cold8, S), None);
assert_eq!(
priceset_improved_mask8_avx2(&nc8, &np8),
scalar_mask::<8>(&nc8, &np8)
);
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
#[cfg_attr(
not(any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "aarch64", target_endian = "little"),
all(target_arch = "wasm32", target_feature = "simd128")
)),
allow(dead_code)
)]
fn priceset_range_vec<const W: usize>(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
deint: impl Fn(&[[u32; 2]], u32) -> Option<[u32; W]>,
mask: impl Fn(&[u32; W], &[u32]) -> u8,
) -> usize {
let mut new_last = last_pos;
let mut buf = [0u32; W];
let c_base = base_cost
.wrapping_add(ll0_price)
.wrapping_add(profile.match_price_from_parts(off_price, 0, stats));
let mut ml = start;
while ml + W <= max + 1 {
let vectorised = if ml + W <= ml_cache.len() {
deint(&ml_cache[ml..ml + W], ml_stamp)
} else {
None
};
if let Some(prices) = vectorised {
for (k, slot) in buf.iter_mut().enumerate() {
*slot = c_base.wrapping_add(prices[k]);
}
} else {
for (k, slot) in buf.iter_mut().enumerate() {
*slot = priceset_next_cost(
profile,
stats,
ml_cache,
ml_stamp,
ml + k,
ll0_price,
off_price,
base_cost,
);
}
}
let base_next = pos + ml;
let mut bits = mask(&buf, &node_prices[base_next..base_next + W]);
while bits != 0 {
let k = bits.trailing_zeros() as usize;
bits &= bits - 1;
let next = base_next + k;
node_prices[next] = buf[k];
nodes[next] = HcOptimalNode {
off,
mlen: (ml + k) as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
ml += W;
}
while ml <= max {
let next_cost = priceset_next_cost(
profile, stats, ml_cache, ml_stamp, ml, ll0_price, off_price, base_cost,
);
let next = pos + ml;
if next_cost < node_prices[next] {
node_prices[next] = next_cost;
nodes[next] = HcOptimalNode {
off,
mlen: ml as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
ml += 1;
}
new_last
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn priceset_cached_prices8_avx2(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 8]> {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m256i, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256, _mm256_movemask_ps,
_mm256_permute4x64_epi64, _mm256_set1_epi32, _mm256_shuffle_epi32, _mm256_storeu_si256,
_mm256_unpackhi_epi64, _mm256_unpacklo_epi64,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m256i, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256, _mm256_movemask_ps,
_mm256_permute4x64_epi64, _mm256_set1_epi32, _mm256_shuffle_epi32, _mm256_storeu_si256,
_mm256_unpackhi_epi64, _mm256_unpacklo_epi64,
};
debug_assert!(cells.len() >= 8);
let base = cells.as_ptr() as *const __m256i;
let v0 = unsafe { _mm256_loadu_si256(base) };
let v1 = unsafe { _mm256_loadu_si256(base.add(1)) };
let s0 = _mm256_shuffle_epi32(v0, 0xD8); let s1 = _mm256_shuffle_epi32(v1, 0xD8); let gens = _mm256_unpackhi_epi64(s0, s1);
let eq = _mm256_cmpeq_epi32(gens, _mm256_set1_epi32(stamp as i32));
if _mm256_movemask_ps(_mm256_castsi256_ps(eq)) as u8 != 0xFF {
return None;
}
let p_scrambled = _mm256_unpacklo_epi64(s0, s1);
let prices = _mm256_permute4x64_epi64(p_scrambled, 0xD8);
let mut out = [0u32; 8];
unsafe { _mm256_storeu_si256(out.as_mut_ptr() as *mut __m256i, prices) };
Some(out)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
#[inline]
#[allow(clippy::too_many_arguments)]
unsafe fn priceset_range_nonabort_avx2(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<8>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices8_avx2(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask8_avx2(nc, np) },
)
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn priceset_cached_prices4_neon(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
use core::arch::aarch64::{vceqq_u32, vdupq_n_u32, vld2q_u32, vminvq_u32, vst1q_u32};
debug_assert!(cells.len() >= 4);
let pair = unsafe { vld2q_u32(cells.as_ptr() as *const u32) };
let eq = vceqq_u32(pair.1, vdupq_n_u32(stamp));
if vminvq_u32(eq) != u32::MAX {
return None;
}
let mut out = [0u32; 4];
unsafe { vst1q_u32(out.as_mut_ptr(), pair.0) };
Some(out)
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn priceset_improved_mask4_neon(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
use core::arch::aarch64::{vaddvq_u32, vandq_u32, vcltq_u32, vld1q_u32, vst1q_u32};
let nc = unsafe { vld1q_u32(next_cost.as_ptr()) };
let np = unsafe { vld1q_u32(node_price.as_ptr()) };
let lt = vcltq_u32(nc, np);
let weights: [u32; 4] = [1, 2, 4, 8];
let w = unsafe { vld1q_u32(weights.as_ptr()) };
let bits = vandq_u32(lt, w);
let _ = vst1q_u32; vaddvq_u32(bits) as u8
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
#[allow(clippy::too_many_arguments)]
unsafe fn priceset_range_nonabort_neon(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_neon(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_neon(nc, np) },
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
unsafe fn priceset_cached_prices4_sse41(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m128i, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128, _mm_movemask_ps,
_mm_set1_epi32, _mm_shuffle_epi32, _mm_storeu_si128, _mm_unpackhi_epi64,
_mm_unpacklo_epi64,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m128i, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128, _mm_movemask_ps,
_mm_set1_epi32, _mm_shuffle_epi32, _mm_storeu_si128, _mm_unpackhi_epi64,
_mm_unpacklo_epi64,
};
debug_assert!(cells.len() >= 4);
let base = cells.as_ptr() as *const __m128i;
let v0 = unsafe { _mm_loadu_si128(base) }; let v1 = unsafe { _mm_loadu_si128(base.add(1)) }; let s0 = _mm_shuffle_epi32(v0, 0xD8); let s1 = _mm_shuffle_epi32(v1, 0xD8); let gens = _mm_unpackhi_epi64(s0, s1); let eq = _mm_cmpeq_epi32(gens, _mm_set1_epi32(stamp as i32));
if _mm_movemask_ps(_mm_castsi128_ps(eq)) as u8 & 0x0F != 0x0F {
return None;
}
let prices = _mm_unpacklo_epi64(s0, s1); let mut out = [0u32; 4];
unsafe { _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, prices) };
Some(out)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
unsafe fn priceset_improved_mask4_sse41(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m128i, _mm_andnot_si128, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128,
_mm_min_epu32, _mm_movemask_ps,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m128i, _mm_andnot_si128, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128,
_mm_min_epu32, _mm_movemask_ps,
};
let nc = unsafe { _mm_loadu_si128(next_cost.as_ptr() as *const __m128i) };
let np = unsafe { _mm_loadu_si128(node_price.as_ptr() as *const __m128i) };
let min = _mm_min_epu32(nc, np);
let le = _mm_cmpeq_epi32(min, nc);
let eq = _mm_cmpeq_epi32(nc, np);
let lt = _mm_andnot_si128(eq, le);
(_mm_movemask_ps(_mm_castsi128_ps(lt)) as u8) & 0x0F
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
#[allow(clippy::too_many_arguments)]
unsafe fn priceset_range_nonabort_sse41(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_sse41(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_sse41(nc, np) },
)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
unsafe fn priceset_cached_prices4_simd128(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
use core::arch::wasm32::{
u32x4_all_true, u32x4_eq, u32x4_shuffle, u32x4_splat, v128, v128_load, v128_store,
};
debug_assert!(cells.len() >= 4);
let base = cells.as_ptr() as *const v128;
let v0 = unsafe { v128_load(base) }; let v1 = unsafe { v128_load(base.add(1)) }; let gens = u32x4_shuffle::<1, 3, 5, 7>(v0, v1); let eq = u32x4_eq(gens, u32x4_splat(stamp));
if !u32x4_all_true(eq) {
return None;
}
let prices = u32x4_shuffle::<0, 2, 4, 6>(v0, v1); let mut out = [0u32; 4];
unsafe { v128_store(out.as_mut_ptr() as *mut v128, prices) };
Some(out)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
unsafe fn priceset_improved_mask4_simd128(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
use core::arch::wasm32::{u32x4_bitmask, u32x4_lt, v128, v128_load};
let nc = unsafe { v128_load(next_cost.as_ptr() as *const v128) };
let np = unsafe { v128_load(node_price.as_ptr() as *const v128) };
u32x4_bitmask(u32x4_lt(nc, np))
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
#[allow(clippy::too_many_arguments)]
unsafe fn priceset_range_nonabort_simd128(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_simd128(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_simd128(nc, np) },
)
}
macro_rules! build_optimal_plan_impl_body {
(
$self:expr,
$strategy_ty:ty,
$current:ident,
$current_abs_start:ident,
$current_len:ident,
$initial_state:ident,
$stats:ident,
$out:ident,
$collect:ident,
$priceset:path $(,)?
) => {{
let current_abs_end = $current_abs_start + $current_len;
let min_match_len = HC_OPT_MIN_MATCH_LEN;
let frontier_limit = $current_len.min(HC_OPT_NUM - 1);
let initial_reps = $initial_state.reps;
let initial_litlen = $initial_state.litlen;
let ldm_block_offset = $initial_state.block_offset;
let mut profile = $initial_state.profile;
profile.sufficient_match_len = $self.hc.sufficient_match_len_for_pass(profile);
debug_assert!(
<$strategy_ty as super::strategy::Strategy>::USE_BT,
"build_optimal_plan_impl_body called on non-BT strategy"
);
let abort_on_worse_match: bool =
<$strategy_ty as super::strategy::Strategy>::OPT_LEVEL == 0;
let opt_level: bool = <$strategy_ty as super::strategy::Strategy>::OPT_LEVEL >= 2;
let mut nodes = core::mem::take(&mut $self.backend.bt_mut().opt_nodes_scratch);
let mut node_prices = core::mem::take(&mut $self.backend.bt_mut().opt_node_prices_scratch);
let frontier_buffer_size = frontier_limit + 2;
if nodes.len() < HC_OPT_NODE_LEN {
nodes = alloc::vec![HcOptimalNode::default(); HC_OPT_NODE_LEN].into_boxed_slice();
}
if node_prices.len() < HC_OPT_NODE_LEN {
node_prices = alloc::vec![u32::MAX; HC_OPT_NODE_LEN].into_boxed_slice();
}
let mut candidates = core::mem::take(&mut $self.backend.bt_mut().opt_candidates_scratch);
candidates.clear();
if candidates.capacity() < MAX_HC_SEARCH_DEPTH {
candidates.reserve_exact(MAX_HC_SEARCH_DEPTH - candidates.capacity());
}
let mut store = core::mem::take(&mut $self.backend.bt_mut().opt_store_scratch);
store.clear();
let mut price_arena = core::mem::take(&mut $self.backend.bt_mut().opt_price_arena);
if price_arena.len() < HC_OPT_PRICE_ARENA_LEN {
price_arena = alloc::vec![[0u32; 2]; HC_OPT_PRICE_ARENA_LEN].into_boxed_slice();
}
let arena_base = price_arena.as_mut_ptr();
let mut ll_cache: &mut [[u32; 2]] =
unsafe { core::slice::from_raw_parts_mut(arena_base, HC_OPT_PRICE_STRIDE) };
let mut ml_cache: &mut [[u32; 2]] = unsafe {
core::slice::from_raw_parts_mut(arena_base.add(HC_OPT_PRICE_STRIDE), HC_OPT_PRICE_STRIDE)
};
$self.backend.bt_mut().opt_ll_price_stamp = $self
.backend
.bt_mut()
.opt_ll_price_stamp
.wrapping_add(1)
.max(1);
let ll_price_stamp = $self.backend.bt_mut().opt_ll_price_stamp;
$self.backend.bt_mut().opt_lit_price_stamp = $self
.backend
.bt_mut()
.opt_lit_price_stamp
.wrapping_add(1)
.max(1);
let lit_price_stamp = $self.backend.bt_mut().opt_lit_price_stamp;
$self.backend.bt_mut().opt_ml_price_stamp = $self
.backend
.bt_mut()
.opt_ml_price_stamp
.wrapping_add(1)
.max(1);
let ml_price_stamp = $self.backend.bt_mut().opt_ml_price_stamp;
let node0_price = BtMatcher::cached_lit_length_price(
profile,
$stats,
initial_litlen,
&mut ll_cache,
ll_price_stamp,
);
nodes[0] = HcOptimalNode {
litlen: initial_litlen as u32,
reps: initial_reps,
..HcOptimalNode::default()
};
node_prices[0] = node0_price;
let sufficient_len = profile.sufficient_match_len;
let ll0_price = BtMatcher::cached_lit_length_price(
profile,
$stats,
0,
&mut ll_cache,
ll_price_stamp,
);
let ll1_price = BtMatcher::cached_lit_length_price(
profile,
$stats,
1,
&mut ll_cache,
ll_price_stamp,
);
let mut pos = 1usize;
let mut last_pos = 0usize;
let mut forced_end: Option<usize> = None;
let mut forced_end_state: Option<HcOptimalNode> = None;
let mut forced_end_price: Option<u32> = None;
let mut seed_forced_shortest_path = false;
let mut opt_ldm = HcOptLdmState {
seq_store: HcRawSeqStore {
pos: 0,
pos_in_sequence: 0,
size: $self.backend.bt_mut().ldm_sequences.len(),
},
..HcOptLdmState::default()
};
let has_ldm = !$self.backend.bt_mut().ldm_sequences.is_empty();
if has_ldm {
if ldm_block_offset > 0 {
$self
.backend
.bt_mut()
.ldm_skip_raw_seq_store_bytes(&mut opt_ldm.seq_store, ldm_block_offset);
}
$self
.backend
.bt_mut()
.ldm_get_next_match_and_update_seq_store(&mut opt_ldm, 0, $current_len);
}
if $current_len >= min_match_len {
let seed_ldm = if has_ldm {
$self.backend.bt_mut().ldm_process_match_candidate(
&mut opt_ldm,
0,
$current_len,
min_match_len,
)
} else {
None
};
candidates.clear();
unsafe {
$self.$collect::<$strategy_ty, true>(
$current_abs_start,
current_abs_end,
profile,
HcCandidateQuery {
reps: initial_reps,
lit_len: initial_litlen,
ldm_candidate: seed_ldm,
},
&mut candidates,
)
};
if !candidates.is_empty() {
last_pos = (min_match_len - 1).min(frontier_limit);
for p in 1..min_match_len.min(frontier_buffer_size) {
BtMatcher::reset_opt_node(&mut nodes[p]);
node_prices[p] = u32::MAX;
let seed_litlen = initial_litlen
.checked_add(p)
.and_then(|s| u32::try_from(s).ok())
.expect("optimal parser seed litlen out of u32 range");
nodes[p].litlen = seed_litlen;
}
}
if let Some(candidate) = candidates.last() {
let longest_len = candidate.match_len.min($current_len);
if longest_len > sufficient_len {
let off_base = BtMatcher::encode_offset_base_with_reps(
candidate.offset as u32,
initial_litlen,
initial_reps,
);
let off_price = profile
.offset_price_for::<ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>($stats, off_base);
let ml_price = BtMatcher::cached_match_length_price(
profile,
$stats,
longest_len,
&mut ml_cache,
ml_price_stamp,
);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, $stats),
);
let forced_price = BtMatcher::add_prices(node_prices[0], seq_cost);
let forced_state = HcOptimalNode {
off: candidate.offset as u32,
mlen: longest_len as u32,
litlen: 0,
reps: initial_reps,
};
if longest_len < frontier_buffer_size && forced_price < node_prices[longest_len] {
nodes[longest_len] = forced_state;
node_prices[longest_len] = forced_price;
}
forced_end = Some(longest_len);
forced_end_state = Some(forced_state);
forced_end_price = Some(forced_price);
seed_forced_shortest_path = true;
}
}
if !seed_forced_shortest_path {
let mut prev_max_len = min_match_len - 1;
for candidate in candidates.iter() {
let max_match_len = candidate.match_len.min(frontier_limit);
if max_match_len < min_match_len {
continue;
}
let start_len = (prev_max_len + 1).max(min_match_len);
if start_len > max_match_len {
prev_max_len = prev_max_len.max(max_match_len);
continue;
}
if max_match_len > last_pos {
BtMatcher::reset_opt_nodes(
&mut nodes,
&mut node_prices,
last_pos + 1,
max_match_len,
);
}
let off_base = BtMatcher::encode_offset_base_with_reps(
candidate.offset as u32,
initial_litlen,
initial_reps,
);
let off_price = profile
.offset_price_for::<ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>($stats, off_base);
debug_assert!(max_match_len < frontier_buffer_size);
let nodes0_price = node_prices[0];
for match_len in (start_len..=max_match_len).rev() {
let ml_price = BtMatcher::cached_match_length_price(
profile,
$stats,
match_len,
&mut ml_cache,
ml_price_stamp,
);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, $stats),
);
let next_cost = BtMatcher::add_prices(nodes0_price, seq_cost);
let node_price = unsafe { *node_prices.get_unchecked(match_len) };
if match_len > last_pos || next_cost < node_price {
let slot = unsafe { nodes.get_unchecked_mut(match_len) };
*slot = HcOptimalNode {
off: candidate.offset as u32,
mlen: match_len as u32,
litlen: 0,
reps: initial_reps,
};
unsafe { *node_prices.get_unchecked_mut(match_len) = next_cost };
if match_len > last_pos {
last_pos = match_len;
}
} else if abort_on_worse_match {
break;
}
}
prev_max_len = prev_max_len.max(max_match_len);
}
if last_pos + 1 < frontier_buffer_size {
node_prices[last_pos + 1] = u32::MAX;
}
}
}
while !seed_forced_shortest_path && pos <= last_pos && pos <= frontier_limit {
debug_assert!(pos + 1 < frontier_buffer_size);
let prev_node = unsafe { *nodes.get_unchecked(pos - 1) };
let prev_node_price = unsafe { *node_prices.get_unchecked(pos - 1) };
if prev_node_price != u32::MAX {
let lit_len = prev_node.litlen as usize + 1;
let lit_price = {
let bt = $self.backend.bt_mut();
BtMatcher::cached_literal_price(
profile,
$stats,
$current[pos - 1],
&mut bt.opt_lit_price_scratch,
&mut bt.opt_lit_price_generation,
lit_price_stamp,
)
};
let ll_delta = BtMatcher::cached_lit_length_delta_price(
profile,
$stats,
lit_len,
&mut ll_cache,
ll_price_stamp,
);
let lit_cost = BtMatcher::add_price_delta(prev_node_price, lit_price, ll_delta);
let node_pos_price = unsafe { *node_prices.get_unchecked(pos) };
if lit_cost <= node_pos_price {
let prev_match = unsafe { *nodes.get_unchecked(pos) };
let slot = unsafe { nodes.get_unchecked_mut(pos) };
*slot = prev_node;
slot.litlen = lit_len as u32;
node_prices[pos] = lit_cost;
#[allow(clippy::collapsible_if)]
if opt_level
&& prev_match.mlen > 0
&& prev_match.litlen == 0
&& pos < $current_len
{
if ll1_price < ll0_price {
let next_lit_price = {
let bt = $self.backend.bt_mut();
BtMatcher::cached_literal_price(
profile,
$stats,
$current[pos],
&mut bt.opt_lit_price_scratch,
&mut bt.opt_lit_price_generation,
lit_price_stamp,
)
};
let with1literal = BtMatcher::add_price_delta(
node_pos_price,
next_lit_price,
ll1_price as i32 - ll0_price as i32,
);
let ll_delta_next = BtMatcher::cached_lit_length_delta_price(
profile,
$stats,
lit_len + 1,
&mut ll_cache,
ll_price_stamp,
);
let with_more_literals =
BtMatcher::add_price_delta(lit_cost, next_lit_price, ll_delta_next);
let next = pos + 1;
let next_price = unsafe { *node_prices.get_unchecked(next) };
if with1literal < with_more_literals && with1literal < next_price {
debug_assert!(pos >= prev_match.mlen as usize);
let prev_pos = pos - prev_match.mlen as usize;
{
let prev_state = unsafe { *nodes.get_unchecked(prev_pos) };
let (_, reps_after_match) = BtMatcher::encode_offset_with_reps(
prev_match.off,
prev_state.litlen as usize,
prev_state.reps,
);
let slot = unsafe { nodes.get_unchecked_mut(next) };
*slot = prev_match;
slot.reps = reps_after_match;
slot.litlen = 1;
node_prices[next] = with1literal;
if next > last_pos {
last_pos = next;
}
}
}
}
}
}
}
let base_cost = unsafe { *node_prices.get_unchecked(pos) };
if base_cost == u32::MAX {
pos += 1;
continue;
}
{
let base_node = unsafe { *nodes.get_unchecked(pos) };
if base_node.mlen > 0 && base_node.litlen == 0 {
debug_assert!(pos >= base_node.mlen as usize);
let prev_pos = pos - base_node.mlen as usize;
let prev_state = unsafe { *nodes.get_unchecked(prev_pos) };
let (_, reps_after_match) = BtMatcher::encode_offset_with_reps(
base_node.off,
prev_state.litlen as usize,
prev_state.reps,
);
unsafe { nodes.get_unchecked_mut(pos).reps = reps_after_match };
}
}
if pos + 8 > $current_len {
pos += 1;
continue;
}
if pos == last_pos {
break;
}
let next_price = unsafe { *node_prices.get_unchecked(pos + 1) };
if abort_on_worse_match
&& next_price <= base_cost.saturating_add(HC_BITCOST_MULTIPLIER / 2)
{
pos += 1;
continue;
}
let abs_pos = $current_abs_start + pos;
let ldm_candidate = if has_ldm {
$self.backend.bt_mut().ldm_process_match_candidate(
&mut opt_ldm,
pos,
$current_len - pos,
min_match_len,
)
} else {
None
};
candidates.clear();
unsafe {
$self.$collect::<$strategy_ty, true>(
abs_pos,
current_abs_end,
profile,
HcCandidateQuery {
reps: nodes.get_unchecked(pos).reps,
lit_len: nodes.get_unchecked(pos).litlen as usize,
ldm_candidate,
},
&mut candidates,
)
};
let base_reps = unsafe { nodes.get_unchecked(pos).reps };
let base_litlen = unsafe { nodes.get_unchecked(pos).litlen as usize };
if let Some(candidate) = candidates.last() {
let longest_len = candidate.match_len.min($current_len - pos);
if longest_len > sufficient_len
|| pos + longest_len >= HC_OPT_NUM
|| pos + longest_len >= $current_len
{
let lit_len = base_litlen;
let off_base = BtMatcher::encode_offset_base_with_reps(
candidate.offset as u32,
lit_len,
base_reps,
);
let off_price = profile
.offset_price_for::<ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>($stats, off_base);
let ml_price = BtMatcher::cached_match_length_price(
profile,
$stats,
longest_len,
&mut ml_cache,
ml_price_stamp,
);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, $stats),
);
let forced_price = BtMatcher::add_prices(base_cost, seq_cost);
let end_pos = (pos + longest_len).min($current_len);
forced_end = Some(end_pos);
forced_end_state = Some(HcOptimalNode {
off: candidate.offset as u32,
mlen: longest_len as u32,
litlen: 0,
reps: base_reps,
});
forced_end_price = Some(forced_price);
break;
}
}
let mut prev_max_len = min_match_len - 1;
for candidate in candidates.iter() {
debug_assert!(pos <= frontier_limit);
let max_match_len = candidate
.match_len
.min($current_len - pos)
.min(frontier_limit - pos);
let min_len = min_match_len;
if max_match_len < min_len {
continue;
}
let start_len = (prev_max_len + 1).max(min_len);
if start_len > max_match_len {
prev_max_len = prev_max_len.max(max_match_len);
continue;
}
let max_next = pos + max_match_len;
if max_next > last_pos {
BtMatcher::reset_opt_nodes(
&mut nodes,
&mut node_prices,
last_pos + 1,
max_next,
);
}
let lit_len = base_litlen;
let off_base = BtMatcher::encode_offset_base_with_reps(
candidate.offset as u32,
lit_len,
base_reps,
);
let off_price = profile
.offset_price_for::<ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>($stats, off_base);
debug_assert!(pos + max_match_len < frontier_buffer_size);
if abort_on_worse_match {
for match_len in (start_len..=max_match_len).rev() {
let next = pos + match_len;
let ml_price = BtMatcher::cached_match_length_price(
profile,
$stats,
match_len,
&mut ml_cache,
ml_price_stamp,
);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, $stats),
);
let next_cost = BtMatcher::add_prices(base_cost, seq_cost);
let node_next_price = unsafe { *node_prices.get_unchecked(next) };
if next > last_pos || next_cost < node_next_price {
let slot = unsafe { nodes.get_unchecked_mut(next) };
*slot = HcOptimalNode {
off: candidate.offset as u32,
mlen: match_len as u32,
litlen: 0,
reps: base_reps,
};
unsafe { *node_prices.get_unchecked_mut(next) = next_cost };
if next > last_pos {
last_pos = next;
}
} else {
break;
}
}
} else {
#[allow(unused_unsafe)]
{
last_pos = last_pos.max(unsafe {
$priceset(
&mut node_prices,
&mut nodes,
ml_cache,
ml_price_stamp,
profile,
$stats,
pos,
start_len,
max_match_len,
ll0_price,
off_price,
base_cost,
candidate.offset as u32,
base_reps,
last_pos,
)
});
}
}
prev_max_len = prev_max_len.max(max_match_len);
}
if last_pos + 1 < frontier_buffer_size {
unsafe {
*node_prices.get_unchecked_mut(last_pos + 1) = u32::MAX;
}
}
pos += 1;
}
if last_pos == 0 {
if $current_len == 0 {
let price = node_prices[0];
return $self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
(price, initial_reps, initial_litlen, 0),
);
}
let lit_price = {
let bt = $self.backend.bt_mut();
BtMatcher::cached_literal_price(
profile,
$stats,
$current[0],
&mut bt.opt_lit_price_scratch,
&mut bt.opt_lit_price_generation,
lit_price_stamp,
)
};
let next_litlen = initial_litlen
.checked_add(1)
.expect("optimal parser next litlen out of usize range");
let ll_delta = BtMatcher::cached_lit_length_delta_price(
profile,
$stats,
next_litlen,
&mut ll_cache,
ll_price_stamp,
);
let price = BtMatcher::add_price_delta(node_prices[0], lit_price, ll_delta);
return $self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
(price, initial_reps, next_litlen, 1),
);
}
let target_pos = forced_end.unwrap_or(last_pos.min(frontier_limit));
let (last_stretch, last_stretch_price) = if let Some(forced_state) = forced_end_state {
(forced_state, forced_end_price.expect("forced state has a price"))
} else {
(nodes[target_pos], node_prices[target_pos])
};
if last_stretch_price == u32::MAX {
return $self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
(u32::MAX, initial_reps, initial_litlen, $current_len),
);
}
if last_stretch.mlen == 0 {
return $self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
(
last_stretch_price,
last_stretch.reps,
last_stretch.litlen as usize,
target_pos.min($current_len),
),
);
}
let mut cur = target_pos.saturating_sub(last_stretch.mlen as usize);
let end_reps = if last_stretch.litlen == 0 {
let prev_state = nodes[cur];
let (_, reps_after_match) = BtMatcher::encode_offset_with_reps(
last_stretch.off,
prev_state.litlen as usize,
prev_state.reps,
);
reps_after_match
} else {
let tail_literals = last_stretch.litlen as usize;
if cur < tail_literals {
return $self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
(
last_stretch_price,
last_stretch.reps,
tail_literals,
target_pos.min($current_len),
),
);
}
cur -= tail_literals;
last_stretch.reps
};
let store_end = cur + 2;
if store.len() <= store_end {
store.resize(store_end + 1, HcOptimalNode::default());
}
let mut store_start;
let mut stretch_pos = cur;
if last_stretch.litlen > 0 {
store[store_end] = HcOptimalNode {
litlen: last_stretch.litlen,
mlen: 0,
..HcOptimalNode::default()
};
store_start = store_end.saturating_sub(1);
store[store_start] = last_stretch;
}
store[store_end] = last_stretch;
store_start = store_end;
loop {
let next_stretch = nodes[stretch_pos];
store[store_start].litlen = next_stretch.litlen;
if next_stretch.mlen == 0 {
break;
}
if store_start == 0 {
break;
}
store_start -= 1;
store[store_start] = next_stretch;
let litlen = next_stretch.litlen as usize;
let mlen = next_stretch.mlen as usize;
debug_assert!(litlen + mlen <= $current_len);
let step = litlen + mlen;
if step == 0 || stretch_pos < step {
break;
}
stretch_pos -= step;
}
let mut tail_literals = initial_litlen;
let mut store_pos = store_start;
while store_pos <= store_end {
let stretch = store[store_pos];
let llen = stretch.litlen as usize;
let mlen = stretch.mlen as usize;
if mlen == 0 {
tail_literals = llen;
store_pos += 1;
continue;
}
$out.push(HcOptimalSequence {
offset: stretch.off,
match_len: mlen as u32,
lit_len: llen as u32,
});
tail_literals = 0;
store_pos += 1;
}
let result = (
last_stretch_price,
end_reps,
if last_stretch.litlen > 0 {
last_stretch.litlen as usize
} else {
tail_literals
},
target_pos.min($current_len),
);
$self.backend.bt_mut().finish_optimal_plan(
HcOptimalPlanBuffers {
nodes,
node_prices,
candidates,
store,
price_arena,
},
result,
)
}};
}
macro_rules! collect_optimal_candidates_initialized_body {
(
$self:expr,
$strategy_ty:ty,
$abs_pos:ident,
$current_abs_end:ident,
$profile:ident,
$query:ident,
$out:ident,
$bt_matchfinder:ident,
$bt_update:ident,
$bt_insert:ident,
$for_each_rep:ident,
$hash3:ident,
$cpl:path $(,)?
) => {{
let use_hash3: bool = <$strategy_ty as super::strategy::Strategy>::USE_HASH3;
debug_assert!(!$self.table.hash_table.is_empty());
debug_assert!($self.table.hash3_log == 0 || !$self.table.hash3_table.is_empty());
debug_assert!(
!use_hash3 || $self.table.hash3_log != 0,
"Strategy::USE_HASH3 = true but runtime hash3_log is 0 — call configure() first",
);
debug_assert!(!$self.table.chain_table.is_empty());
let min_match_len = HC_OPT_MIN_MATCH_LEN;
let reps = $query.reps;
let lit_len = $query.lit_len;
let ldm_candidate = $query.ldm_candidate;
$out.clear();
if $abs_pos < $self.table.skip_insert_until_abs {
if let Some(ldm) = ldm_candidate {
let mut best_len_for_skip = 0usize;
let _ = super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
ldm,
min_match_len,
);
}
return;
}
if $bt_matchfinder {
unsafe { $self.table.$bt_update($abs_pos, $current_abs_end) };
}
let current_idx = $abs_pos - $self.table.history_abs_start;
if current_idx + 4 > $self.table.live_history().len() {
if let Some(ldm) = ldm_candidate {
let mut best_len_for_skip = 0usize;
let _ = super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
ldm,
min_match_len,
);
}
return;
}
let mut best_len_for_skip = 0usize;
let mut skip_further_match_search = false;
let mut rep_len_candidate_found = false;
unsafe {
$self.hc.$for_each_rep(
&$self.table,
$abs_pos,
lit_len,
reps,
$current_abs_end,
min_match_len,
|rep| {
if rep.match_len >= min_match_len {
rep_len_candidate_found = true;
}
let _ = super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
rep,
min_match_len,
);
if rep.match_len > $profile.sufficient_match_len {
skip_further_match_search = true;
}
if $abs_pos + rep.match_len >= $current_abs_end {
skip_further_match_search = true;
}
},
)
};
if use_hash3 && !skip_further_match_search && best_len_for_skip < min_match_len {
$self.table.update_hash3_until($abs_pos);
if let Some(h3) = unsafe {
$self
.table
.$hash3($abs_pos, $current_abs_end, min_match_len)
} {
let _ = super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
h3,
min_match_len,
);
if !rep_len_candidate_found
&& (h3.match_len > $profile.sufficient_match_len
|| $abs_pos + h3.match_len >= $current_abs_end)
{
$self.table.skip_insert_until_abs = $abs_pos + 1;
skip_further_match_search = true;
}
}
}
if !skip_further_match_search && $bt_matchfinder {
unsafe {
$self.table.$bt_insert(
$abs_pos,
$current_abs_end,
$profile,
min_match_len,
&mut best_len_for_skip,
$out,
)
};
} else if !skip_further_match_search {
$self.table.insert_position($abs_pos);
let max_chain_depth = $profile.max_chain_depth.min($self.hc.search_depth);
let concat = $self.table.live_history();
let mut match_end_abs = $abs_pos + 9;
if max_chain_depth > 0 {
for (visited, candidate_abs) in $self
.hc
.chain_candidates(&$self.table, $abs_pos)
.into_iter()
.enumerate()
{
if visited >= max_chain_depth {
break;
}
if candidate_abs == usize::MAX {
break;
}
if candidate_abs < $self.table.window_low_abs_for_target($abs_pos)
|| candidate_abs >= $abs_pos
{
continue;
}
let candidate_idx = candidate_abs - $self.table.history_abs_start;
debug_assert!(
$abs_pos <= $current_abs_end,
"HC chain walker called past current block end"
);
let tail_limit = $current_abs_end - $abs_pos;
let base = concat.as_ptr();
let match_len =
unsafe { $cpl(base.add(candidate_idx), base.add(current_idx), tail_limit) };
if match_len < min_match_len {
continue;
}
let offset = $abs_pos - candidate_abs;
if super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
MatchCandidate {
start: $abs_pos,
offset,
match_len,
},
min_match_len,
) {
let candidate_end = candidate_abs + match_len;
if candidate_end > match_end_abs {
match_end_abs = candidate_end;
}
}
if match_len > HC_OPT_NUM || $abs_pos + match_len >= $current_abs_end {
break;
}
}
}
$self.table.skip_insert_until_abs =
$self.table.skip_insert_until_abs.max(match_end_abs - 8);
}
if let Some(ldm) = ldm_candidate {
let _ = super::bt::BtMatcher::push_candidate_ladder(
$out,
&mut best_len_for_skip,
ldm,
min_match_len,
);
}
}};
}
macro_rules! hash3_candidate_body {
(
$table:expr,
$abs_pos:ident,
$current_abs_end:ident,
$min_match_len:ident,
$cpl:path $(,)?
) => {{
if $table.hash3_log == 0 {
return None;
}
let idx = $abs_pos.checked_sub($table.history_abs_start)?;
let concat = $table.live_history();
if idx + 4 > concat.len() {
return None;
}
let hash3 = $crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx,
$table.hash3_log,
3,
);
let entry = $table
.hash3_table
.get(hash3)
.copied()
.unwrap_or($crate::encoding::match_table::storage::HC_EMPTY);
let candidate_abs =
$crate::encoding::match_table::storage::MatchTable::stored_abs_position_fast(
entry,
$table.position_base,
$table.index_shift,
)?;
if candidate_abs < $table.history_abs_start || candidate_abs >= $abs_pos {
return None;
}
let offset = $abs_pos - candidate_abs;
if offset >= $crate::encoding::bt::HC3_MAX_OFFSET {
return None;
}
let candidate_idx = candidate_abs - $table.history_abs_start;
let tail_limit = $current_abs_end.saturating_sub($abs_pos);
let base = concat.as_ptr();
let match_len = unsafe { $cpl(base.add(candidate_idx), base.add(idx), tail_limit) };
(match_len >= $min_match_len).then_some($crate::encoding::opt::types::MatchCandidate {
start: $abs_pos,
offset,
match_len,
})
}};
}
pub(crate) use hash3_candidate_body;
macro_rules! for_each_repcode_candidate_body {
(
$table:expr,
$abs_pos:ident,
$lit_len:ident,
$reps:ident,
$current_abs_end:ident,
$min_match_len:ident,
$f:ident,
$cpl:path $(,)?
) => {{
let rep_offsets: [Option<usize>; 3] = if $lit_len == 0 {
[
Some($reps[1] as usize),
Some($reps[2] as usize),
($reps[0] > 1).then_some(($reps[0] - 1) as usize),
]
} else {
[
Some($reps[0] as usize),
Some($reps[1] as usize),
Some($reps[2] as usize),
]
};
let concat = $table.live_history();
let current_idx = $abs_pos - $table.history_abs_start;
if current_idx + 4 > concat.len() {
return;
}
let tail_limit = $current_abs_end.saturating_sub($abs_pos);
let base = concat.as_ptr();
let concat_len = concat.len();
for rep in rep_offsets.into_iter().flatten() {
if rep == 0 || rep > $abs_pos {
continue;
}
let candidate_pos = $abs_pos - rep;
if candidate_pos < $table.history_abs_start {
continue;
}
let candidate_idx = candidate_pos - $table.history_abs_start;
let gate_matches = unsafe {
let cand = base.add(candidate_idx).cast::<u32>().read_unaligned();
let cur = base.add(current_idx).cast::<u32>().read_unaligned();
if $min_match_len == 3 {
(cand.to_le() & 0x00FF_FFFF) == (cur.to_le() & 0x00FF_FFFF)
} else {
cand == cur
}
};
if !gate_matches {
continue;
}
let max = (concat_len - candidate_idx)
.min(concat_len - current_idx)
.min(tail_limit);
let match_len = unsafe { $cpl(base.add(candidate_idx), base.add(current_idx), max) };
if match_len < $min_match_len {
continue;
}
$f(MatchCandidate {
start: $abs_pos,
offset: rep,
match_len,
});
}
}};
}
pub(crate) use for_each_repcode_candidate_body;
macro_rules! bt_insert_and_collect_matches_body {
(
$table:expr,
$search_depth:expr,
$abs_pos:ident,
$current_abs_end:ident,
$profile:ident,
$min_match_len:ident,
$best_len_for_skip:ident,
$out:ident,
$cmf:path $(,)?
) => {{
let idx = $abs_pos - $table.history_abs_start;
let concat: &[u8] = unsafe {
let lh = $table.live_history();
core::slice::from_raw_parts(lh.as_ptr(), lh.len())
};
if idx + 8 > concat.len() {
return;
}
debug_assert!(
$abs_pos <= $current_abs_end,
"BT collect called past current block end"
);
let tail_limit = $current_abs_end - $abs_pos;
let hash = $crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx,
$table.hash_log,
$table.search_mls,
);
#[cfg(all(
target_feature = "sse",
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($table.hash_table.as_ptr().add(hash).cast(), _MM_HINT_T0);
}
if idx + 1 + 8 <= concat.len() {
let hash_next =
$crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx + 1,
$table.hash_log,
$table.search_mls,
);
unsafe {
_mm_prefetch(
$table.hash_table.as_ptr().add(hash_next).cast(),
_MM_HINT_T0,
);
}
}
}
let Some(relative_pos) = $table.relative_position($abs_pos) else {
return;
};
let stored = relative_pos + 1;
let bt_mask = $table.bt_mask();
let chain_ptr = $table.chain_table.as_mut_ptr();
debug_assert_eq!($table.chain_table.len(), 2 << $table.bt_log());
let bt_low = $abs_pos.saturating_sub(bt_mask);
let window_low = $table.window_low_abs_for_target($abs_pos);
let win_off = $table
.position_base
.wrapping_sub(1)
.wrapping_sub($table.index_shift)
.wrapping_sub(window_low);
let win_range = $abs_pos - window_low;
let mut match_end_abs = $abs_pos + 9;
let mut compares_left = $profile.max_chain_depth.min($search_depth);
let mut common_length_smaller = 0usize;
let mut common_length_larger = 0usize;
let pair_idx = $table.bt_pair_index_for_abs($abs_pos);
let mut smaller_slot = pair_idx;
let mut larger_slot = pair_idx + 1;
let mut match_stored = $table.hash_table[hash];
$table.hash_table[hash] = stored;
debug_assert!(
$min_match_len >= $crate::encoding::cost_model::HC_FORMAT_MINMATCH,
"min_match_len must be at least HC_FORMAT_MINMATCH"
);
let mut best_len = (*$best_len_for_skip).max($min_match_len - 1);
while compares_left > 0 && (match_stored as usize).wrapping_add(win_off) < win_range {
compares_left -= 1;
let candidate_abs = ($table.position_base + (match_stored as usize - 1))
.wrapping_sub($table.index_shift);
let next_pair_idx = $table.bt_pair_index_for_abs(candidate_abs);
let next_smaller = unsafe { *chain_ptr.add(next_pair_idx) };
let next_larger = unsafe { *chain_ptr.add(next_pair_idx + 1) };
let seed_len = common_length_smaller.min(common_length_larger);
let candidate_idx = candidate_abs - $table.history_abs_start;
let match_len = unsafe { $cmf(concat, idx, candidate_idx, tail_limit, seed_len) };
if match_len > best_len {
let offset = $abs_pos - candidate_abs;
let accepted = $crate::encoding::bt::BtMatcher::push_candidate_ladder(
$out,
$best_len_for_skip,
$crate::encoding::opt::types::MatchCandidate {
start: $abs_pos,
offset,
match_len,
},
$min_match_len,
);
if accepted {
best_len = match_len;
let candidate_end = candidate_abs + match_len;
if candidate_end > match_end_abs {
match_end_abs = candidate_end;
}
if match_len >= tail_limit
|| match_len > $crate::encoding::cost_model::HC_OPT_NUM
{
break;
}
}
}
if match_len >= tail_limit {
break;
}
let candidate_next = candidate_idx + match_len;
let current_next = idx + match_len;
if unsafe {
*concat.get_unchecked(candidate_next) < *concat.get_unchecked(current_next)
} {
unsafe { *chain_ptr.add(smaller_slot) = match_stored };
common_length_smaller = match_len;
if candidate_abs <= bt_low {
smaller_slot = usize::MAX;
break;
}
smaller_slot = next_pair_idx + 1;
match_stored = next_larger;
} else {
unsafe { *chain_ptr.add(larger_slot) = match_stored };
common_length_larger = match_len;
if candidate_abs <= bt_low {
larger_slot = usize::MAX;
break;
}
larger_slot = next_pair_idx;
match_stored = next_smaller;
}
}
if smaller_slot != usize::MAX {
unsafe {
*chain_ptr.add(smaller_slot) = $crate::encoding::match_table::storage::HC_EMPTY
};
}
if larger_slot != usize::MAX {
unsafe {
*chain_ptr.add(larger_slot) = $crate::encoding::match_table::storage::HC_EMPTY
};
}
if let Some(dms) = $table.dms.table() {
let region = $table.dms.region_len();
let dh = $crate::encoding::match_table::storage::MatchTable::hash_position_at(
concat,
idx,
dms.hash_log,
dms.mls,
);
let mut dcur = dms.hash_table[dh];
let mut common_smaller = 0usize;
let mut common_larger = 0usize;
let mut dms_compares = $profile.max_chain_depth.min($search_depth);
while dms_compares > 0 && dcur != $crate::encoding::match_table::storage::HC_EMPTY {
let dict_idx = (dcur - 1) as usize;
if dict_idx >= region || dict_idx >= idx {
break;
}
dms_compares -= 1;
let pair = 2 * dict_idx;
let seed = common_smaller.min(common_larger);
let match_len = unsafe { $cmf(concat, idx, dict_idx, tail_limit, seed) };
if match_len > best_len {
let offset = idx - dict_idx;
let accepted = $crate::encoding::bt::BtMatcher::push_candidate_ladder(
$out,
$best_len_for_skip,
$crate::encoding::opt::types::MatchCandidate {
start: $abs_pos,
offset,
match_len,
},
$min_match_len,
);
if accepted {
best_len = match_len;
let candidate_end = $abs_pos + match_len;
if candidate_end > match_end_abs {
match_end_abs = candidate_end;
}
if match_len > $crate::encoding::cost_model::HC_OPT_NUM {
break;
}
}
}
if match_len >= tail_limit {
break;
}
if concat[dict_idx + match_len] < concat[idx + match_len] {
common_smaller = match_len;
dcur = dms.chain_table[pair + 1];
} else {
common_larger = match_len;
dcur = dms.chain_table[pair];
}
}
}
$table.skip_insert_until_abs = match_end_abs - 8;
}};
}
pub(crate) use bt_insert_and_collect_matches_body;
impl HcMatchGenerator {
fn heap_size(&self) -> usize {
self.table.heap_size() + self.backend.heap_size()
}
fn should_run_btultra2_seed_pass<S: super::strategy::Strategy>(
&self,
current_len: usize,
) -> bool {
if !S::TWO_PASS_SEED {
return false;
}
let HcBackend::Bt(bt) = &self.backend else {
return false;
};
bt.opt_state.lit_length_sum == 0
&& bt.opt_state.dictionary_seed.is_none()
&& !self.table.dictionary_primed_for_frame
&& bt.ldm_sequences.is_empty()
&& self.table.window_size == current_len
&& self.table.history_abs_start == 0
&& self.table.chunk_lens.len() == 1
&& current_len > HC_PREDEF_THRESHOLD
}
fn new(max_window_size: usize) -> Self {
Self {
table: super::match_table::storage::MatchTable::new(max_window_size),
hc: super::hc::HcMatcher::new(2, HC_SEARCH_DEPTH, HC_TARGET_LEN),
backend: HcBackend::Hc,
strategy_tag: super::strategy::StrategyTag::Lazy,
}
}
fn configure(&mut self, config: HcConfig, tag: super::strategy::StrategyTag, window_log: u8) {
use super::strategy::StrategyTag;
self.strategy_tag = tag;
let is_btultra2 = tag == StrategyTag::BtUltra2;
let uses_bt = matches!(
tag,
StrategyTag::Btlazy2
| StrategyTag::BtOpt
| StrategyTag::BtUltra
| StrategyTag::BtUltra2
);
let wants_hash3 = matches!(tag, StrategyTag::BtUltra | StrategyTag::BtUltra2);
let next_hash3_log = if wants_hash3 {
HC3_HASH_LOG.min(window_log as usize)
} else {
0
};
let resize = self.table.hash_log != config.hash_log
|| self.table.chain_log != config.chain_log
|| self.table.hash3_log != next_hash3_log;
self.table.hash_log = config.hash_log;
self.table.chain_log = config.chain_log;
self.table.hash3_log = next_hash3_log;
self.hc.search_depth = if uses_bt {
config.search_depth
} else {
config.search_depth.min(MAX_HC_SEARCH_DEPTH)
};
self.hc.target_len = config.target_len;
self.table.search_depth = self.hc.search_depth;
self.table.is_btultra2 = is_btultra2;
self.table.uses_bt = uses_bt;
self.table.search_mls = config.search_mls;
match (&self.backend, self.table.uses_bt) {
(HcBackend::Hc, true) => {
self.backend = HcBackend::Bt(alloc::boxed::Box::new(super::bt::BtMatcher::new()));
}
(HcBackend::Bt(_), false) => {
self.backend = HcBackend::Hc;
}
_ => {}
}
if resize && !self.table.hash_table.is_empty() {
self.table.hash_table.clear();
self.table.hash3_table.clear();
self.table.chain_table.clear();
}
}
fn seed_dictionary_entropy(
&mut self,
huff: Option<&crate::huff0::huff0_encoder::HuffmanTable>,
ll: Option<&crate::fse::fse_encoder::FSETable>,
ml: Option<&crate::fse::fse_encoder::FSETable>,
of: Option<&crate::fse::fse_encoder::FSETable>,
) {
if let HcBackend::Bt(bt) = &mut self.backend {
bt.opt_state.seed_dictionary_entropy(huff, ll, ml, of);
}
}
#[cfg(feature = "hash")]
fn set_ldm_producer(&mut self, producer: Option<super::ldm::LdmProducer>) {
if let HcBackend::Bt(bt) = &mut self.backend {
bt.ldm_producer = producer;
}
}
#[cfg(feature = "hash")]
fn take_ldm_producer(&mut self) -> Option<super::ldm::LdmProducer> {
if let HcBackend::Bt(bt) = &mut self.backend {
bt.ldm_producer.take()
} else {
None
}
}
fn reset(&mut self, reuse_space: impl FnMut(Vec<u8>)) {
self.table.reset(reuse_space);
if let HcBackend::Bt(bt) = &mut self.backend {
bt.reset();
}
}
fn skip_matching(&mut self, incompressible_hint: Option<bool>) {
self.table.skip_matching(incompressible_hint);
}
#[cfg(test)]
fn start_matching(&mut self, mut handle_sequence: impl for<'a> FnMut(Sequence<'a>)) {
use super::strategy::{self, StrategyTag};
match self.strategy_tag {
StrategyTag::Fast | StrategyTag::Dfast | StrategyTag::Greedy | StrategyTag::Lazy => {
self.start_matching_lazy(&mut handle_sequence)
}
StrategyTag::Btlazy2 => self.start_matching_btlazy2(&mut handle_sequence),
StrategyTag::BtOpt => {
self.start_matching_optimal::<strategy::BtOpt>(&mut handle_sequence)
}
StrategyTag::BtUltra => {
self.start_matching_optimal::<strategy::BtUltra>(&mut handle_sequence)
}
StrategyTag::BtUltra2 => {
self.start_matching_optimal::<strategy::BtUltra2>(&mut handle_sequence)
}
}
}
pub(crate) fn start_matching_strategy<S: super::strategy::Strategy>(
&mut self,
handle_sequence: &mut impl for<'a> FnMut(Sequence<'a>),
) {
debug_assert_eq!(
self.table.uses_bt,
S::USE_BT,
"Strategy::USE_BT disagrees with runtime table.uses_bt at HC dispatch"
);
if S::USE_BT {
self.start_matching_optimal::<S>(handle_sequence)
} else {
self.start_matching_lazy(handle_sequence)
}
}
pub(crate) fn start_matching_lazy(
&mut self,
handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
if self.table.dms.is_primed() {
self.start_matching_lazy_impl::<true>(handle_sequence);
} else {
self.start_matching_lazy_impl::<false>(handle_sequence);
}
}
fn start_matching_lazy_impl<const DICT: bool>(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
self.table.ensure_tables();
let (current_abs_start, current_len) = self.table.current_block_range();
if current_len == 0 {
return;
}
let current_ptr = self.table.get_last_space().as_ptr();
let current: &[u8] = unsafe { core::slice::from_raw_parts(current_ptr, current_len) };
let current_abs_end = current_abs_start + current_len;
self.table
.backfill_boundary_positions(current_abs_start, current_abs_end);
let mut pos = 0usize;
let mut literals_start = 0usize;
while pos + HC_MIN_MATCH_LEN <= current_len {
let abs_pos = current_abs_start + pos;
let lit_len = pos - literals_start;
let best = self
.hc
.find_best_match::<DICT>(&self.table, abs_pos, lit_len);
if let Some(candidate) =
self.hc
.pick_lazy_match::<DICT>(&self.table, abs_pos, lit_len, best)
{
self.table
.insert_match_span(abs_pos, candidate.start + candidate.match_len);
let start = candidate.start - current_abs_start;
let literals = ¤t[literals_start..start];
handle_sequence(Sequence::Triple {
literals,
offset: candidate.offset,
match_len: candidate.match_len,
});
let _ = encode_offset_with_history(
candidate.offset as u32,
literals.len() as u32,
&mut self.table.offset_hist,
);
pos = start + candidate.match_len;
literals_start = pos;
} else {
self.table.insert_position(abs_pos);
let step = ((pos - literals_start) >> 8) + 1;
pos += step;
}
}
while pos + 4 <= current_len {
self.table.insert_position(current_abs_start + pos);
pos += 1;
}
if literals_start < current_len {
handle_sequence(Sequence::Literals {
literals: ¤t[literals_start..],
});
}
}
pub(crate) unsafe fn set_borrowed_window(&mut self, buffer: &[u8]) {
unsafe { self.table.set_borrowed_window(buffer) };
}
pub(crate) fn clear_borrowed_window(&mut self) {
self.table.clear_borrowed_window();
}
pub(crate) fn start_matching_lazy_borrowed(
&mut self,
block_start: usize,
block_end: usize,
handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
self.table.stage_borrowed_block(block_start, block_end);
self.start_matching_lazy(handle_sequence);
}
pub(crate) fn skip_matching_borrowed(
&mut self,
block_start: usize,
block_end: usize,
incompressible_hint: Option<bool>,
) {
self.table.stage_borrowed_block(block_start, block_end);
self.table.skip_matching(incompressible_hint);
}
fn start_matching_btlazy2(&mut self, mut handle_sequence: impl for<'a> FnMut(Sequence<'a>)) {
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
unsafe {
self.start_matching_btlazy2_neon(&mut handle_sequence)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use crate::encoding::fastpath::{FastpathKernel, select_kernel};
match select_kernel() {
FastpathKernel::Avx2Bmi2 => unsafe {
self.start_matching_btlazy2_avx2_bmi2(&mut handle_sequence)
},
FastpathKernel::Sse42 => unsafe {
self.start_matching_btlazy2_sse42(&mut handle_sequence)
},
FastpathKernel::Scalar => self.start_matching_btlazy2_scalar(&mut handle_sequence),
}
}
#[cfg(not(any(
all(target_arch = "aarch64", target_endian = "little"),
target_arch = "x86",
target_arch = "x86_64"
)))]
{
self.start_matching_btlazy2_scalar(&mut handle_sequence)
}
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
unsafe fn start_matching_btlazy2_neon(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
start_matching_btlazy2_body!(
self,
handle_sequence,
collect_optimal_candidates_initialized_neon,
crate::encoding::fastpath::neon::count_match_from_indices
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
unsafe fn start_matching_btlazy2_sse42(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
start_matching_btlazy2_body!(
self,
handle_sequence,
collect_optimal_candidates_initialized_sse42,
crate::encoding::fastpath::sse42::count_match_from_indices
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2,bmi2")]
unsafe fn start_matching_btlazy2_avx2_bmi2(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
start_matching_btlazy2_body!(
self,
handle_sequence,
collect_optimal_candidates_initialized_avx2_bmi2,
crate::encoding::fastpath::avx2_bmi2::count_match_from_indices
)
}
#[cfg(not(all(target_arch = "aarch64", target_endian = "little")))]
#[allow(unused_unsafe)]
fn start_matching_btlazy2_scalar(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
start_matching_btlazy2_body!(
self,
handle_sequence,
collect_optimal_candidates_initialized_scalar,
crate::encoding::fastpath::scalar::count_match_from_indices
)
}
fn start_matching_optimal<S: super::strategy::Strategy>(
&mut self,
mut handle_sequence: impl for<'a> FnMut(Sequence<'a>),
) {
self.table.ensure_tables();
let (current_abs_start, current_len) = self.table.current_block_range();
if current_len == 0 {
return;
}
let current_ptr = self.table.get_last_space().as_ptr();
let current = unsafe { core::slice::from_raw_parts(current_ptr, current_len) };
let current_abs_end = current_abs_start + current_len;
self.table
.apply_limited_update_after_long_match(current_abs_start);
let hash3_start_cursor = self
.table
.skip_insert_until_abs
.max(self.table.history_abs_start);
self.table
.backfill_boundary_positions(current_abs_start, current_abs_end);
self.table.next_to_update3 = hash3_start_cursor;
let live_history = self.table.live_history();
let history_abs_start = self.table.history_abs_start;
self.backend.bt_mut().prepare_ldm_candidates(
live_history,
history_abs_start,
current_abs_start,
current_len,
);
if self.should_run_btultra2_seed_pass::<S>(current_len) {
self.run_btultra2_seed_pass(current, current_abs_start, current_len);
}
let profile = HcOptimalCostProfile::const_for_strategy::<S>();
let mut opt_state =
core::mem::replace(&mut self.backend.bt_mut().opt_state, HcOptState::new());
opt_state.rescale_freqs(current, profile);
let mut best_plan = core::mem::take(&mut self.backend.bt_mut().opt_segment_plan_scratch);
best_plan.clear();
let mut plan_reps = self.table.offset_hist;
let (mut cursor, mut plan_litlen) =
self.table.opt_start_cursor_and_litlen(current_abs_start);
let mut plan_literals_cursor = 0usize;
let match_loop_limit = current_len.saturating_sub(8);
while cursor < match_loop_limit {
let remaining_len = current_len - cursor;
let segment_abs_start = current_abs_start + cursor;
let segment_start = best_plan.len();
let (_, end_reps, end_litlen, consumed_len) = self.build_optimal_plan::<S>(
¤t[cursor..],
segment_abs_start,
remaining_len,
HcOptimalPlanState {
block_offset: cursor,
reps: plan_reps,
litlen: plan_litlen,
profile,
},
&opt_state,
&mut best_plan,
);
BtMatcher::update_plan_stats_segment(
current,
current_len,
&best_plan[segment_start..],
&mut plan_literals_cursor,
&mut plan_reps,
&mut opt_state,
profile.accurate,
);
plan_reps = end_reps;
plan_litlen = end_litlen;
cursor += consumed_len;
}
self.table
.emit_optimal_plan(current_len, &best_plan, &mut handle_sequence);
best_plan.clear();
self.backend.bt_mut().opt_segment_plan_scratch = best_plan;
self.backend.bt_mut().opt_state = opt_state;
}
fn run_btultra2_seed_pass(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
) {
type S = super::strategy::BtUltra2;
let seed_profile = HcOptimalCostProfile::const_for_strategy::<S>();
let mut opt_state =
core::mem::replace(&mut self.backend.bt_mut().opt_state, HcOptState::new());
opt_state.rescale_freqs(current, seed_profile);
let mut seed_reps = self.table.offset_hist;
let (mut cursor, mut seed_litlen) =
self.table.opt_start_cursor_and_litlen(current_abs_start);
let mut seed_literals_cursor = 0usize;
let mut seed_plan = core::mem::take(&mut self.backend.bt_mut().opt_seed_plan_scratch);
seed_plan.clear();
let match_loop_limit = current_len.saturating_sub(8);
while cursor < match_loop_limit {
let remaining_len = current_len - cursor;
let segment_abs_start = current_abs_start + cursor;
let segment_start = seed_plan.len();
let (_, end_reps, end_litlen, consumed_len) = self.build_optimal_plan::<S>(
¤t[cursor..],
segment_abs_start,
remaining_len,
HcOptimalPlanState {
block_offset: cursor,
reps: seed_reps,
litlen: seed_litlen,
profile: seed_profile,
},
&opt_state,
&mut seed_plan,
);
BtMatcher::update_plan_stats_segment(
current,
current_len,
&seed_plan[segment_start..],
&mut seed_literals_cursor,
&mut seed_reps,
&mut opt_state,
seed_profile.accurate,
);
seed_plan.truncate(segment_start);
seed_reps = end_reps;
seed_litlen = end_litlen;
cursor += consumed_len;
}
seed_plan.clear();
self.backend.bt_mut().opt_seed_plan_scratch = seed_plan;
self.backend.bt_mut().opt_state = opt_state;
self.table.position_base = self.table.history_abs_start;
self.table.index_shift = current_len;
self.table.next_to_update3 = current_abs_start;
self.table.skip_insert_until_abs = current_abs_start;
self.table.allow_zero_relative_position = true;
}
fn build_optimal_plan<S: super::strategy::Strategy>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
debug_assert!(S::USE_BT, "build_optimal_plan called on non-BT strategy");
debug_assert_eq!(initial_state.profile.accurate, S::ACCURATE_PRICE);
debug_assert_eq!(
initial_state.profile.favor_small_offsets,
S::FAVOR_SMALL_OFFSETS
);
match (S::ACCURATE_PRICE, S::FAVOR_SMALL_OFFSETS) {
(true, false) => self.build_optimal_plan_impl::<S, true, false>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
),
(true, true) => self.build_optimal_plan_impl::<S, true, true>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
),
(false, false) => self.build_optimal_plan_impl::<S, false, false>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
),
(false, true) => self.build_optimal_plan_impl::<S, false, true>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
),
}
}
#[inline(always)]
fn build_optimal_plan_impl<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
unsafe {
self.build_optimal_plan_impl_neon::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use crate::encoding::fastpath::{FastpathKernel, select_kernel};
match select_kernel() {
FastpathKernel::Avx2Bmi2 => unsafe {
self.build_optimal_plan_impl_avx2_bmi2::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
)
},
FastpathKernel::Sse42 => unsafe {
self.build_optimal_plan_impl_sse42::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
)
},
FastpathKernel::Scalar => self
.build_optimal_plan_impl_scalar::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
),
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
unsafe {
self.build_optimal_plan_impl_simd128::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
)
}
#[cfg(not(any(
all(target_arch = "aarch64", target_endian = "little"),
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
{
self.build_optimal_plan_impl_scalar::<S, ACCURATE_PRICE, FAVOR_SMALL_OFFSETS>(
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
)
}
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
unsafe fn build_optimal_plan_impl_neon<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
build_optimal_plan_impl_body!(
self,
S,
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
collect_optimal_candidates_initialized_neon,
priceset_range_nonabort_neon,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
unsafe fn build_optimal_plan_impl_sse42<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
build_optimal_plan_impl_body!(
self,
S,
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
collect_optimal_candidates_initialized_sse42,
priceset_range_nonabort_sse41,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2,bmi2")]
unsafe fn build_optimal_plan_impl_avx2_bmi2<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
build_optimal_plan_impl_body!(
self,
S,
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
collect_optimal_candidates_initialized_avx2_bmi2,
priceset_range_nonabort_avx2,
)
}
#[cfg(not(all(target_arch = "aarch64", target_endian = "little")))]
#[allow(unused_unsafe)]
#[cfg_attr(
all(target_arch = "wasm32", target_feature = "simd128"),
allow(dead_code)
)]
fn build_optimal_plan_impl_scalar<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
build_optimal_plan_impl_body!(
self,
S,
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
collect_optimal_candidates_initialized_scalar,
priceset_range_nonabort_scalar,
)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[allow(unused_unsafe)]
unsafe fn build_optimal_plan_impl_simd128<
S: super::strategy::Strategy,
const ACCURATE_PRICE: bool,
const FAVOR_SMALL_OFFSETS: bool,
>(
&mut self,
current: &[u8],
current_abs_start: usize,
current_len: usize,
initial_state: HcOptimalPlanState,
stats: &HcOptState,
out: &mut Vec<HcOptimalSequence>,
) -> (u32, [u32; 3], usize, usize) {
build_optimal_plan_impl_body!(
self,
S,
current,
current_abs_start,
current_len,
initial_state,
stats,
out,
collect_optimal_candidates_initialized_scalar,
priceset_range_nonabort_simd128,
)
}
#[cfg(test)]
fn collect_optimal_candidates(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
use super::strategy::{self, StrategyTag};
self.table.ensure_tables();
match self.strategy_tag {
StrategyTag::BtUltra2 => self
.collect_optimal_candidates_initialized::<strategy::BtUltra2, true>(
abs_pos,
current_abs_end,
profile,
query,
out,
),
StrategyTag::BtUltra => self
.collect_optimal_candidates_initialized::<strategy::BtUltra, true>(
abs_pos,
current_abs_end,
profile,
query,
out,
),
StrategyTag::Btlazy2 => self
.collect_optimal_candidates_initialized::<strategy::Btlazy2, true>(
abs_pos,
current_abs_end,
profile,
query,
out,
),
StrategyTag::BtOpt => self
.collect_optimal_candidates_initialized::<strategy::BtOpt, true>(
abs_pos,
current_abs_end,
profile,
query,
out,
),
StrategyTag::Fast | StrategyTag::Dfast | StrategyTag::Greedy | StrategyTag::Lazy => {
self.collect_optimal_candidates_initialized::<strategy::Lazy, false>(
abs_pos,
current_abs_end,
profile,
query,
out,
)
}
}
}
#[allow(dead_code)]
#[inline(always)]
fn collect_optimal_candidates_initialized<
S: super::strategy::Strategy,
const USE_BT_MATCHFINDER: bool,
>(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
unsafe {
self.collect_optimal_candidates_initialized_neon::<S, USE_BT_MATCHFINDER>(
abs_pos,
current_abs_end,
profile,
query,
out,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use crate::encoding::fastpath::{FastpathKernel, select_kernel};
match select_kernel() {
FastpathKernel::Avx2Bmi2 => unsafe {
self.collect_optimal_candidates_initialized_avx2_bmi2::<S, USE_BT_MATCHFINDER>(
abs_pos,
current_abs_end,
profile,
query,
out,
)
},
FastpathKernel::Sse42 => unsafe {
self.collect_optimal_candidates_initialized_sse42::<S, USE_BT_MATCHFINDER>(
abs_pos,
current_abs_end,
profile,
query,
out,
)
},
FastpathKernel::Scalar => self
.collect_optimal_candidates_initialized_scalar::<S, USE_BT_MATCHFINDER>(
abs_pos,
current_abs_end,
profile,
query,
out,
),
}
}
#[cfg(not(any(
all(target_arch = "aarch64", target_endian = "little"),
target_arch = "x86",
target_arch = "x86_64"
)))]
{
self.collect_optimal_candidates_initialized_scalar::<S, USE_BT_MATCHFINDER>(
abs_pos,
current_abs_end,
profile,
query,
out,
)
}
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
unsafe fn collect_optimal_candidates_initialized_neon<
S: super::strategy::Strategy,
const USE_BT_MATCHFINDER: bool,
>(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
collect_optimal_candidates_initialized_body!(
self,
S,
abs_pos,
current_abs_end,
profile,
query,
out,
USE_BT_MATCHFINDER,
bt_update_tree_until_neon,
bt_insert_and_collect_matches_neon,
for_each_repcode_candidate_with_reps_neon,
hash3_candidate_neon,
crate::encoding::fastpath::neon::common_prefix_len_ptr,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
unsafe fn collect_optimal_candidates_initialized_sse42<
S: super::strategy::Strategy,
const USE_BT_MATCHFINDER: bool,
>(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
collect_optimal_candidates_initialized_body!(
self,
S,
abs_pos,
current_abs_end,
profile,
query,
out,
USE_BT_MATCHFINDER,
bt_update_tree_until_sse42,
bt_insert_and_collect_matches_sse42,
for_each_repcode_candidate_with_reps_sse42,
hash3_candidate_sse42,
crate::encoding::fastpath::sse42::common_prefix_len_ptr,
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2,bmi2")]
unsafe fn collect_optimal_candidates_initialized_avx2_bmi2<
S: super::strategy::Strategy,
const USE_BT_MATCHFINDER: bool,
>(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
collect_optimal_candidates_initialized_body!(
self,
S,
abs_pos,
current_abs_end,
profile,
query,
out,
USE_BT_MATCHFINDER,
bt_update_tree_until_avx2_bmi2,
bt_insert_and_collect_matches_avx2_bmi2,
for_each_repcode_candidate_with_reps_avx2_bmi2,
hash3_candidate_avx2_bmi2,
crate::encoding::fastpath::avx2_bmi2::common_prefix_len_ptr,
)
}
#[cfg(not(all(target_arch = "aarch64", target_endian = "little")))]
#[allow(unused_unsafe)]
fn collect_optimal_candidates_initialized_scalar<
S: super::strategy::Strategy,
const USE_BT_MATCHFINDER: bool,
>(
&mut self,
abs_pos: usize,
current_abs_end: usize,
profile: HcOptimalCostProfile,
query: HcCandidateQuery,
out: &mut Vec<MatchCandidate>,
) {
collect_optimal_candidates_initialized_body!(
self,
S,
abs_pos,
current_abs_end,
profile,
query,
out,
USE_BT_MATCHFINDER,
bt_update_tree_until_scalar,
bt_insert_and_collect_matches_scalar,
for_each_repcode_candidate_with_reps_scalar,
hash3_candidate_scalar,
crate::encoding::fastpath::scalar::common_prefix_len_ptr,
)
}
}
#[cfg(any())] #[test]
fn matches() {
let mut matcher = MatchGenerator::new(1000);
let mut original_data = Vec::new();
let mut reconstructed = Vec::new();
let replay_sequence = |seq: Sequence<'_>, reconstructed: &mut Vec<u8>| match seq {
Sequence::Literals { literals } => {
assert!(!literals.is_empty());
reconstructed.extend_from_slice(literals);
}
Sequence::Triple {
literals,
offset,
match_len,
} => {
assert!(offset > 0);
assert!(match_len >= MIN_MATCH_LEN);
reconstructed.extend_from_slice(literals);
assert!(offset <= reconstructed.len());
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
};
matcher.add_data(
alloc::vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![
1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0,
],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[
1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0,
]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![0, 0, 0, 0, 0],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[0, 0, 0, 0, 0]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![7, 8, 9, 10, 11],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[7, 8, 9, 10, 11]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![1, 3, 5, 7, 9],
SuffixStore::with_capacity(100),
|_, _| {},
);
matcher.skip_matching();
original_data.extend_from_slice(&[1, 3, 5, 7, 9]);
reconstructed.extend_from_slice(&[1, 3, 5, 7, 9]);
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![1, 3, 5, 7, 9],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[1, 3, 5, 7, 9]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
matcher.add_data(
alloc::vec![0, 0, 11, 13, 15, 17, 20, 11, 13, 15, 17, 20, 21, 23],
SuffixStore::with_capacity(100),
|_, _| {},
);
original_data.extend_from_slice(&[0, 0, 11, 13, 15, 17, 20, 11, 13, 15, 17, 20, 21, 23]);
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
matcher.next_sequence(|seq| replay_sequence(seq, &mut reconstructed));
assert!(!matcher.next_sequence(|_| {}));
assert_eq!(reconstructed, original_data);
}
#[test]
fn dfast_matches_roundtrip_multi_block_pattern() {
let pattern = [9, 21, 44, 184, 19, 96, 171, 109, 141, 251];
let first_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let second_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let mut matcher = DfastMatchGenerator::new(1 << 22);
let replay_sequence = |decoded: &mut Vec<u8>, seq: Sequence<'_>| match seq {
Sequence::Literals { literals } => decoded.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
decoded.extend_from_slice(literals);
let start = decoded.len() - offset;
for i in 0..match_len {
let byte = decoded[start + i];
decoded.push(byte);
}
}
};
matcher.add_data(first_block.clone(), |_| {});
let mut history = Vec::new();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(history, first_block);
matcher.add_data(second_block.clone(), |_| {});
let prefix_len = history.len();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(&history[prefix_len..], second_block.as_slice());
}
#[test]
fn dfast_accepts_exact_five_byte_match() {
let mut data = Vec::new();
data.push(b'Z'); data.extend_from_slice(b"ABCDE"); data.extend_from_slice(b"!!!!!!!!!!!!!!!!!!!!!!!"); data.extend_from_slice(b"ABCDE"); data.push(b'F'); data.extend_from_slice(b"GHIJKLMNOPQRSTUVWXYZ"); assert_eq!(data.len(), 55);
let mut matcher = DfastMatchGenerator::new(1 << 22);
matcher.add_data(data.clone(), |_| {});
let mut saw_five_byte_match = false;
let mut saw_longer_match = false;
matcher.start_matching(|seq| {
if let Sequence::Triple {
offset, match_len, ..
} = seq
{
if offset == 28 && match_len == 5 {
saw_five_byte_match = true;
} else if offset == 28 && match_len > 5 {
saw_longer_match = true;
}
}
});
assert!(
saw_five_byte_match,
"dfast must accept the exact-5-byte match — a 6-byte floor would skip it"
);
assert!(
!saw_longer_match,
"fixture pinned to length 5 — byte 33 ('F') must terminate the extension"
);
}
#[test]
fn driver_switches_backends_and_initializes_dfast_via_reset() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Default);
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Dfast);
assert_eq!(driver.window_size(), (1u64 << 21));
let mut first = driver.get_next_space();
first[..12].copy_from_slice(b"abcabcabcabc");
first.truncate(12);
driver.commit_space(first);
assert_eq!(driver.get_last_space(), b"abcabcabcabc");
driver.skip_matching_with_hint(None);
let mut second = driver.get_next_space();
second[..12].copy_from_slice(b"abcabcabcabc");
second.truncate(12);
driver.commit_space(second);
let mut reconstructed = b"abcabcabcabc".to_vec();
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
assert_eq!(reconstructed, b"abcabcabcabcabcabcabcabc");
driver.reset(CompressionLevel::Fastest);
assert_eq!(driver.window_size(), (1u64 << 19));
}
#[test]
fn driver_level5_selects_row_backend() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(5));
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Row);
assert_eq!(
driver.parse,
super::strategy::ParseMode::Greedy,
"L5 must route to start_matching_greedy (parse == Greedy)",
);
assert_eq!(
driver.row_matcher().lazy_depth,
0,
"row matcher lazy_depth must mirror the greedy parse mode",
);
}
#[test]
fn driver_level4_greedy_round_trip_single_slice() {
let mut driver = MatchGeneratorDriver::new(64, 2);
driver.reset(CompressionLevel::Level(4));
let input = b"abcdefgh_abcdefgh_abcdefgh_abcdefgh";
let mut space = driver.get_next_space();
space[..input.len()].copy_from_slice(input);
space.truncate(input.len());
driver.commit_space(space);
let mut reconstructed: Vec<u8> = Vec::new();
let mut saw_triple = false;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
saw_triple = true;
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
assert_eq!(
reconstructed,
input.to_vec(),
"L4 greedy parse failed to reconstruct repeating-pattern input",
);
assert!(
saw_triple,
"L4 greedy parse on a repeating pattern must emit at least one match (Triple)",
);
}
#[test]
fn driver_level4_greedy_round_trip_cross_slice() {
let mut driver = MatchGeneratorDriver::new(32, 4);
driver.reset(CompressionLevel::Level(4));
let chunk = b"the quick brown fox jumps over!!";
assert_eq!(chunk.len(), 32);
let mut first = driver.get_next_space();
first[..chunk.len()].copy_from_slice(chunk);
first.truncate(chunk.len());
driver.commit_space(first);
let mut first_recon: Vec<u8> = Vec::new();
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => first_recon.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
first_recon.extend_from_slice(literals);
let start = first_recon.len() - offset;
for i in 0..match_len {
let byte = first_recon[start + i];
first_recon.push(byte);
}
}
});
assert_eq!(
first_recon,
chunk.to_vec(),
"first slice failed to round-trip"
);
let mut second = driver.get_next_space();
second[..chunk.len()].copy_from_slice(chunk);
second.truncate(chunk.len());
driver.commit_space(second);
let mut full = first_recon.clone();
let mut saw_cross_slice_match = false;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => full.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
if offset >= chunk.len() {
saw_cross_slice_match = true;
}
full.extend_from_slice(literals);
let start = full.len() - offset;
for i in 0..match_len {
let byte = full[start + i];
full.push(byte);
}
}
});
let mut expected = chunk.to_vec();
expected.extend_from_slice(chunk);
assert_eq!(
full, expected,
"cross-slice L4 greedy parse failed to reconstruct"
);
assert!(
saw_cross_slice_match,
"L4 greedy parse must match across slice boundaries (history is shared)",
);
}
#[cfg(test)]
impl MatchGeneratorDriver {
pub(crate) fn set_config_override(
&mut self,
search: super::strategy::SearchMethod,
parse: super::strategy::ParseMode,
) {
self.config_override = Some((search, parse));
}
pub(crate) fn reset_on_hc_lazy(&mut self, level: CompressionLevel) {
self.set_config_override(
super::strategy::SearchMethod::HashChain,
super::strategy::ParseMode::Lazy2,
);
self.reset(level);
}
}
#[cfg(test)]
fn drive_roundtrip_with_override(
level: CompressionLevel,
over: Option<(super::strategy::SearchMethod, super::strategy::ParseMode)>,
data: &[u8],
) -> Vec<u8> {
let mut driver = MatchGeneratorDriver::new(1 << 17, 8);
if let Some((s, p)) = over {
driver.set_config_override(s, p);
}
driver.reset(level);
let mut out: Vec<u8> = Vec::with_capacity(data.len());
let mut offset_in_data = 0usize;
while offset_in_data < data.len() {
let mut space = driver.get_next_space();
let take = (data.len() - offset_in_data).min(space.len());
space[..take].copy_from_slice(&data[offset_in_data..offset_in_data + take]);
space.truncate(take);
driver.commit_space(space);
offset_in_data += take;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => out.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
out.extend_from_slice(literals);
let start = out.len() - offset;
for i in 0..match_len {
let byte = out[start + i];
out.push(byte);
}
}
});
}
out
}
#[test]
fn parse_search_matrix_decoupled_roundtrips() {
use super::strategy::{ParseMode, SearchMethod};
let mut data = Vec::new();
for i in 0..4000u32 {
data.extend_from_slice(b"the quick brown fox ");
data.extend_from_slice(&i.to_le_bytes());
}
let got = drive_roundtrip_with_override(
CompressionLevel::Level(5),
Some((SearchMethod::HashChain, ParseMode::Greedy)),
&data,
);
assert_eq!(got, data, "greedy-on-hashchain diverged");
let got = drive_roundtrip_with_override(
CompressionLevel::Level(8),
Some((SearchMethod::RowHash, ParseMode::Lazy2)),
&data,
);
assert_eq!(got, data, "lazy2-on-rowhash diverged");
let got = drive_roundtrip_with_override(
CompressionLevel::Level(6),
Some((SearchMethod::RowHash, ParseMode::Lazy)),
&data,
);
assert_eq!(got, data, "lazy-on-rowhash diverged");
}
#[test]
fn row_mls_knob_gates_matches_and_roundtrips() {
let data: Vec<u8> = (0..4000u32)
.flat_map(|i| {
let mut v = b"abcdefgh".to_vec();
v.extend_from_slice(&i.to_le_bytes());
v
})
.collect();
for mls in [4usize, 5, 6, 7] {
let mut matcher = RowMatchGenerator::new(1 << 22);
let mut cfg = ROW_CONFIG;
cfg.mls = mls;
matcher.configure(cfg);
matcher.add_data(data.clone(), |_| {});
let mut out: Vec<u8> = Vec::with_capacity(data.len());
let mut shortest_match = usize::MAX;
matcher.start_matching(|seq| match seq {
Sequence::Literals { literals } => out.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
out.extend_from_slice(literals);
shortest_match = shortest_match.min(match_len);
let start = out.len() - offset;
for i in 0..match_len {
let byte = out[start + i];
out.push(byte);
}
}
});
assert_eq!(out, data, "mls={mls} round-trip diverged");
if shortest_match != usize::MAX {
assert!(
shortest_match >= mls,
"mls={mls}: emitted a {shortest_match}-byte match below the floor",
);
}
}
}
#[test]
fn parse_mode_follows_search_axis_not_strategy_tag() {
use super::strategy::{ParseMode, SearchMethod};
let mut p = LEVEL_TABLE[15];
assert_eq!(p.parse(), ParseMode::Optimal, "BinaryTree search → Optimal");
p.search = SearchMethod::RowHash;
p.lazy_depth = 0;
assert_eq!(p.parse(), ParseMode::Greedy, "RowHash + depth 0 → Greedy");
p.lazy_depth = 2;
assert_eq!(p.parse(), ParseMode::Lazy2, "RowHash + depth 2 → Lazy2");
}
#[test]
fn config_override_is_consumed_by_reset() {
use super::strategy::{ParseMode, SearchMethod};
let mut driver = MatchGeneratorDriver::new(1 << 17, 8);
driver.set_config_override(SearchMethod::RowHash, ParseMode::Lazy2);
assert!(driver.config_override.is_some());
driver.reset(CompressionLevel::Level(5));
assert!(
driver.config_override.is_none(),
"override must be consumed after one reset",
);
}
#[cfg(test)]
fn l4_greedy_round_trip(slice_size: usize, max_slices: usize, data: &[u8]) -> (usize, usize) {
let mut driver = MatchGeneratorDriver::new(slice_size, max_slices);
driver.reset(CompressionLevel::Level(4));
let mut reconstructed: Vec<u8> = Vec::with_capacity(data.len());
let mut triple_count = 0usize;
let mut max_offset = 0usize;
let mut offset_in_data = 0usize;
while offset_in_data < data.len() {
let mut space = driver.get_next_space();
let space_cap = space.len();
let take = (data.len() - offset_in_data).min(space_cap);
space[..take].copy_from_slice(&data[offset_in_data..offset_in_data + take]);
space.truncate(take);
driver.commit_space(space);
offset_in_data += take;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
triple_count += 1;
if offset > max_offset {
max_offset = offset;
}
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
}
if data.is_empty() {
let mut space = driver.get_next_space();
space.truncate(0);
driver.commit_space(space);
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple { .. } => panic!("empty input must not emit any matches"),
});
}
assert_eq!(reconstructed, data, "L4 greedy round-trip diverged");
(triple_count, max_offset)
}
#[test]
fn driver_level5_greedy_tail_rep_only_reachable() {
let first: &[u8] = b"ABCDABCDABCDABCD"; let second: &[u8] = b"ABCDA"; let mut driver = MatchGeneratorDriver::new(16, 2);
driver.reset(CompressionLevel::Level(5));
let mut first_space = driver.get_next_space();
first_space[..first.len()].copy_from_slice(first);
first_space.truncate(first.len());
driver.commit_space(first_space);
driver.start_matching(|_| {});
let mut second_space = driver.get_next_space();
second_space[..second.len()].copy_from_slice(second);
second_space.truncate(second.len());
driver.commit_space(second_space);
let mut second_slice_triples = 0usize;
driver.start_matching(|seq| {
if matches!(seq, Sequence::Triple { .. }) {
second_slice_triples += 1;
}
});
assert!(
second_slice_triples >= 1,
"tail rep-only position must produce a match in the second slice \
(got {second_slice_triples} triples)",
);
}
#[test]
fn driver_level4_greedy_empty_input_emits_nothing() {
let mut driver = MatchGeneratorDriver::new(64, 2);
driver.reset(CompressionLevel::Level(4));
let mut space = driver.get_next_space();
space.truncate(0);
driver.commit_space(space);
let mut emitted_anything = false;
driver.start_matching(|_| emitted_anything = true);
assert!(!emitted_anything, "empty slice must not emit any sequences",);
}
#[test]
fn driver_level4_greedy_sub_min_lookahead_input() {
let data: &[u8] = b"abcd"; let (triples, _) = l4_greedy_round_trip(64, 2, data);
assert_eq!(
triples, 0,
"sub-min-lookahead input must not emit any matches (got {triples})",
);
}
#[test]
fn driver_level4_greedy_incompressible_input() {
let mut data = alloc::vec::Vec::with_capacity(256);
let mut x: u32 = 0xDEAD_BEEF;
for _ in 0..256 {
x = x.wrapping_mul(1_103_515_245).wrapping_add(12345);
data.push((x >> 16) as u8);
}
let (_triples, _) = l4_greedy_round_trip(64, 8, &data);
}
#[test]
fn driver_level4_greedy_long_literal_run_skip_step_growth() {
let mut data = alloc::vec::Vec::with_capacity(2048);
let mut x: u32 = 0xC0FF_EE00;
for _ in 0..2048 {
x = x.wrapping_mul(0x9E37_79B9).wrapping_add(0xCAFEBABE);
data.push((x >> 24) as u8);
}
let (_triples, _) = l4_greedy_round_trip(512, 8, &data);
}
#[test]
fn driver_level4_greedy_all_zeros_heavy_rep1() {
let data: Vec<u8> = alloc::vec![0u8; 128];
let (triples, max_offset) = l4_greedy_round_trip(64, 8, &data);
assert!(
triples >= 1,
"all-zeros input must produce at least one rep1 match",
);
assert_eq!(
max_offset, 1,
"all-zeros L4 greedy parse should commit at offset 1 (got {max_offset})",
);
}
#[test]
fn driver_level4_greedy_periodic_pattern_rep_cascade() {
let unit: &[u8] = b"alpha_beta_gamma";
assert_eq!(unit.len(), 16);
let mut data: Vec<u8> = Vec::with_capacity(unit.len() * 32);
for _ in 0..32 {
data.extend_from_slice(unit);
}
let (triples, max_offset) = l4_greedy_round_trip(64, 16, &data);
assert!(
triples >= 1,
"periodic 16-byte payload must emit matches (got {triples})",
);
assert!(
max_offset >= 16,
"periodic 16-byte payload must produce at least one offset >= 16 \
(got max_offset = {max_offset})",
);
}
#[test]
fn driver_reset_keeps_strategy_tag_in_sync_with_active_backend() {
use super::strategy::StrategyTag;
fn check(level: CompressionLevel, expected: StrategyTag) {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(level);
assert_eq!(
driver.strategy_tag, expected,
"strategy_tag wrong for {level:?}"
);
assert_eq!(
driver.strategy_tag.backend(),
driver.active_backend(),
"strategy_tag backend disagrees with active_backend for {level:?}"
);
}
check(CompressionLevel::Level(1), StrategyTag::Fast);
check(CompressionLevel::Level(2), StrategyTag::Fast);
check(CompressionLevel::Level(3), StrategyTag::Dfast);
check(CompressionLevel::Level(4), StrategyTag::Dfast);
check(CompressionLevel::Level(5), StrategyTag::Greedy);
check(CompressionLevel::Level(7), StrategyTag::Lazy);
check(CompressionLevel::Level(12), StrategyTag::Lazy);
check(CompressionLevel::Level(13), StrategyTag::Btlazy2);
check(CompressionLevel::Level(14), StrategyTag::Btlazy2);
check(CompressionLevel::Level(15), StrategyTag::Btlazy2);
check(CompressionLevel::Level(16), StrategyTag::BtOpt);
check(CompressionLevel::Level(18), StrategyTag::BtUltra);
check(CompressionLevel::Level(22), StrategyTag::BtUltra2);
check(CompressionLevel::Fastest, StrategyTag::Fast);
check(CompressionLevel::Default, StrategyTag::Dfast);
check(CompressionLevel::Better, StrategyTag::Lazy);
check(CompressionLevel::Best, StrategyTag::Btlazy2);
}
#[test]
fn level_16_17_map_to_btopt_strategy() {
use super::strategy::{BackendTag, StrategyTag};
let p16 = resolve_level_params(CompressionLevel::Level(16), None);
let p17 = resolve_level_params(CompressionLevel::Level(17), None);
assert_eq!(p16.backend(), BackendTag::HashChain);
assert_eq!(p17.backend(), BackendTag::HashChain);
assert_eq!(StrategyTag::for_level(16), StrategyTag::BtOpt);
assert_eq!(StrategyTag::for_level(17), StrategyTag::BtOpt);
}
#[test]
fn level_18_maps_to_btultra_level_19_to_btultra2_strategy() {
use super::strategy::{BackendTag, StrategyTag};
let p18 = resolve_level_params(CompressionLevel::Level(18), None);
let p19 = resolve_level_params(CompressionLevel::Level(19), None);
assert_eq!(p18.backend(), BackendTag::HashChain);
assert_eq!(p19.backend(), BackendTag::HashChain);
assert_eq!(StrategyTag::for_level(18), StrategyTag::BtUltra);
assert_eq!(StrategyTag::for_level(19), StrategyTag::BtUltra2);
}
#[test]
fn level_20_22_map_to_btultra2_strategy() {
use super::strategy::{BackendTag, StrategyTag};
for level in 20..=22 {
let params = resolve_level_params(CompressionLevel::Level(level), None);
assert_eq!(params.backend(), BackendTag::HashChain);
assert_eq!(StrategyTag::for_level(level as u8), StrategyTag::BtUltra2);
}
}
#[test]
fn level22_uses_target_length_and_large_input_tables() {
let params = resolve_level_params(CompressionLevel::Level(22), None);
assert_eq!(params.window_log, 27);
let hc = params.hc.unwrap();
assert_eq!(hc.hash_log, 25);
assert_eq!(hc.chain_log, 27);
assert_eq!(hc.search_depth, 1 << 9);
assert_eq!(hc.target_len, 999);
}
#[test]
fn bt_levels_16_to_21_pin_clevels_params() {
let expected = [
(16u8, 22u8, 22usize, 22usize, 32usize, 48usize),
(17, 23, 22, 23, 32, 64),
(18, 23, 22, 23, 64, 64),
(19, 23, 22, 24, 128, 256),
(20, 25, 23, 25, 128, 256),
(21, 26, 24, 24, 512, 256),
];
for (level, wlog, hlog, clog, sd, tl) in expected {
let p = resolve_level_params(CompressionLevel::Level(level as i32), None);
assert_eq!(p.window_log, wlog, "level {level} window_log");
let hc = p.hc.unwrap();
assert_eq!(hc.hash_log, hlog, "level {level} hash_log");
assert_eq!(hc.chain_log, clog, "level {level} chain_log");
assert_eq!(hc.search_depth, sd, "level {level} search_depth");
assert_eq!(hc.target_len, tl, "level {level} target_len");
}
}
#[test]
fn level22_source_size_hint_uses_btultra2_tiers() {
let p16k = resolve_level_params(CompressionLevel::Level(22), Some(16 * 1024));
assert_eq!(p16k.window_log, 14);
let hc16k = p16k.hc.unwrap();
assert_eq!(hc16k.hash_log, 15);
assert_eq!(hc16k.chain_log, 15);
assert_eq!(hc16k.search_depth, 1 << 10);
assert_eq!(hc16k.target_len, 999);
let p128k = resolve_level_params(CompressionLevel::Level(22), Some(128 * 1024));
assert_eq!(p128k.window_log, 17);
let hc128k = p128k.hc.unwrap();
assert_eq!(hc128k.hash_log, 17);
assert_eq!(hc128k.chain_log, 18);
assert_eq!(hc128k.search_depth, 1 << 11);
assert_eq!(hc128k.target_len, 999);
let p256k = resolve_level_params(CompressionLevel::Level(22), Some(256 * 1024));
assert_eq!(p256k.window_log, 18);
let hc256k = p256k.hc.unwrap();
assert_eq!(hc256k.hash_log, 19);
assert_eq!(hc256k.chain_log, 19);
assert_eq!(hc256k.search_depth, 1 << 13);
assert_eq!(hc256k.target_len, 999);
}
#[test]
fn level22_non_power_of_two_small_source_uses_tier3_params() {
let source_size = 15_027u64;
let params = resolve_level_params(CompressionLevel::Level(22), Some(source_size));
let hc = params.hc.unwrap();
assert_eq!(params.window_log, 14);
assert_eq!(hc.chain_log, 15);
assert_eq!(hc.hash_log, 15);
assert_eq!(hc.search_depth, 1 << 10);
assert_eq!(HC_OPT_MIN_MATCH_LEN, 3);
assert_eq!(hc.target_len, 999);
}
#[test]
fn level22_small_source_uses_window_bounded_hash3_log() {
let mut hc = HcMatchGenerator::new(1 << 14);
hc.configure(
BTULTRA2_HC_CONFIG_L22_16K,
super::strategy::StrategyTag::BtUltra2,
14,
);
assert_eq!(hc.table.hash3_log, 14);
hc.configure(
BTULTRA2_HC_CONFIG_L22,
super::strategy::StrategyTag::BtUltra2,
27,
);
assert_eq!(hc.table.hash3_log, HC3_HASH_LOG);
}
#[test]
fn btultra2_seed_pass_initializes_opt_state() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
let data: Vec<u8> = (0..32 * 1024).map(|i| (i % 251) as u8).collect();
hc.table.add_data(data, |_| {});
hc.start_matching(|_| {});
assert!(
hc.backend.bt_mut().opt_state.lit_length_sum > 0,
"btultra2 first block should seed non-zero sequence statistics"
);
assert!(
hc.backend.bt_mut().opt_state.off_code_sum > 0,
"btultra2 first block should seed offset-code statistics"
);
}
#[test]
fn btultra2_profile_disables_small_offset_handicap() {
let profile = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
assert!(
!profile.favor_small_offsets,
"btultra2 should match upstream zstd opt2 offset pricing"
);
assert!(
profile.accurate,
"btultra2 should use upstream zstd opt2 accurate pricing"
);
}
#[test]
fn btultra_profile_keeps_search_depth_budget() {
let p = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra>();
assert_eq!(
p.max_chain_depth, 64,
"btultra chain-depth budget must match clevels.h level 18 searchLog 6 (1 << 6 = 64)"
);
}
#[test]
fn btopt_profile_keeps_search_depth_budget() {
let p = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtOpt>();
assert_eq!(
p.max_chain_depth, 32,
"btopt should not cap chain depth below upstream zstd btopt search budget"
);
}
#[test]
fn sufficient_match_len_is_clamped_by_target_len() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
hc.hc.target_len = 13;
let profile = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
assert_eq!(hc.hc.sufficient_match_len_for_pass(profile), 13);
}
#[test]
fn opt_modes_use_target_len_as_sufficient_len() {
use super::strategy;
let mut hc = HcMatchGenerator::new(1 << 20);
hc.hc.target_len = 57;
let profiles = [
HcOptimalCostProfile::const_for_strategy::<strategy::BtOpt>(),
HcOptimalCostProfile::const_for_strategy::<strategy::BtUltra>(),
HcOptimalCostProfile::const_for_strategy::<strategy::BtUltra2>(),
];
for profile in profiles {
assert_eq!(hc.hc.sufficient_match_len_for_pass(profile), 57);
}
}
#[test]
fn sufficient_match_len_is_capped_by_opt_num() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.hc.target_len = usize::MAX / 2;
let profile = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
assert_eq!(hc.hc.sufficient_match_len_for_pass(profile), HC_OPT_NUM - 1);
}
#[test]
#[allow(clippy::borrow_deref_ref)]
fn dictionary_entropy_seed_initializes_opt_state_from_tables() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
let huff = crate::huff0::huff0_encoder::HuffmanTable::build_from_data(
b"aaabbbbccccddddeeeeefffffgggg",
);
let ll = crate::fse::fse_encoder::default_ll_table();
let ml = crate::fse::fse_encoder::default_ml_table();
let of = crate::fse::fse_encoder::default_of_table();
hc.seed_dictionary_entropy(Some(&huff), Some(&*ll), Some(&*ml), Some(&*of));
hc.backend.bt_mut().opt_state.rescale_freqs(
b"abcd",
HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>(),
);
let base_ll_freqs: [u32; HC_MAX_LL + 1] = [
4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
];
assert_ne!(
hc.backend.bt_mut().opt_state.lit_length_freq,
base_ll_freqs,
"dictionary entropy should override fallback LL bootstrap frequencies"
);
assert!(
hc.backend
.bt_mut()
.opt_state
.match_length_freq
.iter()
.any(|&v| v != 1),
"dictionary entropy should seed non-uniform ML frequencies"
);
assert_ne!(
hc.backend.bt_mut().opt_state.off_code_freq[0],
6,
"dictionary entropy should override fallback OF bootstrap frequencies"
);
}
#[test]
#[allow(clippy::borrow_deref_ref)]
fn dictionary_fse_seed_applies_without_huffman_seed() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
let ll = crate::fse::fse_encoder::default_ll_table();
let ml = crate::fse::fse_encoder::default_ml_table();
let of = crate::fse::fse_encoder::default_of_table();
hc.seed_dictionary_entropy(None, Some(&*ll), Some(&*ml), Some(&*of));
hc.backend.bt_mut().opt_state.rescale_freqs(
b"abcd",
HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>(),
);
let base_ll_freqs: [u32; HC_MAX_LL + 1] = [
4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
];
assert_ne!(
hc.backend.bt_mut().opt_state.lit_length_freq,
base_ll_freqs,
"FSE seed should still override LL bootstrap frequencies without huffman seed"
);
assert!(
hc.backend
.bt_mut()
.opt_state
.match_length_freq
.iter()
.any(|&v| v != 1),
"FSE seed should still seed non-uniform ML frequencies"
);
assert_ne!(
hc.backend.bt_mut().opt_state.off_code_freq[0],
6,
"FSE seed should still override OF bootstrap frequencies without huffman seed"
);
}
#[test]
#[allow(clippy::borrow_deref_ref)]
fn dictionary_seed_overrides_predef_price_mode_on_tiny_input() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
let ll = crate::fse::fse_encoder::default_ll_table();
let ml = crate::fse::fse_encoder::default_ml_table();
let of = crate::fse::fse_encoder::default_of_table();
hc.seed_dictionary_entropy(None, Some(&*ll), Some(&*ml), Some(&*of));
hc.backend.bt_mut().opt_state.rescale_freqs(
b"abc",
HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>(),
);
assert!(
matches!(
hc.backend.bt_mut().opt_state.price_type,
HcOptPriceType::Dynamic
),
"dictionary-seeded first block should stay in dynamic mode even for tiny src"
);
}
#[test]
fn lit_length_price_blocksize_max_costs_one_extra_bit() {
let profile_predef = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
let mut stats_predef = HcOptState::new();
stats_predef.price_type = HcOptPriceType::Predefined;
let predef_max = profile_predef.lit_length_price(&stats_predef, HC_BLOCKSIZE_MAX);
let predef_prev =
profile_predef.lit_length_price(&stats_predef, HC_BLOCKSIZE_MAX.saturating_sub(1));
assert_eq!(
predef_max,
predef_prev + HC_BITCOST_MULTIPLIER,
"predefined litLength pricing at BLOCKSIZE_MAX must add exactly one bit"
);
let profile_dyn = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
let mut stats_dyn = HcOptState::new();
stats_dyn.price_type = HcOptPriceType::Dynamic;
stats_dyn.lit_length_freq.fill(1);
stats_dyn.lit_length_sum = (HC_MAX_LL + 1) as u32;
stats_dyn.match_length_freq.fill(1);
stats_dyn.match_length_sum = (HC_MAX_ML + 1) as u32;
stats_dyn.off_code_freq.fill(1);
stats_dyn.off_code_sum = (HC_MAX_OFF + 1) as u32;
stats_dyn.lit_freq.fill(1);
stats_dyn.lit_sum = (HC_MAX_LIT + 1) as u32;
stats_dyn.set_base_prices(true);
let dyn_max = profile_dyn.lit_length_price(&stats_dyn, HC_BLOCKSIZE_MAX);
let dyn_prev = profile_dyn.lit_length_price(&stats_dyn, HC_BLOCKSIZE_MAX.saturating_sub(1));
assert_eq!(
dyn_max,
dyn_prev + HC_BITCOST_MULTIPLIER,
"dynamic litLength pricing at BLOCKSIZE_MAX must add exactly one bit"
);
}
#[test]
#[allow(clippy::borrow_deref_ref)]
fn btultra2_seed_pass_disabled_when_dictionary_entropy_seed_present() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
let ll = crate::fse::fse_encoder::default_ll_table();
let ml = crate::fse::fse_encoder::default_ml_table();
let of = crate::fse::fse_encoder::default_of_table();
hc.seed_dictionary_entropy(None, Some(&*ll), Some(&*ml), Some(&*of));
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 1),
"dictionary-seeded first block should skip btultra2 warmup pass"
);
}
#[test]
fn btultra2_seed_pass_disabled_when_prefix_history_exists() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
hc.table.history_abs_start = 17;
hc.table.push_test_chunk(b"abcdefghijklmnop".to_vec());
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 9),
"btultra2 warmup must be first-block only (no prefix history)"
);
}
#[test]
fn btultra2_seed_pass_disabled_for_tiny_block() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD),
"btultra2 warmup should not run at or below predefined threshold"
);
}
#[test]
fn btultra2_seed_pass_disabled_after_stats_initialized() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
hc.backend.bt_mut().opt_state.lit_length_sum = 1;
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 32),
"btultra2 warmup should run only for first block before stats are initialized"
);
}
#[test]
fn btultra2_seed_pass_disabled_when_not_at_frame_start() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
hc.table.window_size = HC_PREDEF_THRESHOLD + 64;
hc.table.chunk_lens.push_back(HC_PREDEF_THRESHOLD + 32);
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 32),
"btultra2 warmup must not run after frame start"
);
}
#[test]
fn btultra2_seed_pass_disabled_when_ldm_sequences_exist() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG,
super::strategy::StrategyTag::BtUltra2,
26,
);
hc.table.window_size = HC_PREDEF_THRESHOLD + 64;
hc.table.chunk_lens.push_back(HC_PREDEF_THRESHOLD + 64);
hc.backend.bt_mut().ldm_sequences.push(HcRawSeq {
lit_length: 8,
offset: 16,
match_length: 32,
});
assert!(
!hc.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 32),
"btultra2 warmup must not run when LDM already produced sequences"
);
}
#[test]
fn literal_price_uses_eight_bits_when_literals_uncompressed() {
let profile = HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>();
let mut stats = HcOptState::new();
stats.set_literals_compressed_for_tests(false);
stats.price_type = HcOptPriceType::Predefined;
assert_eq!(
profile.literal_price(&stats, b'a'),
8 * HC_BITCOST_MULTIPLIER,
"uncompressed literals should cost 8 bits regardless of price mode"
);
}
#[test]
fn update_stats_skips_literal_frequencies_when_uncompressed() {
let mut stats = HcOptState::new();
stats.set_literals_compressed_for_tests(false);
stats.update_stats(3, b"abc", 4, 8);
assert_eq!(
stats.lit_sum, 0,
"literal sum must remain unchanged when literal compression is disabled"
);
assert_eq!(
stats.lit_freq.iter().copied().sum::<u32>(),
0,
"literal frequencies must not be updated when literal compression is disabled"
);
assert_eq!(
stats.lit_length_sum, 1,
"literal-length stats still update for sequence modeling"
);
assert_eq!(
stats.match_length_sum, 1,
"match-length stats still update for sequence modeling"
);
assert_eq!(
stats.off_code_sum, 1,
"offset-code stats still update for sequence modeling"
);
}
#[test]
#[allow(clippy::borrow_deref_ref)]
fn dictionary_huffman_seed_ignored_when_literals_uncompressed() {
let mut stats = HcOptState::new();
stats.set_literals_compressed_for_tests(false);
let huff = crate::huff0::huff0_encoder::HuffmanTable::build_from_data(
b"aaaaabbbbcccddeeff00112233445566778899",
);
let ll = crate::fse::fse_encoder::default_ll_table();
let ml = crate::fse::fse_encoder::default_ml_table();
let of = crate::fse::fse_encoder::default_of_table();
stats.seed_dictionary_entropy(Some(&huff), Some(&*ll), Some(&*ml), Some(&*of));
stats.rescale_freqs(
b"abcd",
HcOptimalCostProfile::const_for_strategy::<super::strategy::BtUltra2>(),
);
assert_eq!(
stats.lit_sum, 0,
"literal sum must stay zero when literals are uncompressed"
);
assert_eq!(
stats.lit_freq.iter().copied().sum::<u32>(),
0,
"literal frequencies must ignore dictionary huffman seed when uncompressed"
);
}
#[test]
fn hc_repcode_candidates_respect_litlen_dependent_rep_order() {
let mut hc = HcMatchGenerator::new(64);
hc.table.history = b"xxxxxxABCDEFABCDEF".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
let abs_pos = 12usize; let current_abs_end = hc.table.history.len();
let reps = [6u32, 3u32, 9u32];
let mut lit_pos_candidates = Vec::new();
hc.hc.for_each_repcode_candidate_with_reps(
&hc.table,
abs_pos,
1,
reps,
current_abs_end,
HC_OPT_MIN_MATCH_LEN,
|c| {
lit_pos_candidates.push(c.offset);
},
);
assert!(
lit_pos_candidates.contains(&6),
"when lit_len>0, rep0 should be considered and match"
);
let mut ll0_candidates = Vec::new();
hc.hc.for_each_repcode_candidate_with_reps(
&hc.table,
abs_pos,
0,
reps,
current_abs_end,
HC_OPT_MIN_MATCH_LEN,
|c| {
ll0_candidates.push(c.offset);
},
);
assert!(
!ll0_candidates.contains(&6),
"when lit_len==0, rep0 is not directly eligible (ll0 semantics)"
);
}
#[test]
fn hc_collect_optimal_candidates_keeps_reps_when_chain_depth_zero() {
let mut hc = HcMatchGenerator::new(64);
hc.hc.search_depth = 0;
hc.table.history = b"xyzxyzxyzxyz".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
let abs_pos = 6usize;
let current_abs_end = hc.table.history.len();
let profile = HcOptimalCostProfile {
max_chain_depth: 0,
sufficient_match_len: usize::MAX / 2,
accurate: false,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
current_abs_end,
profile,
HcCandidateQuery {
reps: [3, 6, 9],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert!(
!out.is_empty(),
"rep candidates should remain available even when chain depth is zero"
);
assert!(
out.iter().any(|c| c.offset == 3),
"rep0 candidate should be retained"
);
}
#[test]
fn hc_collect_optimal_candidates_rep_tail_match_skips_chain_probe() {
let mut hc = HcMatchGenerator::new(64);
hc.table.history = b"aaaaaaaaaa".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.hc.search_depth = 32;
let abs_pos = 6usize;
hc.table.ensure_tables();
hc.table.insert_positions(0, abs_pos);
let profile = HcOptimalCostProfile {
max_chain_depth: 32,
sufficient_match_len: usize::MAX / 2,
accurate: true,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
hc.table.history.len(),
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert!(
out.iter()
.all(|candidate| matches!(candidate.offset, 1 | 4)),
"terminal rep match should return before chain probing adds non-rep offsets"
);
}
#[test]
fn hc_collect_optimal_candidates_long_chain_match_advances_skip_window() {
let mut hc = HcMatchGenerator::new(128);
hc.table.history = b"abcabcabcabcabcabcabcabc".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.hc.search_depth = 32;
let abs_pos = 9usize;
hc.table.ensure_tables();
hc.table.insert_positions(0, abs_pos);
hc.table.skip_insert_until_abs = 0;
let profile = HcOptimalCostProfile {
max_chain_depth: 32,
sufficient_match_len: usize::MAX / 2,
accurate: true,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
hc.table.history.len(),
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert!(
hc.table.skip_insert_until_abs > abs_pos,
"long chain match should advance skip window to avoid redundant immediate insertions"
);
}
#[test]
fn hc_collect_optimal_candidates_chain_fast_skip_uses_match_end_minus_8() {
let mut hc = HcMatchGenerator::new(128);
hc.table.history = b"abcabcabcabcabcabcabcabc".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.hc.search_depth = 32;
let abs_pos = 9usize;
hc.table.ensure_tables();
hc.table.insert_positions(0, abs_pos);
hc.table.skip_insert_until_abs = 0;
let profile = HcOptimalCostProfile {
max_chain_depth: 32,
sufficient_match_len: 10,
accurate: true,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
hc.table.history.len(),
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
let best_match_end = out
.iter()
.map(|candidate| candidate.start.saturating_add(candidate.match_len))
.max()
.expect("expected at least one candidate");
assert!(
hc.table.skip_insert_until_abs > abs_pos,
"chain fast-skip must advance past current position"
);
assert!(
hc.table.skip_insert_until_abs <= best_match_end.saturating_sub(8),
"chain fast-skip must not exceed upstream zstd-style matchEndIdx - 8 bound"
);
}
#[test]
fn hc_collect_optimal_candidates_advances_skip_window_on_plain_bt_path() {
let mut hc = HcMatchGenerator::new(256);
hc.table.history = b"abcdefghijklmnop".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.hc.search_depth = 0;
hc.table.ensure_tables();
let abs_pos = 8usize;
hc.table.skip_insert_until_abs = 0;
let profile = HcOptimalCostProfile {
max_chain_depth: 0,
sufficient_match_len: usize::MAX / 2,
accurate: true,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
hc.table.history.len(),
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert_eq!(
hc.table.skip_insert_until_abs,
abs_pos.saturating_add(1),
"plain BT path should advance skip window by 1 via upstream zstd matchEndIdx baseline"
);
}
#[test]
fn hc_ldm_candidates_are_merged_into_optimal_candidates() {
let mut hc = HcMatchGenerator::new(512);
hc.table.history = (0..256).map(|i| (i % 251) as u8).collect();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
let abs_pos = 128usize;
let current_abs_end = 256usize;
let ldm = MatchCandidate {
start: abs_pos,
offset: 96,
match_len: 40,
};
let profile = HcOptimalCostProfile {
max_chain_depth: 0,
sufficient_match_len: usize::MAX / 2,
accurate: true,
favor_small_offsets: false,
};
let mut out = Vec::new();
hc.collect_optimal_candidates(
abs_pos,
current_abs_end,
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: Some(ldm),
},
&mut out,
);
assert!(
out.iter().any(
|candidate| candidate.offset == ldm.offset && candidate.match_len == ldm.match_len
),
"LDM candidate should be present in optimal candidate set"
);
}
#[test]
fn btultra_and_btultra2_both_keep_dictionary_candidates() {
use super::strategy::StrategyTag;
let test_config = HcConfig {
hash_log: 23,
chain_log: 22,
search_depth: 32,
target_len: 256,
search_mls: 4,
};
let window_log = 20u8;
let prepare_history = |hc: &mut HcMatchGenerator, abs_pos: usize| {
hc.table.history = alloc::vec![0u8; 160];
for i in 0..64 {
hc.table.history[i] = b'a' + (i % 7) as u8;
}
for i in 64..160 {
hc.table.history[i] = b'k' + (i % 5) as u8;
}
for i in 0..24 {
hc.table.history[abs_pos + i] = hc.table.history[16 + i];
}
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.table.ensure_tables();
hc.table.insert_positions(0, abs_pos);
hc.table.dictionary_limit_abs = Some(64);
hc.table.skip_insert_until_abs = 0;
};
let profile = HcOptimalCostProfile {
max_chain_depth: 32,
sufficient_match_len: usize::MAX / 2,
accurate: true,
favor_small_offsets: false,
};
let abs_pos = 96usize;
let mut out = Vec::new();
let mut hc = HcMatchGenerator::new(256);
hc.configure(test_config, StrategyTag::BtUltra2, window_log);
prepare_history(&mut hc, abs_pos);
hc.collect_optimal_candidates(
abs_pos,
160,
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert!(
out.iter().any(|candidate| candidate.offset >= 32),
"btultra2 should retain dictionary candidates on upstream zstd-parity path"
);
let mut hc = HcMatchGenerator::new(256);
hc.configure(test_config, StrategyTag::BtUltra, window_log);
prepare_history(&mut hc, abs_pos);
hc.collect_optimal_candidates(
abs_pos,
160,
profile,
HcCandidateQuery {
reps: [1, 4, 8],
lit_len: 1,
ldm_candidate: None,
},
&mut out,
);
assert!(
out.iter().any(|candidate| candidate.offset >= 32),
"btultra should retain dictionary candidates"
);
}
#[test]
fn driver_small_source_hint_shrinks_dfast_hash_tables() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(3));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let full_long = driver.dfast_matcher().long_hash.len();
let full_short = driver.dfast_matcher().short_hash.len();
assert_eq!(full_long, 1 << DFAST_HASH_BITS);
assert_eq!(
full_short,
1 << (DFAST_HASH_BITS - DFAST_SHORT_HASH_BITS_DELTA)
);
driver.set_source_size_hint(1024);
driver.reset(CompressionLevel::Level(3));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"xyzxyzxyzxyz");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let hinted_long = driver.dfast_matcher().long_hash.len();
let hinted_short = driver.dfast_matcher().short_hash.len();
assert_eq!(driver.window_size(), 1 << MIN_HINTED_WINDOW_LOG);
assert_eq!(hinted_long, 1 << MIN_WINDOW_LOG);
assert_eq!(hinted_short, 1 << MIN_WINDOW_LOG);
assert!(
hinted_long < full_long && hinted_short < full_short,
"tiny source hint should reduce both dfast tables"
);
}
#[test]
fn driver_huge_source_hint_does_not_overflow_table_window_shift() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.set_source_size_hint(u64::MAX);
driver.reset(CompressionLevel::Level(3));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
assert!(
driver.dfast_matcher().long_hash.len() >= 1 << MIN_WINDOW_LOG,
"huge hint must size the dfast table from the real window, not wrap to zero"
);
}
#[test]
fn driver_huge_source_hint_with_dict_does_not_overflow_hc_reserve() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.set_source_size_hint(u64::MAX);
driver.set_dictionary_size_hint(64 * 1024);
driver.reset(CompressionLevel::Level(16));
let window = 1usize << 22;
let expected_history_ceiling = window + (window >> 2) + crate::common::MAX_BLOCK_SIZE as usize;
assert!(
driver.hc_matcher().table.history.capacity() >= expected_history_ceiling,
"huge source + dict hint must reserve the clamped HC history ceiling, got {}",
driver.hc_matcher().table.history.capacity()
);
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
}
#[test]
fn driver_chain_log_override_survives_row_to_hc_fallback() {
let chain_log_override = 10u32;
let ov = super::parameters::ParamOverrides {
chain_log: Some(chain_log_override),
..Default::default()
};
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.set_source_size_hint(1 << 12);
driver.set_param_overrides(Some(ov));
driver.reset(CompressionLevel::Level(6));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
assert_eq!(
driver.hc_matcher().table.chain_log,
chain_log_override as usize,
"explicit chain_log override must survive the Row->HC fallback, got {}",
driver.hc_matcher().table.chain_log
);
}
#[test]
fn driver_small_source_hint_shrinks_row_hash_tables() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(5));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let full_rows = driver.row_matcher().row_heads.len();
assert_eq!(full_rows, 1 << (ROW_L5.hash_bits - ROW_L5.row_log));
driver.set_source_size_hint(1 << 16);
driver.reset(CompressionLevel::Level(5));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"xyzxyzxyzxyz");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
assert_eq!(
driver.active_backend(),
super::strategy::BackendTag::Row,
"windowLog > 14 keeps the upstream row matchfinder"
);
let hinted_rows = driver.row_matcher().row_heads.len();
assert!(
hinted_rows < full_rows,
"a window>14 source hint should reduce the row hash table footprint"
);
driver.set_source_size_hint(1024);
driver.reset(CompressionLevel::Level(5));
assert_eq!(driver.window_size(), 1 << MIN_HINTED_WINDOW_LOG);
assert_eq!(
driver.active_backend(),
super::strategy::BackendTag::HashChain,
"windowLog <= 14 must fall back to the upstream zstd hash-chain matchfinder",
);
}
#[test]
fn row_matches_roundtrip_multi_block_pattern() {
let pattern = [7, 13, 44, 184, 19, 96, 171, 109, 141, 251];
let first_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let second_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.ensure_tables();
let replay_sequence = |decoded: &mut Vec<u8>, seq: Sequence<'_>| match seq {
Sequence::Literals { literals } => decoded.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
decoded.extend_from_slice(literals);
let start = decoded.len() - offset;
for i in 0..match_len {
let byte = decoded[start + i];
decoded.push(byte);
}
}
};
matcher.add_data(first_block.clone(), |_| {});
let mut history = Vec::new();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(history, first_block);
matcher.add_data(second_block.clone(), |_| {});
let prefix_len = history.len();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(&history[prefix_len..], second_block.as_slice());
let third_block: Vec<u8> = (0u8..=255).collect();
matcher.add_data(third_block.clone(), |_| {});
let third_prefix = history.len();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(&history[third_prefix..], third_block.as_slice());
}
#[test]
fn row_short_block_emits_literals_only() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.add_data(b"abcde".to_vec(), |_| {});
let mut saw_triple = false;
let mut reconstructed = Vec::new();
matcher.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple { .. } => saw_triple = true,
});
assert!(
!saw_triple,
"row backend must not emit triples for short blocks"
);
assert_eq!(reconstructed, b"abcde");
saw_triple = false;
matcher.add_data(b"abcdeabcde".to_vec(), |_| {});
matcher.start_matching(|seq| {
if let Sequence::Triple { .. } = seq {
saw_triple = true;
}
});
assert!(
saw_triple,
"row backend should emit triples on repeated data"
);
}
#[test]
fn row_pick_lazy_returns_best_when_lookahead_is_out_of_bounds() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.add_data(b"abcabc".to_vec(), |_| {});
matcher.ensure_tables();
let best = MatchCandidate {
start: 0,
offset: 1,
match_len: ROW_MIN_MATCH_LEN,
};
let picked = matcher
.pick_lazy_match(0, 0, Some(best))
.expect("best candidate must survive");
assert_eq!(picked.start, best.start);
assert_eq!(picked.offset, best.offset);
assert_eq!(picked.match_len, best.match_len);
}
#[test]
fn row_backfills_previous_block_tail_for_cross_boundary_match() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
let mut first_block = alloc::vec![0xA5; 64];
first_block.extend_from_slice(b"XYZ");
let second_block = b"XYZXYZtail".to_vec();
let replay_sequence = |decoded: &mut Vec<u8>, seq: Sequence<'_>| match seq {
Sequence::Literals { literals } => decoded.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
decoded.extend_from_slice(literals);
let start = decoded.len() - offset;
for i in 0..match_len {
let byte = decoded[start + i];
decoded.push(byte);
}
}
};
matcher.add_data(first_block.clone(), |_| {});
let mut reconstructed = Vec::new();
matcher.start_matching(|seq| replay_sequence(&mut reconstructed, seq));
assert_eq!(reconstructed, first_block);
matcher.add_data(second_block.clone(), |_| {});
let mut saw_cross_boundary = false;
let prefix_len = reconstructed.len();
matcher.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == 3
&& match_len >= ROW_MIN_MATCH_LEN
{
saw_cross_boundary = true;
}
replay_sequence(&mut reconstructed, seq);
});
assert!(
saw_cross_boundary,
"row matcher should reuse the 3-byte previous-block tail"
);
assert_eq!(&reconstructed[prefix_len..], second_block.as_slice());
}
#[test]
fn row_skip_matching_with_incompressible_hint_uses_sparse_prefix() {
let data = deterministic_high_entropy_bytes(0xA713_9C5D_44E2_10B1, 4096);
let mut dense = RowMatchGenerator::new(1 << 22);
dense.configure(ROW_CONFIG);
dense.add_data(data.clone(), |_| {});
dense.skip_matching_with_hint(Some(false));
let dense_slots = dense
.row_positions
.iter()
.filter(|&&pos| pos != ROW_EMPTY_SLOT)
.count();
let mut sparse = RowMatchGenerator::new(1 << 22);
sparse.configure(ROW_CONFIG);
sparse.add_data(data, |_| {});
sparse.skip_matching_with_hint(Some(true));
let sparse_slots = sparse
.row_positions
.iter()
.filter(|&&pos| pos != ROW_EMPTY_SLOT)
.count();
assert!(
sparse_slots < dense_slots,
"incompressible hint should seed fewer row slots (sparse={sparse_slots}, dense={dense_slots})"
);
}
#[test]
fn row_skip_matching_with_none_hint_leaves_interior_empty() {
let data = deterministic_high_entropy_bytes(0x9B47_F2A1_8C5E_3306, 4096);
let mut none_hint = RowMatchGenerator::new(1 << 22);
none_hint.configure(ROW_CONFIG);
none_hint.add_data(data.clone(), |_| {});
none_hint.skip_matching_with_hint(None);
let none_slots = none_hint
.row_positions
.iter()
.filter(|&&pos| pos != ROW_EMPTY_SLOT)
.count();
let mut dense = RowMatchGenerator::new(1 << 22);
dense.configure(ROW_CONFIG);
dense.add_data(data, |_| {});
dense.skip_matching_with_hint(Some(false));
let dense_slots = dense
.row_positions
.iter()
.filter(|&&pos| pos != ROW_EMPTY_SLOT)
.count();
assert_eq!(
none_slots, 0,
"None hint at block_start=0 must leave row table fully empty \
(upstream zstd parity — interior NOT inserted, no pre-block backfill possible)",
);
assert!(
dense_slots > 0,
"Some(false) dict-priming path must still insert densely \
(sanity check: control case for the `none_slots == 0` assertion)",
);
}
#[test]
fn driver_unhinted_level2_keeps_default_dfast_hash_table_size() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(3));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let long_len = driver.dfast_matcher().long_hash.len();
let short_len = driver.dfast_matcher().short_hash.len();
assert_eq!(
long_len,
1 << DFAST_HASH_BITS,
"unhinted Level(2) should keep default long-hash table size"
);
assert_eq!(
short_len,
1 << (DFAST_HASH_BITS - DFAST_SHORT_HASH_BITS_DELTA),
"unhinted Level(2) short-hash should be one bit smaller than long-hash"
);
}
#[cfg(any())] #[test]
fn simple_backend_rejects_undersized_pooled_suffix_store() {
let mut driver = MatchGeneratorDriver::new(128 * 1024, 2);
driver.reset(CompressionLevel::Fastest);
driver.suffix_pool.push(SuffixStore::with_capacity(1024));
let mut space = driver.get_next_space();
space.clear();
space.resize(4096, 0xAB);
driver.commit_space(space);
let last_suffix_slots = driver
.simple()
.window
.last()
.expect("window entry must exist after commit")
.suffixes
.slots
.len();
assert!(
last_suffix_slots >= 4096,
"undersized pooled suffix store must not be reused for larger blocks"
);
}
#[test]
fn source_hint_clamps_driver_slice_size_to_window() {
let mut driver = MatchGeneratorDriver::new(128 * 1024, 2);
driver.set_source_size_hint(1024);
driver.reset(CompressionLevel::Default);
let window = driver.window_size() as usize;
assert_eq!(window, 1 << MIN_HINTED_WINDOW_LOG);
assert_eq!(driver.slice_size, window);
let space = driver.get_next_space();
assert_eq!(space.len(), window);
driver.commit_space(space);
}
#[test]
fn pooled_space_keeps_capacity_when_slice_size_shrinks() {
let mut driver = MatchGeneratorDriver::new(128 * 1024, 2);
driver.reset(CompressionLevel::Default);
let large = driver.get_next_space();
let large_capacity = large.capacity();
assert!(large_capacity >= 128 * 1024);
driver.commit_space(large);
driver.set_source_size_hint(1024);
driver.reset(CompressionLevel::Default);
let small = driver.get_next_space();
assert_eq!(small.len(), 1 << MIN_HINTED_WINDOW_LOG);
assert!(
small.capacity() >= large_capacity,
"pooled buffer capacity should be preserved to avoid shrink/grow churn"
);
}
#[test]
fn driver_best_to_fastest_releases_oversized_hc_tables() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset_on_hc_lazy(CompressionLevel::Best);
assert_eq!(driver.window_size(), (1u64 << 22));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
driver.reset(CompressionLevel::Fastest);
assert_eq!(driver.window_size(), (1u64 << 19));
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Simple);
}
#[test]
fn driver_better_to_best_resizes_hc_tables() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(13));
assert_eq!(driver.window_size(), (1u64 << 22));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"abcabcabcabc");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let hc = driver.hc_matcher();
let better_hash_len = hc.table.hash_table.len();
let better_chain_len = hc.table.chain_table.len();
driver.reset(CompressionLevel::Level(15));
assert_eq!(driver.window_size(), (1u64 << 22));
let mut space = driver.get_next_space();
space[..12].copy_from_slice(b"xyzxyzxyzxyz");
space.truncate(12);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
let hc = driver.hc_matcher();
assert!(
hc.table.hash_table.len() > better_hash_len,
"L15 hash_table ({}) should be larger than L13 ({})",
hc.table.hash_table.len(),
better_hash_len
);
assert!(
hc.table.chain_table.len() > better_chain_len,
"L15 chain_table ({}) should be larger than L13 ({})",
hc.table.chain_table.len(),
better_chain_len
);
}
#[cfg(any())]
#[test]
fn prime_with_dictionary_preserves_history_for_first_full_block() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
driver.prime_with_dictionary(b"abcdefgh", [1, 4, 8]);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"abcdefgh");
driver.commit_space(space);
let mut saw_match = false;
driver.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == 8
&& match_len >= MIN_MATCH_LEN
{
saw_match = true;
}
});
assert!(
saw_match,
"first full block should still match dictionary-primed history"
);
}
#[cfg(any())]
#[test]
fn prime_with_large_dictionary_preserves_early_history_until_first_block() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"abcdefgh");
driver.commit_space(space);
let mut saw_match = false;
driver.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == 24
&& match_len >= MIN_MATCH_LEN
{
saw_match = true;
}
});
assert!(
saw_match,
"dictionary bytes should remain addressable until frame output exceeds the live window"
);
}
#[test]
fn prime_with_dictionary_applies_offset_history_even_when_content_is_empty() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
driver.prime_with_dictionary(&[], [11, 7, 3]);
assert_eq!(driver.simple_mut().offset_hist, [11, 7, 3]);
}
#[test]
fn hc_prime_with_empty_dictionary_disables_btultra2_seed_pass() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset_on_hc_lazy(CompressionLevel::Better);
driver.prime_with_dictionary(&[], [11, 7, 3]);
assert_eq!(driver.hc_matcher().table.offset_hist, [11, 7, 3]);
assert!(
!driver
.hc_matcher()
.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 1),
"btultra2 warmup must stay disabled after dictionary priming, even when dict content is empty"
);
}
#[test]
fn primed_snapshot_not_restored_across_ldm_config_change() {
use super::parameters::CompressionParameters;
let dict = b"abcdefghabcdefghabcdefgh";
let ldm_on = CompressionParameters::builder(CompressionLevel::Level(19))
.enable_long_distance_matching(true)
.build()
.unwrap()
.overrides();
let ldm_off = CompressionParameters::builder(CompressionLevel::Level(19))
.build()
.unwrap()
.overrides();
let mut driver = MatchGeneratorDriver::new(1024, 1);
driver.set_param_overrides(Some(ldm_on));
driver.reset(CompressionLevel::Level(19));
driver.prime_with_dictionary(dict, [1, 4, 8]);
driver.capture_primed_dictionary(CompressionLevel::Level(19));
driver.set_param_overrides(Some(ldm_off));
driver.reset(CompressionLevel::Level(19));
assert!(
!driver.restore_primed_dictionary(CompressionLevel::Level(19)),
"primed snapshot restored across an LDM config change (stale producer)",
);
driver.prime_with_dictionary(dict, [1, 4, 8]);
driver.capture_primed_dictionary(CompressionLevel::Level(19));
driver.reset(CompressionLevel::Level(19));
assert!(
driver.restore_primed_dictionary(CompressionLevel::Level(19)),
"primed snapshot not restored under identical LDM config",
);
}
#[test]
fn hc_prime_with_dictionary_disables_btultra2_seed_pass() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset_on_hc_lazy(CompressionLevel::Better);
driver.prime_with_dictionary(b"abcdefgh", [1, 4, 8]);
assert!(
!driver
.hc_matcher()
.should_run_btultra2_seed_pass::<super::strategy::BtUltra2>(HC_PREDEF_THRESHOLD + 1),
"btultra2 warmup must stay disabled after dictionary priming with content"
);
}
#[test]
fn dfast_prime_with_dictionary_preserves_history_for_first_full_block() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(4));
let payload = b"abcdefghijklmnop";
driver.prime_with_dictionary(payload, [1, 4, 8]);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(payload);
driver.commit_space(space);
let mut saw_match = false;
driver.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == payload.len()
&& match_len >= DFAST_MIN_MATCH_LEN
{
saw_match = true;
}
});
assert!(
saw_match,
"dfast backend should match dictionary-primed history in first full block"
);
}
#[test]
fn prime_with_dictionary_does_not_inflate_reported_window_size() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
let before = driver.window_size();
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
let after = driver.window_size();
assert_eq!(
after, before,
"dictionary retention budget must not change reported frame window size"
);
}
#[test]
fn primed_snapshot_not_restored_when_window_hint_differs() {
let mut driver = MatchGeneratorDriver::new(8, 1);
let level = CompressionLevel::Best;
driver.set_source_size_hint(256 * 1024);
driver.reset(level);
let big_window = driver.window_size();
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
driver.capture_primed_dictionary(level);
driver.set_source_size_hint(48 * 1024);
driver.reset(level);
let small_window = driver.window_size();
assert!(
small_window < big_window,
"precondition: the two hints must resolve to different windows \
(small={small_window}, big={big_window})"
);
let restored = driver.restore_primed_dictionary(level);
assert!(
!restored,
"snapshot captured at window {big_window} must NOT be restored into a \
reset advertising window {small_window} (level alone is an insufficient key)"
);
}
#[test]
fn primed_snapshot_restored_for_hints_in_same_window_bucket() {
let mut driver = MatchGeneratorDriver::new(8, 1);
let level = CompressionLevel::Best;
driver.set_source_size_hint(300 * 1024);
driver.reset(level);
let window_a = driver.window_size();
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
driver.capture_primed_dictionary(level);
driver.set_source_size_hint(400 * 1024);
driver.reset(level);
let window_b = driver.window_size();
assert_eq!(
window_a, window_b,
"precondition: same-bucket hints must resolve to the same window \
(a={window_a}, b={window_b})"
);
let restored = driver.restore_primed_dictionary(level);
assert!(
restored,
"snapshot captured at a 300 KiB hint must be restored into a 400 KiB \
hint that resolves to the identical matcher shape (raw bytes over-key)"
);
}
#[test]
fn primed_snapshot_restored_across_level22_tier_hints() {
let mut driver = MatchGeneratorDriver::new(8, 1);
let level = CompressionLevel::Level(22);
driver.set_source_size_hint(20 * 1024);
driver.reset(level);
let window_a = driver.window_size();
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
driver.capture_primed_dictionary(level);
driver.set_source_size_hint(100 * 1024);
driver.reset(level);
let window_b = driver.window_size();
assert_eq!(
window_a, window_b,
"precondition: both hints must land in the same Level 22 upstream zstd tier \
(a={window_a}, b={window_b})"
);
let restored = driver.restore_primed_dictionary(level);
assert!(
restored,
"Level 22 snapshot captured at a 20 KiB hint must be restored into a \
100 KiB hint that resolves to the same upstream zstd tier (different ceil-log \
buckets, identical matcher shape)"
);
}
#[test]
fn primed_snapshot_not_restored_across_fast_attach_copy_boundary() {
let mut driver = MatchGeneratorDriver::new(8, 1);
let level = CompressionLevel::Level(1);
driver.set_source_size_hint(8193);
driver.reset(level);
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
driver.capture_primed_dictionary(level);
driver.set_source_size_hint(8192);
driver.reset(level);
let restored = driver.restore_primed_dictionary(level);
assert!(
!restored,
"a copy-mode snapshot (8193 B hint) must NOT be restored into an \
attach-mode reset (8192 B hint) that resolves to the same params but a \
different dict-table shape"
);
}
#[test]
fn primed_snapshot_fast_attach_does_not_over_key_non_simple_backends() {
let mut driver = MatchGeneratorDriver::new(8, 1);
let level = CompressionLevel::Level(12);
driver.reset(level);
let window_a = driver.window_size();
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
driver.capture_primed_dictionary(level);
driver.set_source_size_hint(64 * 1024 * 1024);
driver.reset(level);
let window_b = driver.window_size();
assert_eq!(
window_a, window_b,
"precondition: the large hint must resolve to the same window as the \
unhinted level (a={window_a}, b={window_b})"
);
let restored = driver.restore_primed_dictionary(level);
assert!(
restored,
"a Row snapshot must restore across an unhinted vs large-hinted \
reset that resolves to the identical matcher — `fast_attach` is a Fast \
backend concept and must not over-key non-Simple shapes"
);
}
#[cfg(any())] #[test]
fn prime_with_dictionary_does_not_reuse_tiny_suffix_store() {
let mut driver = MatchGeneratorDriver::new(8, 2);
driver.reset(CompressionLevel::Fastest);
driver.prime_with_dictionary(b"abcdefghi", [1, 4, 8]);
assert!(
driver
.simple()
.window
.iter()
.all(|entry| entry.data.len() >= MIN_MATCH_LEN),
"dictionary priming must not commit tails shorter than MIN_MATCH_LEN"
);
}
#[test]
fn prime_with_dictionary_counts_only_committed_tail_budget() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
let before = driver.simple_mut().max_window_size;
driver.prime_with_dictionary(b"abcdefghi", [1, 4, 8]);
assert_eq!(
driver.simple_mut().max_window_size,
before + 8,
"retention budget must account only for dictionary bytes actually committed to history"
);
}
#[test]
fn dfast_prime_with_dictionary_counts_four_byte_tail_budget() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(3));
let before = driver.dfast_matcher().max_window_size;
driver.prime_with_dictionary(b"abcdefghijkl", [1, 4, 8]);
assert_eq!(
driver.dfast_matcher().max_window_size,
before + 12,
"dfast retention budget should include 4-byte dictionary tails"
);
}
#[test]
fn row_prime_with_dictionary_preserves_history_for_first_full_block() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
let payload = b"abcdefghijklmnop";
driver.prime_with_dictionary(payload, [1, 4, 8]);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(payload);
driver.commit_space(space);
let mut saw_match = false;
driver.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == payload.len()
&& match_len >= ROW_MIN_MATCH_LEN
{
saw_match = true;
}
});
assert!(
saw_match,
"row backend should match dictionary-primed history in first full block"
);
}
#[test]
fn row_prime_with_dictionary_subtracts_uncommitted_tail_budget() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
let base_window = driver.row_matcher().max_window_size;
driver.prime_with_dictionary(b"abcdefghi", [1, 4, 8]);
assert_eq!(
driver.row_matcher().max_window_size,
base_window + 8,
"row retained window must exclude uncommitted 1-byte tail"
);
}
#[test]
fn prime_with_dictionary_budget_shrinks_after_row_eviction() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
driver.row_matcher_mut().max_window_size = 8;
driver.reported_window_size = 8;
let base_window = driver.row_matcher().max_window_size;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
assert_eq!(driver.row_matcher().max_window_size, base_window + 24);
for block in [b"AAAAAAAA", b"BBBBBBBB"] {
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(block);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
}
assert_eq!(
driver.dictionary_retained_budget, 0,
"dictionary budget should be fully retired once primed dict slices are evicted"
);
assert_eq!(
driver.row_matcher().max_window_size,
base_window,
"retired dictionary budget must not remain reusable for live history"
);
}
#[test]
fn row_get_last_space_then_reset_to_fastest_drops_row_variant() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Row);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"row-data");
driver.commit_space(space);
assert_eq!(driver.get_last_space(), b"row-data");
driver.reset(CompressionLevel::Fastest);
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Simple);
}
#[test]
fn driver_row_commit_recycles_block_buffer_into_pool() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
assert_eq!(driver.active_backend(), super::strategy::BackendTag::Row);
let before_pool = driver.vec_pool.len();
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"row-data-to-recycle");
driver.commit_space(space);
assert!(
driver.vec_pool.len() > before_pool,
"row commit must recycle the committed block buffer into vec_pool \
(before_pool = {before_pool}, after = {})",
driver.vec_pool.len()
);
assert_eq!(driver.get_last_space(), b"row-data-to-recycle");
}
#[test]
fn adjust_params_for_zero_source_size_uses_min_hinted_window_floor() {
let mut params = resolve_level_params(CompressionLevel::Level(4), None);
params.window_log = 22;
let adjusted = adjust_params_for_source_size(params, 0);
assert_eq!(adjusted.window_log, MIN_HINTED_WINDOW_LOG);
}
#[test]
fn common_prefix_len_matches_scalar_reference_across_offsets() {
fn scalar_reference(a: &[u8], b: &[u8]) -> usize {
a.iter()
.zip(b.iter())
.take_while(|(lhs, rhs)| lhs == rhs)
.count()
}
for total_len in [
0usize, 1, 5, 15, 16, 17, 31, 32, 33, 64, 65, 127, 191, 257, 320,
] {
let base: Vec<u8> = (0..total_len)
.map(|i| ((i * 13 + 7) & 0xFF) as u8)
.collect();
for start in [0usize, 1, 3] {
if start > total_len {
continue;
}
let a = &base[start..];
let b = a.to_vec();
assert_eq!(
common_prefix_len(a, &b),
scalar_reference(a, &b),
"equal slices total_len={total_len} start={start}"
);
let len = a.len();
for mismatch in [0usize, 1, 7, 15, 16, 31, 32, 47, 63, 95, 127, 128, 129, 191] {
if mismatch >= len {
continue;
}
let mut altered = b.clone();
altered[mismatch] ^= 0x5A;
assert_eq!(
common_prefix_len(a, &altered),
scalar_reference(a, &altered),
"total_len={total_len} start={start} mismatch={mismatch}"
);
}
if len > 0 {
let mismatch = len - 1;
let mut altered = b.clone();
altered[mismatch] ^= 0xA5;
assert_eq!(
common_prefix_len(a, &altered),
scalar_reference(a, &altered),
"tail mismatch total_len={total_len} start={start} mismatch={mismatch}"
);
}
}
}
let long = alloc::vec![0xAB; 320];
let shorter = alloc::vec![0xAB; 137];
assert_eq!(
common_prefix_len(&long, &shorter),
scalar_reference(&long, &shorter)
);
}
#[test]
fn row_pick_lazy_returns_none_when_next_is_better() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.add_data(alloc::vec![b'a'; 64], |_| {});
matcher.ensure_tables();
let abs_pos = matcher.history_abs_start + 16;
let best = MatchCandidate {
start: abs_pos,
offset: 8,
match_len: ROW_MIN_MATCH_LEN,
};
assert!(
matcher.pick_lazy_match(abs_pos, 0, Some(best)).is_none(),
"lazy picker should defer when next position is clearly better"
);
}
#[test]
fn row_pick_lazy_depth2_returns_none_when_next2_significantly_better() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.lazy_depth = 2;
matcher.search_depth = 0;
matcher.offset_hist = [6, 9, 1];
let mut data = alloc::vec![b'x'; 40];
data[11..30].copy_from_slice(b"EFABCABCAEFABCAEFAB");
matcher.add_data(data, |_| {});
matcher.ensure_tables();
let abs_pos = matcher.history_abs_start + 20;
let best = matcher
.best_match(abs_pos, 0)
.expect("expected baseline repcode match");
assert_eq!(best.offset, 9);
assert_eq!(best.match_len, 6);
if let Some(next) = matcher.best_match(abs_pos + 1, 1) {
assert!(next.match_len <= best.match_len);
}
let next2 = matcher
.best_match(abs_pos + 2, 2)
.expect("expected +2 candidate");
assert!(
next2.match_len > best.match_len + 1,
"+2 candidate must be significantly better for depth-2 lazy skip"
);
assert!(
matcher.pick_lazy_match(abs_pos, 0, Some(best)).is_none(),
"lazy picker should defer when +2 candidate is significantly better"
);
}
#[test]
fn row_pick_lazy_depth2_keeps_best_when_next2_is_only_one_byte_better() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.lazy_depth = 2;
matcher.search_depth = 0;
matcher.offset_hist = [6, 9, 1];
let mut data = alloc::vec![b'x'; 40];
data[11..30].copy_from_slice(b"EFABCABCAEFABCAEFAZ");
matcher.add_data(data, |_| {});
matcher.ensure_tables();
let abs_pos = matcher.history_abs_start + 20;
let best = matcher
.best_match(abs_pos, 0)
.expect("expected baseline repcode match");
assert_eq!(best.offset, 9);
assert_eq!(best.match_len, 6);
let next2 = matcher
.best_match(abs_pos + 2, 2)
.expect("expected +2 candidate");
assert_eq!(next2.match_len, best.match_len + 1);
let chosen = matcher
.pick_lazy_match(abs_pos, 0, Some(best))
.expect("lazy picker should keep current best");
assert_eq!(chosen.start, best.start);
assert_eq!(chosen.offset, best.offset);
assert_eq!(chosen.match_len, best.match_len);
}
#[test]
fn row_hash_and_row_extracts_high_bits() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.add_data(
alloc::vec![
0xAA, 0xBB, 0xCC, 0x11, 0x10, 0x20, 0x30, 0x40, 0xAA, 0xBB, 0xCC, 0x22, 0x50, 0x60,
0x70, 0x80,
],
|_| {},
);
matcher.ensure_tables();
let pos = matcher.history_abs_start + 8;
let (row, tag) = matcher
.hash_and_row(pos)
.expect("row hash should be available");
let idx = pos - matcher.history_abs_start;
let concat = matcher.live_history();
let key_len = matcher.mls.min(6);
let value = u64::from_le_bytes(concat[idx..idx + 8].try_into().unwrap())
& ((1u64 << (key_len * 8)) - 1);
let hash = crate::encoding::fastpath::hash_mix_u64_with_kernel(matcher.hash_kernel, value);
let total_bits = matcher.row_hash_log + ROW_TAG_BITS;
let combined = hash >> (u64::BITS as usize - total_bits);
let expected_row =
((combined >> ROW_TAG_BITS) as usize) & ((1usize << matcher.row_hash_log) - 1);
let expected_tag = combined as u8;
assert_eq!(row, expected_row);
assert_eq!(tag, expected_tag);
}
#[test]
fn row_repcode_skips_candidate_before_history_start() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.history = alloc::vec![b'a'; 20];
matcher.history_start = 0;
matcher.history_abs_start = 10;
matcher.offset_hist = [3, 0, 0];
assert!(matcher.repcode_candidate(12, 1).is_none());
}
#[test]
fn row_repcode_returns_none_when_position_too_close_to_history_end() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.history = b"abcde".to_vec();
matcher.history_start = 0;
matcher.history_abs_start = 0;
matcher.offset_hist = [1, 0, 0];
assert!(matcher.repcode_candidate(4, 1).is_none());
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
#[test]
fn hash_mix_sse42_path_is_available_and_matches_accelerated_impl_when_supported() {
use crate::encoding::fastpath::{self, FastpathKernel};
if !is_x86_feature_detected!("sse4.2") {
return;
}
let v = 0x0123_4567_89AB_CDEFu64;
let accelerated = unsafe { fastpath::sse42::hash_mix_u64(v) };
let dispatched = fastpath::dispatch_hash_mix_u64(v);
let kernel = fastpath::select_kernel();
if kernel == FastpathKernel::Sse42 {
assert_eq!(dispatched, accelerated);
} else {
assert_eq!(dispatched, accelerated, "AVX2/SSE4.2 share CRC32 mix");
}
}
#[cfg(all(feature = "std", target_arch = "aarch64", target_endian = "little"))]
#[test]
fn hash_mix_crc_path_is_available_and_matches_accelerated_impl_when_supported() {
use crate::encoding::fastpath;
if !is_aarch64_feature_detected!("crc") {
return;
}
let v = 0x0123_4567_89AB_CDEFu64;
let accelerated = unsafe { fastpath::neon::hash_mix_u64(v) };
let dispatched = fastpath::dispatch_hash_mix_u64(v);
assert_eq!(dispatched, accelerated);
}
#[test]
fn hc_hash3_position_matches_hash3_formula() {
let bytes = [b'a', b'b', b'c', b'd'];
let read32 = u32::from_le_bytes(bytes);
let expected = (((read32 << 8).wrapping_mul(HC_PRIME3BYTES)) >> (32 - HC3_HASH_LOG)) as usize;
assert_eq!(
super::match_table::storage::MatchTable::hash3_position(&bytes, HC3_HASH_LOG),
expected
);
}
#[test]
fn hc_hash_position_matches_hash4_formula() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(HC_CONFIG, super::strategy::StrategyTag::Lazy, 22);
let bytes = [b'a', b'b', b'c', b'd'];
let read32 = u32::from_le_bytes(bytes);
let expected = ((read32.wrapping_mul(HC_PRIME4BYTES)) >> (32 - hc.table.hash_log)) as usize;
assert_eq!(hc.table.hash_position(&bytes), expected);
}
#[test]
fn btultra2_main_hash_uses_hash4_formula() {
let mut hc = HcMatchGenerator::new(1 << 20);
hc.configure(
BTULTRA2_HC_CONFIG_L22,
super::strategy::StrategyTag::BtUltra2,
27,
);
let bytes = [b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h'];
let read32 = u32::from_le_bytes(bytes[..4].try_into().unwrap());
let expected = ((read32.wrapping_mul(HC_PRIME4BYTES)) >> (32 - hc.table.hash_log)) as usize;
let actual = super::match_table::storage::MatchTable::hash_position_with_mls(
&bytes,
hc.table.hash_log,
super::bt::BtMatcher::HASH_MLS,
);
assert_eq!(actual, expected);
}
#[test]
fn row_candidate_returns_none_when_abs_pos_near_end_of_history() {
let mut matcher = RowMatchGenerator::new(1 << 22);
matcher.configure(ROW_CONFIG);
matcher.history = alloc::vec![b'a'; ROW_MIN_MATCH_LEN - 1];
matcher.history_start = 0;
matcher.history_abs_start = 0;
assert!(matcher.row_candidate(0, 0).is_none());
}
#[test]
fn hc_chain_candidates_returns_sentinels_for_short_suffix() {
let mut hc = HcMatchGenerator::new(32);
hc.table.history = b"abc".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.ensure_tables();
let candidates = hc.hc.chain_candidates(&hc.table, 0);
assert!(candidates.iter().all(|&pos| pos == usize::MAX));
}
#[test]
fn hc_reset_advances_floor_past_prior_frame_entries() {
use super::match_table::storage::MatchTable;
let mut hc = HcMatchGenerator::new(32);
hc.table.add_data(b"abcdeabcde".to_vec(), |_| {});
hc.table.ensure_tables();
hc.table.insert_positions(0, 6);
let prev_end = hc.table.history_abs_end();
assert_eq!(prev_end, 10);
assert!(hc.table.hash_table.iter().any(|&v| v != HC_EMPTY));
hc.reset(|_| {});
assert_eq!(hc.table.history_abs_start, prev_end);
for &slot in hc.table.hash_table.iter() {
if let Some(candidate_abs) =
MatchTable::stored_abs_position_fast(slot, hc.table.position_base, hc.table.index_shift)
{
assert!(
candidate_abs < hc.table.history_abs_start,
"a prior-frame entry must resolve below the advanced floor"
);
}
}
}
#[test]
fn hc_reset_full_zeroes_when_floor_would_cross_ceiling() {
use super::match_table::storage::REBASE_RESET_FLOOR_CEILING;
let mut hc = HcMatchGenerator::new(32);
hc.table.add_data(b"abcdeabcde".to_vec(), |_| {});
hc.table.ensure_tables();
hc.table.hash_table.fill(123);
hc.table.chain_table.fill(456);
hc.table.history_abs_start = REBASE_RESET_FLOOR_CEILING;
hc.reset(|_| {});
assert_eq!(hc.table.history_abs_start, 0);
assert_eq!(hc.table.position_base, 0);
assert!(hc.table.hash_table.iter().all(|&v| v == HC_EMPTY));
assert!(hc.table.chain_table.iter().all(|&v| v == HC_EMPTY));
}
#[test]
fn hc_start_matching_returns_early_for_empty_current_block() {
let mut hc = HcMatchGenerator::new(32);
hc.table.add_data(Vec::new(), |_| {});
let mut called = false;
hc.start_matching(|_| called = true);
assert!(!called, "empty current block should not emit sequences");
}
#[cfg(test)]
fn deterministic_high_entropy_bytes(seed: u64, len: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(len);
let mut state = seed;
for _ in 0..len {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
out.push((state >> 40) as u8);
}
out
}
#[cfg(feature = "bench_internals")]
pub(crate) fn level22_block_ranges(data: &[u8]) -> Vec<(usize, usize)> {
let mut ranges = Vec::new();
let mut cursor = 0usize;
let mut savings = 0i64;
while cursor < data.len() {
let remaining = data.len() - cursor;
let candidate_len = remaining.min(super::cost_model::HC_BLOCKSIZE_MAX);
let block_len = crate::encoding::frame_compressor::optimal_block_size(
CompressionLevel::Level(22),
&data[cursor..cursor + candidate_len],
remaining,
super::cost_model::HC_BLOCKSIZE_MAX,
savings,
)
.min(candidate_len)
.max(1);
ranges.push((cursor, block_len));
cursor += block_len;
if cursor >= super::cost_model::HC_BLOCKSIZE_MAX {
savings = 3;
}
}
ranges
}
#[cfg(feature = "bench_internals")]
fn merge_block_delimiters(sequences: Vec<(usize, usize, usize)>) -> Vec<(usize, usize, usize)> {
let mut out = Vec::with_capacity(sequences.len());
let mut pending_lits = 0usize;
for (lit_len, offset, match_len) in sequences {
if offset == 0 && match_len == 0 {
pending_lits = pending_lits.saturating_add(lit_len);
continue;
}
out.push((lit_len.saturating_add(pending_lits), offset, match_len));
pending_lits = 0;
}
if pending_lits > 0 {
out.push((pending_lits, 0, 0));
}
out
}
#[cfg(feature = "bench_internals")]
pub(crate) fn collect_level22_sequences(data: &[u8]) -> Vec<(usize, usize, usize)> {
merge_block_delimiters(collect_level22_sequences_with_delimiters(data))
.into_iter()
.filter(|(_, offset, match_len)| *offset != 0 || *match_len != 0)
.collect()
}
#[cfg(feature = "bench_internals")]
fn collect_level22_sequences_with_delimiters(data: &[u8]) -> Vec<(usize, usize, usize)> {
let mut driver = MatchGeneratorDriver::new(super::cost_model::HC_BLOCKSIZE_MAX, 1);
driver.set_source_size_hint(data.len() as u64);
driver.reset(CompressionLevel::Level(22));
let mut sequences = Vec::new();
for (chunk_start, chunk_len) in level22_block_ranges(data) {
let chunk = &data[chunk_start..chunk_start + chunk_len];
let mut space = driver.get_next_space();
space[..chunk.len()].copy_from_slice(chunk);
space.truncate(chunk.len());
driver.commit_space(space);
driver.start_matching(|seq| {
let entry = match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
};
sequences.push(entry);
});
}
sequences
}
#[test]
fn hc_sparse_skip_matching_preserves_tail_cross_block_match() {
let mut matcher = HcMatchGenerator::new(1 << 22);
let tail = b"Qz9kLm2Rp";
let mut first = deterministic_high_entropy_bytes(0xD1B5_4A32_9C77_0E19, 4096);
let tail_start = first.len() - tail.len();
first[tail_start..].copy_from_slice(tail);
matcher.table.add_data(first.clone(), |_| {});
matcher.skip_matching(Some(true));
let mut second = tail.to_vec();
second.extend_from_slice(b"after-tail-literals");
matcher.table.add_data(second, |_| {});
let mut first_sequence = None;
matcher.start_matching(|seq| {
if first_sequence.is_some() {
return;
}
first_sequence = Some(match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
});
});
let (literals_len, offset, match_len) =
first_sequence.expect("expected at least one sequence after sparse skip");
assert_eq!(
literals_len, 0,
"first sequence should start at block boundary"
);
assert_eq!(
offset,
tail.len(),
"first match should reference previous tail"
);
assert!(
match_len >= tail.len(),
"tail-aligned cross-block match must be preserved"
);
}
#[test]
fn btultra2_sparse_skip_matching_preserves_tail_cross_block_match() {
let mut matcher = HcMatchGenerator::new(1 << 20);
matcher.configure(
BTULTRA2_HC_CONFIG_L22,
super::strategy::StrategyTag::BtUltra2,
20,
);
let tail = b"Bt9kLm2Rp";
let mut first = deterministic_high_entropy_bytes(0xA9C3_7F21_D4E8_510B, 4096);
let tail_start = first.len() - tail.len();
first[tail_start..].copy_from_slice(tail);
matcher.table.add_data(first, |_| {});
matcher.skip_matching(Some(true));
let mut second = tail.to_vec();
second.extend_from_slice(b"after-tail-literals");
matcher.table.add_data(second, |_| {});
let mut first_sequence = None;
matcher.start_matching(|seq| {
if first_sequence.is_some() {
return;
}
first_sequence = Some(match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
});
});
let (literals_len, offset, match_len) =
first_sequence.expect("expected at least one sequence after sparse BT skip");
assert_eq!(
literals_len, 0,
"BT sparse skip should preserve an immediate boundary match"
);
assert_eq!(
offset,
tail.len(),
"first BT match should reference previous tail"
);
assert!(
match_len >= tail.len(),
"BT sparse skip must seed the dense tail for cross-block matching"
);
}
#[test]
fn hc_sparse_skip_matching_does_not_reinsert_sparse_tail_positions() {
let mut matcher = HcMatchGenerator::new(1 << 22);
let first = deterministic_high_entropy_bytes(0xC2B2_AE3D_27D4_EB4F, 4096);
matcher.table.add_data(first.clone(), |_| {});
matcher.skip_matching(Some(true));
let current_len = first.len();
let current_abs_start =
matcher.table.history_abs_start + matcher.table.window_size - current_len;
let current_abs_end = current_abs_start + current_len;
let dense_tail = HC_MIN_MATCH_LEN + INCOMPRESSIBLE_SKIP_STEP;
let tail_start = current_abs_end
.saturating_sub(dense_tail)
.max(matcher.table.history_abs_start)
.max(current_abs_start);
let overlap_pos = (tail_start..current_abs_end)
.find(|&pos| (pos - current_abs_start).is_multiple_of(INCOMPRESSIBLE_SKIP_STEP))
.expect("fixture should contain at least one sparse-grid overlap in dense tail");
let rel = matcher
.table
.relative_position(overlap_pos)
.expect("overlap position should be representable as relative position");
let chain_idx = rel as usize & ((1 << matcher.table.chain_log) - 1);
assert_ne!(
matcher.table.chain_table[chain_idx],
rel + 1,
"sparse-grid tail positions must not be reinserted (self-loop chain entry)"
);
}
#[test]
fn hc_compact_history_drains_when_threshold_crossed() {
let mut hc = HcMatchGenerator::new(8);
hc.table.history = b"abcdefghijklmnopqrstuvwxyz".to_vec();
hc.table.history_start = 16;
hc.table.compact_history();
assert_eq!(hc.table.history_start, 0);
assert_eq!(hc.table.history, b"qrstuvwxyz");
}
#[test]
fn hc_insert_position_no_rebase_returns_when_relative_pos_unavailable() {
let mut hc = HcMatchGenerator::new(32);
hc.table.history = b"abcdefghijklmnop".to_vec();
hc.table.history_abs_start = 0;
hc.table.position_base = 1;
hc.table.ensure_tables();
let before_hash = hc.table.hash_table.clone();
let before_chain = hc.table.chain_table.clone();
hc.table.insert_position_no_rebase(0);
assert_eq!(hc.table.hash_table, before_hash);
assert_eq!(hc.table.chain_table, before_chain);
}
#[test]
fn hc_insert_positions_advances_next_to_update3_for_contiguous_range() {
let mut hc = HcMatchGenerator::new(64);
hc.table.history = b"abcdefghijklmnopqrstuvwxyz".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.table.ensure_tables();
hc.table.next_to_update3 = 0;
hc.table.insert_positions(0, 9);
assert_eq!(
hc.table.next_to_update3, 9,
"contiguous insert_positions should advance hash3 update cursor"
);
}
#[test]
fn hc_insert_positions_with_step_keeps_next_to_update3_cursor_for_sparse_ranges() {
let mut hc = HcMatchGenerator::new(64);
hc.table.history = b"abcdefghijklmnopqrstuvwxyz".to_vec();
hc.table.history_start = 0;
hc.table.history_abs_start = 0;
hc.table.position_base = 0;
hc.table.ensure_tables();
hc.table.next_to_update3 = 0;
hc.table.insert_positions_with_step(0, 16, 4);
assert_eq!(
hc.table.next_to_update3, 0,
"sparse insert_positions_with_step must not mark skipped positions as hash3-updated"
);
}
#[cfg(any())]
#[test]
fn prime_with_dictionary_budget_shrinks_after_simple_eviction() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Fastest);
driver.simple_mut().max_window_size = 8;
driver.reported_window_size = 8;
let base_window = driver.simple_mut().max_window_size;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
assert_eq!(driver.simple_mut().max_window_size, base_window + 24);
for block in [b"AAAAAAAA", b"BBBBBBBB"] {
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(block);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
}
assert_eq!(
driver.dictionary_retained_budget, 0,
"dictionary budget should be fully retired once primed dict slices are evicted"
);
assert_eq!(
driver.simple_mut().max_window_size,
base_window,
"retired dictionary budget must not remain reusable for live history"
);
}
#[test]
fn prime_with_dictionary_budget_shrinks_after_dfast_eviction() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(3));
driver.dfast_matcher_mut().max_window_size = 8;
driver.reported_window_size = 8;
let base_window = driver.dfast_matcher().max_window_size;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
assert_eq!(driver.dfast_matcher().max_window_size, base_window + 24);
for block in [b"AAAAAAAA", b"BBBBBBBB"] {
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(block);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
}
assert_eq!(
driver.dictionary_retained_budget, 0,
"dictionary budget should be fully retired once primed dict slices are evicted"
);
assert_eq!(
driver.dfast_matcher().max_window_size,
base_window,
"retired dictionary budget must not remain reusable for live history"
);
}
#[test]
fn hc_prime_with_dictionary_preserves_history_for_first_full_block() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset_on_hc_lazy(CompressionLevel::Better);
driver.prime_with_dictionary(b"abcdefgh", [1, 4, 8]);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"abcdefgh");
driver.commit_space(space);
let mut saw_match = false;
driver.start_matching(|seq| {
if let Sequence::Triple {
literals,
offset,
match_len,
} = seq
&& literals.is_empty()
&& offset == 8
&& match_len >= HC_MIN_MATCH_LEN
{
saw_match = true;
}
});
assert!(
saw_match,
"hash-chain backend should match dictionary-primed history in first full block"
);
}
#[test]
fn prime_with_dictionary_budget_shrinks_after_hc_eviction() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset_on_hc_lazy(CompressionLevel::Better);
driver.hc_matcher_mut().table.max_window_size = 8;
driver.reported_window_size = 8;
let base_window = driver.hc_matcher().table.max_window_size;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
assert_eq!(driver.hc_matcher().table.max_window_size, base_window + 24);
for block in [b"AAAAAAAA", b"BBBBBBBB"] {
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(block);
driver.commit_space(space);
driver.skip_matching_with_hint(None);
}
assert_eq!(
driver.dictionary_retained_budget, 0,
"dictionary budget should be fully retired once primed dict slices are evicted"
);
assert_eq!(
driver.hc_matcher().table.max_window_size,
base_window,
"retired dictionary budget must not remain reusable for live history"
);
}
#[test]
fn hc_commit_without_eviction_retires_no_dictionary_budget() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset_on_hc_lazy(CompressionLevel::Better);
driver.hc_matcher_mut().table.max_window_size = 1 << 20;
driver.reported_window_size = 1 << 20;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
let budget_after_prime = driver.dictionary_retained_budget;
assert!(
budget_after_prime > 0,
"priming must retain a non-zero dictionary budget"
);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"AAAAAAAA");
driver.commit_space(space);
driver.skip_matching_with_hint(None);
assert_eq!(
driver.dictionary_retained_budget, budget_after_prime,
"a commit that evicts nothing must retire no dictionary budget"
);
}
#[test]
fn row_commit_without_eviction_retires_no_dictionary_budget() {
let mut driver = MatchGeneratorDriver::new(8, 1);
driver.reset(CompressionLevel::Level(5));
assert!(matches!(driver.storage, MatcherStorage::Row(_)));
driver.row_matcher_mut().max_window_size = 1 << 20;
driver.reported_window_size = 1 << 20;
driver.prime_with_dictionary(b"abcdefghABCDEFGHijklmnop", [1, 4, 8]);
let budget_after_prime = driver.dictionary_retained_budget;
assert!(
budget_after_prime > 0,
"priming must retain a non-zero dictionary budget"
);
let mut space = driver.get_next_space();
space.clear();
space.extend_from_slice(b"AAAAAAAA");
driver.commit_space(space);
driver.skip_matching_with_hint(None);
assert_eq!(
driver.dictionary_retained_budget, budget_after_prime,
"a Row commit that evicts nothing must retire no dictionary budget"
);
}
#[test]
fn hc_rebases_positions_after_u32_boundary() {
let mut matcher = HcMatchGenerator::new(64);
matcher.table.add_data(b"abcdeabcdeabcde".to_vec(), |_| {});
matcher.table.ensure_tables();
matcher.table.position_base = 0;
let history_abs_start: usize = match (u64::from(u32::MAX) + 64).try_into() {
Ok(value) => value,
Err(_) => return,
};
matcher.table.history_abs_start = history_abs_start;
matcher.skip_matching(None);
assert_eq!(
matcher.table.position_base, matcher.table.history_abs_start,
"rebase should anchor to the oldest live absolute position"
);
assert!(
matcher
.table
.hash_table
.iter()
.any(|entry| *entry != HC_EMPTY),
"HC hash table should still be populated after crossing u32 boundary"
);
let abs_pos = matcher.table.history_abs_start + 10;
let candidates = matcher.hc.chain_candidates(&matcher.table, abs_pos);
assert!(
candidates.iter().any(|candidate| *candidate != usize::MAX),
"chain_candidates should return valid matches after rebase"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn row_rebases_positions_after_u32_boundary() {
let mut m = RowMatchGenerator::new(64);
m.add_data(b"abcdeabcdeabcde".to_vec(), |_| {});
let near_ceiling = (u32::MAX as usize) - 16;
m.history_abs_start = near_ceiling;
m.add_data(b"fghij".to_vec(), |_| {});
assert!(
m.history_abs_start < near_ceiling,
"add_data must rebase the absolute origin down when the cursor nears \
u32::MAX (got {})",
m.history_abs_start
);
assert!(
(m.history_abs_start + m.window_size) < u32::MAX as usize,
"after rebase the live window must fit below the u32 position ceiling"
);
}
#[test]
fn hc_rebase_rebuilds_only_inserted_prefix() {
let mut matcher = HcMatchGenerator::new(64);
matcher.table.add_data(b"abcdeabcdeabcde".to_vec(), |_| {});
matcher.table.ensure_tables();
matcher.table.position_base = 0;
let history_abs_start: usize = match (u64::from(u32::MAX) + 64).try_into() {
Ok(value) => value,
Err(_) => return,
};
matcher.table.history_abs_start = history_abs_start;
let abs_pos = matcher.table.history_abs_start + 6;
let mut expected = HcMatchGenerator::new(64);
expected.table.add_data(b"abcdeabcdeabcde".to_vec(), |_| {});
expected.table.ensure_tables();
expected.table.history_abs_start = history_abs_start;
expected.table.position_base = expected.table.history_abs_start;
expected.table.hash_table.fill(HC_EMPTY);
expected.table.chain_table.fill(HC_EMPTY);
for pos in expected.table.history_abs_start..abs_pos {
expected.table.insert_position_no_rebase(pos);
}
matcher.table.maybe_rebase_positions(abs_pos);
assert_eq!(
matcher.table.position_base, matcher.table.history_abs_start,
"rebase should still anchor to the oldest live absolute position"
);
assert_eq!(
matcher.table.hash_table, expected.table.hash_table,
"rebase must rebuild only positions already inserted before abs_pos"
);
assert_eq!(
matcher.table.chain_table, expected.table.chain_table,
"future positions must not be pre-seeded into HC chains during rebase"
);
}
#[cfg(any())] #[test]
fn suffix_store_with_single_slot_does_not_panic_on_keying() {
let mut suffixes = SuffixStore::with_capacity(1);
suffixes.insert(b"abcde", 0);
assert!(suffixes.contains_key(b"abcde"));
assert_eq!(suffixes.get(b"abcde"), Some(0));
}
#[cfg(any())]
#[test]
fn fastest_reset_uses_interleaved_hash_fill_step() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Uncompressed);
assert_eq!(driver.simple().hash_fill_step, 1);
driver.reset(CompressionLevel::Fastest);
assert_eq!(driver.simple().hash_fill_step, FAST_HASH_FILL_STEP);
driver.reset(CompressionLevel::Better);
assert_eq!(
driver.active_backend(),
super::strategy::BackendTag::HashChain
);
assert_eq!(driver.window_size(), (1u64 << 23));
assert_eq!(driver.hc_matcher().hc.lazy_depth, 2);
}
#[cfg(any())] #[test]
fn simple_matcher_updates_offset_history_after_emitting_match() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdeabcdeabcde".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
assert!(matcher.next_sequence(|seq| {
assert_eq!(
seq,
Sequence::Triple {
literals: b"abcde",
offset: 5,
match_len: 10,
}
);
}));
assert_eq!(matcher.offset_hist, [5, 1, 4]);
}
#[cfg(any())] #[test]
fn simple_matcher_zero_literal_repcode_checks_rep1_before_hash_lookup() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdefghijabcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.suffix_idx = 10;
matcher.last_idx_in_sequence = 10;
matcher.offset_hist = [99, 10, 4];
let candidate = matcher.repcode_candidate(&matcher.window.last().unwrap().data[10..], 0);
assert_eq!(candidate, Some((10, 10)));
}
#[cfg(any())] #[test]
fn simple_matcher_repcode_can_target_previous_window_entry() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.skip_matching();
matcher.add_data(
b"abcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.offset_hist = [99, 10, 4];
let candidate = matcher.repcode_candidate(&matcher.window.last().unwrap().data, 0);
assert_eq!(candidate, Some((10, 10)));
}
#[cfg(any())] #[test]
fn simple_matcher_zero_literal_repcode_checks_rep2() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdefghijabcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.suffix_idx = 10;
matcher.last_idx_in_sequence = 10;
matcher.offset_hist = [99, 4, 10];
let candidate = matcher.repcode_candidate(&matcher.window.last().unwrap().data[10..], 0);
assert_eq!(candidate, Some((10, 10)));
}
#[cfg(any())] #[test]
fn simple_matcher_zero_literal_repcode_checks_rep0_minus1() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdefghijabcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.suffix_idx = 10;
matcher.last_idx_in_sequence = 10;
matcher.offset_hist = [11, 4, 99];
let candidate = matcher.repcode_candidate(&matcher.window.last().unwrap().data[10..], 0);
assert_eq!(candidate, Some((10, 10)));
}
#[cfg(any())] #[test]
fn simple_matcher_repcode_rejects_offsets_beyond_searchable_prefix() {
let mut matcher = MatchGenerator::new(64);
matcher.add_data(
b"abcdefghij".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.skip_matching();
matcher.add_data(
b"klmnopqrst".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.suffix_idx = 3;
let candidate = matcher.offset_match_len(14, &matcher.window.last().unwrap().data[3..]);
assert_eq!(candidate, None);
}
#[cfg(any())] #[test]
fn simple_matcher_skip_matching_seeds_every_position_even_with_fast_step() {
let mut matcher = MatchGenerator::new(64);
matcher.hash_fill_step = FAST_HASH_FILL_STEP;
matcher.add_data(
b"abcdefghijklmnop".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.skip_matching();
matcher.add_data(b"bcdef".to_vec(), SuffixStore::with_capacity(64), |_, _| {});
assert!(matcher.next_sequence(|seq| {
assert_eq!(
seq,
Sequence::Triple {
literals: b"",
offset: 15,
match_len: 5,
}
);
}));
assert!(!matcher.next_sequence(|_| {}));
}
#[cfg(any())] #[test]
fn simple_matcher_skip_matching_with_incompressible_hint_uses_sparse_prefix() {
let mut matcher = MatchGenerator::new(128);
let first = b"abcdefghijklmnopqrstuvwxyz012345".to_vec();
let sparse_probe = first[3..3 + MIN_MATCH_LEN].to_vec();
let tail_start = first.len() - MIN_MATCH_LEN;
let tail_probe = first[tail_start..tail_start + MIN_MATCH_LEN].to_vec();
matcher.add_data(first, SuffixStore::with_capacity(256), |_, _| {});
matcher.skip_matching_with_hint(Some(true));
matcher.add_data(sparse_probe, SuffixStore::with_capacity(256), |_, _| {});
let mut sparse_first_is_literals = None;
assert!(matcher.next_sequence(|seq| {
if sparse_first_is_literals.is_none() {
sparse_first_is_literals = Some(matches!(seq, Sequence::Literals { .. }));
}
}));
assert!(
sparse_first_is_literals.unwrap_or(false),
"sparse-start probe should not produce an immediate match"
);
let mut matcher = MatchGenerator::new(128);
matcher.add_data(
b"abcdefghijklmnopqrstuvwxyz012345".to_vec(),
SuffixStore::with_capacity(256),
|_, _| {},
);
matcher.skip_matching_with_hint(Some(true));
matcher.add_data(tail_probe, SuffixStore::with_capacity(256), |_, _| {});
let mut tail_first_is_immediate_match = None;
assert!(matcher.next_sequence(|seq| {
if tail_first_is_immediate_match.is_none() {
tail_first_is_immediate_match =
Some(matches!(seq, Sequence::Triple { literals, .. } if literals.is_empty()));
}
}));
assert!(
tail_first_is_immediate_match.unwrap_or(false),
"dense tail probe should match immediately at block start"
);
}
#[cfg(any())] #[test]
fn simple_matcher_add_suffixes_till_backfills_last_searchable_anchor() {
let mut matcher = MatchGenerator::new(64);
matcher.hash_fill_step = FAST_HASH_FILL_STEP;
matcher.add_data(
b"01234abcde".to_vec(),
SuffixStore::with_capacity(64),
|_, _| {},
);
matcher.add_suffixes_till(10, FAST_HASH_FILL_STEP);
let last = matcher.window.last().unwrap();
let tail = &last.data[5..10];
assert_eq!(last.suffixes.get(tail), Some(5));
}
#[cfg(any())] #[test]
fn simple_matcher_add_suffixes_till_skips_when_idx_below_min_match_len() {
let mut matcher = MatchGenerator::new(128);
matcher.hash_fill_step = FAST_HASH_FILL_STEP;
matcher.add_data(
b"abcdefghijklmnopqrstuvwxyz".to_vec(),
SuffixStore::with_capacity(1 << 16),
|_, _| {},
);
matcher.add_suffixes_till(MIN_MATCH_LEN - 1, FAST_HASH_FILL_STEP);
let last = matcher.window.last().unwrap();
let first_key = &last.data[..MIN_MATCH_LEN];
assert_eq!(last.suffixes.get(first_key), None);
}
#[cfg(any())] #[test]
fn simple_matcher_add_suffixes_till_fast_step_registers_interleaved_positions() {
let mut matcher = MatchGenerator::new(128);
matcher.hash_fill_step = FAST_HASH_FILL_STEP;
matcher.add_data(
b"abcdefghijklmnopqrstuvwxyz".to_vec(),
SuffixStore::with_capacity(1 << 16),
|_, _| {},
);
matcher.add_suffixes_till(17, FAST_HASH_FILL_STEP);
let last = matcher.window.last().unwrap();
for pos in [0usize, 3, 6, 9, 12] {
let key = &last.data[pos..pos + MIN_MATCH_LEN];
assert_eq!(
last.suffixes.get(key),
Some(pos),
"expected interleaved suffix registration at pos {pos}"
);
}
}
#[test]
fn dfast_skip_matching_handles_window_eviction() {
let mut matcher = DfastMatchGenerator::new(16);
matcher.add_data(alloc::vec![1, 2, 3, 4, 5, 6], |_| {});
matcher.skip_matching(None);
matcher.add_data(alloc::vec![7, 8, 9, 10, 11, 12], |_| {});
matcher.skip_matching(None);
matcher.add_data(alloc::vec![7, 8, 9, 10, 11, 12], |_| {});
let mut reconstructed = alloc::vec![7, 8, 9, 10, 11, 12];
matcher.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
assert_eq!(reconstructed, [7, 8, 9, 10, 11, 12, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn dfast_add_data_callback_reports_evicted_len_not_capacity() {
let mut matcher = DfastMatchGenerator::new(8);
let mut first = Vec::with_capacity(64);
first.extend_from_slice(b"abcdefgh");
matcher.add_data(first, |_| {});
let mut second = Vec::with_capacity(64);
second.extend_from_slice(b"ijklmnop");
let mut observed_evicted_len = None;
matcher.add_data(second, |data| {
observed_evicted_len = Some(data.len());
});
assert_eq!(
observed_evicted_len,
Some(8),
"eviction callback must report evicted byte length, not backing capacity"
);
}
#[test]
fn dfast_commit_space_eviction_uses_window_size_delta() {
use crate::encoding::CompressionLevel;
let mut driver = MatchGeneratorDriver::new(10, 1);
driver.reset(CompressionLevel::Level(3));
assert!(matches!(driver.storage, MatcherStorage::Dfast(_)));
driver.dfast_matcher_mut().max_window_size = 10;
driver.dictionary_retained_budget = 100;
let mut space1 = Vec::with_capacity(64);
space1.extend_from_slice(b"abcd");
driver.commit_space(space1);
assert_eq!(
driver.dictionary_retained_budget, 100,
"1st commit fills window 0 → 4, no eviction, no retire"
);
let mut space2 = Vec::with_capacity(64);
space2.extend_from_slice(b"efgh");
driver.commit_space(space2);
assert_eq!(
driver.dictionary_retained_budget, 100,
"2nd commit fills window 4 → 8, no eviction, no retire"
);
let mut space3 = Vec::with_capacity(64);
space3.extend_from_slice(b"ijklm");
driver.commit_space(space3);
assert_eq!(
driver.dictionary_retained_budget, 87,
"3rd commit + trim_after_budget_retire cascade. With the fix \
(evicted=4 from window_size delta) the cascade reclaims 100 \
→ 96 → 92 → 87. With the bug (evicted=5 from data.len()) the \
3rd commit would panic on `data.len() <= max_window_size` \
after the 2nd commit's cascade had already shrunk \
max_window_size to 0."
);
assert_eq!(
driver.dfast_matcher_mut().max_window_size,
0,
"cascade drains max_window_size to 0 once budget reclaim \
exceeds the initial window size"
);
}
#[test]
fn dfast_trim_to_window_evicts_oldest_block_by_length() {
let mut matcher = DfastMatchGenerator::new(16);
let mut first = Vec::with_capacity(64);
first.extend_from_slice(b"abcdefgh");
matcher.add_data(first, |_| {});
let mut second = Vec::with_capacity(64);
second.extend_from_slice(b"ijklmnop");
matcher.add_data(second, |_| {});
assert_eq!(matcher.window_size, 16);
assert_eq!(matcher.window_blocks.len(), 2);
matcher.max_window_size = 8;
matcher.trim_to_window();
assert_eq!(
matcher.window_size, 8,
"exactly one 8-byte block must remain"
);
assert_eq!(matcher.window_blocks.len(), 1);
assert_eq!(matcher.history_abs_start, 8);
}
#[test]
fn dfast_inserts_tail_positions_for_next_block_matching() {
let mut matcher = DfastMatchGenerator::new(1 << 22);
matcher.add_data(b"012345bcdea".to_vec(), |_| {});
let mut history = Vec::new();
matcher.start_matching(|seq| match seq {
Sequence::Literals { literals } => history.extend_from_slice(literals),
Sequence::Triple { .. } => unreachable!("first block should not match history"),
});
assert_eq!(history, b"012345bcdea");
matcher.add_data(b"bcdeabcdeab".to_vec(), |_| {});
let mut saw_first_sequence = false;
matcher.start_matching(|seq| {
assert!(!saw_first_sequence, "expected a single cross-block match");
saw_first_sequence = true;
match seq {
Sequence::Literals { .. } => {
panic!("expected tail-anchored cross-block match before any literals")
}
Sequence::Triple {
literals,
offset,
match_len,
} => {
assert_eq!(literals, b"");
assert_eq!(offset, 5);
assert_eq!(match_len, 11);
let start = history.len() - offset;
for i in 0..match_len {
let byte = history[start + i];
history.push(byte);
}
}
}
});
assert!(
saw_first_sequence,
"expected tail-anchored cross-block match"
);
assert_eq!(history, b"012345bcdeabcdeabcdeab");
}
#[test]
fn hashchain_inserts_tail_positions_for_next_block_matching() {
let mut matcher = HcMatchGenerator::new(1 << 22);
matcher.configure(HC_CONFIG, super::strategy::StrategyTag::Lazy, 22);
matcher.table.add_data(b"PQRSTBCD".to_vec(), |_| {});
let mut history = alloc::vec::Vec::new();
matcher.start_matching(|seq| match seq {
Sequence::Literals { literals } => history.extend_from_slice(literals),
Sequence::Triple { .. } => unreachable!("first block has no internal repeats"),
});
assert_eq!(history, b"PQRSTBCD");
matcher.table.add_data(b"BCDBCDBCDB".to_vec(), |_| {});
let mut first_sequence_offset: Option<usize> = None;
let mut first_sequence_match_len: Option<usize> = None;
matcher.start_matching(|seq| {
if first_sequence_offset.is_some() {
return;
}
match seq {
Sequence::Literals { .. } => {
panic!(
"expected tail-anchored cross-block match before any literals — \
backfill_boundary_positions did not seed positions 5/6/7"
)
}
Sequence::Triple {
literals,
offset,
match_len,
} => {
assert_eq!(literals, b"", "no leading literals on the boundary match");
first_sequence_offset = Some(offset);
first_sequence_match_len = Some(match_len);
}
}
});
let offset = first_sequence_offset.expect(
"expected tail-anchored cross-block match emitted from backfill_boundary_positions",
);
assert!(
(1..=3).contains(&offset),
"boundary match offset {offset} must point into the unhashable tail \
(positions 5/6/7 of an 8-byte block 1) so the test specifically \
locks down backfill_boundary_positions",
);
assert_eq!(
offset, 3,
"candidate position must land at 5 (= block_1_len - 3) so the 4-byte \
window `data[5..9] = b\"BCDB\"` matches block 2's first hash lookup",
);
let match_len = first_sequence_match_len.unwrap();
assert!(
match_len >= HC_MIN_MATCH_LEN,
"match_len {match_len} must clear the HC min-match floor",
);
}
#[test]
fn dfast_dense_skip_matching_backfills_previous_tail_for_next_block() {
let mut matcher = DfastMatchGenerator::new(1 << 22);
let tail = b"Qz9kLm2Rp";
let mut first = b"0123456789abcdef".to_vec();
first.extend_from_slice(tail);
matcher.add_data(first.clone(), |_| {});
matcher.skip_matching(Some(false));
let mut second = tail.to_vec();
second.extend_from_slice(b"after-tail-literals");
matcher.add_data(second, |_| {});
let mut first_sequence = None;
matcher.start_matching(|seq| {
if first_sequence.is_some() {
return;
}
first_sequence = Some(match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
});
});
let (lit_len, offset, match_len) = first_sequence.expect("expected at least one sequence");
assert_eq!(
lit_len, 0,
"expected immediate cross-block match at block start"
);
assert_eq!(
offset,
tail.len(),
"expected dense skip to preserve cross-boundary tail match"
);
assert!(
match_len >= DFAST_MIN_MATCH_LEN,
"match length should satisfy dfast minimum match length"
);
}
#[test]
fn dfast_sparse_skip_matching_preserves_tail_cross_block_match() {
let mut matcher = DfastMatchGenerator::new(1 << 22);
let tail = b"Qz9kLm2Rp";
let mut first = deterministic_high_entropy_bytes(0x9E37_79B9_7F4A_7C15, 4096);
let tail_start = first.len() - tail.len();
first[tail_start..].copy_from_slice(tail);
matcher.add_data(first.clone(), |_| {});
matcher.skip_matching(Some(true));
let mut second = tail.to_vec();
second.extend_from_slice(b"after-tail-literals");
matcher.add_data(second, |_| {});
let mut first_sequence = None;
matcher.start_matching(|seq| {
if first_sequence.is_some() {
return;
}
first_sequence = Some(match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
});
});
let (lit_len, offset, match_len) = first_sequence.expect("expected at least one sequence");
assert_eq!(
lit_len, 0,
"expected immediate cross-block match at block start"
);
assert_eq!(
offset,
tail.len(),
"expected match against densely seeded tail"
);
assert!(
match_len >= DFAST_MIN_MATCH_LEN,
"match length should satisfy dfast minimum match length"
);
}
#[test]
fn dfast_skip_matching_dense_backfills_newly_hashable_long_tail_positions() {
let mut matcher = DfastMatchGenerator::new(1 << 22);
let first = deterministic_high_entropy_bytes(0x7A64_0315_D4E1_91C3, 4096);
let first_len = first.len();
matcher.add_data(first, |_| {});
matcher.skip_matching_dense();
matcher.add_data(alloc::vec![0xAB], |_| {});
matcher.skip_matching_dense();
let target_abs_pos = first_len - 7;
let target_rel = target_abs_pos - matcher.history_abs_start;
let live = matcher.live_history();
assert!(
target_rel + 8 <= live.len(),
"fixture must make the boundary start long-hashable"
);
let long_hash = matcher.long_hash_index(&live[target_rel..]);
let target_slot = matcher.pack_slot(target_abs_pos);
assert_ne!(
target_slot, DFAST_EMPTY_SLOT,
"pack_slot must never return the empty-slot sentinel for a real position"
);
assert_eq!(
matcher.long_hash[long_hash], target_slot,
"dense skip must seed long-hash entry for newly hashable boundary start"
);
}
#[test]
fn dfast_seed_remaining_hashable_starts_seeds_last_short_hash_positions() {
let mut matcher = DfastMatchGenerator::new(1 << 20);
let block = deterministic_high_entropy_bytes(0x13F0_9A6D_55CE_7B21, 64);
matcher.add_data(block, |_| {});
matcher.ensure_hash_tables();
let current_len = matcher.window_blocks.back().copied().unwrap_or(0);
let current_abs_start = matcher.history_abs_start + matcher.window_size - current_len;
let seed_start = current_len - DFAST_MIN_MATCH_LEN;
matcher.seed_remaining_hashable_starts(current_abs_start, current_len, seed_start);
let target_abs_pos = current_abs_start + current_len - 5;
let target_rel = target_abs_pos - matcher.history_abs_start;
let live = matcher.live_history();
assert!(
target_rel + 5 <= live.len(),
"fixture must leave the last short-hash start valid"
);
let short_hash = matcher.short_hash_index(&live[target_rel..]);
let target_slot = matcher.pack_slot(target_abs_pos);
assert_ne!(
target_slot, DFAST_EMPTY_SLOT,
"pack_slot must never return the empty-slot sentinel for a real position"
);
assert_eq!(
matcher.short_hash[short_hash], target_slot,
"tail seeding must include the last 5-byte-hashable start"
);
}
#[test]
fn dfast_seed_remaining_hashable_starts_handles_pos_at_block_end() {
let mut matcher = DfastMatchGenerator::new(1 << 20);
let block = deterministic_high_entropy_bytes(0x7BB2_DA91_441E_C0EF, 64);
matcher.add_data(block, |_| {});
matcher.ensure_hash_tables();
let current_len = matcher.window_blocks.back().copied().unwrap_or(0);
let current_abs_start = matcher.history_abs_start + matcher.window_size - current_len;
matcher.seed_remaining_hashable_starts(current_abs_start, current_len, current_len);
let target_abs_pos = current_abs_start + current_len - 5;
let target_rel = target_abs_pos - matcher.history_abs_start;
let live = matcher.live_history();
assert!(
target_rel + 5 <= live.len(),
"fixture must leave the last short-hash start valid"
);
let short_hash = matcher.short_hash_index(&live[target_rel..]);
let target_slot = matcher.pack_slot(target_abs_pos);
assert_ne!(
target_slot, DFAST_EMPTY_SLOT,
"pack_slot must never return the empty-slot sentinel for a real position"
);
assert_eq!(
matcher.short_hash[short_hash], target_slot,
"tail seeding must still include the last 5-byte-hashable start when pos is at block end"
);
}
#[test]
fn dfast_ensure_room_for_rebases_above_guard_band() {
let mut dfast = DfastMatchGenerator::new(1 << 22);
dfast.set_hash_bits(10, 10);
dfast.ensure_hash_tables();
let early_abs = 1024usize;
let early_packed = dfast.pack_slot(early_abs);
assert_ne!(early_packed, DFAST_EMPTY_SLOT);
dfast.short_hash[0] = early_packed;
dfast.long_hash[0] = early_packed;
let trigger_abs = (u32::MAX as usize) - (DFAST_REBASE_GUARD_BAND as usize) + 1;
assert_eq!(dfast.position_base, 0);
dfast.ensure_room_for(trigger_abs);
assert_eq!(
dfast.position_base, DFAST_REBASE_GUARD_BAND as usize,
"rebase must advance position_base by DFAST_REBASE_GUARD_BAND"
);
assert_eq!(
dfast.short_hash[0], DFAST_EMPTY_SLOT,
"pre-rebase short-hash entries below the reducer must become empty"
);
assert_eq!(
dfast.long_hash[0], DFAST_EMPTY_SLOT,
"pre-rebase long-hash entries below the reducer must become empty"
);
let post_packed = dfast.pack_slot(trigger_abs);
assert_ne!(post_packed, DFAST_EMPTY_SLOT);
let unpacked = dfast.position_base + (post_packed as usize) - 1;
assert_eq!(
unpacked, trigger_abs,
"post-rebase pack/unpack must round-trip the absolute position"
);
}
#[test]
fn dfast_sparse_skip_matching_backfills_previous_tail_for_consecutive_sparse_blocks() {
let mut matcher = DfastMatchGenerator::new(1 << 22);
let boundary_prefix = [0xFA, 0xFB, 0xFC];
let boundary_suffix = [0xFD, 0xEE, 0xAD, 0xBE, 0xEF, 0x11, 0x22, 0x33];
let mut first = deterministic_high_entropy_bytes(0xA5A5_5A5A_C3C3_3C3C, 4096);
let first_tail_start = first.len() - boundary_prefix.len();
first[first_tail_start..].copy_from_slice(&boundary_prefix);
matcher.add_data(first, |_| {});
matcher.skip_matching(Some(true));
let mut second = deterministic_high_entropy_bytes(0xA5A5_5A5A_C3C3_3C3C, 4096);
second[..boundary_suffix.len()].copy_from_slice(&boundary_suffix);
matcher.add_data(second.clone(), |_| {});
matcher.skip_matching(Some(true));
let mut third = boundary_prefix.to_vec();
third.extend_from_slice(&boundary_suffix);
third.extend_from_slice(b"-trailing-literals");
matcher.add_data(third, |_| {});
let mut first_sequence = None;
matcher.start_matching(|seq| {
if first_sequence.is_some() {
return;
}
first_sequence = Some(match seq {
Sequence::Literals { literals } => (literals.len(), 0usize, 0usize),
Sequence::Triple {
literals,
offset,
match_len,
} => (literals.len(), offset, match_len),
});
});
let (lit_len, offset, match_len) = first_sequence.expect("expected at least one sequence");
assert_eq!(
lit_len, 0,
"expected immediate match from the prior sparse-skip boundary"
);
assert_eq!(
offset,
second.len() + boundary_prefix.len(),
"expected match against backfilled first→second boundary start"
);
assert!(
match_len >= DFAST_MIN_MATCH_LEN,
"match length should satisfy dfast minimum match length"
);
}
#[test]
fn fastest_hint_iteration_23_sequences_reconstruct_source() {
fn generate_data(seed: u64, len: usize) -> Vec<u8> {
let mut state = seed;
let mut data = Vec::with_capacity(len);
for _ in 0..len {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
data.push((state >> 33) as u8);
}
data
}
let i = 23u64;
let len = (i * 89 % 16384) as usize;
let mut data = generate_data(i, len);
let repeat = data[128..256].to_vec();
data.extend_from_slice(&repeat);
data.extend_from_slice(&repeat);
let mut driver = MatchGeneratorDriver::new(1024 * 128, 1);
driver.set_source_size_hint(data.len() as u64);
driver.reset(CompressionLevel::Fastest);
let mut space = driver.get_next_space();
space[..data.len()].copy_from_slice(&data);
space.truncate(data.len());
driver.commit_space(space);
let mut rebuilt = Vec::with_capacity(data.len());
let mut saw_triple = false;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => rebuilt.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
saw_triple = true;
rebuilt.extend_from_slice(literals);
assert!(offset > 0, "offset must be non-zero");
assert!(
offset <= rebuilt.len(),
"offset must reference already-produced bytes: offset={} produced={}",
offset,
rebuilt.len()
);
let start = rebuilt.len() - offset;
for idx in 0..match_len {
let b = rebuilt[start + idx];
rebuilt.push(b);
}
}
});
let _ = saw_triple;
assert_eq!(rebuilt, data);
}
#[test]
fn fast_levels_dispatch_per_level_hash_log_and_mls() {
let f1 = resolve_level_params(CompressionLevel::Level(1), None)
.fast
.unwrap();
assert_eq!(f1.hash_log, 14);
assert_eq!(f1.mls, 7);
assert_eq!(f1.step_size, 2);
for n in -7..=-1 {
let f = resolve_level_params(CompressionLevel::Level(n), None)
.fast
.unwrap();
assert_eq!(f.hash_log, 13, "Level({n}) fast_hash_log");
assert_eq!(f.mls, 7, "Level({n}) fast_mls");
let expected_step = ((-n) as usize) + 1;
assert_eq!(f.step_size, expected_step, "Level({n}) fast_step_size");
}
let pf = resolve_level_params(CompressionLevel::Fastest, None);
let ff = pf.fast.unwrap();
assert_eq!(
(pf.window_log, ff.hash_log, ff.mls, ff.step_size),
(19, 14, 6, 2),
);
let pu = resolve_level_params(CompressionLevel::Uncompressed, None);
let fu = pu.fast.unwrap();
assert_eq!(
(pu.window_log, fu.hash_log, fu.mls, fu.step_size),
(17, 14, 6, 2),
);
}
#[test]
fn fast_levels_driver_wiring_threads_cparams_into_inner_matcher() {
let mut driver = MatchGeneratorDriver::new(64 * 1024, 1);
let fast_levels = [
CompressionLevel::Level(1),
CompressionLevel::Fastest,
CompressionLevel::Uncompressed,
CompressionLevel::Level(-1),
CompressionLevel::Level(-2),
CompressionLevel::Level(-3),
CompressionLevel::Level(-4),
CompressionLevel::Level(-5),
CompressionLevel::Level(-6),
CompressionLevel::Level(-7),
];
for &level in &fast_levels {
let p = resolve_level_params(level, None);
assert_eq!(
p.strategy_tag,
super::strategy::StrategyTag::Fast,
"{level:?} must resolve to Fast strategy",
);
crate::encoding::Matcher::reset(&mut driver, CompressionLevel::Default);
crate::encoding::Matcher::reset(&mut driver, level);
let f = p.fast.unwrap();
let m = driver.simple_mut();
assert_eq!(
m.hash_log(),
f.hash_log,
"{level:?}: inner matcher hash_log mismatch — argument swap?",
);
assert_eq!(
m.mls(),
f.mls,
"{level:?}: inner matcher mls mismatch — argument swap?",
);
assert_eq!(
m.step_size(),
f.step_size,
"{level:?}: inner matcher step_size mismatch — stale value carried from prior reset?",
);
}
}
#[test]
fn lazy_band_target_len_matches_default_table() {
let expected: [(i32, usize); 11] = [
(5, 2),
(6, 4),
(7, 8),
(8, 16),
(9, 16),
(10, 16),
(11, 16),
(12, 32),
(13, 32),
(14, 32),
(15, 32),
];
for (level, want) in expected {
let params = resolve_level_params(CompressionLevel::Level(level), None);
let target_len = params
.hc
.map(|hc| hc.target_len)
.or_else(|| params.row.map(|row| row.target_len))
.expect("lazy/greedy level carries hc or row config");
assert_eq!(target_len, want, "L{level}: target_len must match table[0]");
}
}
#[test]
fn upper_lazy_band_params_match_default_table() {
let expected: [(i32, u8, usize, usize, usize); 3] = [
(13, 22, 22, 22, 1 << 4),
(14, 22, 23, 22, 1 << 5),
(15, 22, 23, 23, 1 << 6),
];
for (level, wlog, hlog, clog, sd) in expected {
let params = resolve_level_params(CompressionLevel::Level(level), None);
let hc = params.hc.unwrap();
assert_eq!(hc.search_depth, sd, "L{level}: search_depth");
assert_eq!(params.window_log, wlog, "L{level}: window_log");
assert_eq!(hc.hash_log, hlog, "L{level}: hash_log");
assert_eq!(hc.chain_log, clog, "L{level}: chain_log");
}
}