use super::{
FseTableMode, LastUsedTable, RawSequence, choose_table_from_counts, emit_single_sequence_block,
encode_literal_length, encode_match_len, encode_offset_with_history, min_gain,
min_literals_to_compress, previous_table, remember_last_used_tables,
};
use crate::encoding::frame_compressor::{
CompressState, FseTables, PreviousFseTable, SharedFseTable,
};
use crate::encoding::strategy::StrategyTag;
use crate::encoding::workspace::{RegionVec, Workspace, region_bytes};
use crate::fse::fse_encoder::{FSETable, build_table_from_symbol_counts};
use crate::huff0::huff0_encoder;
use alloc::vec::Vec;
fn choose_table<'a>(
previous: Option<&'a PreviousFseTable>,
default_table: &'a FSETable,
data: impl Iterator<Item = u8>,
max_log: u8,
strategy: StrategyTag,
next: &'a mut Option<SharedFseTable>,
) -> FseTableMode<'a> {
let mut counts = [0usize; 256];
let mut total = 0usize;
let mut max_symbol = 0usize;
for symbol in data {
let symbol = symbol as usize;
counts[symbol] += 1;
total += 1;
max_symbol = max_symbol.max(symbol);
}
choose_table_from_counts(
previous,
default_table,
&mut counts,
total,
max_symbol,
max_log,
strategy,
None,
next,
)
}
fn tables_match(
lhs: &crate::fse::fse_encoder::FSETable,
rhs: &crate::fse::fse_encoder::FSETable,
) -> bool {
lhs.table_size == rhs.table_size
&& (0..=255u8)
.all(|symbol| lhs.symbol_probability(symbol) == rhs.symbol_probability(symbol))
}
#[test]
fn repeat_offset_codes_follow_rfc_mapping() {
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(10, 5, &mut hist), 1);
assert_eq!(hist, [10, 20, 30]);
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(20, 5, &mut hist), 2);
assert_eq!(hist, [20, 10, 30]);
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(30, 5, &mut hist), 3);
assert_eq!(hist, [30, 10, 20]);
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(20, 0, &mut hist), 1);
assert_eq!(hist, [20, 10, 30]);
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(30, 0, &mut hist), 2);
assert_eq!(hist, [30, 10, 20]);
let mut hist = [10, 20, 30];
assert_eq!(encode_offset_with_history(9, 0, &mut hist), 3);
assert_eq!(hist, [9, 10, 20]);
}
#[test]
fn min_literals_to_compress_returns_per_strategy_floor() {
for strat in [
StrategyTag::Fast,
StrategyTag::Dfast,
StrategyTag::Greedy,
StrategyTag::Lazy,
StrategyTag::Btlazy2,
] {
assert_eq!(min_literals_to_compress(strat, false), 64);
assert_eq!(min_literals_to_compress(strat, true), 6);
}
assert_eq!(min_literals_to_compress(StrategyTag::BtOpt, false), 32);
assert_eq!(min_literals_to_compress(StrategyTag::BtOpt, true), 6);
assert_eq!(min_literals_to_compress(StrategyTag::BtUltra, false), 16);
assert_eq!(min_literals_to_compress(StrategyTag::BtUltra, true), 6);
assert_eq!(min_literals_to_compress(StrategyTag::BtUltra2, false), 8);
assert_eq!(min_literals_to_compress(StrategyTag::BtUltra2, true), 6);
}
#[test]
fn min_gain_returns_per_strategy_margin() {
let src = 4096usize;
for strat in [
StrategyTag::Fast,
StrategyTag::Dfast,
StrategyTag::Greedy,
StrategyTag::Lazy,
StrategyTag::Btlazy2,
StrategyTag::BtOpt,
] {
assert_eq!(min_gain(src, strat), (src >> 6) + 2);
}
assert_eq!(min_gain(src, StrategyTag::BtUltra), (src >> 7) + 2);
assert_eq!(min_gain(src, StrategyTag::BtUltra2), (src >> 8) + 2);
assert_eq!(min_gain(0, StrategyTag::Fast), 2);
assert_eq!(min_gain(63, StrategyTag::Fast), 2);
assert_eq!(min_gain(64, StrategyTag::Fast), 3);
}
#[test]
fn use_raw_literal_fallback_uses_payload_vs_srcsize_threshold() {
use super::{compressed_literals_header_bytes, use_raw_literal_fallback};
let strategy = StrategyTag::Fast;
let literals_len = 20usize;
assert_eq!(compressed_literals_header_bytes(literals_len), 3);
assert_eq!(super::uncompressed_literals_header_bytes(literals_len), 1);
assert!(!use_raw_literal_fallback(16, literals_len, strategy));
assert!(!use_raw_literal_fallback(17, literals_len, strategy));
assert!(use_raw_literal_fallback(18, literals_len, strategy));
assert!(use_raw_literal_fallback(19, literals_len, strategy));
assert!(!use_raw_literal_fallback(15, literals_len, strategy));
assert!(!use_raw_literal_fallback(0, literals_len, strategy));
}
#[test]
fn prefer_repeat_eligible_applies_gate() {
use super::prefer_repeat_eligible;
for lit_len in [0usize, 1, 64, 256, 1024] {
assert!(
prefer_repeat_eligible(StrategyTag::Fast, lit_len),
"Fast/{lit_len}"
);
assert!(
prefer_repeat_eligible(StrategyTag::Dfast, lit_len),
"Dfast/{lit_len}"
);
assert!(
prefer_repeat_eligible(StrategyTag::Greedy, lit_len),
"Greedy/{lit_len}"
);
assert!(
!prefer_repeat_eligible(StrategyTag::Lazy, lit_len),
"Lazy/{lit_len}"
);
assert!(
!prefer_repeat_eligible(StrategyTag::Btlazy2, lit_len),
"Btlazy2/{lit_len}"
);
assert!(
!prefer_repeat_eligible(StrategyTag::BtOpt, lit_len),
"BtOpt/{lit_len}"
);
assert!(
!prefer_repeat_eligible(StrategyTag::BtUltra, lit_len),
"BtUltra/{lit_len}"
);
assert!(
!prefer_repeat_eligible(StrategyTag::BtUltra2, lit_len),
"BtUltra2/{lit_len}"
);
}
for lit_len in [1025usize, 2048, 16384] {
assert!(!prefer_repeat_eligible(StrategyTag::Fast, lit_len));
assert!(!prefer_repeat_eligible(StrategyTag::Dfast, lit_len));
assert!(!prefer_repeat_eligible(StrategyTag::Greedy, lit_len));
assert!(!prefer_repeat_eligible(StrategyTag::Btlazy2, lit_len));
}
}
#[test]
fn decide_huff_reuse_prefer_repeat_forces_reuse_for_fast_band() {
use super::{decide_huff_reuse_like_encoder, huff0_encoder};
let prev_training: Vec<u8> = (0..1024u32).map(|i| (i % 256) as u8).collect();
let prev = huff0_encoder::HuffmanTable::build_from_data(&prev_training);
let mut skewed_literals: Vec<u8> = Vec::with_capacity(256);
skewed_literals.extend(core::iter::repeat_n(0u8, 240));
skewed_literals.extend((0..16u8).map(|i| 200 + i));
let mut new_tbl = huff0_encoder::HuffmanTable::build_from_data(&skewed_literals);
let new_desc = new_tbl
.writeable_table_description_size(&mut crate::fse::fse_encoder::FSETable::blank())
.expect("non-empty table emits a description");
let counts_of = |bytes: &[u8]| -> [usize; 256] {
let mut counts = [0usize; 256];
for &b in bytes {
counts[b as usize] += 1;
}
counts
};
let skewed_counts = counts_of(&skewed_literals);
assert!(
decide_huff_reuse_like_encoder(
&new_tbl,
Some(&prev),
new_desc,
&skewed_literals,
&skewed_counts,
StrategyTag::Lazy,
),
"fixture precondition: size-comparison must prefer new for Lazy on skewed literals"
);
for strategy in [StrategyTag::Fast, StrategyTag::Dfast, StrategyTag::Greedy] {
assert!(
!decide_huff_reuse_like_encoder(
&new_tbl,
Some(&prev),
new_desc,
&skewed_literals,
&skewed_counts,
strategy,
),
"{strategy:?} <= 1024 must short-circuit to reuse despite size-comparison favouring new"
);
}
let mut big_skewed = skewed_literals.clone();
big_skewed.extend(core::iter::repeat_n(0u8, 1024));
assert!(
big_skewed.len() > 1024,
"fixture must exceed 1024 to disable preferRepeat"
);
assert!(
decide_huff_reuse_like_encoder(
&new_tbl,
Some(&prev),
new_desc,
&big_skewed,
&counts_of(&big_skewed),
StrategyTag::Fast,
),
"Fast at len > 1024 must NOT short-circuit (gate disabled), falls through to size heuristic"
);
}
#[test]
fn estimator_literals_section_mirrors_emit_for_short_inputs() {
use super::{
CompressedBlockScratch, EntropyOnlyMatcher, EstimatorWorkspace, encode_block_parts,
estimate_block_parts_size,
};
type Inputs = &'static [(usize, bool)];
let cases: &[(StrategyTag, bool, Inputs)] = &[
(
StrategyTag::Fast,
false,
&[
(1, true), (5, true), (8, true),
(8, false),
(63, true),
(63, false),
(64, false),
],
),
(
StrategyTag::BtUltra2,
false,
&[(7, true), (7, false), (8, true), (8, false), (16, false)],
),
(
StrategyTag::BtOpt,
false,
&[(8, true), (31, true), (32, false)],
),
(
StrategyTag::Lazy,
true,
&[(6, true), (7, true), (6, false), (16, false)],
),
];
for (strat, seed_huff, inputs) in cases {
for (len, identical) in *inputs {
let literals: Vec<u8> = if *identical {
alloc::vec![0x5Au8; *len]
} else {
(0..*len as u8).collect()
};
let seed_table = if *seed_huff {
let mut counts = [0usize; 256];
for b in (0..=63u8).chain(64..=127u8) {
counts[b as usize] = 1;
}
Some(huff0_encoder::HuffmanTable::build_from_counts(&counts))
} else {
None
};
let mut est_state = CompressState::<EntropyOnlyMatcher> {
matcher: EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: seed_table.clone(),
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [1, 4, 8],
strategy_tag: *strat,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let mut emit_state = CompressState::<EntropyOnlyMatcher> {
matcher: EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: seed_table,
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [1, 4, 8],
strategy_tag: *strat,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let mut workspace = EstimatorWorkspace::default();
let est = estimate_block_parts_size(&mut est_state, &literals, &[], &mut workspace);
let mut emitted: Vec<u8> = Vec::new();
let mut codes_ws = Workspace::new();
encode_block_parts(
&mut emit_state,
&literals,
&mut [],
&mut code_buffer(&mut codes_ws, 0),
&mut emitted,
);
assert_eq!(
est,
emitted.len(),
"estimator/emit parity broken: strategy={:?} seed_huff={} len={} identical={} est={} emit={}",
strat,
seed_huff,
len,
identical,
est,
emitted.len(),
);
}
}
}
#[test]
fn a_section_with_flat_ends_costs_what_the_emitter_writes_for_it() {
use super::{
CompressedBlockScratch, EntropyOnlyMatcher, EstimatorWorkspace, encode_block_parts,
estimate_block_parts_size,
};
let mut literals: Vec<u8> = (0..4096u32).map(|i| (i % 256) as u8).collect();
literals.extend(core::iter::repeat_n(0u8, 40_000));
literals.extend((0..4096u32).map(|i| (i % 256) as u8));
let make_state = || CompressState::<EntropyOnlyMatcher> {
matcher: EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: None,
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [1, 4, 8],
strategy_tag: StrategyTag::Lazy,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let mut est_state = make_state();
let mut emit_state = make_state();
let mut workspace = EstimatorWorkspace::default();
let est = estimate_block_parts_size(&mut est_state, &literals, &[], &mut workspace);
let mut emitted: Vec<u8> = Vec::new();
let mut codes_ws = Workspace::new();
encode_block_parts(
&mut emit_state,
&literals,
&mut [],
&mut code_buffer(&mut codes_ws, 0),
&mut emitted,
);
assert_eq!(
est,
emitted.len(),
"estimator priced a flat-ended section at {est} bytes, emitter wrote {}",
emitted.len(),
);
assert!(
emitted.len() >= literals.len(),
"expected a raw literals section, got {} bytes for {} literals",
emitted.len(),
literals.len(),
);
assert!(est_state.last_huff_table.is_none());
assert!(emit_state.last_huff_table.is_none());
}
#[test]
fn encode_match_len_uses_correct_upper_range_base() {
assert_eq!(encode_match_len(65539), (52, 0, 16));
assert_eq!(encode_match_len(65540), (52, 1, 16));
assert_eq!(encode_match_len(131074), (52, 65535, 16));
}
#[test]
fn retained_heap_size_counts_the_block_buffers() {
let mut state = CompressState {
matcher: super::EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: None,
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: super::CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [1, 4, 8],
strategy_tag: StrategyTag::Fast,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let before = state.retained_scratch_heap_size();
let block = 64 * 1024;
state.reset_for_frame(
crate::encoding::CompressionLevel::Fastest,
block,
crate::encoding::workspace::IngestPlan::Stream,
);
state.finish_layout(block);
let after = state.retained_scratch_heap_size();
let needed = super::CompressedBlockScratch::workspace_bytes(block);
assert!(
after - before >= needed,
"laying out {needed} bytes of block buffers grew the reported retained size by only {} bytes",
after - before,
);
}
fn code_buffer(ws: &mut Workspace, count: usize) -> RegionVec<u32> {
ws.begin_layout(
region_bytes::<u32>(count),
|bytes| bytes,
crate::encoding::workspace::IngestPlan::Stream,
);
ws.open(0, usize::MAX);
ws.buffer(count)
}
#[test]
fn collected_fast_band_sequences_carry_the_fast_derivation() {
use super::{Sequence, encode_offset_with_history_fast, record_sequence};
let reported = [
(5usize, 100usize, 9usize),
(3, 100, 6),
(0, 4, 7),
(2, 4, 5),
(0, 100, 8),
(7, 250, 4),
(1, 1, 4),
];
let mut expected_hist = [1u32, 4, 8];
let expected: Vec<u32> = reported
.iter()
.map(|&(ll, off, _)| {
encode_offset_with_history_fast(off as u32, ll as u32, &mut expected_hist)
})
.collect();
assert!(expected.contains(&1) && expected.iter().any(|&code| code > 3));
for coded in [true, false] {
let mut ws = Workspace::new();
ws.begin_layout(
region_bytes::<RawSequence>(reported.len()),
|bytes| bytes,
crate::encoding::workspace::IngestPlan::Stream,
);
ws.open(0, usize::MAX);
let mut sequences: RegionVec<RawSequence> = ws.buffer(reported.len());
let mut tail = 0usize;
let mut hist = [1u32, 4, 8];
for &(literal_len, offset, match_len) in &reported {
let seq = Sequence::Triple {
literal_len,
offset,
match_len,
};
record_sequence(seq, &mut tail, &mut sequences, coded, &mut hist);
}
let trailing = Sequence::Literals { len: 11 };
record_sequence(trailing, &mut tail, &mut sequences, coded, &mut hist);
assert_eq!(tail, 11);
let off_bases: Vec<u32> = sequences.iter().map(|seq| seq.off_base).collect();
if coded {
assert_eq!(off_bases, expected);
assert_eq!(hist, expected_hist);
} else {
let raw: Vec<u32> = reported.iter().map(|&(_, off, _)| off as u32).collect();
assert_eq!(off_bases, raw);
assert_eq!(hist, [1, 4, 8]);
}
let lengths: Vec<(u32, u32)> = sequences.iter().map(|seq| (seq.ll, seq.ml)).collect();
let reported_lengths: Vec<(u32, u32)> = reported
.iter()
.map(|&(ll, _, ml)| (ll as u32, ml as u32))
.collect();
assert_eq!(lengths, reported_lengths);
}
}
#[test]
fn estimator_and_emitter_agree_on_a_block_with_sequences() {
use super::{
CompressedBlockScratch, EntropyOnlyMatcher, EstimatorWorkspace, encode_block_parts,
estimate_block_parts_size,
};
let literals: Vec<u8> = (0..512u32).map(|i| (i % 251) as u8).collect();
let sequences: Vec<RawSequence> = (0..64u32)
.map(|i| RawSequence {
ll: i % 8,
ml: 4 + i % 13,
off_base: 1 + (i % 5) * 7,
})
.collect();
for strat in [StrategyTag::Lazy, StrategyTag::BtUltra2] {
let make_state = || CompressState::<EntropyOnlyMatcher> {
matcher: EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: None,
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [1, 4, 8],
strategy_tag: strat,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let mut est_state = make_state();
let mut emit_state = make_state();
let mut workspace = EstimatorWorkspace::default();
let est = estimate_block_parts_size(&mut est_state, &literals, &sequences, &mut workspace);
let mut emitted: Vec<u8> = Vec::new();
let mut to_emit = sequences.clone();
let mut codes_ws = Workspace::new();
let mut codes = code_buffer(&mut codes_ws, to_emit.len());
encode_block_parts(
&mut emit_state,
&literals,
&mut to_emit,
&mut codes,
&mut emitted,
);
assert_eq!(
est_state.offset_hist, emit_state.offset_hist,
"estimator and emitter disagree on the repeat-offset history at {strat:?}",
);
assert!(emitted.len() > literals.len() / 2);
let allowed = 4 + sequences.len() / 16;
let diff = est.abs_diff(emitted.len());
assert!(
diff <= allowed,
"estimator priced {est} bytes against {} emitted for {} sequences at {strat:?}: \
off by {diff}, more than the {allowed} the cost model can round away",
emitted.len(),
sequences.len(),
);
}
}
#[test]
fn raw_partition_fallback_restores_repeat_offset_history() {
let mut state = CompressState {
matcher: super::EntropyOnlyMatcher,
copy_kernel: crate::encoding::fastpath::select_kernel(),
last_huff_table: None,
huff_table_spare: None,
huff_rollback: None,
huff_weights: Default::default(),
seen_content: Default::default(),
fse_tables: FseTables::new(),
block_scratch: super::CompressedBlockScratch::new(),
workspace: Workspace::new(),
offset_hist: [10, 20, 30],
strategy_tag: crate::encoding::strategy::StrategyTag::BtUltra2,
pre_split: None,
huf_optimal_search: true,
literal_compression_disabled: false,
post_split_allowed: true,
};
let source = [0xA5; 8];
let mut sequences = [RawSequence {
ll: 0,
ml: 5,
off_base: 20,
}];
let mut output = Vec::new();
let mut compressed_scratch = Vec::new();
let mut codes_ws = Workspace::new();
let mut code_scratch = code_buffer(&mut codes_ws, sequences.len());
let mut emit_buffers = super::SingleSequenceEmitBuffers {
output: &mut output,
compressed: &mut compressed_scratch,
codes: &mut code_scratch,
};
let emitted_raw = emit_single_sequence_block(
&mut state,
true,
source.len(),
&[],
&mut sequences,
&mut emit_buffers,
);
if emitted_raw {
output.extend_from_slice(&source);
}
assert_eq!(
state.offset_hist,
[10, 20, 30],
"raw post-split fallback must not advance decoder repeat-offset history"
);
assert_eq!(
(output[0] >> 1) & 0b11,
0,
"fixture should force the partition to fall back to a Raw block"
);
}
#[test]
fn a_predefined_or_rle_block_parks_the_custom_table_it_replaces() {
use crate::encoding::frame_compressor::SharedFseTable;
use crate::fse::fse_encoder::FSETable;
for decision in [LastUsedTable::Default, LastUsedTable::Rle(7)] {
let mut previous = Some(PreviousFseTable::Custom(SharedFseTable::new(
FSETable::blank(),
)));
let mut next = None;
super::commit_last_used_table(&mut previous, &mut next, decision);
assert!(
next.is_some(),
"{decision:?}: the displaced custom table is the next block's buffer",
);
}
}
#[test]
fn a_block_that_falls_back_to_raw_leaves_its_axis_reusable() {
use crate::encoding::frame_compressor::SharedFseTable;
use crate::fse::fse_encoder::FSETable;
let mut fse_tables = FseTables::new();
let before = SharedFseTable::new(FSETable::blank());
fse_tables.ll_previous = Some(PreviousFseTable::Custom(before));
let saved = fse_tables.ll_previous.clone();
fse_tables.ll_next = Some(SharedFseTable::new(FSETable::blank()));
super::commit_last_used_table(
&mut fse_tables.ll_previous,
&mut fse_tables.ll_next,
LastUsedTable::Encoded,
);
fse_tables.roll_back_confirmation([saved, None, None]);
let Some(PreviousFseTable::Custom(restored)) = fse_tables.ll_previous.as_ref() else {
panic!("the restored table must be the custom one the axis started with");
};
assert_eq!(
SharedFseTable::strong_count(restored),
1,
"the restored table must be unique, or the next block cannot build into its slot",
);
assert!(
fse_tables.ll_next.is_some(),
"the discarded table is exactly the buffer the next block wants",
);
}
#[test]
fn a_frame_start_keeps_the_last_frames_table_as_a_buffer() {
use crate::encoding::frame_compressor::SharedFseTable;
use crate::fse::fse_encoder::FSETable;
let mut fse_tables = FseTables::new();
fse_tables.ll_previous = Some(PreviousFseTable::Custom(SharedFseTable::new(
FSETable::blank(),
)));
let shared = SharedFseTable::new(FSETable::blank());
let _cache_holds_it = shared.clone();
fse_tables.ml_previous = Some(PreviousFseTable::Custom(shared));
fse_tables.park_previous_before_frame();
assert!(
fse_tables.ll_next.is_some(),
"the frame's own table is what the next one builds into",
);
assert!(
fse_tables.ml_next.is_none(),
"a table the dictionary cache still holds is not a free buffer",
);
}
#[test]
fn remember_last_used_tables_keeps_predefined_and_repeat_modes() {
let mut fse_tables = FseTables::new();
remember_last_used_tables(
&mut fse_tables,
[
LastUsedTable::Default,
LastUsedTable::Default,
LastUsedTable::Default,
],
);
assert!(tables_match(
previous_table(fse_tables.ll_previous.as_ref(), fse_tables.ll_default_ref()).unwrap(),
fse_tables.ll_default_ref()
));
assert!(tables_match(
previous_table(fse_tables.ml_previous.as_ref(), fse_tables.ml_default_ref()).unwrap(),
fse_tables.ml_default_ref()
));
assert!(tables_match(
previous_table(fse_tables.of_previous.as_ref(), fse_tables.of_default_ref()).unwrap(),
fse_tables.of_default_ref()
));
let sample_codes = [0u8, 1u8];
let strat = crate::encoding::strategy::StrategyTag::Lazy;
let mut ll_slot = None;
let mut ml_slot = None;
let mut of_slot = None;
let ll_repeat = choose_table(
fse_tables.ll_previous.as_ref(),
fse_tables.ll_default_ref(),
sample_codes.iter().copied(),
9,
strat,
&mut ll_slot,
);
let ml_repeat = choose_table(
fse_tables.ml_previous.as_ref(),
fse_tables.ml_default_ref(),
sample_codes.iter().copied(),
9,
strat,
&mut ml_slot,
);
let of_repeat = choose_table(
fse_tables.of_previous.as_ref(),
fse_tables.of_default_ref(),
sample_codes.iter().copied(),
8,
strat,
&mut of_slot,
);
assert!(matches!(ll_repeat, FseTableMode::RepeatLast(_)));
assert!(matches!(ml_repeat, FseTableMode::RepeatLast(_)));
assert!(matches!(of_repeat, FseTableMode::RepeatLast(_)));
}
#[test]
fn fast_band_strategies_prefer_repeat_fse_table() {
use crate::encoding::strategy::StrategyTag;
let prev = build_table_from_symbol_counts(&[8, 1], 9, false);
let previous =
PreviousFseTable::Custom(crate::encoding::frame_compressor::SharedFseTable::new(prev));
let fse_tables = FseTables::new();
let mut counts = [0usize; 256];
counts[0] = 4;
counts[1] = 6;
let total = 10;
for strategy in [StrategyTag::Fast, StrategyTag::Dfast, StrategyTag::Greedy] {
let mut slot = None;
let mode = super::choose_table_from_counts(
Some(&previous),
fse_tables.ll_default_ref(),
&mut counts,
total,
1, 9,
strategy,
None,
&mut slot,
);
assert!(
matches!(mode, FseTableMode::RepeatLast(_)),
"fast-band {strategy:?} must reuse the covering previous table",
);
}
}
#[test]
fn remember_last_used_tables_reuses_existing_custom_slot_for_repeat() {
let mut fse_tables = FseTables::new();
let custom = build_table_from_symbol_counts(&[1, 1], 5, false);
fse_tables.ll_previous = Some(PreviousFseTable::Custom(
crate::encoding::frame_compressor::SharedFseTable::new(custom),
));
let before = core::ptr::from_ref(
previous_table(fse_tables.ll_previous.as_ref(), fse_tables.ll_default_ref()).unwrap(),
);
remember_last_used_tables(
&mut fse_tables,
[
LastUsedTable::Keep,
LastUsedTable::Default,
LastUsedTable::Default,
],
);
let after = core::ptr::from_ref(
previous_table(fse_tables.ll_previous.as_ref(), fse_tables.ll_default_ref()).unwrap(),
);
assert_eq!(before, after);
assert!(matches!(
fse_tables.ll_previous.as_ref(),
Some(PreviousFseTable::Custom(_))
));
}
#[test]
fn choose_table_handles_single_symbol_distribution() {
let fse_tables = FseTables::new();
let mut slot = None;
let mode = choose_table(
None,
fse_tables.ll_default_ref(),
core::iter::repeat_n(0u8, 32),
9,
crate::encoding::strategy::StrategyTag::Lazy,
&mut slot,
);
assert!(matches!(mode, FseTableMode::Rle(0)));
}
fn literal_length_ranges(len: u32) -> (u8, u32, usize) {
match len {
0..=15 => (len as u8, 0, 0),
16..=17 => (16, len - 16, 1),
18..=19 => (17, len - 18, 1),
20..=21 => (18, len - 20, 1),
22..=23 => (19, len - 22, 1),
24..=27 => (20, len - 24, 2),
28..=31 => (21, len - 28, 2),
32..=39 => (22, len - 32, 3),
40..=47 => (23, len - 40, 3),
48..=63 => (24, len - 48, 4),
64..=127 => (25, len - 64, 6),
128..=255 => (26, len - 128, 7),
256..=511 => (27, len - 256, 8),
512..=1023 => (28, len - 512, 9),
1024..=2047 => (29, len - 1024, 10),
2048..=4095 => (30, len - 2048, 11),
4096..=8191 => (31, len - 4096, 12),
8192..=16383 => (32, len - 8192, 13),
16384..=32767 => (33, len - 16384, 14),
32768..=65535 => (34, len - 32768, 15),
65536..=131071 => (35, len - 65536, 16),
131072.. => unreachable!(),
}
}
fn match_len_ranges(len: u32) -> (u8, u32, usize) {
match len {
0..=2 => unreachable!(),
3..=34 => (len as u8 - 3, 0, 0),
35..=36 => (32, len - 35, 1),
37..=38 => (33, len - 37, 1),
39..=40 => (34, len - 39, 1),
41..=42 => (35, len - 41, 1),
43..=46 => (36, len - 43, 2),
47..=50 => (37, len - 47, 2),
51..=58 => (38, len - 51, 3),
59..=66 => (39, len - 59, 3),
67..=82 => (40, len - 67, 4),
83..=98 => (41, len - 83, 4),
99..=130 => (42, len - 99, 5),
131..=258 => (43, len - 131, 7),
259..=514 => (44, len - 259, 8),
515..=1026 => (45, len - 515, 9),
1027..=2050 => (46, len - 1027, 10),
2051..=4098 => (47, len - 2051, 11),
4099..=8194 => (48, len - 4099, 12),
8195..=16386 => (49, len - 8195, 13),
16387..=32770 => (50, len - 16387, 14),
32771..=65538 => (51, len - 32771, 15),
65539..=131074 => (52, len - 65539, 16),
131075.. => unreachable!(),
}
}
#[test]
fn literal_length_coding_agrees_with_the_ranges_over_every_length() {
for len in 0..131_072u32 {
assert_eq!(
encode_literal_length(len),
literal_length_ranges(len),
"literal length {len}",
);
}
}
#[test]
fn match_length_coding_agrees_with_the_ranges_over_every_length() {
for len in 3..131_075u32 {
assert_eq!(
encode_match_len(len),
match_len_ranges(len),
"match length {len}"
);
}
}
fn literals_buffer(capacity: usize) -> (Workspace, RegionVec<u8>) {
let mut ws = Workspace::new();
ws.begin_layout(
region_bytes::<u8>(capacity),
|bytes| bytes,
crate::encoding::workspace::IngestPlan::Stream,
);
ws.open(0, usize::MAX);
let literals: RegionVec<u8> = ws.buffer(capacity);
(ws, literals)
}
#[test]
#[should_panic(expected = "workspace buffer sized below what the frame put in it")]
fn literals_past_the_buffer_stop_instead_of_overflowing() {
let (_ws, mut literals) = literals_buffer(4);
super::literal_runs::gather_literals(
crate::encoding::fastpath::FastpathKernel::Scalar,
&[0xA5; 8],
&[],
8,
&mut literals,
);
}
#[test]
#[should_panic(expected = "sequences reach past the block they describe")]
fn sequences_past_the_block_are_refused() {
let (_ws, mut literals) = literals_buffer(64);
let sequences = [RawSequence {
ll: 4,
ml: 60,
off_base: 1,
}];
super::literal_runs::gather_literals(
crate::encoding::fastpath::FastpathKernel::Scalar,
&[0x5A; 32],
&sequences,
0,
&mut literals,
);
}
fn runnable_fastpath_kernels() -> Vec<crate::encoding::fastpath::FastpathKernel> {
use crate::encoding::fastpath::FastpathKernel;
#[allow(unused_mut)]
let mut kernels = alloc::vec![FastpathKernel::Scalar];
#[cfg(all(
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
if std::is_x86_feature_detected!("sse2") {
kernels.push(FastpathKernel::Sse2);
if std::is_x86_feature_detected!("sse4.2") {
kernels.push(FastpathKernel::Sse42);
}
}
#[cfg(all(
feature = "std",
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
if std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("bmi2") {
kernels.push(FastpathKernel::Avx2Bmi2);
}
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
feature = "kernel-neon"
))]
kernels.push(FastpathKernel::Neon);
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
feature = "kernel-simd128"
))]
kernels.push(FastpathKernel::Simd128);
kernels
}
#[test]
fn gathered_literals_are_the_runs_in_order() {
let lengths = [
0usize, 1, 7, 8, 9, 16, 17, 31, 32, 33, 47, 64, 65, 127, 128, 129, 800, 2047, 2048, 3000,
];
let block_len: usize = lengths.iter().map(|&ll| ll + 5).sum::<usize>() + 13;
let block: Vec<u8> = (0..block_len as u32)
.map(|i| (i.wrapping_mul(2654435761) >> 24) as u8)
.collect();
let sequences: Vec<RawSequence> = lengths
.iter()
.map(|&ll| RawSequence {
ll: ll as u32,
ml: 5,
off_base: 1,
})
.collect();
let mut expected = Vec::new();
let mut pos = 0;
for &ll in &lengths {
expected.extend_from_slice(&block[pos..pos + ll]);
pos += ll + 5;
}
expected.extend_from_slice(&block[pos..]);
for kernel in runnable_fastpath_kernels() {
let (_ws, mut literals) = literals_buffer(block_len);
super::literal_runs::gather_literals(kernel, &block, &sequences, 13, &mut literals);
assert_eq!(&literals[..], &expected[..], "{kernel:?}");
}
}
#[test]
fn choose_table_without_previous_does_not_unwrap_none() {
let only_zero_one_table = build_table_from_symbol_counts(&[1, 1], 5, false);
let mut slot = None;
let mode = choose_table(
None,
&only_zero_one_table,
[1u8, 2].into_iter().cycle().take(32),
5,
crate::encoding::strategy::StrategyTag::Lazy,
&mut slot,
);
assert!(matches!(mode, FseTableMode::Encoded(_)));
}