#[cfg(feature = "bench-internals")]
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, TableCarry,
};
use super::workspace::HistoryBuf;
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 =
crate::encoding::incompressible::RAW_SKIP_INDEX_STEP;
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(),
}
}
#[cfg(test)]
fn ingest_capacity(&self) -> usize {
match self {
Self::Simple(m) => m.history_capacity(),
Self::Dfast(m) => m.history.capacity(),
Self::Row(m) => m.history.capacity(),
Self::HashChain(m) => m.table.history.capacity(),
}
}
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 {
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<super::DictionarySizes>,
reset_size_log: Option<u8>,
reset_dict_attach_ok: bool,
reset_shape: Option<(
LevelParams,
usize,
bool,
bool,
Option<super::parameters::LdmOverride>,
)>,
borrowed_pending: Option<(usize, usize)>,
primed: Option<(MatcherStorage, usize, PrimedKey)>,
own_workspace: crate::encoding::workspace::Workspace,
frame_in_place: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct PrimedKey {
level: super::CompressionLevel,
params: LevelParams,
table_bits: usize,
fast_attach: bool,
fast_tagged: bool,
ldm: Option<super::parameters::LdmOverride>,
}
fn hc_attaches_dictionary(hc: &HcMatchGenerator, size_log: Option<u8>) -> bool {
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
};
size_log.is_none_or(|log| log <= cutoff)
}
fn frame_history_bytes(
backend: super::strategy::BackendTag,
workspace: &crate::encoding::workspace::Workspace,
in_place: bool,
hint: Option<u64>,
dict_len: usize,
max_window_size: usize,
level: CompressionLevel,
) -> usize {
use super::match_table::storage::MAX_PRIMED_WINDOW_SIZE;
use crate::encoding::workspace::IngestPlan;
let block = workspace.block_for_window(max_window_size);
let window = max_window_size
.checked_add(dict_len)
.map_or(MAX_PRIMED_WINDOW_SIZE, |w| w.min(MAX_PRIMED_WINDOW_SIZE));
let slack = match backend {
super::strategy::BackendTag::Simple => window,
_ => window >> 2,
};
let ceiling = window + slack + block;
let bytes = hint.map(|bytes| usize::try_from(bytes).unwrap_or(usize::MAX));
let (input, read_slack) = match workspace.ingest() {
IngestPlan::Raw => return 0,
IngestPlan::Slice(_) if in_place => return dict_len.min(ceiling),
IngestPlan::Slice(len) | IngestPlan::PledgedStream(len) => (Some(len), 0),
IngestPlan::Stream => (
bytes.map(|bytes| {
let level_window_log =
crate::encoding::levels::config::resolve_level_params(level, hint).window_log;
bytes.min(1usize << level_window_log)
}),
block,
),
};
match input {
Some(bytes) if bytes < max_window_size => dict_len
.checked_add(bytes + read_slack)
.map_or(ceiling, |sum| sum.min(ceiling)),
_ => ceiling,
}
}
impl MatchGeneratorDriver {
#[cfg(test)]
pub(crate) fn ingest_capacity(&self) -> usize {
self.storage.ingest_capacity()
}
#[cfg(test)]
pub(crate) fn owned_table_and_history_bytes(&self) -> (usize, usize) {
match &self.storage {
MatcherStorage::Simple(_) => panic!("asked of a window backend only"),
MatcherStorage::Dfast(m) => (m.tables.owned_bytes(), m.history.owned_bytes()),
MatcherStorage::Row(m) => (m.tables.owned_bytes(), m.history.owned_bytes()),
MatcherStorage::HashChain(m) => {
(m.table.tables.owned_bytes(), m.table.history.owned_bytes())
}
}
}
#[cfg(any(test, feature = "bench-internals"))]
pub(crate) fn commit_input(&mut self, input: &[u8]) {
self.fill_in_place(input.len(), &mut |history| {
history.extend_from_slice(input);
(input.len(), false)
});
self.commit_filled(input.len());
}
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 {
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,
own_workspace: crate::encoding::workspace::Workspace::new(),
frame_in_place: false,
}
}
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};
match self.active_backend() {
BackendTag::Simple | BackendTag::Dfast | BackendTag::Row => true,
BackendTag::HashChain => matches!(self.search, SearchMethod::HashChain),
}
}
pub(crate) fn frame_scans_in_place(&self) -> bool {
self.frame_in_place
}
pub(crate) fn borrowed_frame_is_independent(&self) -> bool {
match &self.storage {
MatcherStorage::Dfast(d) => !d.tables_hold_earlier_frames,
_ => true,
}
}
pub(crate) fn forget_earlier_frames(&mut self) {
if let MatcherStorage::Dfast(d) = &mut self.storage {
d.forget_earlier_frames();
}
}
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"),
}
}
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;
};
hc_attaches_dictionary(hc, self.reset_size_log)
}
fn skip_matching_for_dictionary_priming(&mut self, dict_len: usize) {
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(dict_len);
} else {
self.simple_mut().skip_matching_for_dict_copy();
}
}
super::strategy::BackendTag::Dfast => {
let attach = dict_len <= crate::encoding::dfast::DFAST_ATTACH_DICT_MAX_LEN
&& 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 => {
self.row_matcher_mut().prime_dictionary_current_block();
}
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, sizes: super::DictionarySizes) {
self.dictionary_size_hint = Some(sizes);
}
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 snapshot = self
.primed
.as_ref()
.map_or(0, |(storage, _, _)| storage.heap_size());
self.storage.heap_size() + snapshot + self.own_workspace.heap_bytes()
}
fn clear_param_overrides(&mut self) {
self.param_overrides = None;
}
fn reset(&mut self, level: CompressionLevel) {
let mut own = core::mem::take(&mut self.own_workspace);
own.begin_layout(
crate::common::MAX_BLOCK_SIZE as usize,
crate::encoding::workspace::no_trailing,
crate::encoding::workspace::IngestPlan::Stream,
);
self.reset_in_workspace(level, &mut own);
own.release_retired();
self.own_workspace = own;
}
fn reset_in_workspace(
&mut self,
level: CompressionLevel,
workspace: &mut crate::encoding::workspace::Workspace,
) {
let hint = self.source_size_hint.take();
let dict_hint = self
.dictionary_size_hint
.take()
.filter(|sizes| sizes.content > 0);
self.reset_size_log = hint.map(source_size_ceil_log);
self.reset_dict_attach_ok =
dict_hint.is_none_or(|sizes| sizes.content <= MAX_FAST_ATTACH_DICT_REGION);
let hinted = hint.is_some();
let overrides = self.param_overrides.unwrap_or_default();
let (params, dict_plan) = match dict_hint {
Some(sizes) => crate::encoding::levels::config::resolve_level_params_with_dict(
level, hint, sizes, &overrides,
),
None => (Self::level_params(level, hint), None),
};
#[cfg_attr(not(test), allow(unused_mut))]
let mut params = params;
#[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 | SearchMethod::BinaryTreeLazy => {
let row = params.row.get_or_insert(ROW_CONFIG);
row.bt = matches!(search, SearchMethod::BinaryTreeLazy);
}
SearchMethod::HashChain | SearchMethod::BinaryTree => {
params.hc.get_or_insert(HC_CONFIG);
}
}
}
if let Some(ov) = self.param_overrides {
crate::encoding::levels::config::apply_frame_overrides(
&mut params,
&ov,
dict_hint.is_some(),
hint,
);
}
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 = crate::encoding::workspace::Table::empty();
m.reset();
}
MatcherStorage::Row(m) => {
m.release_tables();
m.reset();
}
MatcherStorage::HashChain(m) => {
m.table.tables = crate::encoding::workspace::Table::empty();
m.table.chain_off = 0;
m.table.hash3_off = 0;
m.reset();
}
}
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_deferred(
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 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 dict_len = dict_hint.map_or(0, |sizes| sizes.content);
self.frame_in_place = match workspace.ingest() {
crate::encoding::workspace::IngestPlan::Slice(len) => {
(dict_hint.is_none() || fast_attach)
&& self.borrowed_supported()
&& len
.checked_add(dict_len)
.is_some_and(|len| len <= u32::MAX as usize)
}
_ => false,
};
let history_bytes = frame_history_bytes(
next_backend,
workspace,
self.frame_in_place,
hint,
dict_hint.map_or(0, |sizes| sizes.content),
max_window_size,
level,
);
let expected_input = hint.map_or(history_bytes, |bytes| {
usize::try_from(bytes).unwrap_or(usize::MAX)
});
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 = dict_hint.is_some()
&& self.reset_dict_attach_ok
&& self
.reset_size_log
.is_none_or(|log| log <= FAST_ATTACH_DICT_CUTOFF_LOG);
let restored_slot_format = self
.primed
.as_ref()
.filter(|(_, _, captured)| {
dict_hint.is_some()
&& restores_primed_snapshot(
self.reset_size_log,
FAST_ATTACH_DICT_CUTOFF_LOG,
)
&& *captured
== PrimedKey {
level,
params,
table_bits: 0,
fast_attach: false,
fast_tagged: captured.fast_tagged,
ldm: None,
}
})
.map(|(_, _, captured)| captured.fast_tagged);
let carry = if let Some(tagged) = restored_slot_format {
TableCarry::OverwrittenByRestore { tagged }
} else if dict_attach_epoch {
TableCarry::AdvanceEpoch
} else {
TableCarry::Clear
};
let hash_log = if dict_hint.is_some() {
fast.hash_log
} else {
fast.hash_log.min(params.window_log as u32 + 1)
};
m.set_dict_table_hash_log(dict_hint.map(|sizes| {
crate::encoding::cparams::get_cdict_cparams(
crate::encoding::levels::config::numeric_level(level),
sizes.serialized,
&overrides,
)
.hash_log
}));
let tables =
crate::encoding::simple::fast_kernel::hash_table::FastHashTable::workspace_bytes(
hash_log,
);
m.retire_history();
workspace.open_for_match_finder(
tables + m.history_workspace_bytes(history_bytes),
tables,
max_window_size,
expected_input,
);
m.bind_history(workspace, history_bytes);
m.reset(
params.window_log,
hash_log,
fast.mls,
fast.step_size,
expected_input,
dict_hint.map_or(0, |sizes| sizes.content),
carry,
workspace,
);
}
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.set_dict_table_bits(dict_hint.map(|sizes| {
let cd = crate::encoding::cparams::get_cdict_cparams(
crate::encoding::levels::config::numeric_level(level),
sizes.serialized,
&overrides,
);
(cd.hash_log as usize, cd.chain_log as usize)
}));
let dfast_attach_next = dict_hint.is_some_and(|sizes| {
sizes.content <= crate::encoding::dfast::DFAST_ATTACH_DICT_MAX_LEN
}) && self
.reset_size_log
.is_none_or(|log| log <= DFAST_ATTACH_DICT_CUTOFF_LOG);
if dict_hint.is_some() && !dfast_attach_next {
dfast.invalidate_dict_cache();
}
let tables = dfast.tables_workspace_bytes();
dfast.retire_history();
workspace.open_for_match_finder(
tables + dfast.history.workspace_bytes(history_bytes),
tables,
max_window_size,
expected_input,
);
dfast.history.bind(workspace, history_bytes);
let dict_len = dict_hint.map_or(0, |sizes| sizes.content);
let in_place_fits = !self.frame_in_place
|| matches!(
workspace.ingest(),
crate::encoding::workspace::IngestPlan::Slice(len)
if len <= crate::encoding::dfast::DFAST_TAGGED_MAX_REL
);
let tagged = !dfast_attach_next
&& in_place_fits
&& max_window_size.checked_add(dict_len).is_some_and(|window| {
(crate::encoding::dfast::DFAST_TAGGED_WINDOW_FLOOR
..=crate::encoding::dfast::DFAST_TAGGED_WINDOW_LIMIT)
.contains(&window)
});
dfast.bind_tables(workspace, tagged);
dfast.reset();
}
MatcherStorage::Row(row) => {
row.max_window_size = max_window_size;
row.lazy_depth = params.lazy_depth;
row.set_dict_plan(dict_plan);
let mut row_cfg = params.row.expect("Row level row carries a RowConfig");
if hinted && dict_plan.is_none() {
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.retire_history();
workspace.open_for_match_finder(
row.tables_workspace_bytes() + row.history.workspace_bytes(history_bytes),
row.zero_table_bytes(),
max_window_size,
expected_input,
);
row.history.bind(workspace, history_bytes);
row.bind_tables(workspace);
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 && dict_hint.is_none() {
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);
if dict_hint.is_some() && !hc_attaches_dictionary(hc, self.reset_size_log) {
hc.table.dms.invalidate();
}
let tables = hc.table.tables_workspace_bytes();
hc.table.retire_history();
workspace.open_for_match_finder(
tables + hc.table.history.workspace_bytes(history_bytes),
tables,
max_window_size,
expected_input,
);
hc.table.history.bind(workspace, history_bytes);
hc.table.bind_tables(workspace);
hc.reset();
}
}
#[cfg(feature = "ldm")]
{
let derived_ldm = self
.param_overrides
.as_ref()
.and_then(|ov| ov.ldm)
.map(|ldm_ov| crate::encoding::levels::config::frame_ldm_params(¶ms, &ldm_ov));
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 active_ldm = if matches!(params.search, super::strategy::SearchMethod::BinaryTree) {
self.param_overrides.and_then(|ov| ov.ldm)
} else {
None
};
let fast_tagged = matches!(&self.storage, MatcherStorage::Simple(m) if m.slots_tagged());
self.reset_shape = Some((
params,
resolved_table_bits,
fast_attach,
fast_tagged,
active_ldm,
));
self.own_workspace = crate::encoding::workspace::Workspace::new();
}
#[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 leave_workspace(&mut self) {
match &mut self.storage {
MatcherStorage::Simple(m) => m.leave_workspace(),
MatcherStorage::Dfast(m) => {
m.tables.leave_workspace();
m.history.leave_workspace();
}
MatcherStorage::Row(m) => {
m.tables.leave_workspace();
m.history.leave_workspace();
}
MatcherStorage::HashChain(m) => {
m.table.tables.leave_workspace();
m.table.history.leave_workspace();
}
}
}
fn window_size(&self) -> u64 {
self.reported_window_size as u64
}
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 fill_in_place(
&mut self,
capacity: usize,
fill: &mut dyn FnMut(&mut HistoryBuf) -> (usize, bool),
) -> (usize, bool) {
match &mut self.storage {
MatcherStorage::Simple(m) => m.fill_uncommitted(capacity, fill),
MatcherStorage::Dfast(m) => m.fill_uncommitted(capacity, fill),
MatcherStorage::Row(m) => m.fill_uncommitted(capacity, fill),
MatcherStorage::HashChain(m) => m.table.fill_uncommitted(capacity, fill),
}
}
fn uncommitted_input(&self) -> &[u8] {
match &self.storage {
MatcherStorage::Simple(m) => m.uncommitted(),
MatcherStorage::Dfast(m) => m.uncommitted(),
MatcherStorage::Row(m) => m.uncommitted(),
MatcherStorage::HashChain(m) => m.table.uncommitted(),
}
}
fn commit_filled(&mut self, len: usize) {
let evicted_bytes = match &mut self.storage {
MatcherStorage::Simple(m) => m.commit_block(len),
MatcherStorage::Dfast(m) => {
let pre = m.window_size;
m.commit_block(len);
pre + len - m.window_size
}
MatcherStorage::Row(m) => {
let pre = m.window_size;
m.commit_block(len);
pre + len - m.window_size
}
MatcherStorage::HashChain(m) => {
let pre = m.table.window_size;
m.table.commit_block(len);
pre + len - 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 FnMut(Sequence)) {
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),
other => {
unreachable!("HashChain backend with unexpected borrowed search {other:?}")
}
},
}
return;
}
use super::strategy::SearchMethod;
match self.search {
SearchMethod::Fast => {
self.simple_mut().start_matching(&mut handle_sequence);
}
SearchMethod::DoubleFast => {
self.dfast_matcher_mut()
.start_matching(&mut handle_sequence);
}
SearchMethod::RowHash | SearchMethod::BinaryTreeLazy => {
self.row_matcher_mut().start_matching(&mut handle_sequence);
}
SearchMethod::HashChain => {
self.hc_matcher_mut()
.start_matching_lazy(&mut handle_sequence);
}
SearchMethod::BinaryTree => match self.strategy_tag {
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 an optimal strategy tag (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);
}
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 FnMut(Sequence),
) {
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) {
driver.commit_input(&data[chunk_start..chunk_start + chunk_len]);
driver.start_matching(|seq| {
let entry = match seq {
Sequence::Literals { len } => (len, 0usize, 0usize),
Sequence::Triple {
literal_len,
offset,
match_len,
} => (literal_len, offset, match_len),
};
sequences.push(entry);
});
}
sequences
}
#[cfg(test)]
mod tests;