use super::comp_dict::CompDict;
use crate::prelude::Allocator;
use core::mem::size_of;
use core::ptr::read_unaligned;
pub trait Lz77Parameters {
const MAX_OFFSET: usize;
const MAX_LENGTH: usize;
}
#[inline(never)] pub unsafe fn lz77_get_longest_match_fast<
P: Lz77Parameters,
L: Allocator + Copy,
S: Allocator + Copy,
>(
dict: &mut CompDict<L, S>,
source_ptr: *const u8,
source_index: usize,
) -> Lz77Match {
let mut best_match = Lz77Match {
offset: 0,
length: 0,
};
let min_offset = source_index.saturating_sub(P::MAX_OFFSET);
let key = read_unaligned(source_ptr.add(source_index) as *const u16);
let offsets = dict.get_item(key, min_offset, source_index.saturating_sub(1));
for &match_offset in offsets.iter().rev() {
let match_offset = match_offset as usize;
let mut match_length = 2;
let offset_src_ptr = source_ptr.add(match_offset);
let offset_dst_ptr = source_ptr.add(source_index);
let initial_match = read_unaligned(offset_src_ptr as *const u32)
== read_unaligned(offset_dst_ptr as *const u32);
if !initial_match {
match_length +=
(*offset_src_ptr.add(match_length) == *offset_dst_ptr.add(match_length)) as usize;
} else {
match_length = 4;
debug_assert!(P::MAX_LENGTH % size_of::<usize>() == 0);
while match_length < (P::MAX_LENGTH - size_of::<u32>())
&& read_unaligned(offset_src_ptr.add(match_length) as *const usize)
== read_unaligned(offset_dst_ptr.add(match_length) as *const usize)
{
match_length += size_of::<usize>();
}
while match_length < P::MAX_LENGTH
&& *offset_src_ptr.add(match_length) == *offset_dst_ptr.add(match_length)
{
match_length += 1;
}
}
if match_length > best_match.length {
best_match.length = match_length;
best_match.offset = match_offset as isize - source_index as isize;
if match_length == P::MAX_LENGTH {
break;
}
}
}
best_match
}
#[inline(never)] pub unsafe fn lz77_get_longest_match_slow<
P: Lz77Parameters,
L: Allocator + Copy,
S: Allocator + Copy,
>(
dict: &mut CompDict<L, S>,
source_ptr: *const u8,
source_len: usize,
source_index: usize,
) -> Lz77Match {
let mut best_match = Lz77Match {
offset: 0,
length: 0,
};
let min_offset = source_index.saturating_sub(P::MAX_OFFSET);
let key = read_unaligned(source_ptr.add(source_index) as *const u16);
let max_match_length = P::MAX_LENGTH.min(source_len - source_index);
let offsets = dict.get_item(key, min_offset, source_index.saturating_sub(1));
for &match_offset in offsets.iter().rev() {
let match_offset = match_offset as usize;
let mut match_length = 2;
let offset_src_ptr = source_ptr.add(match_offset);
let offset_dst_ptr = source_ptr.add(source_index);
while match_length < max_match_length
&& *offset_src_ptr.add(match_length) == *offset_dst_ptr.add(match_length)
{
match_length += 1;
}
if match_length > best_match.length {
best_match.length = match_length;
best_match.offset = match_offset as isize - source_index as isize;
if match_length == max_match_length {
break;
}
}
}
best_match
}
pub struct Lz77Match {
pub offset: isize,
pub length: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_prelude::*;
#[test]
fn test_longest_match_with_repetition() {
let data = b"abcabcabcabcabc";
let mut dict = CompDict::new(data.len());
unsafe { dict.init(data, 0) }
let match_result = unsafe {
lz77_get_longest_match_slow::<CompressParameters, Global, Global>(
&mut dict,
data.as_ptr(),
data.len(),
3,
)
};
assert_eq!(match_result.length, 12);
assert_eq!(match_result.offset, -3);
}
#[test]
fn test_no_match() {
let data = b"abcdefgh";
let mut dict = CompDict::new(data.len());
unsafe { dict.init(data, 0) }
let match_result = unsafe {
lz77_get_longest_match_slow::<CompressParameters, Global, Global>(
&mut dict,
data.as_ptr(),
data.len(),
2,
)
};
assert_eq!(match_result.length, 0);
}
#[test]
fn test_multiple_matches() {
let data = b"ababababab";
let mut dict = CompDict::new(data.len());
unsafe { dict.init(data, 0) }
let match_result = unsafe {
lz77_get_longest_match_slow::<CompressParameters, Global, Global>(
&mut dict,
data.as_ptr(),
data.len(),
2,
)
};
assert_eq!(match_result.length, 8);
assert_eq!(match_result.offset, -2);
}
#[test]
fn test_boundary_conditions() {
let data = b"ababababab";
let mut dict = CompDict::new(data.len());
unsafe { dict.init(data, 0) }
let match_result = unsafe {
lz77_get_longest_match_slow::<CompressParameters, Global, Global>(
&mut dict,
data.as_ptr(),
data.len(),
data.len() - 3,
)
};
assert_eq!(match_result.length, 3);
assert_eq!(match_result.offset, -2);
}
#[test]
fn test_last_match_on_boundary() {
let data = b"acacacabab";
let mut dict = CompDict::new(data.len());
unsafe { dict.init(data, 0) }
let match_result = unsafe {
lz77_get_longest_match_slow::<CompressParameters, Global, Global>(
&mut dict,
data.as_ptr(),
data.len(),
data.len() - 2,
)
};
assert_eq!(match_result.length, 2);
assert_eq!(match_result.offset, -2);
}
struct CompressParameters;
impl Lz77Parameters for CompressParameters {
const MAX_OFFSET: usize = 0x1FFF;
const MAX_LENGTH: usize = 256;
}
}