use crate::bitwidth::{packed_block_size, packed_partial_block_size, required_bit_width};
use crate::dispatch::{get_pack_fn, PackFn};
use crate::error::{CompressionError, Error};
use crate::simd::scalar::ScalarBackend;
use crate::{BLOCK_SIZE, FORMAT_VERSION};
#[inline]
#[must_use]
pub fn max_compressed_size(input_len: usize) -> usize {
if input_len == 0 {
return 0;
}
let num_full_blocks: usize = input_len / BLOCK_SIZE;
let remaining: usize = input_len % BLOCK_SIZE;
let num_blocks: usize = num_full_blocks + usize::from(remaining > 0);
let packed = packed_block_size(32);
num_blocks
.checked_mul(packed)
.and_then(|v| v.checked_add(num_blocks))
.and_then(|v| v.checked_add(9))
.unwrap_or(usize::MAX)
}
pub fn compress_into(input: &[u32], output: &mut [u8]) -> Result<usize, Error> {
if input.is_empty() {
return Ok(0);
}
if input.len() > u32::MAX as usize {
return Err(CompressionError::InputTooLarge {
max: u32::MAX as usize,
got: input.len(),
}
.into());
}
let num_full_blocks: usize = input.len() / BLOCK_SIZE;
let remaining: usize = input.len() % BLOCK_SIZE;
let num_blocks: usize = num_full_blocks + usize::from(remaining > 0);
let required_size = max_compressed_size(input.len());
if output.len() < required_size {
return Err(CompressionError::OutputTooSmall {
need: required_size,
got: output.len(),
}
.into());
}
let mut offset: usize = 0;
output[offset] = FORMAT_VERSION;
offset += 1;
output[offset..offset + 4].copy_from_slice(&(input.len() as u32).to_le_bytes());
offset += 4;
output[offset..offset + 4].copy_from_slice(&(num_blocks as u32).to_le_bytes());
offset += 4;
let bit_widths_offset: usize = offset;
offset += num_blocks;
let pack: PackFn = get_pack_fn();
for block_idx in 0..num_full_blocks {
let start: usize = block_idx * BLOCK_SIZE;
let block: &[u32; BLOCK_SIZE] = input[start..start + BLOCK_SIZE]
.try_into()
.expect("block slice length is exactly BLOCK_SIZE");
let acc = block.iter().fold(0u32, |acc, &v| acc | v);
let bit_width = required_bit_width(acc);
output[bit_widths_offset + block_idx] = bit_width;
let packed_size: usize = packed_block_size(bit_width);
if packed_size == 0 {
continue;
}
pack(block, bit_width, &mut output[offset..])?;
offset += packed_size;
}
if remaining > 0 {
let start: usize = num_full_blocks * BLOCK_SIZE;
let block: &[u32] = &input[start..];
let acc = block.iter().fold(0u32, |acc, &v| acc | v);
let bit_width: u8 = required_bit_width(acc);
output[bit_widths_offset + num_full_blocks] = bit_width;
let packed_size: usize = packed_partial_block_size(remaining, bit_width);
if packed_size > 0 {
ScalarBackend::pack_partial_block(block, bit_width, &mut output[offset..])?;
offset += packed_size;
}
}
Ok(offset)
}
pub fn compress(input: &[u32]) -> Result<Vec<u8>, Error> {
if input.is_empty() {
return Ok(Vec::new());
}
let max_size = max_compressed_size(input.len());
let mut output = vec![0; max_size];
let bytes_written = compress_into(input, &mut output)?;
output.truncate(bytes_written);
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compress_empty() {
let input: Vec<u32> = vec![];
let compressed = compress(&input).unwrap();
assert!(compressed.is_empty());
}
#[test]
fn test_compress_single_value() {
let input: Vec<u32> = vec![42];
let compressed = compress(&input).unwrap();
assert_eq!(compressed.len(), 11);
assert_eq!(compressed[0], FORMAT_VERSION);
let input_len =
u32::from_le_bytes([compressed[1], compressed[2], compressed[3], compressed[4]]);
assert_eq!(input_len, 1);
let num_blocks =
u32::from_le_bytes([compressed[5], compressed[6], compressed[7], compressed[8]]);
assert_eq!(num_blocks, 1);
assert_eq!(compressed[9], 6);
}
#[test]
fn test_compress_full_block() {
let input: Vec<u32> = (0..128).map(|i| (i % 128) as u32).collect();
let compressed = compress(&input).unwrap();
assert_eq!(compressed.len(), 122);
assert_eq!(compressed[0], FORMAT_VERSION);
let input_len =
u32::from_le_bytes([compressed[1], compressed[2], compressed[3], compressed[4]]);
assert_eq!(input_len, 128);
let num_blocks =
u32::from_le_bytes([compressed[5], compressed[6], compressed[7], compressed[8]]);
assert_eq!(num_blocks, 1);
assert_eq!(compressed[9], 7);
}
#[test]
fn test_compress_multiple_blocks() {
let input: Vec<u32> = (0..256).map(|i| (i % 100) as u32).collect();
let compressed = compress(&input).unwrap();
let num_blocks =
u32::from_le_bytes([compressed[5], compressed[6], compressed[7], compressed[8]]);
assert_eq!(num_blocks, 2);
assert_eq!(compressed[9], 7);
assert_eq!(compressed[10], 7);
}
#[test]
fn test_compress_zeros() {
let input = [0u32; 128];
let compressed = compress(&input).unwrap();
assert_eq!(compressed.len(), 10);
assert_eq!(compressed[0], FORMAT_VERSION);
assert_eq!(compressed[9], 0);
}
#[test]
fn test_compress_partial_block() {
let input: Vec<u32> = (0..200).map(|i| (i % 100) as u32).collect();
let compressed = compress(&input).unwrap();
let input_len =
u32::from_le_bytes([compressed[1], compressed[2], compressed[3], compressed[4]]);
assert_eq!(input_len, 200);
let num_blocks =
u32::from_le_bytes([compressed[5], compressed[6], compressed[7], compressed[8]]);
assert_eq!(num_blocks, 2);
assert_eq!(compressed[9], 7);
assert_eq!(compressed[10], 7);
}
#[test]
fn test_compress_input_too_large() {
let ok = compress(&[0u32; 100]);
assert!(ok.is_ok());
let err = CompressionError::InputTooLarge {
max: u32::MAX as usize,
got: u32::MAX as usize + 1,
};
let _: Error = err.into();
}
#[test]
fn test_compress_different_bit_widths() {
let mut input: Vec<u32> = (0..128).map(|i| (i % 100) as u32).collect();
input.extend((0..128u32).map(|i| i * 1000));
let compressed = compress(&input).unwrap();
assert_eq!(compressed[9], 7); assert_eq!(compressed[10], 17); }
#[test]
fn test_compress_format_layout() {
let input: Vec<u32> = vec![1, 2, 3, 4, 5];
let compressed = compress(&input).unwrap();
assert_eq!(compressed[0], FORMAT_VERSION);
assert_eq!(&compressed[1..5], &[5, 0, 0, 0]);
assert_eq!(&compressed[5..9], &[1, 0, 0, 0]);
assert_eq!(compressed[9], 3);
assert_eq!(compressed.len(), 12);
}
#[test]
fn test_compress_zeros_partial_block() {
let input = vec![0u32; 50]; let compressed = compress(&input).unwrap();
assert_eq!(compressed.len(), 10);
assert_eq!(compressed[9], 0);
}
#[test]
fn test_compress_precomputed_output_size() {
for &n in &[1usize, 127, 128, 129, 255, 256, 257, 512] {
let input: Vec<u32> = (0..n as u32).collect();
let compressed = compress(&input).unwrap();
let input_len =
u32::from_le_bytes([compressed[1], compressed[2], compressed[3], compressed[4]]);
assert_eq!(input_len as usize, n, "input_len header mismatch for n={n}");
}
}
#[test]
fn test_max_compressed_size_overflow_safe() {
let size = max_compressed_size(u32::MAX as usize);
assert!(size > 0, "must not wrap to zero");
let expected_blocks = (u32::MAX as usize + 127) / 128;
assert!(size >= 9 + expected_blocks);
}
#[test]
fn test_compress_into_exact_size_buffer() {
let input: Vec<u32> = (0..256).map(|i| i % 1000).collect();
let compressed = compress(&input).unwrap();
let max_size = max_compressed_size(input.len());
let mut buffer = vec![0u8; max_size];
let bytes_written = compress_into(&input, &mut buffer).unwrap();
assert_eq!(bytes_written, compressed.len());
assert_eq!(&buffer[..bytes_written], &compressed[..]);
}
}