use crate::bit::BitRev;
use crate::error::Error;
use crate::fse;
#[cfg(feature = "alloc")]
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
const MAX_BITS: u8 = 11;
const FAST_TABLELOG: u8 = 11;
#[derive(Clone, Debug)]
pub(crate) struct HuffmanTable {
table: Vec<u16>,
table_x2: Vec<u32>,
max_bits: u8,
}
#[cfg(feature = "profile")]
pub static N13_STATS: [core::sync::atomic::AtomicU64; 3] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_n13_stats() -> [u64; 3] {
use core::sync::atomic::Ordering;
[
N13_STATS[0].swap(0, Ordering::Relaxed),
N13_STATS[1].swap(0, Ordering::Relaxed),
N13_STATS[2].swap(0, Ordering::Relaxed),
]
}
#[cfg(feature = "profile")]
pub static X2_STATS: [core::sync::atomic::AtomicU64; 2] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_x2_stats() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
X2_STATS[0].swap(0, Ordering::Relaxed),
X2_STATS[1].swap(0, Ordering::Relaxed),
)
}
impl HuffmanTable {
#[inline(always)]
pub(crate) fn decode_stream(&self, src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
if dst.is_empty() {
return Ok(());
}
let mut br = BitRev::new(src)?;
if self.use_x2(dst.len(), src.len()) {
#[cfg(feature = "profile")]
X2_STATS[1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
self.decode_into_x2(&mut br, dst)
} else {
self.decode_into_x1(&mut br, dst)
}
}
#[cfg(test)]
#[inline(always)]
pub(crate) fn decode_stream_scalar(&self, src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
if dst.is_empty() {
return Ok(());
}
let mut br = BitRev::new(src)?;
let max = u32::from(self.max_bits);
let dt = self.table.as_slice();
if dt.is_empty() {
return Err(Error::Corruption);
}
let mask = dt.len() - 1;
for slot in dst.iter_mut() {
let _ = br.reload();
let e = dt[br.look_bits(max) as usize & mask];
let nbits = (e >> 8) as u8;
if nbits == 0 {
return Err(Error::Corruption);
}
br.read_bits(u32::from(nbits));
*slot = e as u8;
}
Ok(())
}
#[inline(always)]
#[allow(unsafe_code)]
fn decode_one(br: &mut BitRev<'_>, dt: &[u16], mask: usize, max: u32) -> Result<u8, Error> {
debug_assert!(!dt.is_empty() && dt.len().is_power_of_two() && mask == dt.len() - 1);
let e = *unsafe { dt.get_unchecked(br.look_bits_fast(max) as usize & mask) };
let nbits = (e >> 8) as u8;
if nbits == 0 {
return Err(Error::Corruption);
}
br.skip_bits(u32::from(nbits));
Ok(e as u8)
}
fn use_x2(&self, dst_size: usize, src_size: usize) -> bool {
self.table_x2.len() == self.table.len() && select_x2(dst_size, src_size)
}
#[inline(never)]
fn decode_into_x1(&self, br: &mut BitRev<'_>, dst: &mut [u8]) -> Result<(), Error> {
let max = u32::from(self.max_bits);
let dt = self.table.as_slice();
if dt.is_empty() {
return Err(Error::Corruption);
}
let mask = dt.len() - 1;
let n = dst.len();
let mut i = 0usize;
while i + 5 <= n {
let _ = br.reload();
debug_assert!(i + 4 < n);
for k in 0..5 {
let v = Self::decode_one(br, dt, mask, max)?;
#[allow(unsafe_code)]
unsafe {
*dst.get_unchecked_mut(i + k) = v;
}
}
i += 5;
}
while i < n {
let _ = br.reload();
dst[i] = Self::decode_one(br, dt, mask, max)?;
i += 1;
}
Ok(())
}
#[inline(never)]
fn decode_into_x2(&self, br: &mut BitRev<'_>, dst: &mut [u8]) -> Result<(), Error> {
let max = u32::from(self.max_bits);
let dt = self.table_x2.as_slice();
if dt.is_empty() {
return Err(Error::Corruption);
}
let mask = dt.len() - 1;
let n = dst.len();
let mut i = 0usize;
while i + 10 <= n {
let _ = br.reload();
i += Self::write_x2(br, dt, mask, max, dst, i);
i += Self::write_x2(br, dt, mask, max, dst, i);
i += Self::write_x2(br, dt, mask, max, dst, i);
i += Self::write_x2(br, dt, mask, max, dst, i);
i += Self::write_x2(br, dt, mask, max, dst, i);
}
while i + 2 <= n {
let _ = br.reload();
i += Self::write_x2(br, dt, mask, max, dst, i);
}
while i < n {
let _ = br.reload();
dst[i] = Self::decode_one(
br,
self.table.as_slice(),
self.table.len().saturating_sub(1),
max,
)?;
i += 1;
}
Ok(())
}
#[inline(always)]
fn write_x2(
br: &mut BitRev<'_>,
dt: &[u32],
mask: usize,
max: u32,
dst: &mut [u8],
i: usize,
) -> usize {
debug_assert!(!dt.is_empty() && mask == dt.len() - 1);
debug_assert!(i + 1 < dst.len());
#[allow(unsafe_code)]
let e = *unsafe { dt.get_unchecked(br.look_bits_fast(max) as usize & mask) };
debug_assert!(((e >> 16) & 0xff) != 0);
br.skip_bits((e >> 16) & 0xff);
#[allow(unsafe_code)]
unsafe {
*dst.get_unchecked_mut(i) = e as u8;
*dst.get_unchecked_mut(i + 1) = (e >> 8) as u8;
}
(e >> 24) as usize
}
pub(crate) fn decode_4x(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<(), Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_avx2() && crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { self.decode_4x_avx2(s0, s1, s2, s3, d0, d1, d2, d3) };
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { self.decode_4x_bmi2(s0, s1, s2, s3, d0, d1, d2, d3) };
}
self.decode_4x_inner(s0, s1, s2, s3, d0, d1, d2, d3)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "avx2,bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn decode_4x_avx2(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<(), Error> {
self.decode_4x_inner(s0, s1, s2, s3, d0, d1, d2, d3)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn decode_4x_bmi2(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<(), Error> {
self.decode_4x_inner(s0, s1, s2, s3, d0, d1, d2, d3)
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn decode_4x_inner(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<(), Error> {
if s0.is_empty() || s1.is_empty() || s2.is_empty() || s3.is_empty() {
return Err(Error::Corruption);
}
let dst_size = d0.len() + d1.len() + d2.len() + d3.len();
let src_size = s0.len() + s1.len() + s2.len() + s3.len();
if !self.use_x2(dst_size, src_size) {
return self.decode_4x_x1(s0, s1, s2, s3, d0, d1, d2, d3);
}
#[cfg(feature = "profile")]
X2_STATS[1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if let Some(st) = self.fast_4x2(s0, s1, s2, s3, d0, d1, d2, d3)? {
let mut b0 = BitRev::from_window(s0, st.ip0, st.c0)?;
let mut b1 = BitRev::from_window(s1, st.ip1, st.c1)?;
let mut b2 = BitRev::from_window(s2, st.ip2, st.c2)?;
let mut b3 = BitRev::from_window(s3, st.ip3, st.c3)?;
self.decode_into_x2(&mut b0, &mut d0[st.op0..])?;
self.decode_into_x2(&mut b1, &mut d1[st.op1..])?;
self.decode_into_x2(&mut b2, &mut d2[st.op2..])?;
self.decode_into_x2(&mut b3, &mut d3[st.op3..])?;
return Ok(());
}
let mut b0 = BitRev::new(s0)?;
let mut b1 = BitRev::new(s1)?;
let mut b2 = BitRev::new(s2)?;
let mut b3 = BitRev::new(s3)?;
let max = u32::from(self.max_bits);
let dt = self.table_x2.as_slice();
if dt.is_empty() {
return Err(Error::Corruption);
}
let mask = dt.len() - 1;
let mut i0 = 0usize;
let mut i1 = 0usize;
let mut i2 = 0usize;
let mut i3 = 0usize;
while i0 + 10 <= d0.len()
&& i1 + 10 <= d1.len()
&& i2 + 10 <= d2.len()
&& i3 + 10 <= d3.len()
{
let _ = b0.reload();
let _ = b1.reload();
let _ = b2.reload();
let _ = b3.reload();
i0 += Self::write_x2(&mut b0, dt, mask, max, d0, i0);
i1 += Self::write_x2(&mut b1, dt, mask, max, d1, i1);
i2 += Self::write_x2(&mut b2, dt, mask, max, d2, i2);
i3 += Self::write_x2(&mut b3, dt, mask, max, d3, i3);
i0 += Self::write_x2(&mut b0, dt, mask, max, d0, i0);
i1 += Self::write_x2(&mut b1, dt, mask, max, d1, i1);
i2 += Self::write_x2(&mut b2, dt, mask, max, d2, i2);
i3 += Self::write_x2(&mut b3, dt, mask, max, d3, i3);
i0 += Self::write_x2(&mut b0, dt, mask, max, d0, i0);
i1 += Self::write_x2(&mut b1, dt, mask, max, d1, i1);
i2 += Self::write_x2(&mut b2, dt, mask, max, d2, i2);
i3 += Self::write_x2(&mut b3, dt, mask, max, d3, i3);
i0 += Self::write_x2(&mut b0, dt, mask, max, d0, i0);
i1 += Self::write_x2(&mut b1, dt, mask, max, d1, i1);
i2 += Self::write_x2(&mut b2, dt, mask, max, d2, i2);
i3 += Self::write_x2(&mut b3, dt, mask, max, d3, i3);
i0 += Self::write_x2(&mut b0, dt, mask, max, d0, i0);
i1 += Self::write_x2(&mut b1, dt, mask, max, d1, i1);
i2 += Self::write_x2(&mut b2, dt, mask, max, d2, i2);
i3 += Self::write_x2(&mut b3, dt, mask, max, d3, i3);
}
self.decode_into_x2(&mut b0, &mut d0[i0..])?;
self.decode_into_x2(&mut b1, &mut d1[i1..])?;
self.decode_into_x2(&mut b2, &mut d2[i2..])?;
self.decode_into_x2(&mut b3, &mut d3[i3..])?;
Ok(())
}
#[inline(always)]
fn fast_4x2(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<Option<Fast4x2>, Error> {
if !cfg!(target_pointer_width = "64") {
return Ok(None);
}
if self.max_bits != FAST_TABLELOG || self.table_x2.len() != 1 << FAST_TABLELOG {
return Ok(None);
}
if s0.len() < 8 || s1.len() < 8 || s2.len() < 8 || s3.len() < 8 {
return Ok(None);
}
let dt = self.table_x2.as_slice();
let mut ip0 = s0.len() - 8;
let mut ip1 = s1.len() - 8;
let mut ip2 = s2.len() - 8;
let mut ip3 = s3.len() - 8;
let mut bits0 = init_fast_dstream(s0, ip0);
let mut bits1 = init_fast_dstream(s1, ip1);
let mut bits2 = init_fast_dstream(s2, ip2);
let mut bits3 = init_fast_dstream(s3, ip3);
let mut op0 = 0usize;
let mut op1 = 0usize;
let mut op2 = 0usize;
let mut op3 = 0usize;
loop {
let mut iters = ip0 / 7;
iters = iters.min(ip1 / 7).min(ip2 / 7).min(ip3 / 7);
iters = iters
.min(d0.len().saturating_sub(op0) / 10)
.min(d1.len().saturating_sub(op1) / 10)
.min(d2.len().saturating_sub(op2) / 10)
.min(d3.len().saturating_sub(op3) / 10);
if iters == 0 {
break;
}
let olimit = op3 + iters * 5;
while op3 < olimit {
x2_fast_sym(&mut bits0, &mut op0, d0, dt);
x2_fast_sym(&mut bits1, &mut op1, d1, dt);
x2_fast_sym(&mut bits2, &mut op2, d2, dt);
x2_fast_sym(&mut bits0, &mut op0, d0, dt);
x2_fast_sym(&mut bits1, &mut op1, d1, dt);
x2_fast_sym(&mut bits2, &mut op2, d2, dt);
x2_fast_sym(&mut bits0, &mut op0, d0, dt);
x2_fast_sym(&mut bits1, &mut op1, d1, dt);
x2_fast_sym(&mut bits2, &mut op2, d2, dt);
x2_fast_sym(&mut bits0, &mut op0, d0, dt);
x2_fast_sym(&mut bits1, &mut op1, d1, dt);
x2_fast_sym(&mut bits2, &mut op2, d2, dt);
x2_fast_sym(&mut bits0, &mut op0, d0, dt);
x2_fast_sym(&mut bits1, &mut op1, d1, dt);
x2_fast_sym(&mut bits2, &mut op2, d2, dt);
x2_fast_sym(&mut bits3, &mut op3, d3, dt);
x2_fast_sym(&mut bits3, &mut op3, d3, dt);
reload_fast(&mut bits0, &mut ip0, s0);
x2_fast_sym(&mut bits3, &mut op3, d3, dt);
reload_fast(&mut bits1, &mut ip1, s1);
x2_fast_sym(&mut bits3, &mut op3, d3, dt);
reload_fast(&mut bits2, &mut ip2, s2);
x2_fast_sym(&mut bits3, &mut op3, d3, dt);
reload_fast(&mut bits3, &mut ip3, s3);
}
}
Ok(Some(Fast4x2 {
op0,
op1,
op2,
op3,
ip0,
ip1,
ip2,
ip3,
c0: bits0.trailing_zeros(),
c1: bits1.trailing_zeros(),
c2: bits2.trailing_zeros(),
c3: bits3.trailing_zeros(),
}))
}
#[inline(always)]
fn decode_4x_x1(
&self,
s0: &[u8],
s1: &[u8],
s2: &[u8],
s3: &[u8],
d0: &mut [u8],
d1: &mut [u8],
d2: &mut [u8],
d3: &mut [u8],
) -> Result<(), Error> {
let mut b0 = BitRev::new(s0)?;
let mut b1 = BitRev::new(s1)?;
let mut b2 = BitRev::new(s2)?;
let mut b3 = BitRev::new(s3)?;
let max = u32::from(self.max_bits);
let dt = self.table.as_slice();
if dt.is_empty() {
return Err(Error::Corruption);
}
let mask = dt.len() - 1;
let n = d0.len().min(d1.len()).min(d2.len()).min(d3.len());
let mut i = 0usize;
while i + 4 <= n {
let _ = b0.reload();
let _ = b1.reload();
let _ = b2.reload();
let _ = b3.reload();
debug_assert!(i + 3 < n);
for k in 0..4 {
let v0 = Self::decode_one(&mut b0, dt, mask, max)?;
let v1 = Self::decode_one(&mut b1, dt, mask, max)?;
let v2 = Self::decode_one(&mut b2, dt, mask, max)?;
let v3 = Self::decode_one(&mut b3, dt, mask, max)?;
#[allow(unsafe_code)]
unsafe {
*d0.get_unchecked_mut(i + k) = v0;
*d1.get_unchecked_mut(i + k) = v1;
*d2.get_unchecked_mut(i + k) = v2;
*d3.get_unchecked_mut(i + k) = v3;
}
}
i += 4;
}
while i < n {
let _ = b0.reload();
let _ = b1.reload();
let _ = b2.reload();
let _ = b3.reload();
d0[i] = Self::decode_one(&mut b0, dt, mask, max)?;
d1[i] = Self::decode_one(&mut b1, dt, mask, max)?;
d2[i] = Self::decode_one(&mut b2, dt, mask, max)?;
d3[i] = Self::decode_one(&mut b3, dt, mask, max)?;
i += 1;
}
self.decode_into_x1(&mut b0, &mut d0[n..])?;
self.decode_into_x1(&mut b1, &mut d1[n..])?;
self.decode_into_x1(&mut b2, &mut d2[n..])?;
self.decode_into_x1(&mut b3, &mut d3[n..])?;
Ok(())
}
}
#[inline(always)]
pub(crate) fn read_table(
recycle: Option<HuffmanTable>,
src: &[u8],
) -> Result<(HuffmanTable, usize), Error> {
if src.is_empty() {
return Err(Error::Corruption);
}
let header = src[0];
let mut wbuf = [0u8; 256];
let (wlen, used) = if header >= 128 {
let nsym = header as usize - 127;
let nbytes = nsym.div_ceil(2);
if 1 + nbytes > src.len() {
return Err(Error::Corruption);
}
let w = &mut wbuf[..nsym];
for (i, slot) in w.iter_mut().enumerate() {
debug_assert!(1 + i / 2 < src.len());
#[allow(unsafe_code)]
let b = *unsafe { src.get_unchecked(1 + i / 2) };
*slot = if i % 2 == 0 { b >> 4 } else { b & 0x0F };
}
(nsym, 1 + nbytes)
} else {
let csize = header as usize;
if csize == 0 || 1 + csize > src.len() {
return Err(Error::Corruption);
}
let (wlen, _) = fse::decompress_weights_into(&mut wbuf, &src[1..1 + csize], 255)?;
(wlen, 1 + csize)
};
let table = table_from_weights(recycle, &mut wbuf, wlen, true)?;
Ok((table, used))
}
#[inline(always)]
fn table_from_weights(
recycle: Option<HuffmanTable>,
wbuf: &mut [u8; 256],
n_wo: usize,
want_x2: bool,
) -> Result<HuffmanTable, Error> {
let weights_wo_last = &wbuf[..n_wo];
let (rec_x1, rec_x2) = match recycle {
Some(h) => (h.table, h.table_x2),
#[cfg(all(feature = "std", feature = "alloc"))]
None => huff_pool::take(),
#[cfg(not(all(feature = "std", feature = "alloc")))]
None => (Vec::new(), Vec::new()),
};
if weights_wo_last.is_empty() {
return Err(Error::Corruption);
}
let mut rank = [0u32; 13];
let mut weight_total = 0u32;
for &w in weights_wo_last {
if w > MAX_BITS {
return Err(Error::Corruption);
}
debug_assert!((w as usize) < rank.len());
#[allow(unsafe_code)]
unsafe {
*rank.get_unchecked_mut(w as usize) += 1;
}
if w > 0 {
weight_total += 1 << (w - 1);
}
}
if weight_total == 0 {
return Err(Error::Corruption);
}
let table_log = (31 - weight_total.leading_zeros() + 1) as u8;
if table_log > MAX_BITS {
return Err(Error::Corruption);
}
let total = 1u32 << table_log;
let rest = total - weight_total;
if rest == 0 || (rest & (rest - 1)) != 0 {
return Err(Error::Corruption);
}
let last_weight = (31 - rest.leading_zeros() + 1) as u8;
if last_weight > MAX_BITS {
return Err(Error::Corruption);
}
if n_wo + 1 > 256 {
return Err(Error::Corruption);
}
wbuf[n_wo] = last_weight;
let weights = &wbuf[..n_wo + 1];
debug_assert!((last_weight as usize) < rank.len());
#[allow(unsafe_code)]
unsafe {
*rank.get_unchecked_mut(last_weight as usize) += 1;
}
if rank[1] < 2 || rank[1] % 2 != 0 {
return Err(Error::Corruption);
}
let table_size = 1usize << table_log;
let mut table: Vec<u16> = rec_x1;
table.clear();
table.reserve(1usize << FAST_TABLELOG);
table.resize(table_size, 0);
let nsym_s = weights.len();
if nsym_s > 256 {
return Err(Error::Corruption);
}
let mut symbols_buf = [0u8; 256];
let symbols = &mut symbols_buf[..nsym_s];
let mut rank_start = [0usize; 13];
let mut acc = 0usize;
debug_assert!(table_log as usize <= MAX_BITS as usize);
for w in 0..=table_log as usize {
#[allow(unsafe_code)]
unsafe {
*rank_start.get_unchecked_mut(w) = acc;
acc += *rank.get_unchecked(w) as usize;
}
}
let mut rs = rank_start;
for (s, &w) in weights.iter().enumerate() {
if w == 0 {
continue;
}
debug_assert!((w as usize) < rs.len());
#[allow(unsafe_code)]
let slot = *unsafe { rs.get_unchecked(w as usize) };
if slot >= symbols.len() {
return Err(Error::Corruption);
}
symbols[slot] = s as u8;
#[allow(unsafe_code)]
unsafe {
*rs.get_unchecked_mut(w as usize) += 1;
}
}
let mut pos = 0usize;
let mut syms = symbols.get(rank[0] as usize..).unwrap_or(&[]).iter();
for w in 1..=table_log {
debug_assert!((w as usize) < rank.len());
#[allow(unsafe_code)]
let count = *unsafe { rank.get_unchecked(w as usize) } as usize;
let length = 1usize << (w - 1);
let nb_bits = table_log + 1 - w;
for _ in 0..count {
let sym = *syms.next().ok_or(Error::Corruption)?;
if pos + length > table.len() {
return Err(Error::Corruption);
}
let entry = u16::from(sym) | (u16::from(nb_bits) << 8);
table[pos..pos + length].fill(entry);
pos += length;
}
}
if pos != table.len() {
return Err(Error::Corruption);
}
let (table, table_log) = if want_x2 {
upsample_dtable(table, table_log)
} else {
(table, table_log)
};
let table_x2 = if want_x2 {
#[cfg(feature = "profile")]
X2_STATS[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
x2_from_x1_into(rec_x2, &table, table_log)
} else {
let mut v = rec_x2;
v.clear();
v
};
Ok(HuffmanTable {
table,
table_x2,
max_bits: table_log,
})
}
#[inline(never)]
fn upsample_dtable(table: Vec<u16>, table_log: u8) -> (Vec<u16>, u8) {
if table_log >= FAST_TABLELOG {
return (table, table_log);
}
let scale = FAST_TABLELOG - table_log;
let factor = 1usize << scale;
let src_len = table.len();
let mut wide = table;
let want = 1usize << FAST_TABLELOG;
if wide.len() < want {
wide.reserve(want - wide.len());
}
#[allow(unsafe_code)]
unsafe {
wide.set_len(want);
}
for i in (0..src_len).rev() {
debug_assert!(i < wide.len());
#[allow(unsafe_code)]
let e = unsafe { *wide.get_unchecked(i) };
let base = i << scale;
debug_assert!(base + factor <= wide.len());
#[allow(unsafe_code)]
unsafe { wide.get_unchecked_mut(base..base + factor) }.fill(e);
}
(wide, FAST_TABLELOG)
}
struct Fast4x2 {
op0: usize,
op1: usize,
op2: usize,
op3: usize,
ip0: usize,
ip1: usize,
ip2: usize,
ip3: usize,
c0: u32,
c1: u32,
c2: u32,
c3: u32,
}
#[inline(always)]
fn init_fast_dstream(src: &[u8], ip: usize) -> u64 {
debug_assert!(ip + 8 <= src.len());
let last = src[ip + 7];
let skip = if last == 0 {
0
} else {
8 - (31 - (last as u32).leading_zeros())
};
(crate::simd::load_u64_le(src, ip) | 1) << skip
}
#[inline(always)]
fn x2_fast_sym(bits: &mut u64, op: &mut usize, dst: &mut [u8], dt: &[u32]) {
debug_assert!(dt.len() == 1 << FAST_TABLELOG);
debug_assert!(*op + 1 < dst.len());
#[allow(unsafe_code)]
let e = *unsafe { dt.get_unchecked((*bits >> 53) as usize) };
#[allow(unsafe_code)]
unsafe {
*dst.get_unchecked_mut(*op) = e as u8;
*dst.get_unchecked_mut(*op + 1) = (e >> 8) as u8;
}
*bits <<= (e >> 16) & 0x3F;
*op += (e >> 24) as usize;
}
#[inline(always)]
fn reload_fast(bits: &mut u64, ip: &mut usize, src: &[u8]) {
let ctz = bits.trailing_zeros();
let nb_bytes = (ctz >> 3) as usize;
*ip -= nb_bytes;
debug_assert!(*ip + 8 <= src.len());
*bits = crate::simd::load_u64_le(src, *ip) | 1;
*bits <<= ctz & 7;
}
fn select_x2(dst_size: usize, src_size: usize) -> bool {
if dst_size < 256 {
return false;
}
let q = if src_size >= dst_size {
15
} else {
((src_size * 16) / dst_size).min(15)
};
let d256 = (dst_size >> 8) as u32;
let (_, d0, _, d1) = ALGO_TIME[q];
let time0 = d0.saturating_mul(d256);
let mut time1 = d1.saturating_mul(d256);
time1 += time1 >> 5;
time1 < time0
}
const ALGO_TIME: [(u32, u32, u32, u32); 16] = [
(0, 0, 1, 1),
(0, 0, 1, 1),
(150, 216, 381, 119),
(170, 205, 514, 112),
(177, 199, 539, 110),
(197, 194, 644, 107),
(221, 192, 735, 107),
(256, 189, 881, 106),
(359, 188, 1167, 109),
(582, 187, 1570, 114),
(688, 187, 1712, 122),
(825, 186, 1965, 136),
(976, 185, 2131, 150),
(1180, 186, 2070, 175),
(1377, 185, 1731, 202),
(1412, 185, 1695, 202),
];
#[inline(never)]
fn x2_from_x1_into(recycle: Vec<u32>, table: &[u16], table_log: u8) -> Vec<u32> {
let n = table.len();
let log = u32::from(table_log);
let mut min_nbits = log;
for &e in table {
let nb = u32::from(e >> 8);
if nb > 0 && nb < min_nbits {
min_nbits = nb;
}
}
let mask = n.saturating_sub(1);
let mut out = recycle;
out.clear();
out.resize(n, 0);
for (val, slot) in out.iter_mut().enumerate() {
debug_assert!(val < table.len());
#[allow(unsafe_code)]
let e1 = *unsafe { table.get_unchecked(val) };
let s1 = u32::from(e1 as u8);
let n1 = u32::from(e1 >> 8);
let leftover = log.saturating_sub(n1);
if n1 == 0 || leftover < min_nbits {
*slot = s1 | (n1 << 16) | (1 << 24);
continue;
}
let second_index = (val & ((1usize << leftover) - 1)) << n1;
let e2 = table[second_index & mask];
let s2 = u32::from(e2 as u8);
let n2 = u32::from(e2 >> 8);
if n2 == 0 || n2 > leftover {
*slot = s1 | (n1 << 16) | (1 << 24);
} else {
*slot = s1 | (s2 << 8) | ((n1 + n2) << 16) | (2 << 24);
}
}
out
}
#[cfg(all(feature = "std", feature = "alloc"))]
mod huff_pool {
use alloc::vec::Vec;
use core::cell::RefCell;
const CAP: usize = 4;
thread_local! {
static X1: RefCell<Vec<Vec<u16>>> = const { RefCell::new(Vec::new()) };
static X2: RefCell<Vec<Vec<u32>>> = const { RefCell::new(Vec::new()) };
static W: RefCell<Vec<Vec<u8>>> = const { RefCell::new(Vec::new()) };
}
pub(super) fn take_w() -> Vec<u8> {
let mut v = W
.try_with(|c| c.try_borrow_mut().ok().and_then(|mut p| p.pop()))
.ok()
.flatten()
.unwrap_or_default();
v.clear();
v
}
pub(super) fn give_w(v: Vec<u8>) {
if v.capacity() == 0 {
return;
}
let _ = W.try_with(|c| {
if let Ok(mut p) = c.try_borrow_mut() {
if p.len() < CAP {
p.push(v)
}
}
});
}
pub(super) fn take() -> (Vec<u16>, Vec<u32>) {
let a = X1
.try_with(|c| c.try_borrow_mut().ok().and_then(|mut p| p.pop()))
.ok()
.flatten()
.unwrap_or_default();
let b = X2
.try_with(|c| c.try_borrow_mut().ok().and_then(|mut p| p.pop()))
.ok()
.flatten()
.unwrap_or_default();
(a, b)
}
pub(super) fn give(a: Vec<u16>, b: Vec<u32>) {
if a.capacity() != 0 {
let _ = X1.try_with(|c| {
if let Ok(mut p) = c.try_borrow_mut() {
if p.len() < CAP {
p.push(a)
}
}
});
}
if b.capacity() != 0 {
let _ = X2.try_with(|c| {
if let Ok(mut p) = c.try_borrow_mut() {
if p.len() < CAP {
p.push(b)
}
}
});
}
}
}
#[cfg(all(feature = "std", feature = "alloc"))]
impl Drop for HuffCTable {
fn drop(&mut self) {
huff_pool::give(
core::mem::take(&mut self.table.table),
core::mem::take(&mut self.table.table_x2),
);
huff_pool::give_w(core::mem::take(&mut self.weights_wo_last));
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug)]
pub(crate) struct HuffCTable {
entry: [u32; 256],
#[allow(dead_code)]
table: HuffmanTable,
weights_wo_last: Vec<u8>,
max_nbits: u8,
mean_nbits_x10: u8,
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum HuffUpdate {
Unchanged,
New(HuffCTable),
}
#[cfg(feature = "alloc")]
#[cfg(feature = "profile")]
pub static E11_WALKED: (core::sync::atomic::AtomicU64, core::sync::atomic::AtomicU64) = (
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
);
#[cfg(feature = "profile")]
pub fn take_e11_walked() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
E11_WALKED.0.swap(0, Ordering::Relaxed),
E11_WALKED.1.swap(0, Ordering::Relaxed),
)
}
impl HuffCTable {
fn encode_stream_into(&self, src: &[u8], buf: Vec<u8>) -> Result<Vec<u8>, Error> {
if crate::encode::huff_fast_enabled() {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { self.encode_stream_unrolled_bmi2_into(src, buf) };
}
self.encode_stream_unrolled_into(src, buf)
} else {
self.encode_stream_scalar_into(src, buf)
}
}
#[allow(dead_code)] fn encode_stream(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
if crate::encode::huff_fast_enabled() {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { self.encode_stream_unrolled_bmi2(src) };
}
self.encode_stream_unrolled(src)
} else {
self.encode_stream_scalar(src)
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn encode_stream_unrolled_bmi2(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
self.encode_stream_unrolled(src)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2")]
#[allow(unsafe_code)]
unsafe fn encode_stream_unrolled_bmi2_into(
&self,
src: &[u8],
buf: Vec<u8>,
) -> Result<Vec<u8>, Error> {
self.encode_stream_unrolled_into(src, buf)
}
#[inline]
fn covers_freq(&self, freq: &[u32; 256]) -> bool {
for (s, &c) in freq.iter().enumerate() {
if c != 0 && self.entry[s] >> 16 == 0 {
return false;
}
}
true
}
#[cfg(test)]
fn covers(&self, src: &[u8]) -> bool {
for &b in src {
if self.entry[b as usize] >> 16 == 0 {
return false;
}
}
true
}
fn encode_stream_scalar(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
self.encode_stream_scalar_into(src, Vec::new())
}
fn encode_stream_scalar_into(&self, src: &[u8], buf: Vec<u8>) -> Result<Vec<u8>, Error> {
if src.is_empty() {
return Err(Error::Corruption);
}
let mut bits = crate::bit::BitCStream::from_vec(buf, src.len() + 8);
for &b in src.iter().rev() {
let e = self.entry[b as usize];
let nb = e >> 16;
if nb == 0 {
return Err(Error::Corruption);
}
bits.add_bits(u64::from(e & 0xFFFF), nb);
}
Ok(bits.close())
}
#[inline(always)]
fn encode_stream_unrolled(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
self.encode_stream_unrolled_into(src, Vec::new())
}
fn encode_stream_unrolled_into(&self, src: &[u8], buf: Vec<u8>) -> Result<Vec<u8>, Error> {
if src.is_empty() {
return Err(Error::Corruption);
}
let mut bits = crate::bit::BitCStream::from_vec(buf, src.len() + 8);
self.encode_rev_into(&mut bits, src);
Ok(bits.close())
}
#[inline(always)]
fn encode_rev_into(&self, bits: &mut crate::bit::BitCStream, src: &[u8]) {
crate::prof::note_huff_path(if self.use_fill() {
0
} else {
match self.max_nbits {
0..=3 => 1,
4 => 2,
5 => 3,
6 => 4,
7 => 5,
8 => 6,
9 => 7,
_ => 8,
}
});
crate::prof::note_huff_path(9 + self.max_nbits.min(10));
if self.use_fill() {
self.emit_fill(bits, src);
return;
}
match self.max_nbits {
0..=3 => self.emit_k::<16>(bits, src),
4 => self.emit_k::<14>(bits, src),
5 => self.emit_k::<11>(bits, src),
6 => self.emit_k::<9>(bits, src),
7 => self.emit_k::<8>(bits, src),
8 => self.emit_k::<7>(bits, src),
9 => self.emit_k::<6>(bits, src),
_ => self.emit_k5(bits, src),
}
}
#[inline(always)]
fn use_fill(&self) -> bool {
if self.mean_nbits_x10 > 70 {
return false;
}
let mean_x10 = u32::from(self.mean_nbits_x10.max(1));
let k = k_from_max(self.max_nbits);
600 / mean_x10 > k + 2
}
#[allow(unsafe_code)]
#[inline(always)]
fn emit_k5(&self, bits: &mut crate::bit::BitCStream, src: &[u8]) {
let mut i = src.len();
while i >= 5 {
bits.flush();
for _ in 0..5 {
i -= 1;
debug_assert!(i < src.len());
self.huff_sym(bits, unsafe { *src.get_unchecked(i) });
}
}
self.emit_tail(bits, src, i);
}
#[inline(always)]
#[allow(unsafe_code)]
fn emit_k<const K: usize>(&self, bits: &mut crate::bit::BitCStream, src: &[u8]) {
let mut i = src.len();
while i >= K {
bits.flush();
let mut n = 0usize;
while n < K {
i -= 1;
debug_assert!(i < src.len());
self.huff_sym(bits, unsafe { *src.get_unchecked(i) });
n += 1;
}
}
self.emit_tail(bits, src, i);
}
#[allow(unsafe_code)]
#[inline(always)]
fn emit_fill(&self, bits: &mut crate::bit::BitCStream, src: &[u8]) {
let k = k_from_max(self.max_nbits) as usize;
let mut i = src.len();
while i >= k {
bits.flush();
let mut n = 0usize;
while n < k {
i -= 1;
self.huff_sym(bits, unsafe { *src.get_unchecked(i) });
n += 1;
}
while i > 0 {
let e = self.entry[unsafe { *src.get_unchecked(i - 1) } as usize];
let nb = e >> 16;
debug_assert!(nb != 0, "CTable missing symbol {}", src[i - 1]);
if !bits.huff_fits(nb) {
break;
}
i -= 1;
bits.add_bits_huff(u64::from(e & 0xFFFF), nb);
}
}
self.emit_tail(bits, src, i);
}
#[allow(unsafe_code)]
fn emit_tail(&self, bits: &mut crate::bit::BitCStream, src: &[u8], mut i: usize) {
while i > 0 {
i -= 1;
let b = unsafe { *src.get_unchecked(i) };
let e = self.entry[b as usize];
let nb = e >> 16;
debug_assert!(nb != 0, "CTable missing symbol {b}");
bits.add_bits(u64::from(e & 0xFFFF), nb);
}
}
#[inline(always)]
fn huff_sym(&self, bits: &mut crate::bit::BitCStream, b: u8) {
let e = self.entry[b as usize];
let nb = e >> 16;
debug_assert!(nb != 0, "CTable missing symbol {b}");
bits.add_bits_huff(u64::from(e & 0xFFFF), nb);
}
}
#[cfg(feature = "alloc")]
struct Node {
count: u64,
left: usize,
right: usize,
sym: i16,
}
crate::scratch::scratch_slot!(SC_PRESENT: u8);
crate::scratch::scratch_slot!(SC_NODES: Node);
crate::scratch::scratch_slot!(SC_LEAVES: usize);
crate::scratch::scratch_slot!(SC_INTERNAL: usize);
#[cfg(feature = "alloc")]
#[inline(never)]
fn huffman_nbits(freq: &[u32; 256]) -> Result<[u8; 256], Error> {
let mut present = crate::scratch::lease(&SC_PRESENT);
present.extend(
(0..256u16)
.filter(|&s| freq[s as usize] > 0)
.map(|s| s as u8),
);
if present.len() < 2 {
return Err(Error::Corruption);
}
let mut nbits = [0u8; 256];
if let [a, b] = present[..] {
nbits[a as usize] = 1;
nbits[b as usize] = 1;
return Ok(nbits);
}
let mut nodes = crate::scratch::lease(&SC_NODES);
nodes.extend(present.iter().map(|&s| Node {
count: u64::from(freq[s as usize]),
left: usize::MAX,
right: usize::MAX,
sym: i16::from(s),
}));
#[cfg(feature = "profile")]
{
let n = nodes.len() as u64;
N13_STATS[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
N13_STATS[1].fetch_add(n, core::sync::atomic::Ordering::Relaxed);
N13_STATS[2].fetch_add(n * n, core::sync::atomic::Ordering::Relaxed);
}
fn pop_min(
nodes: &[Node],
leaves: &[usize],
li: &mut usize,
internal: &[usize],
ii: &mut usize,
) -> Option<usize> {
let lc = leaves.get(*li).and_then(|&i| nodes.get(i)).map(|n| n.count);
let ic = internal
.get(*ii)
.and_then(|&i| nodes.get(i))
.map(|n| n.count);
match (lc, ic) {
(Some(l), Some(x)) if l <= x => {
let r = leaves[*li];
*li += 1;
Some(r)
}
(Some(_), Some(_)) | (None, Some(_)) => {
let r = internal[*ii];
*ii += 1;
Some(r)
}
(Some(_), None) => {
let r = leaves[*li];
*li += 1;
Some(r)
}
(None, None) => None,
}
}
let mut leaves = crate::scratch::lease(&SC_LEAVES);
leaves.extend(0..nodes.len());
leaves.sort_by_key(|&i| nodes.get(i).map_or(0, |n| n.count));
let mut internal = crate::scratch::lease(&SC_INTERNAL);
let (mut li, mut ii) = (0usize, 0usize);
while (leaves.len() - li) + (internal.len() - ii) > 1 {
let a = pop_min(&nodes, &leaves, &mut li, &internal, &mut ii).ok_or(Error::Corruption)?;
let b = pop_min(&nodes, &leaves, &mut li, &internal, &mut ii).ok_or(Error::Corruption)?;
let (ca, cb) = match (nodes.get(a), nodes.get(b)) {
(Some(x), Some(y)) => (x.count, y.count),
_ => return Err(Error::Corruption),
};
let parent = nodes.len();
nodes.push(Node {
count: ca + cb,
left: a,
right: b,
sym: -1,
});
internal.push(parent);
}
fn walk(nodes: &[Node], i: usize, depth: u8, nbits: &mut [u8; 256]) {
let Some(node) = nodes.get(i) else { return };
if node.sym >= 0 {
if let Some(slot) = nbits.get_mut(node.sym as usize) {
*slot = depth.max(1);
}
return;
}
walk(nodes, node.left, depth.saturating_add(1), nbits);
walk(nodes, node.right, depth.saturating_add(1), nbits);
}
let root = match (leaves.get(li), internal.get(ii)) {
(Some(&r), None) | (None, Some(&r)) => r,
_ => return Err(Error::Corruption),
};
walk(&nodes, root, 0, &mut nbits);
limit_nbits(&mut nbits, &present, MAX_BITS);
Ok(nbits)
}
#[cfg(feature = "profile")]
pub static E12_SCAN: [core::sync::atomic::AtomicU64; 3] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_e12_scan() -> [u64; 3] {
use core::sync::atomic::Ordering;
[
E12_SCAN[0].swap(0, Ordering::Relaxed),
E12_SCAN[1].swap(0, Ordering::Relaxed),
E12_SCAN[2].swap(0, Ordering::Relaxed),
]
}
#[cfg(feature = "alloc")]
#[inline(never)]
fn limit_nbits(nbits: &mut [u8; 256], present: &[u8], max_bits: u8) {
#[cfg(feature = "profile")]
E12_SCAN[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let max = i32::from(max_bits);
let mut kraft = 0i32;
for &s in present {
if nbits[s as usize] > max_bits || nbits[s as usize] == 0 {
nbits[s as usize] = max_bits;
}
kraft += 1 << (max - i32::from(nbits[s as usize]));
}
let target = 1 << max;
while kraft > target {
#[cfg(feature = "profile")]
{
E12_SCAN[1].fetch_add(present.len() as u64, core::sync::atomic::Ordering::Relaxed);
E12_SCAN[2].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let mut best: Option<usize> = None;
let mut best_nb = 0u8;
for &s in present {
let nb = nbits[s as usize];
if nb < max_bits && (best.is_none() || nb < best_nb) {
best = Some(s as usize);
best_nb = nb;
}
}
let Some(s) = best else {
break;
};
kraft -= 1 << (max - i32::from(nbits[s]) - 1);
nbits[s] += 1;
}
while kraft < target {
#[cfg(feature = "profile")]
{
E12_SCAN[1].fetch_add(present.len() as u64, core::sync::atomic::Ordering::Relaxed);
E12_SCAN[2].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let mut best: Option<usize> = None;
let mut best_nb = 0u8;
for &s in present {
let nb = nbits[s as usize];
if nb > 1 && (best.is_none() || nb > best_nb) {
best = Some(s as usize);
best_nb = nb;
}
}
let Some(s) = best else {
break;
};
kraft += 1 << (max - i32::from(nbits[s]));
nbits[s] -= 1;
}
}
#[cfg(feature = "alloc")]
#[inline(never)]
fn ctable_from_nbits(nbits: &[u8; 256], freq: Option<&[u32; 256]>) -> Result<HuffCTable, Error> {
let max_symbol = nbits
.iter()
.rposition(|&nb| nb > 0)
.ok_or(Error::Corruption)?;
let huff_log = nbits.iter().copied().max().unwrap_or(0);
if huff_log == 0 || huff_log > MAX_BITS {
return Err(Error::Corruption);
}
if max_symbol == 0 {
return Err(Error::Corruption);
}
#[cfg(all(feature = "std", feature = "alloc"))]
let mut weights = huff_pool::take_w();
#[cfg(not(all(feature = "std", feature = "alloc")))]
let mut weights = Vec::new();
weights.resize(max_symbol, 0u8);
for (s, slot) in weights.iter_mut().enumerate() {
debug_assert!(s < nbits.len());
#[allow(unsafe_code)]
let nb = *unsafe { nbits.get_unchecked(s) };
*slot = if nb == 0 { 0 } else { huff_log + 1 - nb };
}
let mut wtmp = [0u8; 256];
if weights.len() > 255 {
return Err(Error::Corruption);
}
wtmp[..weights.len()].copy_from_slice(&weights);
let table = table_from_weights(None, &mut wtmp, weights.len(), cfg!(test))?;
let mut out_nbits = [0u8; 256];
let mut code = [0u16; 256];
let max = table.max_bits;
for (idx, &e) in table.table.iter().enumerate() {
let sym = e as u8;
let nb = (e >> 8) as u8;
if nb == 0 {
continue;
}
#[allow(unsafe_code)]
unsafe {
if *out_nbits.get_unchecked(sym as usize) == 0 {
*out_nbits.get_unchecked_mut(sym as usize) = nb;
let shift = u32::from(max.saturating_sub(nb));
*code.get_unchecked_mut(sym as usize) = (idx >> shift) as u16;
}
}
}
Ok(finish_ctable(
pack_huff_entries(&out_nbits, &code),
table,
weights,
&out_nbits,
freq,
))
}
#[cfg(feature = "alloc")]
pub(crate) fn ctable_from_weights(weights: &[u8]) -> Result<HuffCTable, Error> {
let mut wtmp = [0u8; 256];
if weights.len() > 255 {
return Err(Error::Corruption);
}
wtmp[..weights.len()].copy_from_slice(weights);
let table = table_from_weights(None, &mut wtmp, weights.len(), cfg!(test))?;
let mut out_nbits = [0u8; 256];
let mut code = [0u16; 256];
let max = table.max_bits;
for (idx, &e) in table.table.iter().enumerate() {
let sym = e as u8;
let nb = (e >> 8) as u8;
if nb == 0 {
continue;
}
#[allow(unsafe_code)]
unsafe {
if *out_nbits.get_unchecked(sym as usize) == 0 {
*out_nbits.get_unchecked_mut(sym as usize) = nb;
let shift = u32::from(max.saturating_sub(nb));
*code.get_unchecked_mut(sym as usize) = (idx >> shift) as u16;
}
}
}
Ok(finish_ctable(
pack_huff_entries(&out_nbits, &code),
table,
weights.to_vec(),
&out_nbits,
None,
))
}
#[cfg(feature = "alloc")]
pub(crate) fn read_ctable(src: &[u8]) -> Result<(HuffCTable, usize), Error> {
let (table, used) = read_table(None, src)?;
let _ = table;
let header = src[0];
let weights = if header >= 128 {
let nsym = header as usize - 127;
let nbytes = nsym.div_ceil(2);
if 1 + nbytes > src.len() {
return Err(Error::Corruption);
}
let mut w = vec![0u8; nsym];
for (i, slot) in w.iter_mut().enumerate() {
debug_assert!(1 + i / 2 < src.len());
#[allow(unsafe_code)]
let b = *unsafe { src.get_unchecked(1 + i / 2) };
*slot = if i % 2 == 0 { b >> 4 } else { b & 0x0F };
}
w
} else {
let csize = header as usize;
let (w, _) = fse::decompress_weights(&src[1..1 + csize], 255)?;
w
};
ctable_from_weights(&weights).map(|ct| (ct, used))
}
#[cfg(feature = "alloc")]
fn pack_huff_entries(nbits: &[u8; 256], code: &[u16; 256]) -> [u32; 256] {
let mut entry = [0u32; 256];
for i in 0..256 {
entry[i] = u32::from(code[i]) | (u32::from(nbits[i]) << 16);
}
entry
}
#[cfg(feature = "alloc")]
#[cfg(test)]
pub(crate) fn build_ctable(src: &[u8]) -> Result<HuffCTable, Error> {
let mut freq = [0u32; 256];
for &b in src {
freq[b as usize] += 1;
}
build_ctable_from_freq(&freq)
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub(crate) fn build_ctable_from_freq(freq: &[u32; 256]) -> Result<HuffCTable, Error> {
let nbits = huffman_nbits(freq)?;
ctable_from_nbits(&nbits, Some(freq))
}
#[cfg(feature = "alloc")]
fn huff_mean_nbits_x10(nbits: &[u8; 256], freq: Option<&[u32; 256]>) -> u8 {
let mut acc = 0u64;
let mut n = 0u64;
if let Some(freq) = freq {
for i in 0..256 {
let nb = nbits[i];
if nb != 0 {
let f = u64::from(freq[i]);
acc += f * u64::from(nb);
n += f;
}
}
} else {
for &nb in nbits {
if nb != 0 {
acc += u64::from(nb);
n += 1;
}
}
}
if n == 0 {
return 110;
}
((acc * 10 + n / 2) / n) as u8
}
#[cfg(feature = "alloc")]
#[inline(always)]
fn k_from_max(max_nbits: u8) -> u32 {
match max_nbits {
0..=3 => 16,
4 => 14,
5 => 11,
6 => 9,
7 => 8,
8 => 7,
9 => 6,
_ => 5,
}
}
#[cfg(all(feature = "alloc", test))]
mod nbits_census {
use std::cell::{Cell, RefCell};
thread_local! {
static ON: Cell<bool> = const { Cell::new(false) };
static ROWS: RefCell<Vec<(u8, u8)>> = const { RefCell::new(Vec::new()) };
}
pub(super) fn note(max_nbits: u8, mean_nbits_x10: u8) {
if ON.with(Cell::get) {
ROWS.with(|r| r.borrow_mut().push((max_nbits, mean_nbits_x10)));
}
}
pub(super) fn start() {
ON.with(|c| c.set(true));
ROWS.with(|r| r.borrow_mut().clear());
}
pub(super) fn take() -> Vec<(u8, u8)> {
ON.with(|c| c.set(false));
ROWS.with(|r| core::mem::take(&mut *r.borrow_mut()))
}
}
#[cfg(feature = "alloc")]
fn finish_ctable(
entry: [u32; 256],
table: HuffmanTable,
weights_wo_last: Vec<u8>,
nbits: &[u8; 256],
freq: Option<&[u32; 256]>,
) -> HuffCTable {
let max_nbits = nbits.iter().copied().max().unwrap_or(0);
let mean_nbits_x10 = huff_mean_nbits_x10(nbits, freq);
#[cfg(test)]
nbits_census::note(max_nbits, mean_nbits_x10);
HuffCTable {
entry,
table,
weights_wo_last,
max_nbits,
mean_nbits_x10,
}
}
#[cfg(feature = "alloc")]
#[inline(always)]
fn write_tree_raw(weights: &[u8]) -> Result<Vec<u8>, Error> {
if weights.is_empty() || weights.len() > 128 {
return Err(Error::Corruption);
}
let nsym = weights.len();
let mut out = crate::scratch::pool_take(&SC_TREE);
out.reserve(1 + nsym.div_ceil(2));
out.push(128 + (nsym as u8 - 1));
let mut i = 0usize;
while i < nsym {
let hi = weights[i];
let lo = if i + 1 < nsym { weights[i + 1] } else { 0 };
if hi > 15 || lo > 15 {
return Err(Error::Corruption);
}
out.push((hi << 4) | (lo & 0x0F));
i += 2;
}
Ok(out)
}
#[cfg(feature = "alloc")]
#[inline(never)]
fn write_tree_fse(weights: &[u8]) -> Result<Vec<u8>, Error> {
if weights.len() <= 2 {
return Err(Error::Corruption);
}
let mut count = [0u32; 13];
for &w in weights {
if w as usize >= count.len() {
return Err(Error::Corruption);
}
count[w as usize] += 1;
}
let total = weights.len() as u32;
if count.contains(&total) {
return Err(Error::Corruption);
}
let max_sv = count
.iter()
.rposition(|&c| c > 0)
.ok_or(Error::Corruption)?;
let table_log = fse::optimal_table_log(6, weights.len(), max_sv).min(6);
let norm = fse::normalize_count(&count[..=max_sv], table_log, total, false)?;
let ncount = fse::write_ncount(&norm, table_log)?;
let ct = fse::FseCTable::from_norm(&norm, table_log)?;
let payload = fse::compress_using_ctable(weights, &ct)?;
let csize = ncount.len() + payload.len();
if csize == 0 || csize >= 128 {
return Err(Error::Corruption);
}
let mut out = Vec::with_capacity(1 + csize);
out.push(csize as u8);
out.extend_from_slice(&ncount);
out.extend_from_slice(&payload);
Ok(out)
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub(crate) fn write_tree(ct: &HuffCTable) -> Result<Vec<u8>, Error> {
let weights = &ct.weights_wo_last;
let raw = write_tree_raw(weights).ok();
let fse = match write_tree_fse(weights) {
Ok(fse) if fse.len() > 2 && fse[0] < 128 && fse.len() == 1 + usize::from(fse[0]) => {
let mut wbuf = [0u8; 256];
match fse::decompress_weights_into(&mut wbuf, &fse[1..], 255) {
Ok((wlen, _)) if wbuf[..wlen] == **weights => Some(fse),
_ => None,
}
}
_ => None,
};
match (raw, fse) {
(Some(r), Some(f)) if f.len() < r.len() => {
crate::scratch::pool_give(&SC_TREE, r);
Ok(f)
}
(Some(r), Some(f)) => {
crate::scratch::pool_give(&SC_TREE, f);
Ok(r)
}
(Some(r), None) => Ok(r),
(None, Some(f)) => Ok(f),
_ => Err(Error::Corruption),
}
}
#[cfg(feature = "alloc")]
fn give_tree_buf(v: Vec<u8>) {
crate::scratch::pool_give(&SC_TREE, v);
}
#[cfg(feature = "alloc")]
#[allow(dead_code)] fn write_lit_huff_header(
lit_type: u8,
n_streams: u32,
regen: u32,
csize: u32,
) -> Result<Vec<u8>, Error> {
write_lit_huff_header_into(lit_type, n_streams, regen, csize, Vec::new())
}
fn write_lit_huff_header_into(
lit_type: u8,
n_streams: u32,
regen: u32,
csize: u32,
outbuf: Vec<u8>,
) -> Result<Vec<u8>, Error> {
let mut h = outbuf;
h.clear();
if n_streams == 1 {
if regen > 0x3FF || csize > 0x3FF {
return Err(Error::Corruption);
}
h.push(lit_type | ((regen & 0xF) << 4) as u8);
h.push((((regen >> 4) & 0x3F) as u8) | (((csize & 3) as u8) << 6));
h.push((csize >> 2) as u8);
return Ok(h);
}
if regen <= 0x3FF && csize <= 0x3FF {
h.push(lit_type | (1 << 2) | ((regen & 0xF) << 4) as u8);
h.push((((regen >> 4) & 0x3F) as u8) | (((csize & 3) as u8) << 6));
h.push((csize >> 2) as u8);
} else if regen <= 0x3FFF && csize <= 0x3FFF {
h.push(lit_type | (2 << 2) | ((regen & 0xF) << 4) as u8);
h.push((regen >> 4) as u8);
h.push((((regen >> 12) & 3) as u8) | (((csize & 0x3F) as u8) << 2));
h.push((csize >> 6) as u8);
} else if regen <= 0x3FFFF && csize <= 0x3FFFF {
let lhc = u32::from(lit_type) | (3 << 2) | (regen << 4) | (csize << 22);
h.extend_from_slice(&lhc.to_le_bytes());
h.push((csize >> 10) as u8);
} else {
return Err(Error::Corruption);
}
Ok(h)
}
#[cfg(feature = "alloc")]
#[cfg(feature = "alloc")]
fn body_bytes_exact(ct: &HuffCTable, seg: &[[u32; 256]], n_streams: u32) -> Option<usize> {
let mut total = if n_streams == 4 { 6 } else { 0 };
for h in seg.iter() {
let mut bits: u64 = 0;
let mut any = false;
for (sym, &f) in h.iter().enumerate() {
if f == 0 {
continue;
}
any = true;
debug_assert!(sym < ct.entry.len());
#[allow(unsafe_code)]
let nb = *unsafe { ct.entry.get_unchecked(sym) } >> 16;
if nb == 0 {
return None;
}
bits += u64::from(f) * u64::from(nb);
}
if !any {
return None;
}
let bytes = (bits + 1).div_ceil(8) as usize;
if n_streams == 4 && bytes > 65535 {
return None;
}
total += bytes;
}
Some(total)
}
#[cfg(feature = "alloc")]
#[allow(dead_code)] fn segment_histograms_into(lits: &[u8], n_streams: u32, out: &mut Vec<[u32; 256]>) {
out.clear();
if n_streams != 4 {
let mut h = [0u32; 256];
hist_count(lits, &mut h);
out.push(h);
return;
}
out.resize(4, [0u32; 256]);
segment_histograms_fill(lits, out);
}
#[cfg(test)]
#[allow(dead_code)] fn segment_histograms(lits: &[u8], n_streams: u32) -> Vec<[u32; 256]> {
let mut v = Vec::new();
segment_histograms_into(lits, n_streams, &mut v);
v
}
fn segment_histograms_fill(lits: &[u8], segs: &mut [[u32; 256]]) {
let chunk = lits.len().div_ceil(4);
let mut off = 0usize;
for (i, h) in segs.iter_mut().enumerate() {
let end = if i == 3 {
lits.len()
} else {
(off + chunk).min(lits.len())
};
hist_count(&lits[off..end], h);
off = end;
}
}
#[cfg(feature = "alloc")]
fn hist_count(bytes: &[u8], h: &mut [u32; 256]) {
let mut h1 = [0u32; 256];
let mut h2 = [0u32; 256];
let mut h3 = [0u32; 256];
let mut it = bytes.chunks_exact(4);
for c in &mut it {
h[c[0] as usize] += 1;
h1[c[1] as usize] += 1;
h2[c[2] as usize] += 1;
h3[c[3] as usize] += 1;
}
for &b in it.remainder() {
h[b as usize] += 1;
}
for i in 0..256 {
h[i] += h1[i] + h2[i] + h3[i];
}
}
crate::scratch::scratch_slot!(SC_STREAMS: Vec<u8>);
crate::scratch::scratch_slot!(SC_SEGS: [u32; 256]);
#[cfg(feature = "alloc")]
crate::scratch::pool_slot!(SC_TREE: u8);
#[allow(dead_code)] fn encode_4_streams(ct: &HuffCTable, src: &[u8]) -> Result<Vec<u8>, Error> {
encode_4_streams_into(ct, src, Vec::new())
}
#[cfg(feature = "alloc")]
fn encode_4_streams_into(ct: &HuffCTable, src: &[u8], outbuf: Vec<u8>) -> Result<Vec<u8>, Error> {
let chunk = src.len().div_ceil(4);
let mut streams = crate::scratch::lease_pool(&SC_STREAMS);
let mut off = 0usize;
for i in 0..4 {
let end = if i == 3 {
src.len()
} else {
(off + chunk).min(src.len())
};
let piece = &src[off..end];
if piece.is_empty() {
return Err(Error::Corruption);
}
let reuse = streams.get_mut(i).map(core::mem::take).unwrap_or_default();
let s = ct.encode_stream_into(piece, reuse)?;
if s.len() > 65535 {
return Err(Error::Corruption);
}
if i < streams.len() {
streams[i] = s;
} else {
streams.push(s);
}
off = end;
}
let body: usize = streams.iter().map(|s| s.len()).sum();
let mut out = outbuf;
out.clear();
out.reserve(6 + body);
let [s0, s1, s2, _s3] = &streams[..] else {
return Err(Error::Corruption);
};
out.extend_from_slice(&(s0.len() as u16).to_le_bytes());
out.extend_from_slice(&(s1.len() as u16).to_le_bytes());
out.extend_from_slice(&(s2.len() as u16).to_le_bytes());
for s in streams.iter() {
out.extend_from_slice(s);
}
Ok(out)
}
#[cfg(feature = "alloc")]
#[allow(dead_code)] fn pack_huff_section(
lit_type: u8,
n_streams: u32,
regen: u32,
tree: &[u8],
body: &[u8],
) -> Result<Vec<u8>, Error> {
pack_huff_section_into(lit_type, n_streams, regen, tree, body, Vec::new())
}
fn pack_huff_section_into(
lit_type: u8,
n_streams: u32,
regen: u32,
tree: &[u8],
body: &[u8],
outbuf: Vec<u8>,
) -> Result<Vec<u8>, Error> {
let csize = (tree.len() + body.len()) as u32;
let mut out = write_lit_huff_header_into(lit_type, n_streams, regen, csize, outbuf)?;
out.extend_from_slice(tree);
out.extend_from_slice(body);
Ok(out)
}
#[cfg(feature = "alloc")]
pub(crate) fn literals_worth_huffman(lits: &[u8]) -> bool {
const SAMPLE: usize = 1024;
if lits.len() < 64 {
return true;
}
let mut freq = [0u32; 256];
let step = ((lits.len() / SAMPLE).max(1)) | 1;
let mut n = 0u32;
let mut i = 0usize;
while i < lits.len() && n < SAMPLE as u32 {
freq[lits[i] as usize] += 1;
i += step;
n += 1;
}
if n == 0 {
return true;
}
let distinct = freq.iter().filter(|&&f| f != 0).count() as u64;
if distinct.saturating_mul(2) >= lits.len() as u64 {
return false;
}
let sum_sq: u64 = freq.iter().map(|&f| u64::from(f) * u64::from(f)).sum();
sum_sq.saturating_mul(128) >= u64::from(n) * u64::from(n)
}
#[cfg(feature = "alloc")]
pub(crate) fn lit_sample_peak(lits: &[u8]) -> u32 {
const SAMPLE: usize = 256;
if lits.len() < 64 {
return 0;
}
let mut freq = [0u32; 256];
let step = (lits.len() / SAMPLE).max(1);
let mut n = 0u32;
let mut i = 0usize;
while i < lits.len() && n < SAMPLE as u32 {
freq[lits[i] as usize] += 1;
i += step;
n += 1;
}
if n == 0 {
return 0;
}
let mut max = 0u32;
for f in freq {
if f > max {
max = f;
}
}
max.saturating_mul(1000) / n
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub(crate) fn encode_literals_section(
lits: &[u8],
prev: Option<&HuffCTable>,
) -> Result<(Vec<u8>, HuffUpdate), Error> {
let n = lits.len() as u32;
if n == 0 {
return Ok((vec![0], HuffUpdate::Unchanged));
}
let all_same = n >= 2 && lits.iter().all(|&b| b == lits[0]);
if all_same {
return Ok((write_raw_or_rle(lits, true), HuffUpdate::Unchanged));
}
if n < 8 {
return Ok((write_raw_or_rle(lits, false), HuffUpdate::Unchanged));
}
if n >= 64 && !literals_worth_huffman(lits) {
return Ok((write_raw_or_rle(lits, false), HuffUpdate::Unchanged));
}
crate::prof::note_lit_try(0);
let raw_len = raw_section_len(n);
let mut best: Option<Vec<u8>> = None;
let mut best_len = raw_len;
let mut update = HuffUpdate::Unchanged;
let preferred: u32 = if n >= 256 { 4 } else { 1 };
let mut segs = crate::scratch::lease(&SC_SEGS);
segment_histograms_into(lits, preferred, &mut segs);
let mut freq = [0u32; 256];
for h in segs.iter() {
for (s, &c) in h.iter().enumerate() {
freq[s] += c;
}
}
let new_tbl = match build_ctable_from_freq(&freq) {
Ok(ct) => match write_tree(&ct) {
Ok(t) => Some((ct, t)),
Err(_) => None,
},
Err(_) => None,
};
if let Some(prev_ct) = prev {
#[cfg(feature = "profile")]
{
E11_WALKED
.0
.fetch_add(lits.len() as u64, core::sync::atomic::Ordering::Relaxed);
E11_WALKED
.1
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if prev_ct.covers_freq(&freq) {
let futile = match &new_tbl {
Some((ct, tree)) => {
match (
body_bytes_exact(prev_ct, &segs, preferred),
body_bytes_exact(ct, &segs, preferred),
) {
(Some(bp), Some(bn)) => bp >= bn + tree.len() + 8,
_ => false,
}
}
None => false,
};
if futile {
crate::prof::note_lit_try(6);
}
if !futile {
crate::prof::note_lit_try(1);
if let Some(sec) = try_huff_section(3, preferred, n, &[], prev_ct, lits) {
if sec.len() < best_len {
crate::prof::note_lit_try(2);
best_len = sec.len();
if let Some(old) = best.replace(sec) {
sec_pool_give(old);
}
update = HuffUpdate::Unchanged;
}
} else if preferred == 4 {
if let Some(sec) = try_huff_section(3, 1, n, &[], prev_ct, lits) {
if sec.len() < best_len {
best_len = sec.len();
if let Some(old) = best.replace(sec) {
sec_pool_give(old);
}
update = HuffUpdate::Unchanged;
}
}
}
}
}
}
if let Some((ct, tree)) = new_tbl {
{
crate::prof::note_lit_try(3);
if let Some(sec) = try_huff_section(2, preferred, n, &tree, &ct, lits) {
if sec.len() < best_len {
crate::prof::note_lit_try(4);
if let Some(old) = best.replace(sec) {
sec_pool_give(old);
}
update = HuffUpdate::New(ct);
}
} else if preferred == 4 {
if let Some(sec) = try_huff_section(2, 1, n, &tree, &ct, lits) {
if sec.len() < best_len {
if let Some(old) = best.replace(sec) {
sec_pool_give(old);
}
update = HuffUpdate::New(ct);
}
}
}
}
give_tree_buf(tree);
}
let best = match best {
Some(sec) => sec,
None => {
crate::prof::note_lit_try(5);
write_raw_or_rle(lits, false)
}
};
crate::prof::note_lit_margin(raw_len, best.len());
Ok((best, update))
}
#[cfg(feature = "alloc")]
fn try_huff_section(
lit_type: u8,
n_streams: u32,
regen: u32,
tree: &[u8],
ct: &HuffCTable,
lits: &[u8],
) -> Option<Vec<u8>> {
let buf = body_pool_take();
let body = if n_streams == 1 {
ct.encode_stream_into(lits, buf).ok()?
} else {
encode_4_streams_into(ct, lits, buf).ok()?
};
let sec = pack_huff_section_into(lit_type, n_streams, regen, tree, &body, sec_pool_take()).ok();
body_pool_give(body);
sec
}
#[cfg(all(feature = "std", feature = "alloc"))]
fn sec_pool_take() -> Vec<u8> {
SC_SEC.with(|c| c.borrow_mut().pop()).unwrap_or_default()
}
#[cfg(all(feature = "std", feature = "alloc"))]
pub(crate) fn sec_pool_give(v: Vec<u8>) {
if v.capacity() == 0 {
return;
}
SC_SEC.with(|c| {
let mut p = c.borrow_mut();
if p.len() < 6 {
p.push(v);
}
});
}
#[cfg(all(feature = "std", feature = "alloc"))]
thread_local! {
static SC_SEC: core::cell::RefCell<Vec<Vec<u8>>> =
const { core::cell::RefCell::new(Vec::new()) };
}
#[cfg(not(all(feature = "std", feature = "alloc")))]
fn sec_pool_take() -> Vec<u8> {
Vec::new()
}
#[cfg(not(all(feature = "std", feature = "alloc")))]
pub(crate) fn sec_pool_give(_v: Vec<u8>) {}
#[cfg(all(feature = "std", feature = "alloc"))]
fn body_pool_take() -> Vec<u8> {
SC_BODY.with(|c| c.borrow_mut().pop()).unwrap_or_default()
}
#[cfg(all(feature = "std", feature = "alloc"))]
fn body_pool_give(v: Vec<u8>) {
if v.capacity() == 0 {
return;
}
SC_BODY.with(|c| {
let mut p = c.borrow_mut();
if p.len() < 4 {
p.push(v);
}
});
}
#[cfg(all(feature = "std", feature = "alloc"))]
thread_local! {
static SC_BODY: core::cell::RefCell<Vec<Vec<u8>>> =
const { core::cell::RefCell::new(Vec::new()) };
}
#[cfg(not(all(feature = "std", feature = "alloc")))]
fn body_pool_take() -> Vec<u8> {
Vec::new()
}
#[cfg(not(all(feature = "std", feature = "alloc")))]
fn body_pool_give(_v: Vec<u8>) {}
#[cfg(feature = "alloc")]
#[cfg(test)]
fn huff_section_roundtrips(sec: &[u8], lits: &[u8]) -> bool {
if sec.is_empty() {
return false;
}
let lit_type = sec[0] & 3;
let size_fmt = (sec[0] >> 2) & 3;
let n_streams = match (lit_type, size_fmt) {
(2, 0) => 1,
(2, 1..=3) => 4,
_ => return false,
};
let hlen = match size_fmt {
0 | 1 => 3,
2 => 4,
3 => 5,
_ => return false,
};
if sec.len() <= hlen {
return false;
}
let payload = &sec[hlen..];
let hdr_csize = match size_fmt {
0 | 1 => (((u32::from(sec[1]) >> 6) + (u32::from(sec[2]) << 2)) & 0x3FF) as usize,
2 => ((u32::from(sec[2]) >> 2) + (u32::from(sec[3]) << 6)) as usize & 0x3FFF,
3 => {
((u32::from(sec[2]) >> 6) + (u32::from(sec[3]) << 2) + (u32::from(sec[4]) << 10))
as usize
& 0x3FFFF
}
_ => return false,
};
if hdr_csize != payload.len() {
return false;
}
let Ok((table, tree)) = read_table(None, payload) else {
return false;
};
if tree > payload.len() {
return false;
}
huff_body_roundtrips(&table, &payload[tree..], lits, n_streams)
}
#[cfg(test)]
fn huff_body_roundtrips(table: &HuffmanTable, body: &[u8], lits: &[u8], n_streams: u32) -> bool {
let mut out = vec![0u8; lits.len()];
if n_streams == 1 {
if table.decode_stream(body, &mut out).is_err() {
return false;
}
return out == lits;
}
if body.len() < 6 {
return false;
}
let s1 = u16::from_le_bytes([body[0], body[1]]) as usize;
let s2 = u16::from_le_bytes([body[2], body[3]]) as usize;
let s3 = u16::from_le_bytes([body[4], body[5]]) as usize;
let total = body.len() - 6;
if s1 + s2 + s3 > total {
return false;
}
let s4 = total - s1 - s2 - s3;
let rest = &body[6..];
let chunk = lits.len().div_ceil(4);
let mut off = 0usize;
let mut dst = 0usize;
let sizes = [s1, s2, s3, s4];
for (i, &sz) in sizes.iter().enumerate() {
let end = if i == 3 {
lits.len()
} else {
(dst + chunk).min(lits.len())
};
if off + sz > rest.len() || dst > end {
return false;
}
if table
.decode_stream(&rest[off..off + sz], &mut out[dst..end])
.is_err()
{
return false;
}
off += sz;
dst = end;
}
out == lits
}
#[cfg(feature = "alloc")]
#[cfg(feature = "alloc")]
fn raw_section_len(n: u32) -> usize {
let hdr = if n < 32 {
1
} else if n < 4096 {
2
} else {
3
};
hdr + n as usize
}
fn write_raw_or_rle(lits: &[u8], rle: bool) -> Vec<u8> {
let n = lits.len() as u32;
let ty: u8 = if rle { 1 } else { 0 };
let mut dst = Vec::new();
if n < 32 {
dst.push((n << 3) as u8 | ty);
} else if n < 4096 {
dst.push((1 << 2) | ty | ((n & 0xF) << 4) as u8);
dst.push((n >> 4) as u8);
} else {
dst.push((3 << 2) | ty | ((n & 0xF) << 4) as u8);
dst.push((n >> 4) as u8);
dst.push((n >> 12) as u8);
}
if rle {
debug_assert!(!lits.is_empty());
if let Some(&b) = lits.first() {
dst.push(b);
}
} else {
dst.extend_from_slice(lits);
}
dst
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
#[test]
fn rle_literals_section_emits_type_1_and_round_trips() {
for n in [2usize, 7, 63, 64, 300, 5000] {
let lits = vec![b'q'; n];
let (sec, upd) = encode_literals_section(&lits, None).expect("rle lits");
assert!(matches!(upd, HuffUpdate::Unchanged), "n={n}");
assert_eq!(sec[0] & 3, 1, "n={n}: literals section type must be RLE");
let mut r = crate::reader::Reader::new(&sec);
let mut st = crate::compressed::BlockState::new();
let got =
crate::compressed::decode_literals(Vec::new(), &mut r, &mut st).expect("decode");
assert_eq!(got, lits, "n={n}");
}
let mut mixed = vec![b'q'; 64];
mixed[10] = b'r';
let (sec, _) = encode_literals_section(&mixed, None).expect("mixed");
assert_ne!(sec[0] & 3, 1, "mixed literals must not be RLE");
}
use super::*;
#[test]
fn huffman_length_sweep() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
for n in [
8, 9, 16, 31, 32, 63, 64, 127, 128, 224, 255, 256, 267, 400, 512,
] {
let mut src = Vec::new();
while src.len() < n {
src.extend_from_slice(fox);
}
src.truncate(n);
let ct = build_ctable(&src).expect("build");
let stream = ct.encode_stream(&src).expect("encode");
let mut out = vec![0u8; src.len()];
ct.table
.decode_stream(&stream, &mut out)
.unwrap_or_else(|e| panic!("orig n={n}: {e:?}"));
assert_eq!(out, src, "orig-table n={n}");
let mut scalar_d = vec![0u8; src.len()];
ct.table
.decode_stream_scalar(&stream, &mut scalar_d)
.expect("decode scalar");
assert_eq!(scalar_d, src, "decode unroll vs scalar n={n}");
let scalar = ct.encode_stream_scalar(&src).expect("scalar");
assert_eq!(stream, scalar, "unrolled vs per-byte add_bits n={n}");
let (sec, upd) = encode_literals_section(&src, None).expect("section");
if n >= 224 {
assert_eq!(sec[0] & 3, 2, "Huffman Compressed literals n={n}");
match upd {
HuffUpdate::New(_) => {}
HuffUpdate::Unchanged => panic!("expected a new Huffman table n={n}"),
}
assert!(
huff_section_roundtrips(&sec, &src),
"read_table section n={n}"
);
}
}
}
#[test]
fn incompressible_literals_stay_raw() {
let mut src = vec![0u8; 4096];
let mut x = 0xA5A5_5A5A_u64;
for b in &mut src {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*b = x as u8;
}
assert!(!literals_worth_huffman(&src));
let (sec, _) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 0, "incomp literals should be raw");
}
#[test]
fn fox_literals_still_huffman() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 512 {
src.extend_from_slice(fox);
}
let ct = build_ctable(&src).expect("build");
let stream = ct.encode_stream(&src).expect("encode");
let mut out = vec![0u8; src.len()];
ct.table.decode_stream(&stream, &mut out).expect("decode");
assert_eq!(out, src);
let (sec, _) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 2, "fox text should still Huffman");
}
#[test]
fn huffman_section_roundtrip_via_read_table() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 224 {
src.extend_from_slice(fox);
}
src.truncate(224);
let (sec, upd) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 2, "expected Compressed Huffman literals");
match upd {
HuffUpdate::New(_) => {}
HuffUpdate::Unchanged => panic!("expected a new Huffman table"),
}
let lit_type = sec[0] & 3;
let size_fmt = (sec[0] >> 2) & 3;
let header_len = match (lit_type, size_fmt) {
(2 | 3, 0 | 1) => 3,
(2 | 3, 2) => 4,
(2 | 3, 3) => 5,
_ => panic!("unexpected header"),
};
let payload = &sec[header_len..];
let (table, tree) = read_table(None, payload).expect("read_table");
let mut out = vec![0u8; src.len()];
table
.decode_stream(&payload[tree..], &mut out)
.expect("decode_stream");
assert_eq!(out, src);
let mut frame = Vec::new();
crate::encode::write_frame_header(
&mut frame,
src.len() as u64,
10,
true,
Some(src.len() as u64),
None,
false,
);
let mut block = sec.clone();
block.push(0);
let n = block.len() as u32;
let hdr = 1u32 | (2 << 1) | (n << 3);
frame.push(hdr as u8);
frame.push((hdr >> 8) as u8);
frame.push((hdr >> 16) as u8);
frame.extend_from_slice(&block);
frame.extend_from_slice(&crate::xxh64::content_checksum(&src).to_le_bytes());
let got = crate::decompress(&frame).expect("frame decode");
assert_eq!(got, src);
}
#[test]
fn covers_rejects_unseen_symbol() {
let src = b"aaaaabbbbbccccc";
let ct = build_ctable(src).expect("build");
assert!(ct.covers(src));
assert!(!ct.covers(b"aaaaabbbbbcccccZ"));
}
#[test]
fn covers_freq_matches_covers_oracle() {
let base = b"aaaaabbbbbccccc";
let ct = build_ctable(base).expect("build");
let mut cases: Vec<Vec<u8>> = vec![
base.to_vec(),
b"aaaaabbbbbcccccZ".to_vec(),
b"a".to_vec(),
b"Z".to_vec(),
Vec::new(),
b"abcabcabc".to_vec(),
];
for b in 0..=255u8 {
cases.push(vec![b; 3]);
cases.push([base.as_slice(), &[b]].concat());
}
for c in &cases {
let mut freq = [0u32; 256];
for &b in c {
freq[b as usize] += 1;
}
assert_eq!(
ct.covers_freq(&freq),
ct.covers(c),
"covers_freq disagreed with the oracle on {:?}",
&c[..c.len().min(20)]
);
}
}
#[test]
fn encode_stream_unrolled_matches_scalar() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 4096 {
src.extend_from_slice(fox);
}
let ct = build_ctable(&src).expect("build");
for n in 1..=64 {
let s = &src[..n];
let a = ct.encode_stream(s).expect("fast");
let b = ct.encode_stream_scalar(s).expect("scalar");
assert_eq!(a, b, "n={n}");
}
for &n in &[65usize, 127, 128, 255, 256, 257, 511, 512, 1024, 4096] {
let s = &src[..n.min(src.len())];
let a = ct.encode_stream(s).expect("fast");
let b = ct.encode_stream_scalar(s).expect("scalar");
assert_eq!(a, b, "n={}", s.len());
}
let mut noise = vec![0u8; 1024];
let mut x = 0xC0FF_EE00_u64;
for b in &mut noise {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*b = x as u8;
}
let ct2 = build_ctable(&noise).expect("noise table");
let a = ct2.encode_stream(&noise).expect("fast");
let b = ct2.encode_stream_scalar(&noise).expect("scalar");
assert_eq!(a, b, "noise");
let mut peaked = vec![b'a'; 4096];
peaked.extend_from_slice(b"bc");
let ct3 = build_ctable(&peaked).expect("peaked table");
let a = ct3.encode_stream(&peaked).expect("fast");
let b = ct3.encode_stream_scalar(&peaked).expect("scalar");
assert_eq!(a, b, "peaked");
assert!(
ct3.max_nbits <= 3 || ct3.use_fill(),
"peaked max={} mean_x10={} should take K16 or fill",
ct3.max_nbits,
ct3.mean_nbits_x10
);
}
#[test]
fn huff_pack_dispatch_separates_peaked_from_flat() {
let mut peaked = vec![b'a'; 8192];
peaked.extend_from_slice(b"bcdefgh");
let ct = build_ctable(&peaked).expect("peaked");
assert!(
ct.use_fill() || ct.max_nbits <= 7,
"peaked should fill or take a wide K max={} mean_x10={}",
ct.max_nbits,
ct.mean_nbits_x10
);
let mut flat = vec![0u8; 8192];
for (i, b) in flat.iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let ct_f = build_ctable(&flat).expect("flat");
assert!(
!ct_f.use_fill(),
"flat/long-code must not fill max={} mean_x10={}",
ct_f.max_nbits,
ct_f.mean_nbits_x10
);
assert_eq!(k_from_max(9), 6);
assert_eq!(k_from_max(11), 5);
assert_eq!(k_from_max(7), 8);
assert_eq!(k_from_max(3), 16);
}
#[test]
fn decode_stream_unrolled_matches_scalar() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 4096 {
src.extend_from_slice(fox);
}
let ct = build_ctable(&src).expect("build");
for n in 1..=64 {
let s = &src[..n];
let stream = ct.encode_stream_scalar(s).expect("enc");
let mut a = vec![0u8; n];
let mut b = vec![0u8; n];
ct.table.decode_stream(&stream, &mut a).expect("fast");
ct.table
.decode_stream_scalar(&stream, &mut b)
.expect("scalar");
assert_eq!(a, b, "n={n}");
assert_eq!(a, s, "roundtrip n={n}");
}
for &n in &[65usize, 127, 128, 255, 256, 257, 511, 512, 1024, 4096] {
let s = &src[..n.min(src.len())];
let stream = ct.encode_stream_scalar(s).expect("enc");
let mut a = vec![0u8; s.len()];
let mut b = vec![0u8; s.len()];
ct.table.decode_stream(&stream, &mut a).expect("fast");
ct.table
.decode_stream_scalar(&stream, &mut b)
.expect("scalar");
assert_eq!(a, b, "n={}", s.len());
assert_eq!(a, s);
}
let mut noise = vec![0u8; 1024];
let mut x = 0xC0FF_EE00_u64;
for b in &mut noise {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*b = x as u8;
}
let ct2 = build_ctable(&noise).expect("noise table");
let stream = ct2.encode_stream_scalar(&noise).expect("enc");
let mut a = vec![0u8; noise.len()];
let mut b = vec![0u8; noise.len()];
ct2.table.decode_stream(&stream, &mut a).expect("fast");
ct2.table
.decode_stream_scalar(&stream, &mut b)
.expect("scalar");
assert_eq!(a, b, "noise");
assert_eq!(a, noise);
}
#[test]
fn encode_4_streams_matches_sequential_1x() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 1024 {
src.extend_from_slice(fox);
}
src.truncate(1024);
let ct = build_ctable(&src).expect("build");
let four = encode_4_streams(&ct, &src).expect("4x");
let chunk = src.len().div_ceil(4);
let mut off = 0usize;
let mut body = Vec::new();
let mut hdr = Vec::new();
for i in 0..4 {
let end = if i == 3 {
src.len()
} else {
(off + chunk).min(src.len())
};
let s = ct.encode_stream(&src[off..end]).expect("1x");
if i < 3 {
hdr.extend_from_slice(&(s.len() as u16).to_le_bytes());
}
body.extend_from_slice(&s);
off = end;
}
assert_eq!(&four[..6], hdr.as_slice());
assert_eq!(&four[6..], body.as_slice());
}
#[test]
fn decode_4x_matches_sequential() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 1024 {
src.extend_from_slice(fox);
}
src.truncate(1024);
let ct = build_ctable(&src).expect("build");
let packed = encode_4_streams(&ct, &src).expect("4x enc");
let s1 = u16::from_le_bytes([packed[0], packed[1]]) as usize;
let s2 = u16::from_le_bytes([packed[2], packed[3]]) as usize;
let s3 = u16::from_le_bytes([packed[4], packed[5]]) as usize;
let rest = &packed[6..];
let s4 = rest.len() - s1 - s2 - s3;
let chunk = src.len().div_ceil(4);
let mut lock = vec![0u8; src.len()];
let (d0, r) = lock.split_at_mut(chunk);
let (d1, r) = r.split_at_mut(chunk);
let (d2, d3) = r.split_at_mut(chunk);
ct.table
.decode_4x(
&rest[..s1],
&rest[s1..s1 + s2],
&rest[s1 + s2..s1 + s2 + s3],
&rest[s1 + s2 + s3..s1 + s2 + s3 + s4],
d0,
d1,
d2,
d3,
)
.expect("lockstep");
let mut seq = vec![0u8; src.len()];
let mut off = 0usize;
let mut dst = 0usize;
for (i, &sz) in [s1, s2, s3, s4].iter().enumerate() {
let end = if i == 3 { seq.len() } else { dst + chunk };
ct.table
.decode_stream(&rest[off..off + sz], &mut seq[dst..end])
.expect("seq");
off += sz;
dst = end;
}
assert_eq!(lock, seq);
assert_eq!(lock, src);
}
#[test]
fn select_x2_follows_c_breakpoints() {
assert!(!select_x2(255, 32), "dst < 256 (1-stream): X1");
assert!(select_x2(256, 32), "256B Q=2, table already built: X2");
assert!(select_x2(128 * 1024, 16 * 1024), "128KiB at ~12% : X2");
assert!(
!select_x2(128 * 1024, 128 * 1024),
"Q=15 incompressible: X1"
);
}
#[test]
fn huffman_four_stream_and_tree_encodings() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 512 {
src.extend_from_slice(fox);
}
src.truncate(512);
let (sec, upd) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 2, "Compressed Huffman");
let size_fmt = (sec[0] >> 2) & 3;
assert_ne!(size_fmt, 0, "4-stream size format, got {size_fmt}");
match upd {
HuffUpdate::New(_) => {}
HuffUpdate::Unchanged => panic!("expected a new Huffman table"),
}
assert!(huff_section_roundtrips(&sec, &src), "4-stream read_table");
let ct = build_ctable(&src).expect("build");
let raw = write_tree_raw(&ct.weights_wo_last).expect("raw tree");
assert!(raw[0] >= 128, "direct 4-bit weight header");
let (t_raw, n_raw) = read_table(None, &raw).expect("read raw tree");
assert_eq!(n_raw, raw.len());
let stream = ct.encode_stream(&src).expect("encode");
let mut out = vec![0u8; src.len()];
t_raw
.decode_stream(&stream, &mut out)
.expect("raw-tree decode");
assert_eq!(out, src);
let fse = write_tree_fse(&ct.weights_wo_last).expect("FSE tree");
assert!(fse[0] < 128, "FSE-compressed weight header");
assert_eq!(fse.len(), 1 + usize::from(fse[0]));
let (got_w, _) = fse::decompress_weights(&fse[1..], 255).expect("weights");
assert_eq!(
got_w,
ct.weights_wo_last,
"FSE weight roundtrip len got={} want={}",
got_w.len(),
ct.weights_wo_last.len()
);
let (t_fse, n_fse) = read_table(None, &fse).expect("read FSE tree");
assert_eq!(n_fse, fse.len());
out.fill(0);
t_fse
.decode_stream(&stream, &mut out)
.expect("FSE-tree decode");
assert_eq!(out, src);
let chosen = write_tree(&ct).expect("write_tree");
read_table(None, &chosen).expect("chosen tree");
}
#[test]
fn huffman_one_stream_below_256() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 224 {
src.extend_from_slice(fox);
}
src.truncate(224);
let (sec, _) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 2);
assert_eq!(sec[0] >> 2 & 3, 0, "1-stream size format 0");
}
#[test]
fn huffman_five_byte_header_csize_matches() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut src = Vec::new();
while src.len() < 20_000 {
src.extend_from_slice(fox);
}
src.truncate(20_000);
let (sec, _) = encode_literals_section(&src, None).expect("section");
assert_eq!(sec[0] & 3, 2);
assert_eq!(sec[0] >> 2 & 3, 3, "18-bit 4-stream header");
assert!(huff_section_roundtrips(&sec, &src));
let mut frame = Vec::new();
crate::encode::write_frame_header(
&mut frame,
src.len() as u64,
15,
true,
Some(src.len() as u64),
None,
false,
);
let mut block = sec.clone();
block.push(0);
let n = block.len() as u32;
let hdr = 1u32 | (2 << 1) | (n << 3);
frame.push(hdr as u8);
frame.push((hdr >> 8) as u8);
frame.push((hdr >> 16) as u8);
frame.extend_from_slice(&block);
frame.extend_from_slice(&crate::xxh64::content_checksum(&src).to_le_bytes());
let got = crate::decompress(&frame).expect("frame decode");
assert_eq!(got, src);
}
#[ignore = "needs corpora/data/silesia; run with --ignored --nocapture"]
#[test]
fn silesia_huff_nbits_census() {
let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("corpora/data/silesia");
if !root.is_dir() {
return;
}
for name in ["mr", "mozilla", "sao", "nci", "xml", "x-ray"] {
let path = root.join(name);
let src = match std::fs::read(&path) {
Ok(s) => s,
Err(_) => continue,
};
nbits_census::start();
crate::encode::compress(&src, 1).expect("compress");
let rows = nbits_census::take();
if rows.is_empty() {
println!("{name}: 0 Huffman tables");
continue;
}
let n = rows.len() as u32;
let mut max_hist = [0u32; 12];
let mut mean_le50 = 0u32;
let mut mean_le55 = 0u32;
let mut mean_le60 = 0u32;
let mut mean_le70 = 0u32;
let mut max_le3 = 0u32;
let mut max_le7 = 0u32;
let mut sum_mean = 0u32;
for &(max_nb, mean_x10) in &rows {
if (max_nb as usize) < max_hist.len() {
max_hist[max_nb as usize] += 1;
}
if max_nb <= 3 {
max_le3 += 1;
}
if max_nb <= 7 {
max_le7 += 1;
}
if mean_x10 <= 50 {
mean_le50 += 1;
}
if mean_x10 <= 55 {
mean_le55 += 1;
}
if mean_x10 <= 60 {
mean_le60 += 1;
}
if mean_x10 <= 70 {
mean_le70 += 1;
}
sum_mean += u32::from(mean_x10);
}
println!(
"{name}: tables={n} mean={:.1} max_hist={:?} max<=3={:.0}% max<=7={:.0}% mean<=5.0={:.0}% <=5.5={:.0}% <=6.0={:.0}% <=7.0={:.0}%",
f64::from(sum_mean) / 10.0 / f64::from(n),
max_hist,
100.0 * f64::from(max_le3) / f64::from(n),
100.0 * f64::from(max_le7) / f64::from(n),
100.0 * f64::from(mean_le50) / f64::from(n),
100.0 * f64::from(mean_le55) / f64::from(n),
100.0 * f64::from(mean_le60) / f64::from(n),
100.0 * f64::from(mean_le70) / f64::from(n),
);
}
}
}