use core::cell::Cell;
use fearless_simd::Simd;
use fearless_simd_macros::simd;
use super::match_state::{BlockInput, MatchState};
use super::mem::{
common_suffix, count_at_simd, count_slices_simd, hash_at, hash_ptr, hash_word, load32, load64,
probe_pair, probe_window, window_word,
};
use super::seq_store::{SeqStore, offset_to_offbase, repcode_to_offbase};
use super::window::HASH_READ_SIZE;
const SEARCH_STRENGTH: u32 = 8;
pub(crate) fn fill_hash_table(ms: &mut MatchState, prefix: &super::mem::Mem<'_>, end: u32) {
let h_bits = ms.cparams.hash_log;
let mls = ms.cparams.min_match;
let iend = i64::from(end) - i64::from(HASH_READ_SIZE);
let mut ip = i64::from(ms.next_to_update);
while ip + 3 < iend + 2 {
let curr = ip as u32;
ms.hash_table[hash_ptr(prefix, curr, h_bits, mls)] = curr;
ip += 3;
}
}
pub(crate) fn compress_block(
ms: &mut MatchState,
seq_store: &mut SeqStore,
rep: &mut [u32; 3],
block: &BlockInput<'_>,
) -> usize {
fearless_simd::dispatch!(ms.backend.0, simd => match ms.cparams.min_match {
5 => compress_block_mls::<_, 5>(simd, ms, seq_store, rep, block),
6 => compress_block_mls::<_, 6>(simd, ms, seq_store, rep, block),
7.. => compress_block_mls::<_, 7>(simd, ms, seq_store, rep, block),
_ => compress_block_mls::<_, 4>(simd, ms, seq_store, rep, block),
})
}
#[simd]
fn compress_block_mls<S: Simd, const MLS: u32>(
simd: S,
ms: &mut MatchState,
seq_store: &mut SeqStore,
rep: &mut [u32; 3],
block: &BlockInput<'_>,
) -> usize {
let cparams = ms.cparams;
let prefix = block.window.prefix;
let base = prefix.start();
let prefix_start_index = ms.lowest_prefix_index(block.end, cparams.window_log);
let mut rep_offset1 = rep[0];
let mut rep_offset2 = rep[1];
let mut offset_saved1 = 0u32;
let mut offset_saved2 = 0u32;
let mut ip0 = block.start;
ip0 += u32::from(ip0 == prefix_start_index);
{
let window_low = ms.lowest_prefix_index(ip0, cparams.window_log);
let max_rep = ip0 - window_low;
if rep_offset2 > max_rep {
offset_saved2 = rep_offset2;
rep_offset2 = 0;
}
if rep_offset1 > max_rep {
offset_saved1 = rep_offset1;
rep_offset1 = 0;
}
}
let table = ms.hash_table.as_mut_slice();
let scan = |origin: u32| Scan {
src: prefix.slice(origin, block.end),
origin,
prefix_start: (prefix_start_index - origin) as usize,
start: (block.start - origin) as usize,
hlog: cparams.hash_log,
step_size: cparams.target_length as usize + usize::from(cparams.target_length == 0) + 1,
};
let in_prefix = block.start >= prefix_start_index
&& rep_offset1.max(rep_offset2) <= ip0 - prefix_start_index;
let (literals, [offset_1, offset_2]) = if in_prefix {
let scan = scan(prefix_start_index);
let ip0 = (ip0 - prefix_start_index) as usize;
compress_scan::<S, MLS, true>(
simd,
table,
seq_store,
&scan,
ip0,
[rep_offset1, rep_offset2],
)
} else {
let scan = scan(base);
let ip0 = (ip0 - base) as usize;
compress_scan::<S, MLS, false>(
simd,
table,
seq_store,
&scan,
ip0,
[rep_offset1, rep_offset2],
)
};
rep_offset1 = offset_1;
rep_offset2 = offset_2;
offset_saved2 = if offset_saved1 != 0 && rep_offset1 != 0 {
offset_saved1
} else {
offset_saved2
};
rep[0] = if rep_offset1 != 0 {
rep_offset1
} else {
offset_saved1
};
rep[1] = if rep_offset2 != 0 {
rep_offset2
} else {
offset_saved2
};
literals
}
struct Scan<'a> {
src: &'a [u8],
origin: u32,
prefix_start: usize,
start: usize,
hlog: u32,
step_size: usize,
}
#[inline(always)]
fn compress_scan<S: Simd, const MLS: u32, const IN_PREFIX: bool>(
simd: S,
table: &mut [u32],
seq_store: &mut SeqStore,
scan: &Scan<'_>,
mut ip0: usize,
reps: [u32; 2],
) -> (usize, [u32; 2]) {
let Scan {
src,
origin: base,
hlog,
step_size,
..
} = *scan;
let prefix_start = if IN_PREFIX { 0 } else { scan.prefix_start };
let prefix_start_index = base + prefix_start as u32;
let history = &src[prefix_start..];
let istart = scan.start;
let iend = src.len();
let ilimit = iend.saturating_sub(HASH_READ_SIZE as usize);
let index = |p: usize| p as u32 + base;
let read32 = |p: usize| load32(src, p);
let k_step_incr: usize = 1 << (SEARCH_STRENGTH - 1);
let mut anchor = istart;
let [mut rep_offset1, mut rep_offset2] = reps;
assert!(!table.is_empty());
let mask = table.len() - 1;
let table = Cell::from_mut(table).as_slice_of_cells();
let hash = |p: usize| hash_at::<MLS>(src, p, hlog) & mask;
'start: loop {
let mut step = step_size;
let mut next_step = ip0 + k_step_incr;
let mut ip2 = ip0 + step;
let Some((mut w0, mut w2)) = probe_pair(src, ip0, step) else {
break 'start;
};
let slot = |word: u64| &table[hash_word::<MLS>(word, hlog) & mask];
let mut slot0 = slot(window_word::<0>(w0));
let mut slot1 = slot(window_word::<1>(w0));
let mut match_idx = slot0.get();
let mut current0;
enum Found {
Rep,
Offset,
}
let found = loop {
let rval = read32(ip2 - rep_offset1 as usize);
current0 = index(ip0);
slot0.set(current0);
if window_word::<0>(w2) as u32 == rval && rep_offset1 > 0 {
slot1.set(current0 + 1);
ip0 = ip2;
break Found::Rep;
}
if super::mem::matches32(
history,
match_idx.wrapping_sub(prefix_start_index),
window_word::<0>(w0) as u32,
) {
slot1.set(current0 + 1);
break Found::Offset;
}
match_idx = slot1.get();
slot0 = slot1;
slot1 = slot(window_word::<0>(w2));
ip0 += 1;
current0 = index(ip0);
slot0.set(current0);
if super::mem::matches32(
history,
match_idx.wrapping_sub(prefix_start_index),
window_word::<1>(w0) as u32,
) {
if step <= 4 {
slot1.set(index(ip2));
}
break Found::Offset;
}
match_idx = slot1.get();
slot0 = slot1;
slot1 = slot(window_word::<1>(w2));
ip0 = ip2;
w0 = w2;
ip2 = ip0 + step;
if ip2 >= next_step {
step += 1;
next_step += k_step_incr;
}
match probe_window(src, ip2) {
Some(window) => w2 = window,
None => break 'start,
}
};
let (offcode, mut m_length, match0);
match found {
Found::Rep => {
let mut m0 = ip0 - rep_offset1 as usize;
let back = usize::from(src[ip0 - 1] == src[m0 - 1]);
ip0 -= back;
m0 -= back;
match0 = m0;
offcode = repcode_to_offbase(1);
m_length = back + 4;
}
Found::Offset => {
let mut m0 = (match_idx - base) as usize;
rep_offset2 = rep_offset1;
rep_offset1 = (ip0 - m0) as u32;
offcode = offset_to_offbase(rep_offset1);
let back = common_suffix(&src[anchor..ip0], &src[prefix_start..m0]);
ip0 -= back;
m0 -= back;
m_length = back + 4;
match0 = m0;
}
}
m_length += count_at_simd(simd, src, ip0 + m_length, match0 + m_length);
seq_store.store_seq_from(src, anchor, ip0 - anchor, offcode, m_length);
ip0 += m_length;
anchor = ip0;
if ip0 <= ilimit {
let current2 = (current0 - base) as usize + 2;
table[hash(current2)].set(current0 + 2);
let back2 = load64(src, ip0 - 2);
table[hash_word::<MLS>(back2, hlog) & mask].set(index(ip0 - 2));
let mut word = (back2 >> 16) as u32;
if rep_offset2 > 0 {
while word == read32(ip0 - rep_offset2 as usize) {
let r_length =
count_at_simd(simd, src, ip0 + 4, ip0 + 4 - rep_offset2 as usize) + 4;
core::mem::swap(&mut rep_offset1, &mut rep_offset2);
table[hash(ip0)].set(index(ip0));
ip0 += r_length;
seq_store.store_seq_only(0, repcode_to_offbase(1), r_length);
anchor = ip0;
if ip0 > ilimit {
break;
}
word = read32(ip0);
}
}
}
}
(iend - anchor, [rep_offset1, rep_offset2])
}
pub(crate) fn compress_block_ext_dict(
ms: &mut MatchState,
seq_store: &mut SeqStore,
rep: &mut [u32; 3],
block: &BlockInput<'_>,
) -> usize {
fearless_simd::dispatch!(ms.backend.0, simd => match ms.cparams.min_match {
5 => compress_block_ext_dict_mls::<_, 5>(simd, ms, seq_store, rep, block),
6 => compress_block_ext_dict_mls::<_, 6>(simd, ms, seq_store, rep, block),
7.. => compress_block_ext_dict_mls::<_, 7>(simd, ms, seq_store, rep, block),
_ => compress_block_ext_dict_mls::<_, 4>(simd, ms, seq_store, rep, block),
})
}
#[simd]
fn compress_block_ext_dict_mls<S: Simd, const MLS: u32>(
simd: S,
ms: &mut MatchState,
seq_store: &mut SeqStore,
rep: &mut [u32; 3],
block: &BlockInput<'_>,
) -> usize {
let cparams = ms.cparams;
let hlog = cparams.hash_log;
let step_size = cparams.target_length as usize + usize::from(cparams.target_length == 0) + 1;
let window = block.window;
let prefix = window.prefix;
let ext = window.ext;
let end_index = block.end;
let low_limit = ms.lowest_match_index(end_index, cparams.window_log);
let dict_start_index = low_limit;
let dict_limit = ms.window.dict_limit;
let prefix_start_index = dict_limit.max(low_limit);
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let mut offset_saved1 = 0u32;
let mut offset_saved2 = 0u32;
if prefix_start_index == dict_start_index {
return compress_block(ms, seq_store, rep, block);
}
let base = prefix.start();
let src = prefix.slice(base, block.end);
let istart = (block.start - base) as usize;
let iend = src.len();
let ilimit = iend.saturating_sub(8);
let index = |p: usize| p as u32 + base;
assert!(!ms.hash_table.is_empty());
let mask = ms.hash_table.len() - 1;
let hash = |p: usize| hash_at::<MLS>(src, p, hlog) & mask;
let read32 = |p: usize| load32(src, p);
let read_any = |idx: u32| {
if idx < prefix_start_index {
ext.read32(idx)
} else {
load32(src, (idx - base) as usize)
}
};
let byte_any = |idx: u32| {
if idx < prefix_start_index {
ext.byte(idx)
} else {
src[(idx - base) as usize]
}
};
let count_any = |ip: usize, m: u32, start: u32| {
if start < prefix_start_index {
window.count_2segments(
index(ip),
m,
end_index,
prefix_start_index,
prefix_start_index,
)
} else {
count_slices_simd(simd, &src[ip..], &src[(m - base) as usize..])
}
};
let k_step_incr: usize = 1 << (SEARCH_STRENGTH - 1);
let mut ip0 = istart;
let mut anchor = istart;
{
let curr = index(ip0);
let max_rep = curr - dict_start_index;
if offset_2 >= max_rep {
offset_saved2 = offset_2;
offset_2 = 0;
}
if offset_1 >= max_rep {
offset_saved1 = offset_1;
offset_1 = 0;
}
}
let table = ms.hash_table.as_mut_slice();
'start: loop {
let mut step = step_size;
let mut next_step = ip0 + k_step_incr;
let mut ip1 = ip0 + 1;
let mut ip2 = ip0 + step;
let mut ip3 = ip2 + 1;
if ip3 >= ilimit {
break 'start;
}
let mut hash0 = hash(ip0);
let mut hash1 = hash(ip1);
let mut idx = table[hash0 & mask];
let mut current0;
enum Found {
Rep(u32),
Offset,
}
let found = loop {
{
let rep_index = index(ip2).wrapping_sub(offset_1);
let rval = if prefix_start_index.wrapping_sub(rep_index) >= 4 && offset_1 > 0 {
read_any(rep_index)
} else {
read32(ip2) ^ 1
};
current0 = index(ip0);
table[hash0 & mask] = current0;
if read32(ip2) == rval {
ip0 = ip2;
break Found::Rep(rep_index);
}
}
{
let mval = if idx >= dict_start_index {
read_any(idx)
} else {
read32(ip0) ^ 1
};
if read32(ip0) == mval {
break Found::Offset;
}
}
idx = table[hash1 & mask];
hash0 = hash1;
hash1 = hash(ip2);
ip0 = ip1;
ip1 = ip2;
ip2 = ip3;
current0 = index(ip0);
table[hash0 & mask] = current0;
{
let mval = if idx >= dict_start_index {
read_any(idx)
} else {
read32(ip0) ^ 1
};
if read32(ip0) == mval {
break Found::Offset;
}
}
idx = table[hash1 & mask];
hash0 = hash1;
hash1 = hash(ip2);
ip0 = ip1;
ip1 = ip2;
ip2 = ip0 + step;
ip3 = ip1 + step;
if ip2 >= next_step {
step += 1;
next_step += k_step_incr;
}
if ip3 >= ilimit {
break 'start;
}
};
let (offcode, mut m_length, mut match0): (u32, usize, u32);
match found {
Found::Rep(rep_index) => {
let back = usize::from(src[ip0 - 1] == byte_any(rep_index - 1));
ip0 -= back;
match0 = rep_index - back as u32;
offcode = repcode_to_offbase(1);
m_length = back + 4;
}
Found::Offset => {
let offset = current0 - idx;
let low_match = if idx < prefix_start_index {
dict_start_index
} else {
prefix_start_index
};
match0 = idx;
offset_2 = offset_1;
offset_1 = offset;
offcode = offset_to_offbase(offset);
m_length = 4;
while ip0 > anchor && match0 > low_match && src[ip0 - 1] == byte_any(match0 - 1) {
ip0 -= 1;
match0 -= 1;
m_length += 1;
}
}
}
m_length += count_any(ip0 + m_length, match0 + m_length as u32, match0);
seq_store.store_seq_from(src, anchor, ip0 - anchor, offcode, m_length);
ip0 += m_length;
anchor = ip0;
if ip1 < ip0 {
table[hash1 & mask] = index(ip1);
}
if ip0 <= ilimit {
table[hash((current0 - base) as usize + 2)] = current0 + 2;
table[hash(ip0 - 2)] = index(ip0 - 2);
while ip0 <= ilimit {
let rep_index2 = index(ip0).wrapping_sub(offset_2);
let overlap_ok = prefix_start_index.wrapping_sub(1).wrapping_sub(rep_index2) >= 3;
if overlap_ok && offset_2 > 0 && read_any(rep_index2) == read32(ip0) {
let rep_length2 = count_any(ip0 + 4, rep_index2 + 4, rep_index2) + 4;
core::mem::swap(&mut offset_1, &mut offset_2);
seq_store.store_seq_only(0, repcode_to_offbase(1), rep_length2);
table[hash(ip0)] = index(ip0);
ip0 += rep_length2;
anchor = ip0;
continue;
}
break;
}
}
}
offset_saved2 = if offset_saved1 != 0 && offset_1 != 0 {
offset_saved1
} else {
offset_saved2
};
rep[0] = if offset_1 != 0 {
offset_1
} else {
offset_saved1
};
rep[1] = if offset_2 != 0 {
offset_2
} else {
offset_saved2
};
iend - anchor
}