#![allow(clippy::drop_non_drop)]
use crate::bit::BitRev;
use crate::error::Error;
use crate::fse::{self, FseTable};
use crate::huffman::{self, HuffmanTable};
use crate::reader::Reader;
#[cfg(feature = "profile")]
pub static DEC_LIT32: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static DEC_MATCH32: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static DEC_LIT16: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static DEC_LIT64: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static DEC_MATCH16: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static DEC_BAND: [core::sync::atomic::AtomicU64; 8] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub static DEC_BAND_B: [core::sync::atomic::AtomicU64; 8] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub static DEC_UNTIERED: [core::sync::atomic::AtomicU64; 16] =
[const { core::sync::atomic::AtomicU64::new(0) }; 16];
#[cfg(feature = "profile")]
pub fn take_dec_untiered() -> [u64; 16] {
use core::sync::atomic::Ordering::Relaxed;
let mut a = [0u64; 16];
for i in 0..16 {
a[i] = DEC_UNTIERED[i].swap(0, Relaxed);
}
a
}
#[cfg(feature = "profile")]
#[inline(always)]
fn note_untiered(len: usize) {
use core::sync::atomic::Ordering::Relaxed;
let b = match len {
0..=16 => 0usize,
17..=32 => 1,
33..=64 => 2,
65..=128 => 3,
129..=256 => 4,
257..=512 => 5,
513..=1024 => 6,
_ => 7,
};
DEC_UNTIERED[b].fetch_add(1, Relaxed);
DEC_UNTIERED[8 + b].fetch_add(len as u64, Relaxed);
}
#[cfg(not(feature = "profile"))]
#[inline(always)]
fn note_untiered(_len: usize) {}
#[cfg(feature = "profile")]
pub fn take_dec_bands() -> ([u64; 8], [u64; 8]) {
use core::sync::atomic::Ordering::Relaxed;
let mut a = [0u64; 8];
let mut b = [0u64; 8];
for i in 0..8 {
a[i] = DEC_BAND[i].swap(0, Relaxed);
b[i] = DEC_BAND_B[i].swap(0, Relaxed);
}
(a, b)
}
#[cfg(feature = "profile")]
#[inline(always)]
fn note_band(i: usize, len: usize) {
use core::sync::atomic::Ordering::Relaxed;
DEC_BAND[i].fetch_add(1, Relaxed);
DEC_BAND_B[i].fetch_add(len as u64, Relaxed);
}
#[cfg(not(feature = "profile"))]
#[inline(always)]
fn note_band(_i: usize, _len: usize) {}
#[cfg(feature = "profile")]
pub fn take_dec_copies() -> (u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
DEC_LIT32.swap(0, Relaxed),
DEC_MATCH32.swap(0, Relaxed),
DEC_LIT16.swap(0, Relaxed),
DEC_MATCH16.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub fn take_dec_lit64() -> u64 {
DEC_LIT64.swap(0, core::sync::atomic::Ordering::Relaxed)
}
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
pub(crate) struct BlockState {
pub lit_buf: Vec<u8>,
pub huff: Option<HuffmanTable>,
pub ll: Option<FseTable>,
pub of: Option<FseTable>,
pub ml: Option<FseTable>,
pub reps: [u32; 3],
}
impl BlockState {
pub(crate) fn new() -> Self {
Self {
lit_buf: Vec::new(),
huff: None,
ll: None,
of: None,
ml: None,
reps: [1, 4, 8],
}
}
pub(crate) fn from_dict(dict: Option<&crate::dict::Dictionary>) -> Self {
let Some(d) = dict else {
return Self::new();
};
let Some(e) = d.entropy() else {
return Self::new();
};
Self {
lit_buf: Vec::new(),
huff: Some(e.huff_d.clone()),
ll: Some(e.ll_d.clone()),
of: Some(e.of_d.clone()),
ml: Some(e.ml_d.clone()),
reps: e.reps,
}
}
}
pub(crate) fn decode_compressed_block(
payload: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if block_avx2_on() && crate::simd::has_avx2() && crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
decode_compressed_block_avx2(
payload,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
};
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
decode_compressed_block_bmi2(
payload,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
};
}
decode_compressed_block_inner(
payload,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn decode_compressed_block_bmi2(
payload: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
decode_compressed_block_inner(
payload,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "avx2,bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn decode_compressed_block_avx2(
payload: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
decode_compressed_block_inner(
payload,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
}
#[inline(always)]
fn decode_compressed_block_inner(
payload: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
let mut r = Reader::new(payload);
let before = r.remaining();
let literals = {
let _l = crate::prof::scope(crate::prof::Stage::DecodeLiterals);
let recycle = core::mem::take(&mut state.lit_buf);
decode_literals(recycle, &mut r, state)?
};
crate::prof::note_emit_lit((before - r.remaining()) as u64);
crate::prof::note_emit_seq(r.remaining() as u64);
let seq_bytes = r.take(r.remaining())?;
let _s = crate::prof::scope(crate::prof::Stage::DecodeSeq);
let r = decode_sequences(
seq_bytes,
&literals,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
);
state.lit_buf = literals;
r
}
#[inline(always)]
pub(crate) fn decode_literals(
recycle: Vec<u8>,
r: &mut Reader<'_>,
state: &mut BlockState,
) -> Result<Vec<u8>, Error> {
let first = r.u8()?;
let lit_type = first & 3;
let size_fmt = (first >> 2) & 3;
let (regen, csize, n_streams, header_rest) = match lit_type {
0 | 1 => {
let (regen, consumed_after_first) = match size_fmt {
0 | 2 => (u32::from(first >> 3), 0usize),
1 => {
let b1 = r.u8()?;
((u32::from(first >> 4) + (u32::from(b1) << 4)), 1)
}
3 => {
let b1 = r.u8()?;
let b2 = r.u8()?;
(
u32::from(first >> 4) + (u32::from(b1) << 4) + (u32::from(b2) << 12),
2,
)
}
_ => return Err(Error::Corruption),
};
let _ = consumed_after_first;
(regen, regen, 1u32, 0u32)
}
2 | 3 => {
let (regen, csize, streams, extra) = match size_fmt {
0 | 1 => {
let b1 = r.u8()?;
let b2 = r.u8()?;
let regen = (u32::from(first >> 4) + (u32::from(b1) << 4)) & 0x3FF;
let csize = ((u32::from(b1) >> 6) + (u32::from(b2) << 2)) & 0x3FF;
let streams = if size_fmt == 0 { 1 } else { 4 };
(regen, csize, streams, 0u8)
}
2 => {
let b1 = r.u8()?;
let b2 = r.u8()?;
let b3 = r.u8()?;
let regen =
u32::from(first >> 4) + (u32::from(b1) << 4) + ((u32::from(b2) & 3) << 12);
let csize = (u32::from(b2) >> 2) + (u32::from(b3) << 6);
(regen, csize & 0x3FFF, 4, 0u8)
}
3 => {
let b1 = r.u8()?;
let b2 = r.u8()?;
let b3 = r.u8()?;
let b4 = r.u8()?;
let regen = (u32::from(first) >> 4)
+ (u32::from(b1) << 4)
+ ((u32::from(b2) & 0x3F) << 12);
let csize = (u32::from(b2) >> 6) + (u32::from(b3) << 2) + (u32::from(b4) << 10);
(regen, csize & 0x3FFFF, 4, 0u8)
}
_ => return Err(Error::Corruption),
};
let _ = extra;
(regen, csize, streams, 0)
}
_ => return Err(Error::Corruption),
};
let _ = header_rest;
match lit_type {
0 => {
let src = r.take(regen as usize)?;
let mut out = recycle;
out.clear();
out.extend_from_slice(src);
Ok(out)
}
1 => {
let b = r.u8()?;
let mut out = recycle;
out.clear();
out.resize(regen as usize, b);
Ok(out)
}
2 => {
let section = r.take(csize as usize)?;
let huff_recycle = state.huff.take();
let (table, tree_size) = huffman::read_table(huff_recycle, section)?;
state.huff = Some(table);
let table = state.huff.as_ref().ok_or(Error::Corruption)?;
decode_huff_streams(recycle, table, §ion[tree_size..], regen, n_streams)
}
3 => {
let table = state.huff.as_ref().ok_or(Error::Corruption)?;
let section = r.take(csize as usize)?;
decode_huff_streams(recycle, table, section, regen, n_streams)
}
_ => Err(Error::Corruption),
}
}
#[inline(always)]
fn decode_huff_streams(
recycle: Vec<u8>,
table: &HuffmanTable,
src: &[u8],
regen: u32,
n_streams: u32,
) -> Result<Vec<u8>, Error> {
let mut out = recycle;
out.clear();
out.resize(regen as usize, 0);
if n_streams == 1 {
table.decode_stream(src, &mut out)?;
return Ok(out);
}
if src.len() < 6 {
return Err(Error::Corruption);
}
let s1 = u16::from_le_bytes([src[0], src[1]]) as usize;
let s2 = u16::from_le_bytes([src[2], src[3]]) as usize;
let s3 = u16::from_le_bytes([src[4], src[5]]) as usize;
let total = src.len() - 6;
if s1 + s2 + s3 > total {
return Err(Error::Corruption);
}
let s4 = total - s1 - s2 - s3;
let rest = &src[6..];
if s1 == 0 || s2 == 0 || s3 == 0 || s4 == 0 {
return Err(Error::Corruption);
}
let n = out.len();
let chunk = regen.div_ceil(4) as usize;
if chunk == 0 || n < 4 {
return Err(Error::Corruption);
}
let (d0, rest_d) = out.split_at_mut(chunk.min(n));
let n1 = rest_d.len();
let (d1, rest_d) = rest_d.split_at_mut(chunk.min(n1));
let n2 = rest_d.len();
let (d2, d3) = rest_d.split_at_mut(chunk.min(n2));
if d0.is_empty() || d1.is_empty() || d2.is_empty() || d3.is_empty() {
return Err(Error::Corruption);
}
table.decode_4x(
&rest[..s1],
&rest[s1..s1 + s2],
&rest[s1 + s2..s1 + s2 + s3],
&rest[s1 + s2 + s3..],
d0,
d1,
d2,
d3,
)?;
Ok(out)
}
pub(crate) const LL_BASE: [u32; 36] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, 48, 64,
128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
];
pub(crate) const LL_BITS: [u8; 36] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16,
];
pub(crate) const ML_BASE: [u32; 53] = [
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 131, 259, 515, 1027,
2051, 4099, 8195, 16387, 32771, 65539,
];
pub(crate) const ML_BITS: [u8; 53] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
];
const fn pack_ll() -> [u32; 36] {
let mut o = [0u32; 36];
let mut i = 0;
while i < 36 {
o[i] = LL_BASE[i] | ((LL_BITS[i] as u32) << 24);
i += 1;
}
o
}
const fn pack_ml() -> [u32; 53] {
let mut o = [0u32; 53];
let mut i = 0;
while i < 53 {
o[i] = ML_BASE[i] | ((ML_BITS[i] as u32) << 24);
i += 1;
}
o
}
pub(crate) const LL_PACK: [u32; 36] = pack_ll();
pub(crate) const ML_PACK: [u32; 53] = pack_ml();
#[inline(always)]
const fn pk_base(w: u32) -> u32 {
w & 0x00FF_FFFF
}
#[inline(always)]
const fn pk_bits(w: u32) -> u8 {
(w >> 24) as u8
}
pub(crate) fn decode_sequences(
src: &[u8],
literals: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if seqloop_avx2_on() && crate::simd::has_avx2() && crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
decode_sequences_avx2(
src,
literals,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
};
}
decode_sequences_inner(
src,
literals,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "avx2,bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn decode_sequences_avx2(
src: &[u8],
literals: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
decode_sequences_inner(
src,
literals,
out,
window_size,
block_max,
state,
dict,
frame_start,
frame_skipped,
)
}
static SEQLOOP_AVX2_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_seqloop_avx2_arm(on: bool) {
SEQLOOP_AVX2_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline(always)]
fn seqloop_avx2_on() -> bool {
!matches!(
SEQLOOP_AVX2_ARM.load(core::sync::atomic::Ordering::Relaxed),
1
)
}
#[inline(always)]
fn decode_sequences_inner(
src: &[u8],
literals: &[u8],
out: &mut Vec<u8>,
window_size: u64,
block_max: u32,
state: &mut BlockState,
dict: &[u8],
frame_start: usize,
frame_skipped: usize,
) -> Result<(), Error> {
if src.is_empty() {
return Err(Error::Corruption);
}
let g_hdr = crate::prof::scope(crate::prof::Stage::DecSeqHeader);
let mut pos = 0usize;
let byte0 = src[0];
pos += 1;
let nseq = if byte0 == 0 {
drop(g_hdr);
let _g = crate::prof::scope(crate::prof::Stage::DecSeqTail);
out.extend_from_slice(literals);
return Ok(());
} else if byte0 < 128 {
byte0 as u32
} else if byte0 < 255 {
if pos >= src.len() {
return Err(Error::Corruption);
}
let b1 = src[pos];
pos += 1;
((u32::from(byte0) - 128) << 8) + u32::from(b1)
} else {
if pos + 1 >= src.len() {
return Err(Error::Corruption);
}
let b1 = src[pos];
let b2 = src[pos + 1];
pos += 2;
u32::from(b1) + (u32::from(b2) << 8) + 0x7F00
};
if pos >= src.len() {
return Err(Error::Corruption);
}
let modes = src[pos];
pos += 1;
if modes & 3 != 0 {
return Err(Error::Corruption);
}
let ll_mode = modes >> 6;
let of_mode = (modes >> 4) & 3;
let ml_mode = (modes >> 2) & 3;
drop(g_hdr);
let g_tab = crate::prof::scope(crate::prof::Stage::DecSeqTables);
let (ll, n) = seq_table(
&src[pos..],
ll_mode,
35,
9,
state.ll.take(),
fse::default_ll,
)?;
pos += n;
let (of, n) = seq_table(
&src[pos..],
of_mode,
31,
8,
state.of.take(),
fse::default_of,
)?;
pos += n;
let (ml, n) = seq_table(
&src[pos..],
ml_mode,
52,
9,
state.ml.take(),
fse::default_ml,
)?;
pos += n;
let bitstream = &src[pos..];
let mut br = BitRev::new(bitstream)?;
let mut ll_s = ll.init_state(&mut br);
let mut of_s = of.init_state(&mut br);
let mut ml_s = ml.init_state(&mut br);
drop(g_tab);
let mut lit_pos = 0usize;
let mctx = MatchCtx {
dict,
frame_start,
frame_skipped,
window_size,
block_max,
wide: matchcopy_on(),
};
let litcopy_arm = litcopy_on();
let seqcheck = seqcheck_hoisted();
let nodict = dict.is_empty() && frame_start == 0 && frame_skipped == 0;
let win_sz = window_size;
let blk_max = block_max;
let wide_arm = matchcopy_on();
let llv = ll.view();
let ofv = of.view();
let mlv = ml.view();
#[cfg(feature = "dupladder")]
let dup = DUP_ARM.load(core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "dupladder")]
let dup_k = DUP_K.load(core::sync::atomic::Ordering::Relaxed);
let g_loop = crate::prof::scope(crate::prof::Stage::DecSeqLoop);
let mut rem = nseq;
while rem != 0 {
rem -= 1;
let _ = br.reload();
let ll_w = llv.entry_u32(ll_s);
let of_w = ofv.entry_u32(of_s);
let ml_w = mlv.entry_u32(ml_s);
let ll_code = crate::fse::fse_symbol(ll_w) as usize;
let of_code = u32::from(crate::fse::fse_symbol(of_w));
let ml_code = crate::fse::fse_symbol(ml_w) as usize;
debug_assert!(ll_code <= 35 && ml_code <= 52 && of_code <= 31);
if !seqcheck && (ll_code > 35 || ml_code > 52 || of_code > 31) {
return Err(Error::Corruption);
}
let offset_add = br.read_bits(of_code);
#[allow(unsafe_code)]
let (ll_w2, ml_w2) = unsafe {
debug_assert!(ll_code < LL_PACK.len() && ml_code < ML_PACK.len());
(
*LL_PACK.get_unchecked(ll_code),
*ML_PACK.get_unchecked(ml_code),
)
};
let (ml_bits, ll_bits) = (pk_bits(ml_w2), pk_bits(ll_w2));
let (ll_base, ml_base) = (pk_base(ll_w2), pk_base(ml_w2));
let ml_add = br.read_bits(u32::from(ml_bits));
let ll_add = br.read_bits(u32::from(ll_bits));
let litlen = ll_base + ll_add;
let matchlen = ml_base + ml_add;
let offset_value = if of_code == 0 {
1
} else {
(1u32 << of_code) + offset_add
};
#[cfg(feature = "dupladder")]
{
use core::hint::black_box;
for _ in 0..dup_k {
match dup {
1 => {
black_box(llv.entry_u32(ll_s));
black_box(ofv.entry_u32(of_s));
black_box(mlv.entry_u32(ml_s));
}
2 => {
let sv = br.dup_save();
black_box(br.read_bits(of_code as u32));
black_box(br.read_bits(u32::from(ml_bits)));
black_box(br.read_bits(u32::from(ll_bits)));
br.dup_restore(sv);
}
3 => {
let sv = br.dup_save();
black_box(FseTable::advance_w(ll_w, &mut br));
black_box(FseTable::advance_w(ml_w, &mut br));
black_box(FseTable::advance_w(of_w, &mut br));
br.dup_restore(sv);
}
4 => {
let sv = br.dup_save();
black_box(br.reload());
black_box(br.reload());
br.dup_restore(sv);
}
5 => {
let (lp, len) = (lit_pos, out.len());
let _ = copy_literals(literals, &mut lit_pos, litlen, out, litcopy_arm);
lit_pos = lp;
out.truncate(len);
}
_ => {}
}
}
}
copy_literals(literals, &mut lit_pos, litlen, out, litcopy_arm)?;
#[cfg(feature = "dupladder")]
if dup == 6 {
for _ in 0..dup_k {
let sv = state.reps;
let _ =
core::hint::black_box(resolve_offset(offset_value, litlen, &mut state.reps));
state.reps = sv;
}
}
let offset = resolve_offset(offset_value, litlen, &mut state.reps)?;
#[cfg(feature = "dupladder")]
if dup == 7 {
for _ in 0..dup_k {
let len = out.len();
let _ = copy_match(out, &mctx, offset, matchlen);
out.truncate(len);
}
}
if nodict {
copy_match_nodict(out, offset, matchlen, win_sz, blk_max, wide_arm)?;
} else {
copy_match(out, &mctx, offset, matchlen)?;
}
if rem != 0 {
let _ = br.reload();
ll_s = FseTable::advance_w(ll_w, &mut br);
ml_s = FseTable::advance_w(ml_w, &mut br);
of_s = FseTable::advance_w(of_w, &mut br);
}
}
drop(g_loop);
{
let _g = crate::prof::scope(crate::prof::Stage::DecSeqTail);
out.extend_from_slice(&literals[lit_pos..]);
}
state.ll = Some(ll);
state.of = Some(of);
state.ml = Some(ml);
Ok(())
}
#[cfg(test)]
pub(crate) fn debug_seq_codes(
src: &[u8],
state: &BlockState,
) -> Result<(u32, u8, Vec<(u32, u32, u32, u8, u8, u8)>), Error> {
if src.is_empty() {
return Err(Error::Corruption);
}
let mut pos = 0usize;
let byte0 = src[0];
pos += 1;
let nseq = if byte0 == 0 {
return Ok((0, 0, Vec::new()));
} else if byte0 < 128 {
byte0 as u32
} else if byte0 < 255 {
let b1 = *src.get(pos).ok_or(Error::Corruption)?;
pos += 1;
((u32::from(byte0) - 128) << 8) + u32::from(b1)
} else {
let b1 = *src.get(pos).ok_or(Error::Corruption)?;
let b2 = *src.get(pos + 1).ok_or(Error::Corruption)?;
pos += 2;
u32::from(b1) + (u32::from(b2) << 8) + 0x7F00
};
let modes = *src.get(pos).ok_or(Error::Corruption)?;
pos += 1;
let ll_mode = modes >> 6;
let of_mode = (modes >> 4) & 3;
let ml_mode = (modes >> 2) & 3;
let (ll, n) = seq_table(
&src[pos..],
ll_mode,
35,
9,
state.ll.clone(),
fse::default_ll,
)?;
pos += n;
let (of, n) = seq_table(
&src[pos..],
of_mode,
31,
8,
state.of.clone(),
fse::default_of,
)?;
pos += n;
let (ml, n) = seq_table(
&src[pos..],
ml_mode,
52,
9,
state.ml.clone(),
fse::default_ml,
)?;
pos += n;
let bitstream = &src[pos..];
let mut br = BitRev::new(bitstream)?;
let mut ll_s = ll.init_state(&mut br);
let mut of_s = of.init_state(&mut br);
let mut ml_s = ml.init_state(&mut br);
let mut out = Vec::with_capacity(nseq as usize);
for i in 0..nseq {
let _ = br.reload();
let ll_e = ll.entry(ll_s);
let of_e = of.entry(of_s);
let ml_e = ml.entry(ml_s);
let llc = ll_e.symbol;
let ofc = of_e.symbol;
let mlc = ml_e.symbol;
if llc > 35 || mlc > 52 || ofc > 31 {
return Err(Error::Corruption);
}
let offset_add = br.read_bits(u32::from(ofc));
let ml_add = br.read_bits(u32::from(ML_BITS[mlc as usize]));
let ll_add = br.read_bits(u32::from(LL_BITS[llc as usize]));
let litlen = LL_BASE[llc as usize] + ll_add;
let matchlen = ML_BASE[mlc as usize] + ml_add;
let ov = if ofc == 0 {
1
} else {
(1u32 << ofc) + offset_add
};
out.push((litlen, matchlen, ov, llc, mlc, ofc));
if i + 1 != nseq {
let _ = br.reload();
ll_s = FseTable::advance(ll_e, &mut br);
ml_s = FseTable::advance(ml_e, &mut br);
of_s = FseTable::advance(of_e, &mut br);
}
}
Ok((nseq, modes, out))
}
#[inline(always)]
fn seq_table(
src: &[u8],
mode: u8,
max_sym: usize,
max_log: u8,
prev: Option<FseTable>,
predefined: fn() -> Result<FseTable, Error>,
) -> Result<(FseTable, usize), Error> {
match mode {
0 => {
#[cfg(feature = "profile")]
N21_PREDEF.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
Ok((predefined()?, 0))
}
1 => {
let sym = *src.first().ok_or(Error::Corruption)?;
if usize::from(sym) > max_sym {
return Err(Error::Corruption);
}
Ok((FseTable::rle(u16::from(sym)), 1))
}
2 => fse::read_ncount_into(prev, src, max_sym, max_log),
3 => {
let t = prev.ok_or(Error::Corruption)?;
Ok((t, 0))
}
_ => Err(Error::Corruption),
}
}
static SEQCHECK_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_seqcheck_arm(on: bool) {
SEQCHECK_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline(always)]
fn seqcheck_hoisted() -> bool {
use core::sync::atomic::Ordering;
match SEQCHECK_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_SEQCHECK_HOIST")
.map(|v| v != "0")
.unwrap_or(true);
SEQCHECK_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
#[allow(unsafe_code)]
#[inline(always)]
fn copy_literals(
literals: &[u8],
lit_pos: &mut usize,
litlen: u32,
out: &mut Vec<u8>,
arm: bool,
) -> Result<(), Error> {
let n = litlen as usize;
let end = lit_pos.checked_add(n).ok_or(Error::Corruption)?;
if end > literals.len() {
return Err(Error::Corruption);
}
let len = out.len();
if arm && n <= 16 && *lit_pos + 16 <= literals.len() && out.capacity() - len >= 16 {
unsafe {
#[cfg(feature = "profile")]
DEC_LIT16.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
core::ptr::copy_nonoverlapping(
literals.as_ptr().add(*lit_pos),
out.as_mut_ptr().add(len),
16,
);
out.set_len(len + n);
}
*lit_pos = end;
return Ok(());
}
copy_literals_cold(literals, lit_pos, end, n, out, arm, len)
}
#[allow(unsafe_code)]
#[inline(never)]
#[cold]
fn copy_literals_cold(
literals: &[u8],
lit_pos: &mut usize,
end: usize,
n: usize,
out: &mut alloc::vec::Vec<u8>,
arm: bool,
len: usize,
) -> Result<(), Error> {
if arm && n <= 32 && *lit_pos + 32 <= literals.len() && out.capacity() - len >= 32 {
unsafe {
#[cfg(feature = "profile")]
DEC_LIT32.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
core::ptr::copy_nonoverlapping(
literals.as_ptr().add(*lit_pos),
out.as_mut_ptr().add(len),
32,
);
out.set_len(len + n);
}
*lit_pos = end;
return Ok(());
}
if arm && n <= 64 && *lit_pos + 64 <= literals.len() && out.capacity() - len >= 64 {
unsafe {
#[cfg(feature = "profile")]
DEC_LIT64.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
core::ptr::copy_nonoverlapping(
literals.as_ptr().add(*lit_pos),
out.as_mut_ptr().add(len),
64,
);
out.set_len(len + n);
}
*lit_pos = end;
return Ok(());
}
out.extend_from_slice(&literals[*lit_pos..end]);
*lit_pos = end;
Ok(())
}
#[cfg(feature = "dupladder")]
pub static DUP_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
#[cfg(feature = "dupladder")]
pub static DUP_K: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(1);
#[cfg(feature = "dupladder")]
pub fn set_dup_arm(a: u8) {
DUP_ARM.store(a, core::sync::atomic::Ordering::Relaxed);
}
#[cfg(feature = "dupladder")]
pub fn set_dup_k(k: u8) {
DUP_K.store(k.max(1), core::sync::atomic::Ordering::Relaxed);
}
static BLOCK_AVX2_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
pub fn set_block_avx2_arm(on: bool) {
BLOCK_AVX2_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline(always)]
fn block_avx2_on() -> bool {
BLOCK_AVX2_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static LUT_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
static LITCOPY_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
static MATCHCOPY_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
pub fn set_lut_arm(on: bool) {
LUT_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
pub fn set_litcopy_arm(on: bool) {
LITCOPY_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
pub fn set_matchcopy_arm(on: bool) {
MATCHCOPY_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline(always)]
pub(crate) fn lut_on() -> bool {
LUT_ARM.load(core::sync::atomic::Ordering::Relaxed) == 2
}
#[inline(always)]
fn litcopy_on() -> bool {
LITCOPY_ARM.load(core::sync::atomic::Ordering::Relaxed) == 2
}
#[inline(always)]
fn matchcopy_on() -> bool {
MATCHCOPY_ARM.load(core::sync::atomic::Ordering::Relaxed) == 2
}
pub(crate) fn code_from_base(val: u32, base: &[u32], bits: &[u8]) -> (u8, u32, u8) {
let mut i = base.len() - 1;
loop {
if val >= base[i] {
return (i as u8, val - base[i], bits[i]);
}
if i == 0 {
return (0, val, 0);
}
i -= 1;
}
}
const LL_LUT_LEN: usize = 64;
const ML_LUT_LEN: usize = 256;
const fn build_code_lut<const N: usize>(base: &[u32]) -> [u8; N] {
let mut out = [0u8; N];
let mut v = 0usize;
while v < N {
let mut i = base.len() - 1;
loop {
if v as u32 >= base[i] {
out[v] = i as u8;
break;
}
if i == 0 {
out[v] = 0;
break;
}
i -= 1;
}
v += 1;
}
out
}
static LL_CODE_LUT: [u8; LL_LUT_LEN] = build_code_lut::<LL_LUT_LEN>(&LL_BASE);
static ML_CODE_LUT: [u8; ML_LUT_LEN] = build_code_lut::<ML_LUT_LEN>(&ML_BASE);
pub(crate) fn ll_code(len: u32, lut: bool) -> (u8, u32, u8) {
if lut && (len as usize) < LL_LUT_LEN {
let c = LL_CODE_LUT[len as usize] as usize;
let base = LL_BASE[c];
return if len >= base {
(c as u8, len - base, LL_BITS[c])
} else {
(0, len, 0)
};
}
code_from_base(len, &LL_BASE, &LL_BITS)
}
pub(crate) fn ml_code(len: u32, lut: bool) -> (u8, u32, u8) {
if lut && (len as usize) < ML_LUT_LEN {
let c = ML_CODE_LUT[len as usize] as usize;
let base = ML_BASE[c];
return if len >= base {
(c as u8, len - base, ML_BITS[c])
} else {
(0, len, 0)
};
}
code_from_base(len, &ML_BASE, &ML_BITS)
}
pub(crate) fn of_code(offset_value: u32) -> (u8, u32) {
if offset_value <= 1 {
return (0, 0);
}
let code = 31 - offset_value.leading_zeros();
let extra = offset_value - (1u32 << code);
(code as u8, extra)
}
pub(crate) fn offset_value_for(offset: u32, litlen: u32, reps: &[u32; 3]) -> u32 {
if litlen == 0 {
if offset == reps[1] {
1
} else if offset == reps[2] {
2
} else if reps[0] > 1 && offset == reps[0] - 1 {
3
} else {
offset.saturating_add(3)
}
} else if offset == reps[0] {
1
} else if offset == reps[1] {
2
} else if offset == reps[2] {
3
} else {
offset.saturating_add(3)
}
}
pub(crate) fn resolve_offset(
offset_value: u32,
litlen: u32,
reps: &mut [u32; 3],
) -> Result<u32, Error> {
let offset = if offset_value > 3 {
offset_value - 3
} else if litlen == 0 {
match offset_value {
1 => reps[1],
2 => reps[2],
3 => reps[0]
.checked_sub(1)
.filter(|&o| o > 0)
.ok_or(Error::Corruption)?,
_ => return Err(Error::Corruption),
}
} else {
match offset_value {
1 => reps[0],
2 => reps[1],
3 => reps[2],
_ => return Err(Error::Corruption),
}
};
let is_new = offset_value > 3 || (offset_value == 3 && litlen == 0);
if is_new {
reps[2] = reps[1];
reps[1] = reps[0];
reps[0] = offset;
} else {
let which = if litlen == 0 {
offset_value + 1
} else {
offset_value
};
match which {
1 => {}
2 => reps.swap(0, 1),
3 => reps.rotate_right(1),
_ => {}
}
}
Ok(offset)
}
struct MatchCtx<'a> {
dict: &'a [u8],
frame_start: usize,
frame_skipped: usize,
window_size: u64,
block_max: u32,
wide: bool,
}
#[inline(always)]
fn copy_match_nodict(
out: &mut Vec<u8>,
offset: u32,
matchlen: u32,
window_size: u64,
block_max: u32,
wide: bool,
) -> Result<(), Error> {
let off = offset as usize;
if off == 0 {
return Err(Error::Corruption);
}
let produced = out.len();
if off > produced {
return Err(Error::Corruption);
}
if (off as u64) > window_size {
return Err(Error::Corruption);
}
let len = matchlen as usize;
if len > block_max as usize {
return Err(Error::Corruption);
}
copy_from_decoded(out, produced - off, len, wide)
}
#[cfg(feature = "profile")]
pub static N21_PREDEF: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_n21_predef() -> u64 {
N21_PREDEF.swap(0, core::sync::atomic::Ordering::Relaxed)
}
#[cfg(feature = "profile")]
pub static D3_ITERS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_d3_iters() -> u64 {
D3_ITERS.swap(0, core::sync::atomic::Ordering::Relaxed)
}
#[cfg(feature = "profile")]
pub static D4_PATHS: [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_d4_paths() -> [u64; 3] {
use core::sync::atomic::Ordering;
[
D4_PATHS[0].swap(0, Ordering::Relaxed),
D4_PATHS[1].swap(0, Ordering::Relaxed),
D4_PATHS[2].swap(0, Ordering::Relaxed),
]
}
#[inline(always)]
#[allow(clippy::explicit_counter_loop)]
fn copy_match(
out: &mut Vec<u8>,
ctx: &MatchCtx<'_>,
offset: u32,
matchlen: u32,
) -> Result<(), Error> {
let MatchCtx {
dict,
frame_start,
frame_skipped,
window_size,
block_max,
wide,
} = *ctx;
let off = offset as usize;
if off == 0 {
return Err(Error::Corruption);
}
let retained = out.len().saturating_sub(frame_start);
let produced = retained.saturating_add(frame_skipped);
let virtual_len = dict.len() + produced;
if off > virtual_len {
return Err(Error::Corruption);
}
let src_pos0 = virtual_len - off;
if src_pos0 >= dict.len() && (off as u64) > window_size {
return Err(Error::Corruption);
}
let len = matchlen as usize;
if len > block_max as usize {
return Err(Error::Corruption);
}
if src_pos0 >= dict.len() {
let frame_off = src_pos0 - dict.len();
if frame_off < frame_skipped {
return Err(Error::Corruption);
}
let i = frame_start + (frame_off - frame_skipped);
#[cfg(feature = "profile")]
D4_PATHS[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
return copy_from_decoded(out, i, len, wide);
}
out.reserve(len);
let from_dict = core::cmp::min(len, dict.len() - src_pos0);
out.extend_from_slice(&dict[src_pos0..src_pos0 + from_dict]);
let rest = len - from_dict;
#[cfg(feature = "profile")]
D4_PATHS[usize::from(rest > 0) + 1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if rest > 0 {
if frame_skipped > 0 {
return Err(Error::Corruption);
}
return copy_from_decoded(out, frame_start, rest, wide);
}
Ok(())
}
#[inline(always)]
#[allow(unsafe_code)]
fn copy_from_decoded(out: &mut Vec<u8>, src: usize, len: usize, wide: bool) -> Result<(), Error> {
if src >= out.len() {
return Err(Error::Corruption);
}
if len == 0 {
return Ok(());
}
let offset = out.len() - src;
if offset == 0 {
return Err(Error::Corruption);
}
if offset == 1 {
note_band(0, len);
let b = out[src];
out.resize(out.len() + len, b);
return Ok(());
}
if wide && len <= 16 && offset >= 16 && out.capacity() - out.len() >= 16 {
let dst_at = out.len();
unsafe {
let p = out.as_mut_ptr();
#[cfg(feature = "profile")]
DEC_MATCH16.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
note_band(2, len);
core::ptr::copy_nonoverlapping(p.add(src), p.add(dst_at), 16);
out.set_len(dst_at + len);
}
return Ok(());
}
copy_from_decoded_cold(out, src, len, wide, offset)
}
#[allow(unsafe_code)]
#[inline(never)]
#[cold]
fn copy_from_decoded_cold(
out: &mut Vec<u8>,
src: usize,
len: usize,
wide: bool,
offset: usize,
) -> Result<(), Error> {
if wide && len <= 32 && offset >= 32 && out.capacity() - out.len() >= 32 {
let dst_at = out.len();
unsafe {
let p = out.as_mut_ptr();
#[cfg(feature = "profile")]
DEC_MATCH32.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
note_band(if len <= 16 { 5 } else { 1 }, len);
core::ptr::copy_nonoverlapping(p.add(src), p.add(dst_at), 32);
out.set_len(dst_at + len);
}
return Ok(());
}
if wide && len <= 64 && offset >= 64 && out.capacity() - out.len() >= 64 {
let dst_at = out.len();
unsafe {
let p = out.as_mut_ptr();
note_band(6, len);
core::ptr::copy_nonoverlapping(p.add(src), p.add(dst_at), 64);
out.set_len(dst_at + len);
}
return Ok(());
}
if offset >= len {
note_band(3, len);
note_untiered(len);
out.extend_from_within(src..src + len);
return Ok(());
}
note_band(4, len);
out.reserve(len);
let mut copied = 0usize;
while copied < len {
let avail = out.len() - src;
if avail == 0 {
return Err(Error::Corruption);
}
let take = (len - copied).min(avail);
out.extend_from_within(src..src + take);
copied += take;
#[cfg(feature = "profile")]
D3_ITERS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packed_tables_match_rfc() {
for i in 0..LL_BASE.len() {
assert_eq!(pk_base(LL_PACK[i]), LL_BASE[i], "LL_BASE[{i}]");
assert_eq!(pk_bits(LL_PACK[i]), LL_BITS[i], "LL_BITS[{i}]");
}
for i in 0..ML_BASE.len() {
assert_eq!(pk_base(ML_PACK[i]), ML_BASE[i], "ML_BASE[{i}]");
assert_eq!(pk_bits(ML_PACK[i]), ML_BITS[i], "ML_BITS[{i}]");
}
}
#[test]
fn repeat_offset_rfc_table18() {
let mut reps = [1u32, 4, 8];
assert_eq!(resolve_offset(1114, 11, &mut reps).unwrap(), 1111);
assert_eq!(reps, [1111, 1, 4]);
assert_eq!(resolve_offset(1, 22, &mut reps).unwrap(), 1111);
assert_eq!(reps, [1111, 1, 4]);
assert_eq!(resolve_offset(2225, 22, &mut reps).unwrap(), 2222);
assert_eq!(reps, [2222, 1111, 1]);
}
#[test]
fn copy_from_decoded_matches_byte_push() {
for off in [1usize, 2, 3, 4, 7, 8, 15, 16, 31, 32, 33, 40, 70, 128, 300] {
for len in [1usize, 2, 3, 5, 7, 8, 9, 15, 16, 31, 32, 33, 40, 64, 1000] {
for spare in [0usize, 31, 32, 4096] {
let prefix: Vec<u8> = (0..off.max(8)).map(|i| (i % 251) as u8).collect();
let mut slow = prefix.clone();
let mut fast = Vec::with_capacity(prefix.len() + spare);
fast.extend_from_slice(&prefix);
let src = slow.len() - off;
for _ in 0..len {
let b = slow[slow.len() - off];
slow.push(b);
}
copy_from_decoded(&mut fast, src, len, true).unwrap();
assert_eq!(slow, fast, "off={off} len={len} spare={spare}");
}
}
}
}
#[test]
fn copy_from_decoded_publishes_exactly_len() {
for off in [32usize, 33, 64, 200] {
for len in [1usize, 5, 17, 31, 32] {
let prefix: Vec<u8> = (0..off + 64).map(|i| (i % 251) as u8).collect();
let before = prefix.len();
let mut v = Vec::with_capacity(before + 4096);
v.extend_from_slice(&prefix);
copy_from_decoded(&mut v, before - off, len, true).unwrap();
assert_eq!(v.len(), before + len, "off={off} len={len}");
assert_eq!(&v[..before], &prefix[..], "prefix damaged");
for k in 0..len {
assert_eq!(v[before + k], prefix[before - off + k], "off={off} k={k}");
}
}
}
}
#[test]
fn rle_seq_table_rejects_out_of_range_symbol() {
for &(max_sym, ok_sym, bad_sym) in &[(35usize, 35u8, 36u8), (31, 31, 32), (52, 52, 53)] {
let good = seq_table(&[ok_sym], 1, max_sym, 9, None, fse::default_ll);
assert!(good.is_ok(), "max_sym={max_sym} sym={ok_sym} should build");
let bad = seq_table(&[bad_sym], 1, max_sym, 9, None, fse::default_ll);
assert!(
matches!(bad, Err(Error::Corruption)),
"max_sym={max_sym} sym={bad_sym} must be rejected, got {:?}",
bad.map(|_| ())
);
}
assert!(matches!(
seq_table(&[255u8], 1, 31, 8, None, fse::default_of),
Err(Error::Corruption)
));
}
#[test]
fn ll_ml_code_lut_matches_linear_scan() {
let mut probes: Vec<u32> = (0..4096).collect();
for &b in LL_BASE.iter().chain(ML_BASE.iter()) {
probes.push(b.saturating_sub(1));
probes.push(b);
probes.push(b.saturating_add(1));
}
probes.push(LL_LUT_LEN as u32 - 1);
probes.push(LL_LUT_LEN as u32);
probes.push(ML_LUT_LEN as u32 - 1);
probes.push(ML_LUT_LEN as u32);
probes.extend([65535, 65536, 65537, 100_000, u32::MAX - 1]);
for v in probes {
assert_eq!(
ll_code(v, true),
code_from_base(v, &LL_BASE, &LL_BITS),
"ll_code({v})"
);
assert_eq!(
ml_code(v, true),
code_from_base(v, &ML_BASE, &ML_BITS),
"ml_code({v})"
);
}
}
#[test]
fn copy_literals_fast_matches_checked() {
let lits: Vec<u8> = (0..300u32).map(|i| (i % 251) as u8).collect();
for &pos in &[0usize, 1, 7, 63, 280, 284, 299] {
for n in 0usize..=40 {
if pos + n > lits.len() {
continue;
}
for spare in [0usize, 1, 15, 16, 64] {
let mut fast = Vec::with_capacity(8 + spare);
fast.extend_from_slice(b"PREFIX!!");
let mut want = fast.clone();
want.extend_from_slice(&lits[pos..pos + n]);
let mut p = pos;
copy_literals(&lits, &mut p, n as u32, &mut fast, true).unwrap();
assert_eq!(fast, want, "pos={pos} n={n} spare={spare}");
assert_eq!(p, pos + n, "lit_pos pos={pos} n={n}");
}
}
}
}
#[test]
fn copy_literals_rejects_overrun() {
let lits = [1u8, 2, 3];
let mut out = Vec::with_capacity(64);
let mut p = 0usize;
assert!(copy_literals(&lits, &mut p, 4, &mut out, true).is_err());
let mut p2 = 2usize;
assert!(copy_literals(&lits, &mut p2, u32::MAX, &mut out, true).is_err());
}
#[test]
fn code_lut_exhaustive_over_lut_domain() {
for v in 0..(ML_LUT_LEN as u32 * 2) {
assert_eq!(ll_code(v, true), code_from_base(v, &LL_BASE, &LL_BITS));
assert_eq!(ml_code(v, true), code_from_base(v, &ML_BASE, &ML_BITS));
}
}
}