use super::HufTables;
use super::huf::{self, Flags, Options, Streams};
use crate::Backend;
use crate::compressor::core::error::CoreError;
use crate::compressor::core::params::Strategy;
use crate::shared::format::{EncodingType, LIT_HUF_LOG};
pub(crate) const fn min_gain(src_size: usize, strategy: Strategy) -> usize {
let min_log = if strategy as u32 >= Strategy::BtUltra as u32 {
strategy as u32 - 1
} else {
6
};
(src_size >> min_log) + 2
}
const fn raw_header_size(src_size: usize) -> usize {
1 + (src_size > 31) as usize + (src_size > 4095) as usize
}
fn write_raw_header(dst: &mut [u8], kind: EncodingType, src_size: usize, header_size: usize) {
let kind = kind as u32;
let size = src_size as u32;
match header_size {
1 => dst[0] = (kind + (size << 3)) as u8,
2 => dst[..2].copy_from_slice(&((kind + (1 << 2) + (size << 4)) as u16).to_le_bytes()),
_ => dst[..3].copy_from_slice(&(kind + (3 << 2) + (size << 4)).to_le_bytes()[..3]),
}
}
pub(crate) fn no_compress(dst: &mut [u8], src: &[u8]) -> Result<usize, CoreError> {
let header_size = raw_header_size(src.len());
if src.len() + header_size > dst.len() {
return Err(CoreError::DstSizeTooSmall);
}
write_raw_header(dst, EncodingType::Basic, src.len(), header_size);
dst[header_size..header_size + src.len()].copy_from_slice(src);
Ok(src.len() + header_size)
}
pub(crate) fn compress_rle(dst: &mut [u8], src: &[u8]) -> usize {
let header_size = raw_header_size(src.len());
write_raw_header(dst, EncodingType::Rle, src.len(), header_size);
dst[header_size] = src[0];
header_size + 1
}
fn min_literals_to_compress(strategy: Strategy, repeat: huf::Repeat) -> usize {
let shift = (9 - strategy as u32).min(3);
if repeat == huf::Repeat::Valid {
6
} else {
8 << shift
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct LiteralsSettings {
pub(crate) strategy: Strategy,
pub(crate) disable_compression: bool,
pub(crate) suspect_uncompressible: bool,
pub(crate) backend: Backend,
}
pub(crate) fn try_compress(
dst: &mut [u8],
src: &[u8],
prev: &HufTables,
next: &mut HufTables,
settings: LiteralsSettings,
) -> Result<usize, CoreError> {
const KB: usize = 1 << 10;
let src_size = src.len();
let lh_size = 3 + usize::from(src_size >= KB) + usize::from(src_size >= 16 * KB);
let mut single_stream = src_size < 256;
let mut kind = EncodingType::Compressed;
let strategy = settings.strategy;
next.clone_from(prev);
if settings.disable_compression {
return Ok(0);
}
if src_size < min_literals_to_compress(strategy, prev.repeat) {
return Ok(0);
}
if dst.len() < lh_size + 1 {
return Err(CoreError::DstSizeTooSmall);
}
let mut repeat = prev.repeat;
let flags = Flags {
prefer_repeat: strategy < Strategy::Lazy && src_size <= 1024,
optimal_depth: strategy >= Strategy::BtUltra,
suspect_uncompressible: settings.suspect_uncompressible,
};
if repeat == huf::Repeat::Valid && lh_size == 3 {
single_stream = true;
}
let options = Options {
max_symbol_value: huf::SYMBOL_VALUE_MAX,
huff_log: LIT_HUF_LOG,
streams: if single_stream {
Streams::Single
} else {
Streams::Four
},
flags,
backend: settings.backend,
};
let result = huf::compress(
&mut dst[lh_size..],
src,
options,
&mut next.table,
&mut repeat,
);
if repeat != huf::Repeat::None {
kind = EncodingType::Repeat;
}
let c_lit_size = match result {
Ok(size) if size != 0 && size < src_size - min_gain(src_size, strategy) => size,
_ => {
next.clone_from(prev);
return Ok(0);
}
};
if c_lit_size == 1 && (src_size >= 8 || src.iter().all(|&b| b == src[0])) {
next.clone_from(prev);
return Ok(compress_rle(dst, src));
}
if kind == EncodingType::Compressed {
next.repeat = huf::Repeat::Check;
}
let kind = kind as u32;
let size = src_size as u32;
let c_size = c_lit_size as u32;
match lh_size {
3 => {
let lhc = kind + (u32::from(!single_stream) << 2) + (size << 4) + (c_size << 14);
dst[..3].copy_from_slice(&lhc.to_le_bytes()[..3]);
}
4 => {
let lhc = kind + (2 << 2) + (size << 4) + (c_size << 18);
dst[..4].copy_from_slice(&lhc.to_le_bytes());
}
_ => {
let lhc = kind + (3 << 2) + (size << 4) + (c_size << 22);
dst[..4].copy_from_slice(&lhc.to_le_bytes());
dst[4] = (c_lit_size >> 10) as u8;
}
}
Ok(lh_size + c_lit_size)
}
#[cfg(test)]
mod tests {
use super::*;
fn settings(strategy: Strategy) -> LiteralsSettings {
LiteralsSettings {
strategy,
disable_compression: false,
suspect_uncompressible: false,
backend: Backend::SCALAR,
}
}
#[test]
fn raw_headers_grow_with_the_literal_count() {
let mut dst = [0u8; 5000];
assert_eq!(no_compress(&mut dst, &[1; 31]), Ok(32));
assert_eq!(dst[0], 31 << 3);
assert_eq!(no_compress(&mut dst, &[1; 32]), Ok(34));
assert_eq!(no_compress(&mut dst, &[1; 4096]), Ok(4099));
assert_eq!(
no_compress(&mut dst[..3], &[1; 3]),
Err(CoreError::DstSizeTooSmall)
);
}
#[test]
fn repeated_literals_become_an_rle_section() {
let prev = HufTables::default();
let mut next = HufTables::default();
let mut dst = [0u8; 256];
let size = try_compress(
&mut dst,
&[7; 100],
&prev,
&mut next,
settings(Strategy::Fast),
)
.expect("literals");
assert_eq!(size, 3);
assert_eq!(dst[2], 7);
}
#[test]
fn a_valid_previous_table_admits_short_rle_sections() {
let prev = HufTables {
repeat: huf::Repeat::Valid,
..HufTables::default()
};
let mut next = HufTables::default();
let mut dst = [0u8; 64];
let size = try_compress(
&mut dst,
&[9; 6],
&prev,
&mut next,
settings(Strategy::Lazy2),
);
assert_eq!(size, Ok(2));
assert_eq!(dst[..2], [1 | (6 << 3), 9]);
}
#[test]
fn short_or_disabled_literals_stay_raw() {
let prev = HufTables::default();
let mut next = HufTables::default();
let mut dst = [0u8; 256];
let size = try_compress(
&mut dst,
b"short",
&prev,
&mut next,
settings(Strategy::Fast),
);
assert_eq!(size, Ok(0));
let disabled = LiteralsSettings {
disable_compression: true,
..settings(Strategy::BtUltra2)
};
let text = b"abababababababababababababababababababababababababababababababababababab";
assert_eq!(
try_compress(&mut dst, text, &prev, &mut next, disabled),
Ok(0)
);
assert!(
try_compress(
&mut dst[..2],
text,
&prev,
&mut next,
settings(Strategy::BtUltra2)
)
.is_err()
);
}
#[test]
fn min_gain_tightens_for_ultra_strategies() {
assert_eq!(min_gain(1 << 12, Strategy::Fast), (1 << 6) + 2);
assert_eq!(min_gain(1 << 12, Strategy::BtUltra2), (1 << 4) + 2);
}
}