use super::encode::{encode_sequence, Lz4HcError};
use super::types::{
count_back, get_clevel_params, mid_hash4_ptr, mid_hash8_ptr, DictCtxDirective, HcCCtxInternal,
HcStrategy, LZ4MID_HASHSIZE, LZ4MID_HASHTABLESIZE,
};
use crate::block::types::{
self as bt, LimitedOutputDirective, LASTLITERALS, LZ4_DISTANCE_MAX, MFLIMIT, MINMATCH, ML_BITS,
ML_MASK, RUN_MASK,
};
#[derive(Clone, Copy, Debug, Default)]
pub struct Match {
pub off: i32,
pub len: i32,
pub back: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DictSearchMode {
Hc,
Ext,
}
pub unsafe fn hc_search_ext_dict(
ip: *const u8,
ip_index: u32,
i_low_limit: *const u8,
i_high_limit: *const u8,
dict_ctx: *const HcCCtxInternal,
g_dict_end_index: u32,
current_best_ml: i32,
mut nb_attempts: i32,
) -> Match {
let ctx = &*dict_ctx;
let l_dict_end_index =
(ctx.end as usize).wrapping_sub(ctx.prefix_start as usize) + ctx.dict_limit as usize;
let l_dict_match_index_init = ctx.hash_table[super::types::hash_ptr(ip) as usize];
let mut l_dict_match_index = l_dict_match_index_init;
let mut match_index = l_dict_match_index
.wrapping_add(g_dict_end_index)
.wrapping_sub(l_dict_end_index as u32);
let mut best_ml = current_best_ml;
let mut offset: i32 = 0;
let mut s_back: i32 = 0;
debug_assert!(l_dict_end_index <= bt::GB);
while ip_index.wrapping_sub(match_index) <= LZ4_DISTANCE_MAX && nb_attempts > 0 {
nb_attempts -= 1;
let match_ptr = ctx
.prefix_start
.sub(ctx.dict_limit as usize)
.add(l_dict_match_index as usize);
if bt::read32(match_ptr) == bt::read32(ip) {
let v_limit_raw = ip.add(l_dict_end_index.wrapping_sub(l_dict_match_index as usize));
let v_limit = if v_limit_raw > i_high_limit {
i_high_limit
} else {
v_limit_raw
};
let mlt = bt::count(ip.add(MINMATCH), match_ptr.add(MINMATCH), v_limit) as i32
+ MINMATCH as i32;
let back = if ip > i_low_limit {
count_back(ip, match_ptr, i_low_limit, ctx.prefix_start)
} else {
0
};
let mlt = mlt - back;
if mlt > best_ml {
best_ml = mlt;
offset = ip_index.wrapping_sub(match_index) as i32;
s_back = back;
}
}
let next_offset = ctx.chain_table[(l_dict_match_index as u16) as usize] as u32;
l_dict_match_index = l_dict_match_index.wrapping_sub(next_offset);
match_index = match_index.wrapping_sub(next_offset);
}
Match {
len: best_ml,
off: offset,
back: s_back,
}
}
unsafe fn mid_search_hc_dict(
ip: *const u8,
ip_index: u32,
i_high_limit: *const u8,
dict_ctx: *const HcCCtxInternal,
g_dict_end_index: u32,
) -> Match {
hc_search_ext_dict(
ip,
ip_index,
ip, i_high_limit,
dict_ctx,
g_dict_end_index,
MINMATCH as i32 - 1, 2, )
}
unsafe fn mid_search_ext_dict(
ip: *const u8,
ip_index: u32,
i_high_limit: *const u8,
dict_ctx: *const HcCCtxInternal,
g_dict_end_index: u32,
) -> Match {
let ctx = &*dict_ctx;
let l_dict_end_index =
(ctx.end as usize).wrapping_sub(ctx.prefix_start as usize) + ctx.dict_limit as usize;
let hash4_table: *const u32 = ctx.hash_table.as_ptr();
let hash8_table: *const u32 = hash4_table.add(LZ4MID_HASHTABLESIZE);
debug_assert!(l_dict_end_index <= bt::GB);
{
let l8_dict_match_index = *hash8_table.add(mid_hash8_ptr(ip) as usize);
let m8_index = l8_dict_match_index
.wrapping_add(g_dict_end_index)
.wrapping_sub(l_dict_end_index as u32);
if ip_index.wrapping_sub(m8_index) <= LZ4_DISTANCE_MAX {
let match_ptr = ctx
.prefix_start
.sub(ctx.dict_limit as usize)
.add(l8_dict_match_index as usize);
let dict_remaining = l_dict_end_index.wrapping_sub(l8_dict_match_index as usize);
let ip_remaining = (i_high_limit as usize).wrapping_sub(ip as usize);
let safe_len = dict_remaining.min(ip_remaining);
let mlt = bt::count(ip, match_ptr, ip.add(safe_len)) as i32;
if mlt >= MINMATCH as i32 {
return Match {
len: mlt,
off: ip_index.wrapping_sub(m8_index) as i32,
back: 0,
};
}
}
}
{
let l4_dict_match_index = *hash4_table.add(mid_hash4_ptr(ip) as usize);
let m4_index = l4_dict_match_index
.wrapping_add(g_dict_end_index)
.wrapping_sub(l_dict_end_index as u32);
if ip_index.wrapping_sub(m4_index) <= LZ4_DISTANCE_MAX {
let match_ptr = ctx
.prefix_start
.sub(ctx.dict_limit as usize)
.add(l4_dict_match_index as usize);
let dict_remaining = l_dict_end_index.wrapping_sub(l4_dict_match_index as usize);
let ip_remaining = (i_high_limit as usize).wrapping_sub(ip as usize);
let safe_len = dict_remaining.min(ip_remaining);
let mlt = bt::count(ip, match_ptr, ip.add(safe_len)) as i32;
if mlt >= MINMATCH as i32 {
return Match {
len: mlt,
off: ip_index.wrapping_sub(m4_index) as i32,
back: 0,
};
}
}
}
Match {
off: 0,
len: 0,
back: 0,
}
}
#[inline(always)]
pub unsafe fn add_position(h_table: *mut u32, h_value: u32, index: u32) {
*h_table.add(h_value as usize) = index;
}
pub unsafe fn fill_htable(cctx: &mut HcCCtxInternal, dict: *const u8, size: usize) {
let hash4_table: *mut u32 = cctx.hash_table.as_mut_ptr();
let hash8_table: *mut u32 = hash4_table.add(LZ4MID_HASHTABLESIZE);
let prefix_ptr: *const u8 = dict;
let prefix_idx: u32 = cctx.dict_limit;
if size <= LZ4MID_HASHSIZE {
return;
}
let target: u32 = prefix_idx + size as u32 - LZ4MID_HASHSIZE as u32;
let mut idx: u32 = cctx.next_to_update;
while idx < target {
add_position(
hash4_table,
mid_hash4_ptr(prefix_ptr.add((idx - prefix_idx) as usize)),
idx,
);
add_position(
hash8_table,
mid_hash8_ptr(prefix_ptr.add((idx + 1 - prefix_idx) as usize)),
idx + 1,
);
idx = idx.wrapping_add(3);
}
idx = if size > 32 * bt::KB + LZ4MID_HASHSIZE {
target.wrapping_sub(32 * bt::KB as u32)
} else {
cctx.next_to_update
};
while idx < target {
add_position(
hash8_table,
mid_hash8_ptr(prefix_ptr.add((idx - prefix_idx) as usize)),
idx,
);
idx = idx.wrapping_add(1);
}
cctx.next_to_update = target;
}
pub unsafe fn select_dict_search_mode(dict_ctx: *const HcCCtxInternal) -> Option<DictSearchMode> {
if dict_ctx.is_null() {
return None;
}
let ctx = &*dict_ctx;
if get_clevel_params(ctx.compression_level as i32).strat == HcStrategy::Lz4Mid {
Some(DictSearchMode::Ext)
} else {
Some(DictSearchMode::Hc)
}
}
#[inline(always)]
unsafe fn dispatch_dict_search(
mode: DictSearchMode,
ip: *const u8,
ip_index: u32,
i_high_limit: *const u8,
dict_ctx: *const HcCCtxInternal,
g_dict_end_index: u32,
) -> Match {
match mode {
DictSearchMode::Hc => {
mid_search_hc_dict(ip, ip_index, i_high_limit, dict_ctx, g_dict_end_index)
}
DictSearchMode::Ext => {
mid_search_ext_dict(ip, ip_index, i_high_limit, dict_ctx, g_dict_end_index)
}
}
}
pub unsafe fn lz4mid_compress(
ctx: &mut HcCCtxInternal,
src: *const u8,
dst: *mut u8,
src_size_ptr: &mut i32,
max_output_size: i32,
limit: LimitedOutputDirective,
dict: DictCtxDirective,
) -> i32 {
let hash4_table: *mut u32 = ctx.hash_table.as_mut_ptr();
let hash8_table: *mut u32 = hash4_table.add(LZ4MID_HASHTABLESIZE);
let mut ip: *const u8 = src;
let mut anchor: *const u8 = ip;
let iend: *const u8 = ip.add(*src_size_ptr as usize);
let mflimit: *const u8 = iend.sub(MFLIMIT);
let matchlimit: *const u8 = iend.sub(LASTLITERALS);
let ilimit: *const u8 = iend.sub(LZ4MID_HASHSIZE);
let mut op: *mut u8 = dst;
let mut oend: *mut u8 = op.add(max_output_size as usize);
let prefix_ptr: *const u8 = ctx.prefix_start;
let prefix_idx: u32 = ctx.dict_limit;
let ilimit_idx: u32 =
((ilimit as usize).wrapping_sub(prefix_ptr as usize) as u32).wrapping_add(prefix_idx);
let dict_start: *const u8 = ctx.dict_start;
let dict_idx: u32 = ctx.low_limit;
let g_dict_end_index: u32 = ctx.low_limit;
let dict_search_mode: Option<DictSearchMode> = if dict == DictCtxDirective::UsingDictCtxHc {
select_dict_search_mode(ctx.dict_ctx)
} else {
None
};
let mut match_length: u32 = 0;
let mut match_distance: u32 = 0;
debug_assert!(*src_size_ptr >= 0);
if *src_size_ptr > 0 {
debug_assert!(!src.is_null());
}
if max_output_size > 0 {
debug_assert!(!dst.is_null());
}
if *src_size_ptr < 0 || max_output_size < 0 {
return 0;
}
if (*src_size_ptr as u32) > 0x7E00_0000u32 {
return 0;
}
if limit == LimitedOutputDirective::FillOutput {
oend = oend.sub(LASTLITERALS);
}
let do_compress = *src_size_ptr >= bt::LZ4_MIN_LENGTH as i32;
let mut overflow_info: Option<(u32, u32)> = None;
if do_compress {
#[allow(clippy::never_loop)]
'compress: loop {
while ip <= mflimit {
let ip_index_start: u32 = ((ip as usize).wrapping_sub(prefix_ptr as usize) as u32)
.wrapping_add(prefix_idx);
let found: Option<(u32, u32)> = 'find: {
{
let h8 = mid_hash8_ptr(ip);
let pos8 = *hash8_table.add(h8 as usize);
debug_assert!((h8 as usize) < LZ4MID_HASHTABLESIZE);
debug_assert!(pos8 < ip_index_start);
add_position(hash8_table, h8, ip_index_start);
if ip_index_start.wrapping_sub(pos8) <= LZ4_DISTANCE_MAX {
if pos8 >= prefix_idx {
let match_ptr = prefix_ptr.add((pos8 - prefix_idx) as usize);
debug_assert!(match_ptr < ip);
let mlt = bt::count(ip, match_ptr, matchlimit);
if mlt >= MINMATCH as u32 {
break 'find Some((mlt, ip_index_start - pos8));
}
} else if pos8 >= dict_idx {
let match_ptr = dict_start.add((pos8 - dict_idx) as usize);
let safe_len = ((prefix_idx - pos8) as usize)
.min((matchlimit as usize).wrapping_sub(ip as usize));
let mlt = bt::count(ip, match_ptr, ip.add(safe_len));
if mlt >= MINMATCH as u32 {
break 'find Some((mlt, ip_index_start - pos8));
}
}
}
}
{
let h4 = mid_hash4_ptr(ip);
let pos4 = *hash4_table.add(h4 as usize);
debug_assert!((h4 as usize) < LZ4MID_HASHTABLESIZE);
debug_assert!(pos4 < ip_index_start);
add_position(hash4_table, h4, ip_index_start);
if ip_index_start.wrapping_sub(pos4) <= LZ4_DISTANCE_MAX {
if pos4 >= prefix_idx {
let match_ptr = prefix_ptr.add((pos4 - prefix_idx) as usize);
debug_assert!(match_ptr < ip);
debug_assert!(match_ptr >= prefix_ptr);
let mlt = bt::count(ip, match_ptr, matchlimit);
if mlt >= MINMATCH as u32 {
let h8_next = mid_hash8_ptr(ip.add(1));
let pos8_next = *hash8_table.add(h8_next as usize);
let m2_distance = ip_index_start + 1 - pos8_next;
let mut best_mlt = mlt;
let mut best_dist = ip_index_start - pos4;
if m2_distance <= LZ4_DISTANCE_MAX
&& pos8_next >= prefix_idx
&& ip < mflimit
{
let m2_ptr =
prefix_ptr.add((pos8_next - prefix_idx) as usize);
let ml2 = bt::count(ip.add(1), m2_ptr, matchlimit);
if ml2 > best_mlt {
add_position(hash8_table, h8_next, ip_index_start + 1);
ip = ip.add(1); best_mlt = ml2;
best_dist = m2_distance;
}
}
break 'find Some((best_mlt, best_dist));
}
} else if pos4 >= dict_idx {
let match_ptr = dict_start.add((pos4 - dict_idx) as usize);
let safe_len = ((prefix_idx - pos4) as usize)
.min((matchlimit as usize).wrapping_sub(ip as usize));
let mlt = bt::count(ip, match_ptr, ip.add(safe_len));
if mlt >= MINMATCH as u32 {
break 'find Some((mlt, ip_index_start - pos4));
}
}
}
}
if dict == DictCtxDirective::UsingDictCtxHc {
if ip_index_start.wrapping_sub(g_dict_end_index) < LZ4_DISTANCE_MAX - 8 {
if let Some(mode) = dict_search_mode {
let d_match = dispatch_dict_search(
mode,
ip,
ip_index_start,
matchlimit,
ctx.dict_ctx,
g_dict_end_index,
);
if d_match.len >= MINMATCH as i32 {
debug_assert_eq!(d_match.back, 0);
break 'find Some((d_match.len as u32, d_match.off as u32));
}
}
}
}
None
};
if let Some((ml, md)) = found {
match_length = ml;
match_distance = md;
while ((ip > anchor) as u8)
& (((ip as usize).wrapping_sub(prefix_ptr as usize) as u32 > match_distance)
as u8)
!= 0
&& *ip.sub(1) == *ip.sub(match_distance as usize + 1)
{
ip = ip.sub(1);
match_length += 1;
}
add_position(hash8_table, mid_hash8_ptr(ip.add(1)), ip_index_start + 1);
add_position(hash8_table, mid_hash8_ptr(ip.add(2)), ip_index_start + 2);
add_position(hash4_table, mid_hash4_ptr(ip.add(1)), ip_index_start + 1);
let saved_op = op;
match encode_sequence(
&mut ip,
&mut op,
&mut anchor,
match_length as i32,
match_distance as i32,
limit,
oend,
) {
Ok(()) => {}
Err(Lz4HcError::OutputTooSmall) => {
op = saved_op;
overflow_info = Some((match_length, match_distance));
break 'compress;
}
}
let end_match_idx: u32 = ((ip as usize).wrapping_sub(prefix_ptr as usize)
as u32)
.wrapping_add(prefix_idx);
let pos_m2 = end_match_idx.wrapping_sub(2);
if pos_m2 < ilimit_idx {
if (ip as usize).wrapping_sub(prefix_ptr as usize) > 5 {
add_position(hash8_table, mid_hash8_ptr(ip.sub(5)), end_match_idx - 5);
}
add_position(hash8_table, mid_hash8_ptr(ip.sub(3)), end_match_idx - 3);
add_position(hash8_table, mid_hash8_ptr(ip.sub(2)), end_match_idx - 2);
add_position(hash4_table, mid_hash4_ptr(ip.sub(2)), end_match_idx - 2);
add_position(hash4_table, mid_hash4_ptr(ip.sub(1)), end_match_idx - 1);
}
} else {
let skip = 1 + ((ip as usize).wrapping_sub(anchor as usize) >> 9);
ip = ip.add(skip);
}
} break 'compress;
} }
if let Some((ml, md)) = overflow_info {
if limit != LimitedOutputDirective::FillOutput {
return 0;
}
let ll = (ip as usize).wrapping_sub(anchor as usize); let ll_addbytes = (ll + 240) / 255;
let ll_total_cost = 1 + ll_addbytes + ll;
let max_lit_pos = oend.sub(3);
if op.add(ll_total_cost) <= max_lit_pos {
let bytes_left_for_ml =
(max_lit_pos as usize).wrapping_sub(op.add(ll_total_cost) as usize);
let max_ml_size = MINMATCH + (ML_MASK as usize - 1) + bytes_left_for_ml * 255;
debug_assert!(max_ml_size < i32::MAX as usize);
let adj_ml = if ml as usize > max_ml_size {
max_ml_size as u32
} else {
ml
};
let check_val =
(oend.add(LASTLITERALS) as isize) - (op.add(ll_total_cost + 2) as isize) - 1
+ adj_ml as isize;
if check_val >= MFLIMIT as isize {
let _ = encode_sequence(
&mut ip,
&mut op,
&mut anchor,
adj_ml as i32,
md as i32,
LimitedOutputDirective::NotLimited,
oend,
);
}
}
}
{
let last_run_size: usize = (iend as usize).wrapping_sub(anchor as usize);
let ll_add: usize = (last_run_size + 255 - RUN_MASK as usize) / 255;
let total_size: usize = 1 + ll_add + last_run_size;
if limit == LimitedOutputDirective::FillOutput {
oend = oend.add(LASTLITERALS);
}
let (last_run_size, _ll_add) =
if limit != LimitedOutputDirective::NotLimited && op.add(total_size) > oend {
if limit == LimitedOutputDirective::LimitedOutput {
return 0;
}
let lrs = (oend as usize).wrapping_sub(op as usize).saturating_sub(1);
let la = (lrs + 256 - RUN_MASK as usize) / 256;
(lrs - la, la)
} else {
(last_run_size, ll_add)
};
ip = anchor.add(last_run_size);
if last_run_size >= RUN_MASK as usize {
let mut accumulator = last_run_size - RUN_MASK as usize;
*op = (RUN_MASK << ML_BITS) as u8;
op = op.add(1);
while accumulator >= 255 {
*op = 255;
op = op.add(1);
accumulator -= 255;
}
*op = accumulator as u8;
op = op.add(1);
} else {
*op = (last_run_size << ML_BITS as usize) as u8;
op = op.add(1);
}
debug_assert!(last_run_size <= (oend as usize).wrapping_sub(op as usize));
core::ptr::copy_nonoverlapping(anchor, op, last_run_size);
op = op.add(last_run_size);
}
debug_assert!(ip >= src);
debug_assert!(ip <= iend);
*src_size_ptr = (ip as usize).wrapping_sub(src as usize) as i32;
debug_assert!(op >= dst);
debug_assert!(op <= oend);
debug_assert!((op as usize).wrapping_sub(dst as usize) < i32::MAX as usize);
(op as usize).wrapping_sub(dst as usize) as i32
}