use super::FinalizeOptions;
use super::samples::{SampleSet, TrainingError, refuse};
use crate::bit_io::BitWriter;
use crate::decoding::dictionary::MAGIC_NUM as DICT_MAGIC_NUM;
use crate::encoding::blocks::{
encode_literal_length, encode_match_len, encode_offset, encode_offset_with_history,
encode_offset_with_history_fast,
};
use crate::encoding::workspace::Workspace;
use crate::encoding::{
CompressionLevel, DictionarySizes, EncoderDictionary, FrameCompressor, HistoryBuf,
MatchGeneratorDriver, Matcher, Sequence,
};
use crate::fse::fse_encoder::{FSETable, write_ncount_at_log};
use crate::huff0::huff0_encoder::{HuffmanEncoder, HuffmanTable, WeightScratch};
use std::{io, vec::Vec};
pub(super) const DICT_SIZE_MIN: usize = 256;
const START_REPS: [u32; 3] = [1, 4, 8];
const MIN_CONTENT_SIZE: usize = 8;
const HUF_MAX_BITS: usize = 11;
const OF_LOG: u8 = 8;
const ML_LOG: u8 = 9;
const LL_LOG: u8 = 9;
const OFFCODE_MAX: u32 = 30;
const BLOCK_SIZE_MAX: usize = 128 << 10;
pub(super) fn finalize<'s>(
content: &[u8],
samples: &SampleSet<'s>,
count: usize,
dict_size: usize,
options: FinalizeOptions,
) -> io::Result<Vec<u8>> {
let mut out = Vec::new();
let mut analysis = Analysis::default();
finalize_into(
&mut out,
&mut analysis,
content,
samples,
count,
dict_size,
options,
)?;
Ok(out)
}
type AnalysisCompressor<'s> = FrameCompressor<&'s [u8], Vec<u8>, Recorder>;
#[derive(Default)]
pub(super) struct Analysis<'s> {
compressor: Option<(i32, AnalysisCompressor<'s>)>,
frame: Vec<u8>,
compressed: Vec<bool>,
huffman: WeightScratch,
}
pub(super) fn finalize_into<'s>(
out: &mut Vec<u8>,
analysis: &mut Analysis<'s>,
content: &[u8],
samples: &SampleSet<'s>,
count: usize,
dict_size: usize,
options: FinalizeOptions,
) -> io::Result<()> {
check_size(dict_size)?;
if content.is_empty() {
return Err(invalid("raw dictionary content must not be empty"));
}
let dict_id = options.dict_id.unwrap_or_else(|| derive_dict_id(content));
if dict_id == 0 {
return Err(invalid("dictionary id must be non-zero"));
}
out.clear();
out.reserve(dict_size);
out.extend_from_slice(&DICT_MAGIC_NUM);
out.extend_from_slice(&dict_id.to_le_bytes());
analyze_entropy(out, analysis, content, samples, count, options.level)?;
for rep in START_REPS {
out.extend_from_slice(&rep.to_le_bytes());
}
let room = match dict_size.checked_sub(out.len()) {
Some(room) if room >= MIN_CONTENT_SIZE => room,
_ => return Err(too_small()),
};
let content = &content[content.len() - content.len().min(room)..];
if content.len() < MIN_CONTENT_SIZE {
out.resize(out.len() + MIN_CONTENT_SIZE - content.len(), 0);
}
out.extend_from_slice(content);
Ok(())
}
pub(super) fn check_size(dict_size: usize) -> io::Result<()> {
if dict_size < DICT_SIZE_MIN {
return Err(too_small());
}
Ok(())
}
fn too_small() -> io::Error {
refuse(
TrainingError::DictionaryTooSmall,
"dictionary size too small to fit header and offset history",
)
}
fn invalid(reason: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, reason)
}
fn derive_dict_id(raw_content: &[u8]) -> u32 {
let mut h = 0xcbf2_9ce4_8422_2325u64;
for &b in raw_content {
h ^= u64::from(b);
h = h.wrapping_mul(0x0100_0000_01b3);
}
((h % ((1u64 << 31) - 32768)) + 32768) as u32
}
struct EntropyCounts {
literals: [usize; 256],
offset_codes: [usize; 32],
match_lengths: [usize; 53],
literal_lengths: [usize; 36],
}
fn analyze_entropy<'s>(
out: &mut Vec<u8>,
analysis: &mut Analysis<'s>,
content: &[u8],
samples: &SampleSet<'s>,
count: usize,
level: i32,
) -> io::Result<()> {
let reach = content.len() as u64 + BLOCK_SIZE_MAX as u64;
let offcode_max = reach.ilog2();
if offcode_max > OFFCODE_MAX {
return Err(invalid(
"dictionary content is too large to describe its offsets",
));
}
let mut counts = EntropyCounts {
literals: [1; 256],
offset_codes: [1; 32],
match_lengths: [1; 53],
literal_lengths: [1; 36],
};
count_samples(&mut counts, analysis, content, samples, count, level)?;
let huffman = &mut analysis.huffman;
let mut literals = literals_table(&mut counts.literals, huffman);
literals.fill_weight_description_from_codes(huffman.weight_fse_table());
let mut writer = BitWriter::from(&mut *out);
HuffmanEncoder::new(&literals, &mut writer).write_table();
huffman.recycle(literals);
write_ncount_at_log(
&counts.offset_codes[..=offcode_max as usize],
OF_LOG,
&mut writer,
);
write_ncount_at_log(&counts.match_lengths, ML_LOG, &mut writer);
write_ncount_at_log(&counts.literal_lengths, LL_LOG, &mut writer);
writer.flush();
Ok(())
}
fn literals_table(counts: &mut [usize; 256], scratch: &mut WeightScratch) -> HuffmanTable {
while counts.iter().map(|&count| count as u64).sum::<u64>() >= u64::from(u32::MAX) {
for count in counts.iter_mut() {
*count = count.div_ceil(2);
}
}
let literals = HuffmanTable::build_limited_in(counts, HUF_MAX_BITS, scratch);
if literals.table_log() != 8 {
return literals;
}
*counts = [2; 256];
counts[0] = 4;
counts[253] = 1;
counts[254] = 1;
scratch.recycle(literals);
let flat = HuffmanTable::build_limited_in(counts, HUF_MAX_BITS, scratch);
debug_assert_eq!(flat.table_log(), 9);
flat
}
fn count_samples<'s>(
counts: &mut EntropyCounts,
analysis: &mut Analysis<'s>,
content: &[u8],
samples: &SampleSet<'s>,
count: usize,
level: i32,
) -> io::Result<()> {
let dictionary = crate::decoding::Dictionary::from_raw_content(0, content.to_vec())
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let Analysis {
compressor,
frame,
compressed,
..
} = analysis;
if compressor.as_ref().is_none_or(|(built, _)| *built != level) {
let recorder = Recorder {
inner: MatchGeneratorDriver::new(BLOCK_SIZE_MAX, 1),
blocks: Vec::new(),
sequences: Vec::new(),
};
let mut built =
FrameCompressor::new_with_matcher(recorder, CompressionLevel::from_level(level));
built.forbid_post_split();
*compressor = Some((level, built));
}
let (_, compressor) = compressor.as_mut().expect("built above");
compressor
.set_encoder_dictionary(EncoderDictionary::from_dictionary(dictionary))
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let average = samples.leading(count).len() / count.max(1);
for index in 0..count {
let sample = samples.sample(index);
let block = &sample[..sample.len().min(BLOCK_SIZE_MAX)];
if block.is_empty() {
continue;
}
compressor.set_source(block);
compressor.set_source_size_hint(average as u64);
compressor.compress_known_into(block.len() as u64, frame);
let fast_offset_codes = compressor.uses_fast_offset_codes();
compressed_blocks(frame, compressed);
let recorder = compressor.matcher_mut();
debug_assert_eq!(compressed.len(), recorder.blocks.len());
let mut reps = START_REPS;
let mut start = 0;
for (block, &counted) in recorder.blocks.iter().zip(compressed.iter()) {
let block_sequences = &recorder.sequences[start..block.sequences_end];
start = block.sequences_end;
if !counted {
continue;
}
for (total, &seen) in counts.literals.iter_mut().zip(&block.literals) {
*total += seen as usize;
}
for &(ll, offset, ml) in block_sequences {
let off_base = if fast_offset_codes {
encode_offset_with_history_fast(offset, ll, &mut reps)
} else {
encode_offset_with_history(offset, ll, &mut reps)
};
counts.literal_lengths[encode_literal_length(ll).0 as usize] += 1;
counts.match_lengths[encode_match_len(ml).0 as usize] += 1;
let code = encode_offset(off_base).0 as usize;
debug_assert!(code < counts.offset_codes.len());
counts.offset_codes[code] += 1;
}
}
recorder.blocks.clear();
recorder.sequences.clear();
}
Ok(())
}
fn compressed_blocks(frame: &[u8], out: &mut Vec<bool>) {
out.clear();
let Ok((_, header_len)) = crate::decoding::frame::read_frame_header_with_format(frame, false)
else {
return;
};
let mut at = usize::from(header_len);
while let Some(header) = frame.get(at..at + 3) {
let word = u32::from(header[0]) | u32::from(header[1]) << 8 | u32::from(header[2]) << 16;
let kind = (word >> 1) & 3;
out.push(kind == 2);
if word & 1 == 1 {
return;
}
let body = if kind == 1 { 1 } else { (word >> 3) as usize };
at += 3 + body;
}
}
struct Recorder {
inner: MatchGeneratorDriver,
blocks: Vec<RecordedBlock>,
sequences: Vec<(u32, u32, u32)>,
}
struct RecordedBlock {
literals: [u32; 256],
sequences_end: usize,
}
impl Recorder {
fn record_skipped(&mut self) {
self.blocks.push(RecordedBlock {
literals: [0; 256],
sequences_end: self.sequences.len(),
});
}
}
impl Matcher for Recorder {
fn get_last_space(&mut self) -> &[u8] {
self.inner.get_last_space()
}
fn fill_in_place(
&mut self,
capacity: usize,
fill: &mut dyn FnMut(&mut HistoryBuf) -> (usize, bool),
) -> (usize, bool) {
self.inner.fill_in_place(capacity, fill)
}
fn uncommitted_input(&self) -> &[u8] {
self.inner.uncommitted_input()
}
fn commit_filled(&mut self, len: usize) {
self.inner.commit_filled(len);
}
fn skip_matching(&mut self) {
self.inner.skip_matching();
self.record_skipped();
}
fn skip_matching_with_hint(&mut self, incompressible_hint: Option<bool>) {
self.inner.skip_matching_with_hint(incompressible_hint);
self.record_skipped();
}
fn start_matching(&mut self, mut handle_sequence: impl FnMut(Sequence)) {
let block_start = self.sequences.len();
let mut tail = 0usize;
let Self {
inner, sequences, ..
} = self;
inner.start_matching(|sequence| {
match sequence {
Sequence::Literals { len } => tail += len,
Sequence::Triple {
literal_len,
offset,
match_len,
} => {
sequences.push((literal_len as u32, offset as u32, match_len as u32));
}
}
handle_sequence(sequence);
});
let block = self.inner.get_last_space();
let mut literals = [0u32; 256];
let mut pos = 0usize;
let mut count = |bytes: &[u8]| {
for &byte in bytes {
literals[usize::from(byte)] += 1;
}
};
for &(ll, _, ml) in &self.sequences[block_start..] {
count(&block[pos..pos + ll as usize]);
pos += ll as usize + ml as usize;
}
count(&block[pos..pos + tail]);
let sequences_end = self.sequences.len();
self.blocks.push(RecordedBlock {
literals,
sequences_end,
});
}
fn reset(&mut self, level: CompressionLevel) {
self.inner.reset(level);
}
fn reset_in_workspace(&mut self, level: CompressionLevel, workspace: &mut Workspace) {
self.inner.reset_in_workspace(level, workspace);
}
fn leave_workspace(&mut self) {
self.inner.leave_workspace();
}
fn set_source_size_hint(&mut self, size: u64) {
self.inner.set_source_size_hint(size);
}
fn set_dictionary_size_hint(&mut self, sizes: DictionarySizes) {
self.inner.set_dictionary_size_hint(sizes);
}
fn clear_param_overrides(&mut self) {
self.inner.clear_param_overrides();
}
fn prime_with_dictionary(&mut self, dict_content: &[u8], offset_hist: [u32; 3]) {
self.inner.prime_with_dictionary(dict_content, offset_hist);
}
fn dictionary_is_resident(&self) -> bool {
self.inner.dictionary_is_resident()
}
fn reapply_resident_dictionary(&mut self, offset_hist: [u32; 3]) {
self.inner.reapply_resident_dictionary(offset_hist);
}
fn restore_primed_dictionary(&mut self, level: CompressionLevel) -> bool {
self.inner.restore_primed_dictionary(level)
}
fn capture_primed_dictionary(&mut self, level: CompressionLevel) {
self.inner.capture_primed_dictionary(level);
}
fn invalidate_primed_dictionary(&mut self) {
self.inner.invalidate_primed_dictionary();
}
fn seed_dictionary_entropy(
&mut self,
huff: Option<&HuffmanTable>,
ll: Option<&FSETable>,
ml: Option<&FSETable>,
of: Option<&FSETable>,
) {
self.inner.seed_dictionary_entropy(huff, ll, ml, of);
}
fn supports_dictionary_priming(&self) -> bool {
self.inner.supports_dictionary_priming()
}
fn block_samples_match_dict(&self, block: &[u8]) -> bool {
self.inner.block_samples_match_dict(block)
}
fn heap_size(&self) -> usize {
self.inner.heap_size()
}
fn window_size(&self) -> u64 {
self.inner.window_size()
}
}
#[cfg(test)]
mod tests;