#![allow(dead_code)]
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use super::cost_model::{HC_MAX_LIT, HcOptState, HcOptimalCostProfile};
use super::opt::ldm::{HcOptLdmState, HcRawSeq, HcRawSeqStore};
use super::opt::types::{HcOptimalNode, HcOptimalPlanBuffers, HcOptimalSequence, MatchCandidate};
pub(crate) const HC3_MAX_OFFSET: usize = 1 << 18;
#[derive(Clone)]
pub(crate) struct BtMatcher {
pub(crate) opt_state: HcOptState,
pub(crate) opt_nodes_scratch: Box<[MaybeUninit<HcOptimalNode>]>,
pub(crate) opt_node_prices_scratch: Box<[MaybeUninit<u32>]>,
pub(crate) opt_candidates_scratch: Vec<MatchCandidate>,
pub(crate) opt_store_scratch: Vec<HcOptimalNode>,
pub(crate) opt_segment_plan_scratch: Vec<HcOptimalSequence>,
pub(crate) opt_seed_plan_scratch: Vec<HcOptimalSequence>,
pub(crate) opt_price_arena: alloc::boxed::Box<[[u32; 2]]>,
pub(crate) opt_ll_price_stamp: u32,
pub(crate) opt_lit_price_scratch: [u32; HC_MAX_LIT + 1],
pub(crate) opt_lit_price_generation: [u32; HC_MAX_LIT + 1],
pub(crate) opt_lit_price_stamp: u32,
pub(crate) opt_ml_price_stamp: u32,
pub(crate) ldm_sequences: Vec<HcRawSeq>,
}
impl BtMatcher {
pub(crate) const HASH_MLS: usize = 4;
pub(crate) fn estimated_workspace_bytes() -> usize {
use super::cost_model::{HC_OPT_NODE_LEN, HC_OPT_NUM, HC_OPT_PRICE_ARENA_LEN};
use super::hc::MAX_HC_SEARCH_DEPTH;
let frontier = HC_OPT_NUM + 1;
core::mem::size_of::<Self>()
+ 2 * HC_OPT_NODE_LEN * core::mem::size_of::<HcOptimalNode>()
+ HC_OPT_NODE_LEN * core::mem::size_of::<u32>()
+ HC_OPT_PRICE_ARENA_LEN * core::mem::size_of::<[u32; 2]>()
+ 2 * frontier * core::mem::size_of::<HcOptimalSequence>()
+ MAX_HC_SEARCH_DEPTH * core::mem::size_of::<MatchCandidate>()
}
pub(crate) fn push_candidate_ladder(
out: &mut Vec<MatchCandidate>,
best_len_for_skip: &mut usize,
candidate: MatchCandidate,
min_match_len: usize,
) -> bool {
if candidate.match_len < min_match_len {
return false;
}
if candidate.match_len > *best_len_for_skip {
out.push(candidate);
*best_len_for_skip = candidate.match_len;
return true;
}
false
}
#[inline(always)]
pub(crate) fn push_ldm_candidate(
out: &mut Vec<MatchCandidate>,
ldm: MatchCandidate,
min_match_len: usize,
) {
let mut best_len = out.last().map_or(0, |c| c.match_len);
let _ = Self::push_candidate_ladder(out, &mut best_len, ldm, min_match_len);
}
pub(crate) fn new() -> Self {
Self {
opt_state: HcOptState::new(),
opt_nodes_scratch: Box::default(),
opt_node_prices_scratch: Box::default(),
opt_candidates_scratch: Vec::new(),
opt_store_scratch: Vec::new(),
opt_segment_plan_scratch: Vec::new(),
opt_seed_plan_scratch: Vec::new(),
opt_price_arena: alloc::boxed::Box::default(),
opt_ll_price_stamp: 0,
opt_lit_price_scratch: [0; HC_MAX_LIT + 1],
opt_lit_price_generation: [0; HC_MAX_LIT + 1],
opt_lit_price_stamp: 0,
opt_ml_price_stamp: 0,
ldm_sequences: Vec::new(),
}
}
pub(crate) fn heap_size(&self) -> usize {
self.opt_nodes_scratch.len() * core::mem::size_of::<HcOptimalNode>()
+ self.opt_node_prices_scratch.len() * core::mem::size_of::<u32>()
+ self.opt_candidates_scratch.capacity() * core::mem::size_of::<MatchCandidate>()
+ self.opt_store_scratch.capacity() * core::mem::size_of::<HcOptimalNode>()
+ (self.opt_segment_plan_scratch.capacity() + self.opt_seed_plan_scratch.capacity())
* core::mem::size_of::<HcOptimalSequence>()
+ self.opt_price_arena.len() * core::mem::size_of::<[u32; 2]>()
+ self.ldm_sequences.capacity() * core::mem::size_of::<HcRawSeq>()
}
pub(crate) fn reset(&mut self) {
self.opt_state.reset();
self.opt_candidates_scratch.clear();
self.opt_store_scratch.clear();
self.opt_segment_plan_scratch.clear();
self.opt_seed_plan_scratch.clear();
self.opt_lit_price_scratch = [0; HC_MAX_LIT + 1];
self.opt_lit_price_generation = [0; HC_MAX_LIT + 1];
self.opt_lit_price_stamp = 0;
self.ldm_sequences.clear();
}
pub(crate) fn ldm_skip_raw_seq_store_bytes(
&self,
seq_store: &mut HcRawSeqStore,
nb_bytes: usize,
) {
let mut curr_pos = seq_store.pos_in_sequence.saturating_add(nb_bytes);
while curr_pos > 0 && seq_store.pos < seq_store.size {
let curr_seq = self.ldm_sequences[seq_store.pos];
let seq_len = curr_seq.lit_length.saturating_add(curr_seq.match_length);
if curr_pos >= seq_len {
curr_pos -= seq_len;
seq_store.pos += 1;
} else {
seq_store.pos_in_sequence = curr_pos;
break;
}
}
if curr_pos == 0 || seq_store.pos == seq_store.size {
seq_store.pos_in_sequence = 0;
}
}
pub(crate) fn ldm_get_next_match_and_update_seq_store(
&self,
opt_ldm: &mut HcOptLdmState,
curr_pos_in_block: usize,
block_bytes_remaining: usize,
) {
if opt_ldm.seq_store.size == 0 || opt_ldm.seq_store.pos >= opt_ldm.seq_store.size {
opt_ldm.start_pos_in_block = usize::MAX;
opt_ldm.end_pos_in_block = usize::MAX;
return;
}
let curr_seq = self.ldm_sequences[opt_ldm.seq_store.pos];
let curr_block_end_pos = curr_pos_in_block.saturating_add(block_bytes_remaining);
let literals_bytes_remaining = curr_seq
.lit_length
.saturating_sub(opt_ldm.seq_store.pos_in_sequence);
let match_bytes_remaining = if literals_bytes_remaining == 0 {
curr_seq.match_length.saturating_sub(
opt_ldm
.seq_store
.pos_in_sequence
.saturating_sub(curr_seq.lit_length),
)
} else {
curr_seq.match_length
};
if literals_bytes_remaining >= block_bytes_remaining {
opt_ldm.start_pos_in_block = usize::MAX;
opt_ldm.end_pos_in_block = usize::MAX;
self.ldm_skip_raw_seq_store_bytes(&mut opt_ldm.seq_store, block_bytes_remaining);
return;
}
opt_ldm.start_pos_in_block = curr_pos_in_block.saturating_add(literals_bytes_remaining);
opt_ldm.end_pos_in_block = opt_ldm
.start_pos_in_block
.saturating_add(match_bytes_remaining);
opt_ldm.offset = curr_seq.offset;
if opt_ldm.end_pos_in_block > curr_block_end_pos {
opt_ldm.end_pos_in_block = curr_block_end_pos;
self.ldm_skip_raw_seq_store_bytes(
&mut opt_ldm.seq_store,
curr_block_end_pos.saturating_sub(curr_pos_in_block),
);
} else {
self.ldm_skip_raw_seq_store_bytes(
&mut opt_ldm.seq_store,
literals_bytes_remaining.saturating_add(match_bytes_remaining),
);
}
}
pub(crate) fn ldm_maybe_add_match(
&self,
opt_ldm: &HcOptLdmState,
curr_pos_in_block: usize,
min_match: usize,
) -> Option<MatchCandidate> {
let _ = self;
let pos_diff = curr_pos_in_block.saturating_sub(opt_ldm.start_pos_in_block);
let candidate_match_length = opt_ldm
.end_pos_in_block
.saturating_sub(opt_ldm.start_pos_in_block)
.saturating_sub(pos_diff);
if curr_pos_in_block < opt_ldm.start_pos_in_block
|| curr_pos_in_block >= opt_ldm.end_pos_in_block
|| candidate_match_length < min_match
{
return None;
}
Some(MatchCandidate {
start: curr_pos_in_block,
offset: opt_ldm.offset,
match_len: candidate_match_length,
})
}
pub(crate) fn ldm_process_match_candidate(
&self,
opt_ldm: &mut HcOptLdmState,
curr_pos_in_block: usize,
remaining_bytes: usize,
min_match: usize,
) -> Option<MatchCandidate> {
if opt_ldm.seq_store.size == 0 || opt_ldm.seq_store.pos >= opt_ldm.seq_store.size {
return None;
}
if curr_pos_in_block >= opt_ldm.end_pos_in_block {
if curr_pos_in_block > opt_ldm.end_pos_in_block {
let pos_overshoot = curr_pos_in_block.saturating_sub(opt_ldm.end_pos_in_block);
self.ldm_skip_raw_seq_store_bytes(&mut opt_ldm.seq_store, pos_overshoot);
}
self.ldm_get_next_match_and_update_seq_store(
opt_ldm,
curr_pos_in_block,
remaining_bytes,
);
}
self.ldm_maybe_add_match(opt_ldm, curr_pos_in_block, min_match)
}
pub(crate) fn finish_optimal_plan(
&mut self,
buffers: HcOptimalPlanBuffers,
result: (u32, [u32; 3], usize, usize),
) -> (u32, [u32; 3], usize, usize) {
let HcOptimalPlanBuffers {
nodes,
node_prices,
mut candidates,
store,
price_arena,
candidates_searched_at: _,
pass: _,
} = buffers;
candidates.clear();
self.opt_nodes_scratch = nodes;
self.opt_node_prices_scratch = node_prices;
self.opt_candidates_scratch = candidates;
self.opt_store_scratch = store;
self.opt_price_arena = price_arena;
result
}
pub(crate) fn encode_offset_with_reps(
actual_offset: u32,
lit_len: usize,
reps: [u32; 3],
) -> (u32, [u32; 3]) {
let mut next_reps = reps;
let encoded = if lit_len > 0 {
if actual_offset == reps[0] {
1
} else if actual_offset == reps[1] {
2
} else if actual_offset == reps[2] {
3
} else {
actual_offset.saturating_add(3)
}
} else if actual_offset == reps[1] {
1
} else if actual_offset == reps[2] {
2
} else if reps[0] > 1 && actual_offset == reps[0] - 1 {
3
} else {
actual_offset.saturating_add(3)
};
if lit_len > 0 {
match encoded {
1 => {}
2 => {
next_reps[1] = next_reps[0];
next_reps[0] = actual_offset;
}
_ => {
next_reps[2] = next_reps[1];
next_reps[1] = next_reps[0];
next_reps[0] = actual_offset;
}
}
} else {
match encoded {
1 => {
next_reps[1] = next_reps[0];
next_reps[0] = actual_offset;
}
_ => {
next_reps[2] = next_reps[1];
next_reps[1] = next_reps[0];
next_reps[0] = actual_offset;
}
}
}
(encoded, next_reps)
}
#[inline(always)]
pub(crate) fn encode_offset_base_with_reps(
actual_offset: u32,
lit_len: usize,
reps: [u32; 3],
) -> u32 {
if lit_len > 0 {
if actual_offset == reps[0] {
1
} else if actual_offset == reps[1] {
2
} else if actual_offset == reps[2] {
3
} else {
actual_offset.saturating_add(3)
}
} else if actual_offset == reps[1] {
1
} else if actual_offset == reps[2] {
2
} else if reps[0] > 1 && actual_offset == reps[0] - 1 {
3
} else {
actual_offset.saturating_add(3)
}
}
#[inline]
pub(crate) fn record_sequence_stats(
block: &[u8],
sequence: HcOptimalSequence,
literals_start: &mut usize,
reps: &mut [u32; 3],
opt_state: &mut HcOptState,
) {
let lit_len = sequence.lit_len as usize;
let match_len = sequence.match_len as usize;
let Some(start) = literals_start.checked_add(lit_len) else {
return;
};
let Some(end) = start.checked_add(match_len) else {
return;
};
if end > block.len() {
return;
}
let literals = &block[*literals_start..start];
let (off_base, next_reps) =
Self::encode_offset_with_reps(sequence.offset, literals.len(), *reps);
opt_state.update_stats(literals.len(), literals, off_base, match_len);
*reps = next_reps;
*literals_start = end;
}
#[inline(always)]
pub(crate) unsafe fn reset_opt_node_prices(node_prices: *mut u32, start: usize, end: usize) {
for i in start..=end {
unsafe { node_prices.add(i).write(u32::MAX) };
}
}
#[inline(always)]
pub(crate) fn add_price_delta(price: u32, add: u32, delta: i32) -> u32 {
#[cfg(debug_assertions)]
{
let sum = price as i64 + add as i64 + delta as i64;
debug_assert!((0..=u32::MAX as i64).contains(&sum));
}
price.wrapping_add(add).wrapping_add_signed(delta)
}
#[inline(always)]
pub(crate) fn add_prices(lhs: u32, rhs: u32) -> u32 {
let sum = lhs + rhs;
debug_assert!(sum >= lhs);
sum
}
#[inline(always)]
pub(crate) fn cached_literal_price<const ACCURATE: bool>(
profile: HcOptimalCostProfile,
stats: &HcOptState,
byte: u8,
prices: &mut [u32; HC_MAX_LIT + 1],
generations: &mut [u32; HC_MAX_LIT + 1],
stamp: u32,
) -> u32 {
if !ACCURATE {
return profile.literal_price::<false>(stats, byte);
}
let idx = byte as usize;
unsafe {
if *generations.get_unchecked(idx) == stamp {
return *prices.get_unchecked(idx);
}
let price = profile.literal_price::<ACCURATE>(stats, byte);
*prices.get_unchecked_mut(idx) = price;
*generations.get_unchecked_mut(idx) = stamp;
price
}
}
#[inline(always)]
pub(crate) fn cached_lit_length_price<const ACCURATE: bool>(
profile: HcOptimalCostProfile,
stats: &HcOptState,
lit_len: usize,
cache: &mut [[u32; 2]],
stamp: u32,
) -> u32 {
if !ACCURATE || lit_len >= cache.len() {
return profile.lit_length_price::<ACCURATE>(stats, lit_len);
}
unsafe {
let cell = cache.get_unchecked_mut(lit_len);
if cell[1] == stamp {
return cell[0];
}
let price = profile.lit_length_price::<ACCURATE>(stats, lit_len);
cell[0] = price;
cell[1] = stamp;
price
}
}
#[inline(always)]
pub(crate) fn cached_lit_length_delta_price<const ACCURATE: bool>(
profile: HcOptimalCostProfile,
stats: &HcOptState,
lit_len: usize,
cache: &mut [[u32; 2]],
stamp: u32,
) -> i32 {
if lit_len == 0 {
return 0;
}
let price =
Self::cached_lit_length_price::<ACCURATE>(profile, stats, lit_len, cache, stamp);
let previous =
Self::cached_lit_length_price::<ACCURATE>(profile, stats, lit_len - 1, cache, stamp);
price as i32 - previous as i32
}
#[inline(always)]
pub(crate) fn cached_match_length_price<const ACCURATE: bool>(
profile: HcOptimalCostProfile,
stats: &HcOptState,
match_len: usize,
cache: &mut [[u32; 2]],
stamp: u32,
) -> u32 {
if !ACCURATE || match_len >= cache.len() {
return profile.match_length_price::<ACCURATE>(stats, match_len);
}
unsafe {
let cell = cache.get_unchecked_mut(match_len);
if cell[1] == stamp {
return cell[0];
}
let price = profile.match_length_price::<ACCURATE>(stats, match_len);
cell[0] = price;
cell[1] = stamp;
price
}
}
}
#[cfg(test)]
mod ldm_helper_tests;
#[cfg(test)]
mod tests;