use super::buffer_backend::BufferBackend;
use super::decode_buffer::DecodeBuffer;
use super::ringbuffer::RingBuffer;
use crate::decoding::dictionary::DictionaryHandle;
use crate::fse::SeqFSETable;
use crate::huff0::HuffmanTable;
use alloc::vec::Vec;
use core::ops::{Deref, DerefMut};
use crate::blocks::sequence_section::{
MAX_LITERAL_LENGTH_CODE, MAX_MATCH_LENGTH_CODE, MAX_OFFSET_CODE,
};
pub struct DecoderScratch<B: BufferBackend = RingBuffer> {
pub huf: HuffmanScratch,
pub fse: FSEScratch,
pub buffer: DecodeBuffer<B>,
pub offset_hist: [u32; 3],
pub literals_buffer: Vec<u8>,
pub block_content_buffer: Vec<u8>,
}
pub struct WorkspaceRef<'a, B: BufferBackend> {
pub huf: &'a mut HuffmanScratch,
pub fse: &'a mut FSEScratch,
pub buffer: &'a mut DecodeBuffer<B>,
pub offset_hist: &'a mut [u32; 3],
pub literals_buffer: &'a mut Vec<u8>,
pub block_content_buffer: &'a mut Vec<u8>,
}
pub(crate) trait Workspace {
type Backend: BufferBackend;
fn split(&mut self) -> WorkspaceRef<'_, Self::Backend>;
}
impl<B: BufferBackend> Workspace for DecoderScratch<B> {
type Backend = B;
fn split(&mut self) -> WorkspaceRef<'_, B> {
WorkspaceRef {
huf: &mut self.huf,
fse: &mut self.fse,
buffer: &mut self.buffer,
offset_hist: &mut self.offset_hist,
literals_buffer: &mut self.literals_buffer,
block_content_buffer: &mut self.block_content_buffer,
}
}
}
pub struct DirectScratch<'o, 'p> {
pub huf: &'p mut HuffmanScratch,
pub fse: &'p mut FSEScratch,
pub buffer: DecodeBuffer<super::user_slice_buf::UserSliceBackend<'o>>,
pub offset_hist: &'p mut [u32; 3],
pub literals_buffer: &'p mut Vec<u8>,
pub block_content_buffer: &'p mut Vec<u8>,
}
impl<'o, 'p> Workspace for DirectScratch<'o, 'p> {
type Backend = super::user_slice_buf::UserSliceBackend<'o>;
fn split(&mut self) -> WorkspaceRef<'_, Self::Backend> {
WorkspaceRef {
huf: &mut *self.huf,
fse: &mut *self.fse,
buffer: &mut self.buffer,
offset_hist: &mut *self.offset_hist,
literals_buffer: &mut *self.literals_buffer,
block_content_buffer: &mut *self.block_content_buffer,
}
}
}
impl<B: BufferBackend> DecoderScratch<B> {
pub fn new(window_size: usize) -> DecoderScratch<B> {
DecoderScratch {
huf: HuffmanScratch {
table: HuffmanTable::new(),
table_source: TableSource::Local,
dict: None,
},
fse: FSEScratch {
offsets: AlignedFSETable::new(MAX_OFFSET_CODE),
literal_lengths: AlignedFSETable::new(MAX_LITERAL_LENGTH_CODE),
match_lengths: AlignedFSETable::new(MAX_MATCH_LENGTH_CODE),
offsets_long_share: 0,
ddict_is_cold: false,
ll_source: TableSource::Local,
of_source: TableSource::Local,
ml_source: TableSource::Local,
dict: None,
},
buffer: DecodeBuffer::new(window_size),
offset_hist: [1, 4, 8],
block_content_buffer: Vec::new(),
literals_buffer: Vec::new(),
}
}
pub fn workspace_bytes(&self) -> usize {
self.buffer.capacity()
+ self.literals_buffer.capacity()
+ self.block_content_buffer.capacity()
+ self.huf.heap_bytes()
+ self.fse.heap_bytes()
}
pub fn reset(&mut self, window_size: usize) {
self.offset_hist = [1, 4, 8];
self.literals_buffer.clear();
self.block_content_buffer.clear();
let block_cap = (window_size.min(crate::common::MAX_BLOCK_SIZE as usize)).max(8);
if self.literals_buffer.capacity() < block_cap {
self.literals_buffer.resize(block_cap, 0);
self.literals_buffer.clear();
}
if self.block_content_buffer.capacity() < block_cap {
self.block_content_buffer.resize(block_cap, 0);
self.block_content_buffer.clear();
}
self.buffer.reset(window_size);
self.fse.literal_lengths.reset();
self.fse.match_lengths.reset();
self.fse.offsets.reset();
self.fse.offsets_long_share = 0;
self.fse.detach_dict();
self.fse.ddict_is_cold = false;
self.huf.table.reset();
self.huf.detach_dict();
}
pub fn init_from_dict(&mut self, dict: &DictionaryHandle) {
let d = dict.as_dict();
self.fse.attach_dict(dict.clone());
self.huf.attach_dict(dict.clone());
self.offset_hist = d.offset_hist;
self.buffer.set_dict(dict.clone());
self.fse.ddict_is_cold = true;
}
}
#[derive(Clone)]
pub struct HuffmanScratch {
pub table: HuffmanTable,
table_source: TableSource,
dict: Option<DictionaryHandle>,
}
impl HuffmanScratch {
pub fn new() -> HuffmanScratch {
HuffmanScratch {
table: HuffmanTable::new(),
table_source: TableSource::Local,
dict: None,
}
}
pub fn heap_bytes(&self) -> usize {
self.table.heap_bytes()
}
pub(crate) fn huf_table(&self) -> &HuffmanTable {
match self.table_source {
TableSource::Local => &self.table,
TableSource::Dict => {
&self
.dict
.as_ref()
.expect("Dict table source requires an attached dictionary handle")
.as_dict()
.huf
.table
}
}
}
pub(crate) fn attach_dict(&mut self, dict: DictionaryHandle) {
self.table_source = TableSource::Dict;
self.dict = Some(dict);
}
pub(crate) fn detach_dict(&mut self) {
self.table_source = TableSource::Local;
self.dict = None;
}
#[inline]
pub(crate) fn mark_table_local(&mut self) {
self.table_source = TableSource::Local;
}
pub(crate) fn reinit_resolved_from(&mut self, other: &HuffmanScratch) {
self.table.reinit_from(other.huf_table());
self.detach_dict();
}
}
impl Default for HuffmanScratch {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum TableSource {
Local,
Dict,
}
#[derive(Clone)]
pub struct FSEScratch {
pub offsets: AlignedFSETable,
pub literal_lengths: AlignedFSETable,
pub match_lengths: AlignedFSETable,
pub offsets_long_share: u32,
pub ddict_is_cold: bool,
ll_source: TableSource,
of_source: TableSource,
ml_source: TableSource,
dict: Option<DictionaryHandle>,
}
impl FSEScratch {
pub fn heap_bytes(&self) -> usize {
self.offsets.heap_bytes()
+ self.literal_lengths.heap_bytes()
+ self.match_lengths.heap_bytes()
}
pub fn new() -> FSEScratch {
FSEScratch {
offsets: AlignedFSETable::new(MAX_OFFSET_CODE),
literal_lengths: AlignedFSETable::new(MAX_LITERAL_LENGTH_CODE),
match_lengths: AlignedFSETable::new(MAX_MATCH_LENGTH_CODE),
offsets_long_share: 0,
ddict_is_cold: false,
ll_source: TableSource::Local,
of_source: TableSource::Local,
ml_source: TableSource::Local,
dict: None,
}
}
pub fn reinit_from(&mut self, other: &Self) {
self.literal_lengths.reinit_from(other.ll_table());
self.offsets.reinit_from(other.of_table());
self.match_lengths.reinit_from(other.ml_table());
self.offsets_long_share = other.offsets_long_share;
self.ddict_is_cold = false;
self.ll_source = TableSource::Local;
self.of_source = TableSource::Local;
self.ml_source = TableSource::Local;
self.dict = None;
}
pub(crate) fn ll_table(&self) -> &SeqFSETable {
match self.ll_source {
TableSource::Local => &self.literal_lengths,
TableSource::Dict => &self.dict_ref().fse.literal_lengths,
}
}
pub(crate) fn of_table(&self) -> &SeqFSETable {
match self.of_source {
TableSource::Local => &self.offsets,
TableSource::Dict => &self.dict_ref().fse.offsets,
}
}
pub(crate) fn ml_table(&self) -> &SeqFSETable {
match self.ml_source {
TableSource::Local => &self.match_lengths,
TableSource::Dict => &self.dict_ref().fse.match_lengths,
}
}
fn dict_ref(&self) -> &crate::decoding::dictionary::Dictionary {
self.dict
.as_ref()
.expect("Dict table source requires an attached dictionary handle")
.as_dict()
}
pub(crate) fn attach_dict(&mut self, dict: DictionaryHandle) {
self.offsets_long_share = dict.as_dict().fse.offsets_long_share;
self.ll_source = TableSource::Dict;
self.of_source = TableSource::Dict;
self.ml_source = TableSource::Dict;
self.dict = Some(dict);
}
pub(crate) fn detach_dict(&mut self) {
self.dict = None;
self.ll_source = TableSource::Local;
self.of_source = TableSource::Local;
self.ml_source = TableSource::Local;
}
#[inline]
pub(crate) fn mark_ll_local(&mut self) {
self.ll_source = TableSource::Local;
}
#[inline]
pub(crate) fn mark_of_local(&mut self) {
self.of_source = TableSource::Local;
}
#[inline]
pub(crate) fn mark_ml_local(&mut self) {
self.ml_source = TableSource::Local;
}
}
impl Default for FSEScratch {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(target_arch = "aarch64", repr(align(128)))]
#[cfg_attr(not(target_arch = "aarch64"), repr(align(64)))]
#[derive(Clone)]
pub struct AlignedFSETable(SeqFSETable);
impl AlignedFSETable {
fn new(max_symbol: u8) -> Self {
Self(SeqFSETable::new(max_symbol))
}
}
impl Deref for AlignedFSETable {
type Target = SeqFSETable;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for AlignedFSETable {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decoding::dictionary::Dictionary;
#[test]
fn init_from_dict_marks_fse_ddict_is_cold() {
extern crate std;
let dict_raw =
std::fs::read("./dict_tests/dictionary").expect("dictionary fixture should load");
let dict = DictionaryHandle::from_dictionary(
Dictionary::decode_dict(&dict_raw).expect("dictionary should parse"),
);
let mut scratch: DecoderScratch = DecoderScratch::new(1024);
assert!(
!scratch.fse.ddict_is_cold,
"fresh DecoderScratch must not advertise a cold dict"
);
scratch.init_from_dict(&dict);
assert!(
scratch.fse.ddict_is_cold,
"init_from_dict must set ddict_is_cold = true"
);
}
#[test]
fn reinit_from_clears_cold_dict_flag() {
let mut dst = FSEScratch::new();
dst.ddict_is_cold = true; let src = FSEScratch::new(); dst.reinit_from(&src);
assert!(
!dst.ddict_is_cold,
"reinit_from must clear ddict_is_cold on a local-only snapshot"
);
}
#[test]
fn init_from_dict_is_zero_copy_cow_then_reset_detaches() {
extern crate std;
let dict_raw =
std::fs::read("./dict_tests/dictionary").expect("dictionary fixture should load");
let dict = DictionaryHandle::from_dictionary(
Dictionary::decode_dict(&dict_raw).expect("dictionary should parse"),
);
let mut scratch: DecoderScratch = DecoderScratch::new(1024);
scratch.init_from_dict(&dict);
let dict_ll_len = dict.as_dict().fse.literal_lengths.decode().len();
assert!(
dict_ll_len > 0,
"dict fixture should carry a built LL table"
);
assert_eq!(
scratch.fse.ll_table().decode().len(),
dict_ll_len,
"Dict-sourced axis must resolve to the shared dictionary's table"
);
assert!(
scratch.fse.literal_lengths.decode().is_empty(),
"init_from_dict must not copy table bytes into local scratch (COW)"
);
let dict_huf_bits = dict.as_dict().huf.table.max_num_bits;
assert!(
dict_huf_bits > 0,
"dict fixture should carry a built HUF table"
);
assert_eq!(
scratch.huf.huf_table().max_num_bits,
dict_huf_bits,
"Dict-sourced HUF axis must resolve to the dictionary's table"
);
assert_eq!(
scratch.huf.table.max_num_bits, 0,
"init_from_dict must not copy the HUF table into local scratch (COW)"
);
scratch.reset(1024);
assert!(
scratch.fse.ll_table().decode().is_empty(),
"reset must detach the dictionary copy-on-write source"
);
assert_eq!(
scratch.huf.huf_table().max_num_bits,
0,
"reset must detach the HUF dictionary copy-on-write source"
);
}
}