use alloc::vec::Vec;
use super::CompressionLevel;
use super::Matcher;
use super::Sequence;
use super::cost_model::HC_FORMAT_MINMATCH;
#[cfg(test)]
use super::cost_model::HC_MAX_LIT;
#[cfg(test)]
use super::cost_model::{
HC_BITCOST_MULTIPLIER, HC_OPT_NUM, 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::hc::HC_MIN_MATCH_LEN;
#[cfg(test)]
use super::match_table::storage::HC3_HASH_LOG;
#[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;
#[cfg(test)]
use super::opt::types::{HcCandidateQuery, 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 = 8;
pub(crate) const DFAST_SKIP_STEP_GROWTH_INTERVAL: usize = 1 << DFAST_SKIP_SEARCH_STRENGTH;
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;
pub(crate) const HC_SEARCH_DEPTH: usize = 16;
pub(crate) const HC_OPT_MIN_MATCH_LEN: usize = HC_FORMAT_MINMATCH;
pub(crate) const HC_TARGET_LEN: usize = 48;
use super::levels::config::*;
use super::hc::generator::HcMatchGenerator;
mod dict_prime;
#[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,
pub(crate) 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_dict_attach_ok: bool,
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_deferred(
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_dict_attach_ok: true,
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)]
pub(crate) fn row_matcher(&self) -> &RowMatchGenerator {
match &self.storage {
MatcherStorage::Row(m) => m,
_ => panic!("row backend must be initialized by reset() before use"),
}
}
pub(crate) 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_dict_attach_ok
&& 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 block_samples_match_dict(&self, block: &[u8]) -> bool {
match &self.storage {
MatcherStorage::Simple(m) => m.block_samples_match_dict(block),
_ => true,
}
}
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);
self.reset_dict_attach_ok =
dict_hint.is_none_or(|size| size <= MAX_FAST_ATTACH_DICT_REGION);
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.tables = 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_dict_attach_ok
&& 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,
}
});
let hash_log = if dict_hint.is_some_and(|s| s > 0) {
fast.hash_log
} else {
fast.hash_log.min(params.window_log as u32 + 1)
};
m.reset(
params.window_log,
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")]
{
let derived_ldm = 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),
};
seed.derive(strategy_ord)
});
if let MatcherStorage::HashChain(hc) = &mut self.storage {
let producer = derived_ldm.map(|p| match hc.take_ldm_producer() {
Some(mut existing) if existing.params() == p => {
existing.clear();
existing
}
_ => super::ldm::LdmProducer::new(p),
});
hc.set_ldm_producer(producer);
}
}
let fast_attach = matches!(next_backend, super::strategy::BackendTag::Simple)
&& self.reset_dict_attach_ok
&& 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));
}
#[inline]
fn dictionary_is_resident(&self) -> bool {
self.dictionary_is_resident_impl()
}
#[inline]
fn reapply_resident_dictionary(&mut self, offset_hist: [u32; 3]) {
self.reapply_resident_dictionary_impl(offset_hist)
}
#[inline]
fn prime_with_dictionary(&mut self, dict_content: &[u8], offset_hist: [u32; 3]) {
self.prime_with_dictionary_impl(dict_content, offset_hist)
}
#[inline]
fn restore_primed_dictionary(&mut self, level: super::CompressionLevel) -> bool {
self.restore_primed_dictionary_impl(level)
}
#[inline]
fn capture_primed_dictionary(&mut self, level: super::CompressionLevel) {
self.capture_primed_dictionary_impl(level)
}
#[inline]
fn invalidate_primed_dictionary(&mut self) {
self.invalidate_primed_dictionary_impl()
}
#[inline]
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>,
) {
self.seed_dictionary_entropy_impl(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>),
}
#[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
}
#[cfg(test)]
mod tests;