use crate::{Error, Level, Result};
#[cfg(feature = "simd")]
pub fn compress_simd(data: &[u8], level: Level) -> Result<Vec<u8>> {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
unsafe { return compress_avx2(data, level); }
}
}
crate::lz4::compress_with_level(data, level)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2(input: &[u8], level: Level) -> Result<Vec<u8>> {
use std::arch::x86_64::*;
if input.is_empty() {
return Ok(vec![0, 0, 0, 0]);
}
if input.len() > u32::MAX as usize {
return Err(Error::InputTooLarge {
size: input.len(),
max_size: u32::MAX as usize,
});
}
let mut output = Vec::with_capacity(input.len() + input.len() / 255 + 16);
output.extend_from_slice(&(input.len() as u32).to_le_bytes());
const HASH_LOG: usize = 12;
const HASH_TABLE_SIZE: usize = 1 << HASH_LOG;
let mut hash_table = vec![-1i32; HASH_TABLE_SIZE];
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
let input_limit = if input_end > 5 { input_end - 5 } else { 0 };
let hash_multiplier = _mm256_set1_epi32(2654435761u32 as i32);
let shift_amount = 32 - HASH_LOG;
match level {
Level::Fast => compress_avx2_fast(input, &mut output, &mut hash_table, input_limit)?,
Level::Balanced => {
compress_avx2_balanced(input, &mut output, &mut hash_table, input_limit)?
}
Level::Best => compress_avx2_best(input, &mut output, &mut hash_table, input_limit)?,
}
Ok(output)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_fast(
input: &[u8],
output: &mut Vec<u8>,
hash_table: &mut [i32],
input_limit: usize,
) -> Result<()> {
use std::arch::x86_64::*;
const MIN_MATCH: usize = 4;
const MAX_DISTANCE: usize = 65535;
const HASH_LOG: usize = 12;
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
while pos < input_limit {
if pos + MIN_MATCH + 32 <= input_end {
let _data_vec = _mm256_loadu_si256(input.as_ptr().add(pos) as *const __m256i);
let hash = hash4_simd(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let match_len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);
if match_len >= MIN_MATCH {
emit_sequence(output, input, &mut anchor, pos, candidate_pos, match_len);
pos += match_len;
anchor = pos;
continue;
}
}
}
hash_table[hash] = pos as i32;
}
pos += 2; }
emit_final_literals(output, input, anchor, input_end);
Ok(())
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_balanced(
input: &[u8],
output: &mut Vec<u8>,
hash_table: &mut [i32],
input_limit: usize,
) -> Result<()> {
const MIN_MATCH: usize = 4;
const MAX_DISTANCE: usize = 65535;
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
while pos < input_limit {
if pos + MIN_MATCH <= input_end {
let hash = hash4_simd(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let match_len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);
if match_len >= MIN_MATCH {
emit_sequence(output, input, &mut anchor, pos, candidate_pos, match_len);
pos += match_len;
anchor = pos;
continue;
}
}
}
hash_table[hash] = pos as i32;
}
pos += 1;
}
emit_final_literals(output, input, anchor, input_end);
Ok(())
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_best(
input: &[u8],
output: &mut Vec<u8>,
hash_table: &mut [i32],
input_limit: usize,
) -> Result<()> {
const MIN_MATCH: usize = 4;
const MAX_DISTANCE: usize = 65535;
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
while pos < input_limit {
let mut best_match_pos = 0;
let mut best_match_len = 0;
if pos + MIN_MATCH <= input_end {
let hash = hash4_simd(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);
if len >= MIN_MATCH {
best_match_pos = candidate_pos;
best_match_len = len;
}
}
}
hash_table[hash] = pos as i32;
}
if best_match_len > 0 && pos + 1 < input_limit {
let next_pos = pos + 1;
if next_pos + MIN_MATCH <= input_end {
let hash = hash4_simd(&input[next_pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = next_pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let len = count_match_avx2(&input[candidate_pos..], &input[next_pos..], input_end - next_pos);
if len > best_match_len + 2 {
pos += 1;
best_match_pos = candidate_pos;
best_match_len = len;
}
}
}
}
}
if best_match_len >= MIN_MATCH {
emit_sequence(output, input, &mut anchor, pos, best_match_pos, best_match_len);
pos += best_match_len;
anchor = pos;
} else {
pos += 1;
}
}
emit_final_literals(output, input, anchor, input_end);
Ok(())
}
#[inline]
fn hash4_simd(data: &[u8]) -> usize {
if data.len() < 4 {
return 0;
}
let value = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
((value.wrapping_mul(2654435761)) >> 20) as usize
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn count_match_avx2(a: &[u8], b: &[u8], max_len: usize) -> usize {
use std::arch::x86_64::*;
let limit = a.len().min(b.len()).min(max_len);
let mut len = 0;
while len + 32 <= limit {
let va = _mm256_loadu_si256(a.as_ptr().add(len) as *const __m256i);
let vb = _mm256_loadu_si256(b.as_ptr().add(len) as *const __m256i);
let cmp = _mm256_cmpeq_epi8(va, vb);
let mask = _mm256_movemask_epi8(cmp);
if mask != -1 {
len += mask.trailing_ones() as usize;
return len;
}
len += 32;
}
while len < limit && a[len] == b[len] {
len += 1;
}
len
}
fn emit_sequence(
output: &mut Vec<u8>,
input: &[u8],
anchor: &mut usize,
pos: usize,
match_pos: usize,
match_len: usize,
) {
const MIN_MATCH: usize = 4;
let literal_len = pos - *anchor;
let lit_token = if literal_len >= 15 { 15 } else { literal_len };
let match_token = if match_len >= MIN_MATCH + 15 {
15
} else {
match_len - MIN_MATCH
};
output.push(((lit_token << 4) | match_token) as u8);
if literal_len >= 15 {
let mut remaining = literal_len - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[*anchor..pos]);
let offset = (pos - match_pos) as u16;
output.extend_from_slice(&offset.to_le_bytes());
if match_len >= MIN_MATCH + 15 {
let mut remaining = match_len - MIN_MATCH - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
}
fn emit_final_literals(output: &mut Vec<u8>, input: &[u8], anchor: usize, input_end: usize) {
let final_literals = input_end - anchor;
if final_literals > 0 {
let lit_token = if final_literals >= 15 { 15 } else { final_literals };
output.push((lit_token << 4) as u8);
if final_literals >= 15 {
let mut remaining = final_literals - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[anchor..]);
}
}
#[cfg(not(feature = "simd"))]
pub fn compress_simd(data: &[u8], level: Level) -> Result<Vec<u8>> {
Err(Error::InvalidInput(
"SIMD compression requires 'simd' feature".to_string(),
))
}
#[cfg(all(test, feature = "simd"))]
mod tests {
use super::*;
#[test]
fn test_simd_basic() {
let data = b"Hello, World! This is SIMD compression.";
let compressed = compress_simd(data, Level::Balanced).unwrap();
let decompressed = crate::lz4::decompress(&compressed).unwrap();
assert_eq!(data, &decompressed[..]);
}
#[test]
fn test_simd_repetitive() {
let data = vec![b'A'; 10000];
let compressed = compress_simd(&data, Level::Balanced).unwrap();
assert!(compressed.len() < data.len());
let decompressed = crate::lz4::decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
#[test]
fn test_simd_all_levels() {
let data = b"Test data for all compression levels".repeat(100);
for level in [Level::Fast, Level::Balanced, Level::Best] {
let compressed = compress_simd(&data, level).unwrap();
let decompressed = crate::lz4::decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_avx2_detection() {
if is_x86_feature_detected!("avx2") {
println!("AVX2 is available on this CPU");
} else {
println!("AVX2 not available, using scalar fallback");
}
}
}