pub(crate) mod commands;
pub(crate) mod constants;
pub(crate) mod histogram;
pub(crate) mod q0;
pub(crate) mod q1;
pub(crate) mod tables;
pub(crate) mod workspace;
pub(crate) use crate::compressor::core::shared::{bits, huffman, match_len};
use super::dispatch::{self, Kernels};
use fearless_simd::{Level, Simd};
use self::bits::{BYTE_PADDING_SLACK, BitWriter, ByteBuffer, inject_byte_padding};
use self::constants::{OUTPUT_RESERVE_CONST, OUTPUT_SLACK, WINDOW_BITS_FAST};
use self::q1::TwoPassState;
use self::workspace::OnePassArena;
use crate::compressor::core::rfc9841::window::ResolvedWindow;
use crate::compressor::shared::SharedBrotliError;
use crate::compressor::{BrotliCompressError, BrotliResult, CompressParams, QualityLevel};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum FastQuality {
Q0,
Q1,
}
impl TryFrom<QualityLevel> for FastQuality {
type Error = BrotliCompressError;
fn try_from(value: QualityLevel) -> Result<Self, Self::Error> {
match value {
QualityLevel::Q0 => Ok(Self::Q0),
QualityLevel::Q1 => Ok(Self::Q1),
other => Err(BrotliCompressError::UnsupportedQuality(usize::from(other))),
}
}
}
pub(crate) enum FastCore {
OnePass { arena: Box<OnePassArena> },
TwoPass { state: Box<TwoPassState> },
}
impl FastCore {
fn retained_bytes(&self) -> usize {
match self {
Self::OnePass { arena } => {
size_of::<OnePassArena>()
+ arena.tree.capacity() * size_of::<huffman::HuffmanNode>()
}
Self::TwoPass { state } => {
size_of::<TwoPassState>()
+ size_of::<workspace::TwoPassArena>()
+ state.arena.tmp_tree.capacity() * size_of::<huffman::HuffmanNode>()
+ state.commands.capacity() * size_of::<u32>()
+ state.literals.capacity()
}
}
}
fn new(quality: FastQuality) -> Self {
match quality {
FastQuality::Q0 => Self::OnePass {
arena: Box::default(),
},
FastQuality::Q1 => Self::TwoPass {
state: Box::default(),
},
}
}
const fn quality(&self) -> FastQuality {
match self {
Self::OnePass { .. } => FastQuality::Q0,
Self::TwoPass { .. } => FastQuality::Q1,
}
}
fn reset(&mut self) {
match self {
Self::OnePass { arena } => arena.reset(),
Self::TwoPass { state } => state.reset(),
}
}
fn table_entries(&self, input_size: usize) -> usize {
match self {
Self::OnePass { .. } => q0::TableBits::for_input(input_size).entries(),
Self::TwoPass { .. } => q1::TableBits::for_input(input_size).entries(),
}
}
}
#[inline(always)]
pub(crate) fn encode_fragment<S: Simd, const INDEPENDENT: bool>(
simd: S,
core: &mut FastCore,
input: &[u8],
is_last: bool,
table: &mut [i32],
w: &mut BitWriter<'_, impl ByteBuffer + ?Sized>,
) {
match core {
FastCore::OnePass { arena } => q0::compress_fragment::<_, INDEPENDENT>(
simd,
arena,
input,
is_last,
q0::TableBits::for_input(input.len()),
table,
w,
),
FastCore::TwoPass { state } => q1::compress_fragment::<_, INDEPENDENT>(
simd,
state,
input,
is_last,
q1::TableBits::for_input(input.len()),
table,
w,
),
}
}
pub(crate) struct FastEncoder {
kernels: Box<dyn Kernels>,
core: FastCore,
block_size_limit: usize,
header: (u16, u32),
last_bytes: u16,
last_bytes_bits: u32,
table: Vec<i32>,
storage: Vec<u8>,
finished: bool,
}
impl FastEncoder {
pub(crate) fn select_fragment_kernels(&mut self, level: Level) {
self.kernels = dispatch::select_independent(level);
}
pub(crate) fn begin_fragment(&mut self, prefix: &[u8]) -> BrotliResult<()> {
self.last_bytes = 0;
self.last_bytes_bits = 0;
let _ = prefix;
Ok(())
}
pub(crate) const fn fragment_aligned(&self) -> bool {
self.last_bytes_bits == 0
}
pub(crate) fn new(level: Level, params: &CompressParams) -> BrotliResult<Self> {
let quality = FastQuality::try_from(params.quality())?;
if params.lgwin().is_large() {
return Err(SharedBrotliError::UnsupportedLargeWindow {
quality: usize::from(params.quality()),
}
.into());
}
let window = ResolvedWindow::new(params);
let lgwin = window.encoder_bits();
let (last_bytes, last_bytes_bits) = window.at_least(WINDOW_BITS_FAST).header();
Ok(Self {
kernels: dispatch::select(level),
core: FastCore::new(quality),
block_size_limit: 1usize << lgwin,
header: (last_bytes, last_bytes_bits),
last_bytes,
last_bytes_bits,
table: Vec::new(),
storage: Vec::new(),
finished: false,
})
}
pub(crate) const fn block_size_limit(&self) -> usize {
self.block_size_limit
}
pub(crate) fn matches(&self, params: &CompressParams) -> bool {
let Ok(quality) = FastQuality::try_from(params.quality()) else {
return false;
};
if params.lgwin().is_large() {
return false;
}
let window = ResolvedWindow::new(params);
self.core.quality() == quality
&& self.block_size_limit == 1usize << window.encoder_bits()
&& self.header == window.at_least(WINDOW_BITS_FAST).header()
}
pub(crate) fn reset(&mut self) {
self.core.reset();
(self.last_bytes, self.last_bytes_bits) = self.header;
self.finished = false;
}
pub(crate) fn retained_bytes(&self) -> usize {
self.core.retained_bytes()
+ size_of_val(&*self.kernels)
+ self.table.capacity() * size_of::<i32>()
+ self.storage.capacity()
}
pub(crate) const fn fragment_reserve(input_len: usize) -> BrotliResult<usize> {
let Some(doubled) = input_len.checked_mul(2) else {
return Err(BrotliCompressError::BufferOverflow);
};
let Some(reserve) = doubled.checked_add(OUTPUT_RESERVE_CONST) else {
return Err(BrotliCompressError::BufferOverflow);
};
match reserve.checked_add(OUTPUT_SLACK) {
Some(reserve) => Ok(reserve),
None => Err(BrotliCompressError::BufferOverflow),
}
}
fn prepare_table(&mut self, input_len: usize) -> usize {
let entries = self.core.table_entries(input_len);
if self.table.len() < entries {
self.table = vec![0i32; entries];
} else {
self.table[..entries].fill(0);
}
entries
}
fn run_fragment(
&mut self,
input: &[u8],
is_last: bool,
entries: usize,
storage: &mut [u8],
) -> BrotliResult<usize> {
storage[0] = self.last_bytes as u8;
storage[1] = (self.last_bytes >> 8) as u8;
let Self {
kernels,
core,
table,
last_bytes_bits,
..
} = self;
let mut w = BitWriter::new(storage, *last_bytes_bits as usize);
let table = &mut table[..entries];
kernels.fast(core, input, is_last, table, &mut w);
if w.overflowed() {
return Err(BrotliCompressError::BufferOverflow);
}
let position = w.position();
let complete = position >> 3;
self.last_bytes = u16::from(w.byte(complete));
self.last_bytes_bits = (position & 7) as u32;
self.finished = is_last;
Ok(complete)
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn encode_block_append(
&mut self,
input: &[u8],
is_last: bool,
output: &mut Vec<u8>,
) -> BrotliResult<usize> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit);
let start = output.len();
let position = start
.checked_mul(8)
.and_then(|bits| bits.checked_add(self.last_bytes_bits as usize))
.ok_or(BrotliCompressError::BufferOverflow)?;
output.extend_from_slice(&self.last_bytes.to_le_bytes());
let entries = self.prepare_table(input.len());
let mut writer = BitWriter::append(output, position);
self.kernels.fast_append(
&mut self.core,
input,
is_last,
&mut self.table[..entries],
&mut writer,
);
let position = writer.position();
let complete = position >> 3;
self.last_bytes = u16::from(writer.byte(complete));
self.last_bytes_bits = (position & 7) as u32;
self.finished = is_last;
output.truncate(complete);
Ok(complete - start)
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn encode_block(&mut self, input: &[u8], is_last: bool) -> BrotliResult<&[u8]> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit);
let reserve = Self::fragment_reserve(input.len())?;
if self.storage.len() < reserve {
self.storage = vec![0u8; reserve];
}
let entries = self.prepare_table(input.len());
let mut storage = core::mem::take(&mut self.storage);
let outcome = self.run_fragment(input, is_last, entries, &mut storage);
self.storage = storage;
let complete = outcome?;
Ok(&self.storage[..complete])
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn flush_block(&mut self, input: &[u8]) -> BrotliResult<&[u8]> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit);
let reserve = Self::fragment_reserve(input.len())?;
if self.storage.len() < reserve {
self.storage = vec![0u8; reserve];
}
let mut storage = core::mem::take(&mut self.storage);
let outcome = self.run_flush(input, &mut storage);
self.storage = storage;
let complete = outcome?;
match self.storage.get(..complete) {
Some(output) => Ok(output),
None => Err(BrotliCompressError::BufferOverflow),
}
}
fn run_flush(&mut self, input: &[u8], storage: &mut [u8]) -> BrotliResult<usize> {
let complete = if input.is_empty() {
0
} else {
let entries = self.prepare_table(input.len());
self.run_fragment(input, false, entries, storage)?
};
let padded = match storage.get_mut(complete..) {
Some(tail) if tail.len() >= BYTE_PADDING_SLACK => {
inject_byte_padding(&mut self.last_bytes, &mut self.last_bytes_bits, tail)
}
_ => return Err(BrotliCompressError::BufferOverflow),
};
Ok(complete + padded)
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn encode_block_into(
&mut self,
input: &[u8],
is_last: bool,
dst: &mut [u8],
) -> BrotliResult<usize> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit);
if dst.len() < Self::fragment_reserve(input.len())? {
return Err(BrotliCompressError::OutputTooSmall);
}
let entries = self.prepare_table(input.len());
self.run_fragment(input, is_last, entries, dst)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compressor::WindowBits;
#[test]
fn direct_appends_preserve_prefixes_and_partial_bytes_on_every_backend() {
for backend in crate::compressor::Backend::available() {
for quality in [QualityLevel::Q0, QualityLevel::Q1] {
for independent in [false, true] {
let params =
CompressParams::new(quality, WindowBits::standard(10).expect("window"));
let mut fixed = FastEncoder::new(backend.0, ¶ms).expect("encoder");
let mut append = FastEncoder::new(backend.0, ¶ms).expect("encoder");
if independent {
fixed.select_fragment_kernels(backend.0);
append.select_fragment_kernels(backend.0);
}
let mut expected = b"existing prefix".to_vec();
let mut actual = expected.clone();
for (data, last) in [(&[b'a'; 1024][..], false), (&[b'b'; 17][..], true)] {
let encoded = fixed.encode_block(data, last).expect("encode");
expected.extend_from_slice(encoded);
let written = append
.encode_block_append(data, last, &mut actual)
.expect("append");
assert_eq!(written, encoded.len());
assert_eq!(actual, expected, "{backend:?}, {quality:?}, {independent}");
}
}
}
}
}
#[test]
fn window_header_matches_the_reference_encoding() {
let header = |lgwin| {
ResolvedWindow::new(&CompressParams::new(
QualityLevel::Q0,
WindowBits::standard(lgwin).expect("a legal window"),
))
.header()
};
assert_eq!(header(16), (0, 1));
assert_eq!(header(17), (1, 7));
assert_eq!(header(18), (3, 4));
assert_eq!(header(22), (11, 4));
assert_eq!(header(24), (15, 4));
assert_eq!(header(10), (0x21, 7));
}
#[test]
fn quality_routing_accepts_only_the_fast_path() {
assert_eq!(
FastQuality::try_from(QualityLevel::Q0).ok(),
Some(FastQuality::Q0)
);
assert_eq!(
FastQuality::try_from(QualityLevel::Q1).ok(),
Some(FastQuality::Q1)
);
assert!(matches!(
FastQuality::try_from(QualityLevel::Q5),
Err(BrotliCompressError::UnsupportedQuality(5))
));
}
#[test]
fn block_size_limit_follows_the_requested_window() -> Result<(), BrotliCompressError> {
let level = Level::new();
for lgwin in [10u8, 16, 18, 22, 24] {
let lgwin_bits = WindowBits::standard(lgwin).unwrap_or(WindowBits::DEFAULT);
let params = CompressParams::new(QualityLevel::Q0, lgwin_bits);
let encoder = FastEncoder::new(level, ¶ms)?;
assert_eq!(
encoder.block_size_limit(),
1 << usize::from(lgwin_bits.bits())
);
}
Ok(())
}
#[test]
fn table_sizing_depends_on_the_quality() {
assert_eq!(FastCore::new(FastQuality::Q0).table_entries(10), 512);
assert_eq!(FastCore::new(FastQuality::Q1).table_entries(10), 256);
assert_eq!(
FastCore::new(FastQuality::Q0).table_entries(1 << 20),
32_768
);
assert_eq!(
FastCore::new(FastQuality::Q1).table_entries(1 << 20),
131_072
);
}
}