use archmage::autoversion;
pub(crate) mod bt;
pub(crate) mod fast_ht;
pub(crate) mod hc;
pub(crate) mod ht;
#[cfg(feature = "unchecked")]
pub(crate) mod raw;
pub(crate) mod turbo;
pub(crate) const MATCHFINDER_WINDOW_ORDER: u32 = 15;
pub(crate) const MATCHFINDER_WINDOW_SIZE: u32 = 1 << MATCHFINDER_WINDOW_ORDER;
pub(crate) const MATCHFINDER_INITVAL: i16 = i16::MIN;
#[inline(always)]
pub(crate) fn lz_hash(seq: u32, num_bits: u32) -> u32 {
seq.wrapping_mul(0x1E35A7BD) >> (32 - num_bits)
}
#[inline(always)]
pub(crate) fn lz_extend(strptr: &[u8], matchptr: &[u8], start_len: u32, max_len: u32) -> u32 {
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
lz_extend_wasm128(strptr, matchptr, start_len, max_len)
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
{
lz_extend_word(strptr, matchptr, start_len, max_len)
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline(always)]
fn lz_extend_wasm128(strptr: &[u8], matchptr: &[u8], start_len: u32, max_len: u32) -> u32 {
use archmage::prelude::*;
let mut len = start_len;
let max = max_len as usize;
while (len as usize) + 16 <= max {
let off = len as usize;
let sa: &[u8; 16] = strptr[off..off + 16].try_into().unwrap();
let ma: &[u8; 16] = matchptr[off..off + 16].try_into().unwrap();
let sv = v128_load(sa);
let mv = v128_load(ma);
let ne = i8x16_ne(sv, mv);
let mask = i8x16_bitmask(ne) as u32;
if mask != 0 {
len += mask.trailing_zeros();
return len.min(max_len);
}
len += 16;
}
if (len as usize) + 8 <= max {
let off = len as usize;
let sw = u64::from_le_bytes(strptr[off..off + 8].try_into().unwrap());
let mw = u64::from_le_bytes(matchptr[off..off + 8].try_into().unwrap());
let xor = sw ^ mw;
if xor != 0 {
len += xor.trailing_zeros() >> 3;
return len.min(max_len);
}
len += 8;
}
while (len as usize) < max && strptr[len as usize] == matchptr[len as usize] {
len += 1;
}
len
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
#[inline(always)]
fn lz_extend_word(strptr: &[u8], matchptr: &[u8], start_len: u32, max_len: u32) -> u32 {
use crate::fast_bytes::{get_byte, load_u64_le};
let mut len = start_len;
let max = max_len as usize;
while (len as usize) + 8 <= max {
let off = len as usize;
let sw = load_u64_le(strptr, off);
let mw = load_u64_le(matchptr, off);
let xor = sw ^ mw;
if xor != 0 {
len += xor.trailing_zeros() >> 3;
return len.min(max_len);
}
len += 8;
}
while (len as usize) < max && get_byte(strptr, len as usize) == get_byte(matchptr, len as usize)
{
len += 1;
}
len
}
#[autoversion]
pub(crate) fn matchfinder_init(data: &mut [i16]) {
data.fill(MATCHFINDER_INITVAL);
}
#[autoversion]
pub(crate) fn matchfinder_rebase(data: &mut [i16]) {
for entry in data.iter_mut() {
*entry = entry.saturating_add(i16::MIN);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lz_hash_deterministic() {
let h1 = lz_hash(0x12345678, 15);
let h2 = lz_hash(0x12345678, 15);
assert_eq!(h1, h2);
assert!(h1 < (1 << 15));
}
#[test]
fn test_lz_hash_distribution() {
let h1 = lz_hash(0x00000000, 15);
let h2 = lz_hash(0x00000001, 15);
let h3 = lz_hash(0xFFFFFFFF, 15);
assert_ne!(h1, h2);
assert_ne!(h2, h3);
}
#[test]
fn test_lz_extend_identical() {
let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assert_eq!(lz_extend(&data, &data, 0, 10), 10);
}
#[test]
fn test_lz_extend_partial() {
let a = [1, 2, 3, 4, 5, 6, 7, 8];
let b = [1, 2, 3, 4, 0, 0, 0, 0];
assert_eq!(lz_extend(&a, &b, 0, 8), 4);
}
#[test]
fn test_lz_extend_with_start() {
let a = [0, 0, 1, 2, 3, 4, 5, 6, 7, 8];
let b = [9, 9, 1, 2, 3, 4, 0, 0, 0, 0];
assert_eq!(lz_extend(&a, &b, 2, 10), 6);
}
#[test]
fn test_matchfinder_init() {
let mut data = [0i16; 16];
matchfinder_init(&mut data);
for &v in &data {
assert_eq!(v, MATCHFINDER_INITVAL);
}
}
#[test]
fn hc_compress_across_window_boundary_no_panic() {
use crate::compress::{CompressionLevel, Compressor};
let window = MATCHFINDER_WINDOW_SIZE as usize;
let mut data = Vec::with_capacity(window + 300);
let pattern: Vec<u8> = (0..=255u8).collect();
while data.len() < window - 10 {
data.extend_from_slice(&pattern);
}
for i in 0..310u16 {
data.push((i & 0xFF) as u8);
}
for level in 1..=9u32 {
let mut compressor = Compressor::new(CompressionLevel::new(level));
let bound = Compressor::zlib_compress_bound(data.len());
let mut output = vec![0u8; bound];
let result = compressor.zlib_compress(&data, &mut output, crate::Unstoppable);
assert!(
result.is_ok(),
"compression at level {level} should not panic or error"
);
let compressed_len = result.unwrap();
let mut decompressor = crate::Decompressor::new();
let mut decompressed = vec![0u8; data.len()];
let dec_result = decompressor.zlib_decompress(
&output[..compressed_len],
&mut decompressed,
crate::Unstoppable,
);
assert!(
dec_result.is_ok(),
"decompression roundtrip at level {level}"
);
assert_eq!(&decompressed, &data, "data roundtrip at level {level}");
}
}
#[test]
fn hc_skip_bytes_bail_leaves_stale_base_offset() {
use crate::compress::{CompressionLevel, Compressor};
for total_len in [33025, 33026, 33027, 33028] {
let data = vec![0u8; total_len];
for level in 1..=9u32 {
let mut compressor = Compressor::new(CompressionLevel::new(level));
let bound = Compressor::zlib_compress_bound(data.len());
let mut output = vec![0u8; bound];
let result = compressor.zlib_compress(&data, &mut output, crate::Unstoppable);
assert!(
result.is_ok(),
"compression at level {level} with length {total_len} should not panic"
);
let compressed_len = result.unwrap();
let mut decompressor = crate::Decompressor::new();
let mut decompressed = vec![0u8; data.len()];
let dec_result = decompressor.zlib_decompress(
&output[..compressed_len],
&mut decompressed,
crate::Unstoppable,
);
assert!(
dec_result.is_ok(),
"roundtrip at level {level} with length {total_len}"
);
assert_eq!(
&decompressed, &data,
"data mismatch at level {level} with length {total_len}"
);
}
}
}
#[test]
fn test_matchfinder_rebase() {
let mut data = [0i16, 100, -100, i16::MAX, i16::MIN, -32768];
matchfinder_rebase(&mut data);
assert_eq!(data[0], i16::MIN);
assert_eq!(data[1], 100 | i16::MIN);
assert_eq!(data[2], i16::MIN);
assert_eq!(data[3], i16::MAX | i16::MIN); assert_eq!(data[4], i16::MIN);
assert_eq!(data[5], i16::MIN);
}
}