use super::lz4mid::Match;
use super::types::{count_back, hash_ptr, DictCtxDirective, HcCCtxInternal, LZ4HC_MAXD_MASK};
use crate::block::types::{self as bt, LZ4_DISTANCE_MAX, MINMATCH};
#[inline(always)]
fn delta_next(chain_table: &[u16], idx: u32) -> u32 {
chain_table[idx as usize & LZ4HC_MAXD_MASK] as u32
}
#[inline]
pub unsafe fn insert(hc4: &mut HcCCtxInternal, ip: *const u8) {
let prefix_ptr = hc4.prefix_start;
let prefix_idx = hc4.dict_limit;
let target = (ip.offset_from(prefix_ptr) as u32).wrapping_add(prefix_idx);
let mut idx = hc4.next_to_update;
debug_assert!(ip >= prefix_ptr);
debug_assert!(target >= prefix_idx);
while idx < target {
let h = hash_ptr(prefix_ptr.add((idx - prefix_idx) as usize)) as usize;
let mut delta = idx.wrapping_sub(hc4.hash_table[h]);
if delta > LZ4_DISTANCE_MAX {
delta = LZ4_DISTANCE_MAX;
}
hc4.chain_table[idx as usize & LZ4HC_MAXD_MASK] = delta as u16;
hc4.hash_table[h] = idx;
idx += 1;
}
hc4.next_to_update = target;
}
#[inline(always)]
pub fn rotate_pattern(rotate: usize, pattern: u32) -> u32 {
let bits_to_rotate = (rotate & (core::mem::size_of::<u32>() - 1)) << 3;
if bits_to_rotate == 0 {
return pattern;
}
pattern.rotate_left(bits_to_rotate as u32)
}
#[inline]
pub unsafe fn count_pattern(mut ip: *const u8, i_end: *const u8, pattern32: u32) -> usize {
let i_start = ip;
#[cfg(target_pointer_width = "64")]
let pattern: usize = (pattern32 as usize) | ((pattern32 as usize) << 32);
#[cfg(not(target_pointer_width = "64"))]
let pattern: usize = pattern32 as usize;
let word_size = core::mem::size_of::<usize>();
while ip.add(word_size) <= i_end {
let diff = bt::read_arch(ip) ^ pattern;
if diff != 0 {
ip = ip.add(bt::nb_common_bytes(diff) as usize);
return ip.offset_from(i_start) as usize;
}
ip = ip.add(word_size);
}
#[cfg(target_endian = "little")]
{
let mut pattern_byte = pattern32 as u64;
while ip < i_end && *ip == (pattern_byte as u8) {
ip = ip.add(1);
pattern_byte >>= 8;
if pattern_byte == 0 {
pattern_byte = pattern32 as u64;
}
}
}
#[cfg(not(target_endian = "little"))]
{
let mut bit_offset = 24u32; while ip < i_end {
let byte = (pattern32 >> bit_offset) as u8;
if *ip != byte {
break;
}
ip = ip.add(1);
if bit_offset == 0 {
bit_offset = 24;
} else {
bit_offset -= 8;
}
}
}
ip.offset_from(i_start) as usize
}
#[inline]
pub unsafe fn reverse_count_pattern(mut ip: *const u8, i_low: *const u8, pattern: u32) -> usize {
let i_start = ip;
while ip >= i_low.add(4) {
if bt::read32(ip.sub(4)) != pattern {
break;
}
ip = ip.sub(4);
}
let pattern_bytes = pattern.to_ne_bytes();
let mut byte_idx: isize = 3; while ip > i_low {
if *ip.sub(1) != pattern_bytes[byte_idx as usize] {
break;
}
ip = ip.sub(1);
byte_idx -= 1;
if byte_idx < 0 {
byte_idx = 3;
}
}
i_start.offset_from(ip) as usize
}
#[inline(always)]
pub fn protect_dict_end(dict_limit: u32, match_index: u32) -> bool {
(dict_limit.wrapping_sub(1).wrapping_sub(match_index)) >= 3
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RepeatState {
Untested,
Not,
Confirmed,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum HcFavor {
CompressionRatio = 0,
DecompressionSpeed = 1,
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn insert_and_get_wider_match(
hc4: &mut HcCCtxInternal,
ip: *const u8,
i_low_limit: *const u8,
i_high_limit: *const u8,
mut longest: i32,
max_nb_attempts: i32,
pattern_analysis: bool,
chain_swap: bool,
dict: DictCtxDirective,
favor_dec_speed: bool,
) -> Match {
let prefix_ptr = hc4.prefix_start;
let prefix_idx = hc4.dict_limit;
let ip_index = (ip.offset_from(prefix_ptr) as u32).wrapping_add(prefix_idx);
let within_start_distance = hc4.low_limit.wrapping_add(LZ4_DISTANCE_MAX + 1) > ip_index;
let lowest_match_index = if within_start_distance {
hc4.low_limit
} else {
ip_index - LZ4_DISTANCE_MAX
};
let dict_start = hc4.dict_start;
let dict_idx = hc4.low_limit;
let dict_end = dict_start.add((prefix_idx - dict_idx) as usize);
let look_back_length = ip.offset_from(i_low_limit) as i32;
let mut nb_attempts = max_nb_attempts;
let mut match_chain_pos: u32 = 0;
let pattern = bt::read32(ip);
let mut repeat = RepeatState::Untested;
let mut src_pattern_length: usize = 0;
let mut offset: i32 = 0;
let mut s_back: i32 = 0;
insert(hc4, ip);
let mut match_index = hc4.hash_table[hash_ptr(ip) as usize];
'chain_loop: while match_index >= lowest_match_index && nb_attempts > 0 {
let mut match_length: i32 = 0;
nb_attempts -= 1;
debug_assert!(match_index < ip_index);
if favor_dec_speed && (ip_index - match_index < 8) {
} else if match_index >= prefix_idx {
let match_ptr = prefix_ptr.add((match_index - prefix_idx) as usize);
debug_assert!(match_ptr < ip);
debug_assert!(longest >= 1);
if bt::read16(i_low_limit.add((longest - 1) as usize))
== bt::read16(
match_ptr
.sub(look_back_length as usize)
.add((longest - 1) as usize),
)
&& bt::read32(match_ptr) == pattern
{
let back = if look_back_length != 0 {
count_back(ip, match_ptr, i_low_limit, prefix_ptr)
} else {
0
};
match_length = MINMATCH as i32
+ bt::count(ip.add(MINMATCH), match_ptr.add(MINMATCH), i_high_limit) as i32;
match_length -= back;
if match_length > longest {
longest = match_length;
offset = (ip_index - match_index) as i32;
s_back = back;
}
}
} else {
let match_ptr = dict_start.add((match_index - dict_idx) as usize);
debug_assert!(match_index >= dict_idx);
if match_index <= prefix_idx.wrapping_sub(4) && bt::read32(match_ptr) == pattern {
let mut v_limit = ip.add((prefix_idx - match_index) as usize);
if v_limit > i_high_limit {
v_limit = i_high_limit;
}
let mut mlt = bt::count(ip.add(MINMATCH), match_ptr.add(MINMATCH), v_limit) as i32
+ MINMATCH as i32;
if ip.add(mlt as usize) == v_limit && v_limit < i_high_limit {
mlt += bt::count(ip.add(mlt as usize), prefix_ptr, i_high_limit) as i32;
}
let back = if look_back_length != 0 {
count_back(ip, match_ptr, i_low_limit, dict_start)
} else {
0
};
mlt -= back;
if mlt > longest {
longest = mlt;
offset = (ip_index - match_index) as i32;
s_back = back;
}
}
}
if chain_swap && match_length == longest {
debug_assert!(look_back_length == 0); if match_index.wrapping_add(longest as u32) <= ip_index {
const K_TRIGGER: i32 = 4;
let mut distance_to_next_match: u32 = 1;
let end = longest - MINMATCH as i32 + 1;
let mut step: i32 = 1;
let mut accel: i32 = 1 << K_TRIGGER;
let mut pos: i32 = 0;
while pos < end {
let candidate_dist = delta_next(&hc4.chain_table, match_index + pos as u32);
step = accel >> K_TRIGGER;
accel += 1;
if candidate_dist > distance_to_next_match {
distance_to_next_match = candidate_dist;
match_chain_pos = pos as u32;
accel = 1 << K_TRIGGER;
}
pos += step;
}
if distance_to_next_match > 1 {
if distance_to_next_match > match_index {
break 'chain_loop; }
match_index -= distance_to_next_match;
continue 'chain_loop;
}
}
}
{
let dist_next_match = delta_next(&hc4.chain_table, match_index);
if pattern_analysis && dist_next_match == 1 && match_chain_pos == 0 {
let match_candidate_idx = match_index.wrapping_sub(1);
if repeat == RepeatState::Untested {
if ((pattern & 0xFFFF) == (pattern >> 16))
&& ((pattern & 0xFF) == (pattern >> 24))
{
repeat = RepeatState::Confirmed;
src_pattern_length = count_pattern(ip.add(4), i_high_limit, pattern) + 4;
} else {
repeat = RepeatState::Not;
}
}
if repeat == RepeatState::Confirmed
&& match_candidate_idx >= lowest_match_index
&& protect_dict_end(prefix_idx, match_candidate_idx)
{
let ext_dict = match_candidate_idx < prefix_idx;
let match_ptr = if ext_dict {
dict_start.add((match_candidate_idx - dict_idx) as usize)
} else {
prefix_ptr.add((match_candidate_idx - prefix_idx) as usize)
};
if bt::read32(match_ptr) == pattern {
let i_limit = if ext_dict { dict_end } else { i_high_limit };
let mut forward_pattern_length =
count_pattern(match_ptr.add(4), i_limit, pattern) + 4;
if ext_dict && match_ptr.add(forward_pattern_length) == i_limit {
let rotated = rotate_pattern(forward_pattern_length, pattern);
forward_pattern_length +=
count_pattern(prefix_ptr, i_high_limit, rotated);
}
{
let lowest_match_ptr = if ext_dict { dict_start } else { prefix_ptr };
let mut back_length =
reverse_count_pattern(match_ptr, lowest_match_ptr, pattern);
if !ext_dict
&& match_ptr.sub(back_length) == prefix_ptr
&& dict_idx < prefix_idx
{
let rotated =
rotate_pattern((0usize).wrapping_sub(back_length), pattern);
back_length += reverse_count_pattern(dict_end, dict_start, rotated);
}
back_length = (match_candidate_idx
- match_candidate_idx
.wrapping_sub(back_length as u32)
.max(lowest_match_index))
as usize;
debug_assert!(
match_candidate_idx.wrapping_sub(back_length as u32)
>= lowest_match_index
);
let current_segment_length = back_length + forward_pattern_length;
if current_segment_length >= src_pattern_length
&& forward_pattern_length <= src_pattern_length
{
let new_match_index = match_candidate_idx
.wrapping_add(forward_pattern_length as u32)
.wrapping_sub(src_pattern_length as u32);
if protect_dict_end(prefix_idx, new_match_index) {
match_index = new_match_index;
} else {
debug_assert!(
new_match_index >= prefix_idx.wrapping_sub(3)
&& new_match_index < prefix_idx
&& !ext_dict
);
match_index = prefix_idx;
}
} else {
let new_match_index =
match_candidate_idx.wrapping_sub(back_length as u32);
if !protect_dict_end(prefix_idx, new_match_index) {
debug_assert!(
new_match_index >= prefix_idx.wrapping_sub(3)
&& new_match_index < prefix_idx
&& !ext_dict
);
match_index = prefix_idx;
} else {
match_index = new_match_index;
if look_back_length == 0 {
let max_ml = current_segment_length.min(src_pattern_length);
if (longest as usize) < max_ml {
debug_assert!(
prefix_ptr
.sub(prefix_idx as usize)
.add(match_index as usize)
!= ip
);
let dist = ip.offset_from(prefix_ptr) as u32
+ prefix_idx
- match_index;
if dist > LZ4_DISTANCE_MAX {
break 'chain_loop; }
longest = max_ml as i32;
offset = (ip_index - match_index) as i32;
debug_assert!(s_back == 0);
}
let dist_to_next =
delta_next(&hc4.chain_table, match_index);
if dist_to_next > match_index {
break 'chain_loop; }
match_index -= dist_to_next;
}
}
}
}
continue 'chain_loop; }
}
}
}
match_index =
match_index.wrapping_sub(delta_next(&hc4.chain_table, match_index + match_chain_pos));
}
if dict == DictCtxDirective::UsingDictCtxHc && nb_attempts > 0 && within_start_distance {
let dict_ctx = &*hc4.dict_ctx;
let dict_end_offset = (dict_ctx.end.offset_from(dict_ctx.prefix_start) as usize)
.wrapping_add(dict_ctx.dict_limit as usize);
debug_assert!(dict_end_offset <= 1 << 30); let mut dict_match_index = dict_ctx.hash_table[hash_ptr(ip) as usize];
let mut match_index_dc = dict_match_index
.wrapping_add(lowest_match_index)
.wrapping_sub(dict_end_offset as u32);
while ip_index.wrapping_sub(match_index_dc) <= LZ4_DISTANCE_MAX && nb_attempts > 0 {
nb_attempts -= 1;
let match_ptr = dict_ctx
.prefix_start
.sub(dict_ctx.dict_limit as usize)
.add(dict_match_index as usize);
if bt::read32(match_ptr) == pattern {
let mut v_limit = ip.add(dict_end_offset - dict_match_index as usize);
if v_limit > i_high_limit {
v_limit = i_high_limit;
}
let mut mlt = bt::count(ip.add(MINMATCH), match_ptr.add(MINMATCH), v_limit) as i32
+ MINMATCH as i32;
let back = if look_back_length != 0 {
count_back(ip, match_ptr, i_low_limit, dict_ctx.prefix_start)
} else {
0
};
mlt -= back;
if mlt > longest {
longest = mlt;
offset = (ip_index - match_index_dc) as i32;
s_back = back;
}
}
let next_offset = delta_next(&dict_ctx.chain_table, dict_match_index);
dict_match_index = dict_match_index.wrapping_sub(next_offset);
match_index_dc = match_index_dc.wrapping_sub(next_offset);
}
}
debug_assert!(longest >= 0);
Match {
len: longest,
off: offset,
back: s_back,
}
}
#[inline]
pub unsafe fn insert_and_find_best_match(
hc4: &mut HcCCtxInternal,
ip: *const u8,
i_limit: *const u8,
max_nb_attempts: i32,
pattern_analysis: bool,
dict: DictCtxDirective,
) -> Match {
insert_and_get_wider_match(
hc4,
ip,
ip, i_limit,
MINMATCH as i32 - 1,
max_nb_attempts,
pattern_analysis,
false, dict,
false, )
}