use crate::error::Error;
use crate::pre_split;
use crate::sequences_encode::{self, FseEntropyState, SeqStore};
const WINDOW_START_INDEX: usize = 2;
const K_SEARCH_STRENGTH: u32 = 8;
const HASH_READ_SIZE: usize = 8;
const BLOCK_SIZE_MAX: usize = 128 * 1024;
const MIN_CBLOCK_SIZE: usize = 2;
const BLOCK_HEADER_SIZE: usize = 3;
const WINDOWLOG_ABSOLUTEMIN: u32 = 10;
const HASHLOG_MIN: u32 = 6;
const SHORT_CACHE_TAG_BITS: u32 = 8;
const WINDOWLOG_MAX: u32 = 31;
const CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_MAGIC: u32 = 0xFD2F_B528;
const MAGIC_DICTIONARY: u32 = 0xEC30_A437;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)]
pub(crate) enum Strategy {
Fast = 1,
Dfast = 2,
Greedy = 3,
Lazy = 4,
Lazy2 = 5,
Btlazy2 = 6,
Btopt = 7,
Btultra = 8,
Btultra2 = 9,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct CParams {
pub window_log: u32,
pub chain_log: u32,
pub hash_log: u32,
#[allow(dead_code)]
pub search_log: u32,
pub min_match: u32,
pub target_length: u32,
pub strategy: Strategy,
}
type CParamsRow = (u32, u32, u32, u32, u32, u32, Strategy);
#[rustfmt::skip]
const DEFAULT_CPARAMETERS: [[CParamsRow; 23]; 4] = {
use Strategy::*;
[
[
(19, 12, 13, 1, 6, 1, Fast),
(19, 13, 14, 1, 7, 0, Fast),
(20, 15, 16, 1, 6, 0, Fast),
(21, 16, 17, 1, 5, 0, Dfast),
(21, 18, 18, 1, 5, 0, Dfast),
(21, 18, 19, 3, 5, 2, Greedy),
(21, 18, 19, 3, 5, 4, Lazy),
(21, 19, 20, 4, 5, 8, Lazy),
(21, 19, 20, 4, 5, 16, Lazy2),
(22, 20, 21, 4, 5, 16, Lazy2),
(22, 21, 22, 5, 5, 16, Lazy2),
(22, 21, 22, 6, 5, 16, Lazy2),
(22, 22, 23, 6, 5, 32, Lazy2),
(22, 22, 22, 4, 5, 32, Btlazy2),
(22, 22, 23, 5, 5, 32, Btlazy2),
(22, 23, 23, 6, 5, 32, Btlazy2),
(22, 22, 22, 5, 5, 48, Btopt),
(23, 23, 22, 5, 4, 64, Btopt),
(23, 23, 22, 6, 3, 64, Btultra),
(23, 24, 22, 7, 3, 256, Btultra2),
(25, 25, 23, 7, 3, 256, Btultra2),
(26, 26, 24, 7, 3, 512, Btultra2),
(27, 27, 25, 9, 3, 999, Btultra2),
],
[
(18, 12, 13, 1, 5, 1, Fast),
(18, 13, 14, 1, 6, 0, Fast),
(18, 14, 14, 1, 5, 0, Dfast),
(18, 16, 16, 1, 4, 0, Dfast),
(18, 16, 17, 3, 5, 2, Greedy),
(18, 17, 18, 5, 5, 2, Greedy),
(18, 18, 19, 3, 5, 4, Lazy),
(18, 18, 19, 4, 4, 4, Lazy),
(18, 18, 19, 4, 4, 8, Lazy2),
(18, 18, 19, 5, 4, 8, Lazy2),
(18, 18, 19, 6, 4, 8, Lazy2),
(18, 18, 19, 5, 4, 12, Btlazy2),
(18, 19, 19, 7, 4, 12, Btlazy2),
(18, 18, 19, 4, 4, 16, Btopt),
(18, 18, 19, 4, 3, 32, Btopt),
(18, 18, 19, 6, 3, 128, Btopt),
(18, 19, 19, 6, 3, 128, Btultra),
(18, 19, 19, 8, 3, 256, Btultra),
(18, 19, 19, 6, 3, 128, Btultra2),
(18, 19, 19, 8, 3, 256, Btultra2),
(18, 19, 19, 10, 3, 512, Btultra2),
(18, 19, 19, 12, 3, 512, Btultra2),
(18, 19, 19, 13, 3, 999, Btultra2),
],
[
(17, 12, 12, 1, 5, 1, Fast),
(17, 12, 13, 1, 6, 0, Fast),
(17, 13, 15, 1, 5, 0, Fast),
(17, 15, 16, 2, 5, 0, Dfast),
(17, 17, 17, 2, 4, 0, Dfast),
(17, 16, 17, 3, 4, 2, Greedy),
(17, 16, 17, 3, 4, 4, Lazy),
(17, 16, 17, 3, 4, 8, Lazy2),
(17, 16, 17, 4, 4, 8, Lazy2),
(17, 16, 17, 5, 4, 8, Lazy2),
(17, 16, 17, 6, 4, 8, Lazy2),
(17, 17, 17, 5, 4, 8, Btlazy2),
(17, 18, 17, 7, 4, 12, Btlazy2),
(17, 18, 17, 3, 4, 12, Btopt),
(17, 18, 17, 4, 3, 32, Btopt),
(17, 18, 17, 6, 3, 256, Btopt),
(17, 18, 17, 6, 3, 128, Btultra),
(17, 18, 17, 8, 3, 256, Btultra),
(17, 18, 17, 10, 3, 512, Btultra),
(17, 18, 17, 5, 3, 256, Btultra2),
(17, 18, 17, 7, 3, 512, Btultra2),
(17, 18, 17, 9, 3, 512, Btultra2),
(17, 18, 17, 11, 3, 999, Btultra2),
],
[
(14, 12, 13, 1, 5, 1, Fast),
(14, 14, 15, 1, 5, 0, Fast),
(14, 14, 15, 1, 4, 0, Fast),
(14, 14, 15, 2, 4, 0, Dfast),
(14, 14, 14, 4, 4, 2, Greedy),
(14, 14, 14, 3, 4, 4, Lazy),
(14, 14, 14, 4, 4, 8, Lazy2),
(14, 14, 14, 6, 4, 8, Lazy2),
(14, 14, 14, 8, 4, 8, Lazy2),
(14, 15, 14, 5, 4, 8, Btlazy2),
(14, 15, 14, 9, 4, 8, Btlazy2),
(14, 15, 14, 3, 4, 12, Btopt),
(14, 15, 14, 4, 3, 24, Btopt),
(14, 15, 14, 5, 3, 32, Btultra),
(14, 15, 15, 6, 3, 64, Btultra),
(14, 15, 15, 7, 3, 256, Btultra),
(14, 15, 15, 5, 3, 48, Btultra2),
(14, 15, 15, 6, 3, 128, Btultra2),
(14, 15, 15, 7, 3, 256, Btultra2),
(14, 15, 15, 8, 3, 256, Btultra2),
(14, 15, 15, 8, 3, 512, Btultra2),
(14, 15, 15, 9, 3, 512, Btultra2),
(14, 15, 15, 10, 3, 999, Btultra2),
],
]
};
const ZSTD_MAX_CLEVEL: i32 = 22;
const ZSTD_CLEVEL_DEFAULT: i32 = 3;
const ZSTD_MIN_CLEVEL: i32 = -(1 << 17);
fn highbit32(x: u32) -> u32 {
debug_assert!(x >= 1);
31 - x.leading_zeros()
}
fn cycle_log(hash_log: u32, strat: Strategy) -> u32 {
let bt_scale = (strat as i32 >= Strategy::Btlazy2 as i32) as u32;
hash_log - bt_scale
}
fn dict_and_window_log(window_log: u32, src_size: u64, dict_size: u64) -> u32 {
if dict_size == 0 {
return window_log;
}
let max_window_size = 1u64 << WINDOWLOG_MAX;
let window_size = 1u64 << window_log;
let dict_and_window_size = dict_size.wrapping_add(window_size);
if window_size >= dict_size.wrapping_add(src_size) {
window_log
} else if dict_and_window_size >= max_window_size {
WINDOWLOG_MAX
} else {
highbit32((dict_and_window_size as u32).wrapping_sub(1)) + 1
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum CParamMode {
NoAttachDict,
CreateCDict,
}
pub(crate) fn get_cparams(level: i32, src_size: u64, dict_size: u64) -> CParams {
get_cparams_mode(level, src_size, dict_size, CParamMode::NoAttachDict)
}
pub(crate) fn get_cparams_create_cdict(level: i32, dict_size: u64) -> CParams {
get_cparams_mode(
level,
CONTENTSIZE_UNKNOWN,
dict_size,
CParamMode::CreateCDict,
)
}
fn get_cparams_mode(level: i32, src_size: u64, dict_size: u64, mode: CParamMode) -> CParams {
let unknown = src_size == CONTENTSIZE_UNKNOWN;
let r_size = if unknown && dict_size == 0 {
CONTENTSIZE_UNKNOWN
} else {
let added: u64 = if unknown && dict_size > 0 { 500 } else { 0 };
src_size.wrapping_add(dict_size).wrapping_add(added)
};
let table_id = (r_size <= 256 * 1024) as usize
+ (r_size <= 128 * 1024) as usize
+ (r_size <= 16 * 1024) as usize;
let row = if level == 0 {
ZSTD_CLEVEL_DEFAULT
} else {
level.clamp(0, ZSTD_MAX_CLEVEL)
} as usize;
let (w, c, h, s, l, t, strat) = DEFAULT_CPARAMETERS[table_id][row];
let mut cp = CParams {
window_log: w,
chain_log: c,
hash_log: h,
search_log: s,
min_match: l,
target_length: t,
strategy: strat,
};
if level < 0 {
cp.target_length = (-level.max(ZSTD_MIN_CLEVEL)) as u32;
}
adjust_cparams_internal(cp, src_size, dict_size, mode)
}
fn adjust_cparams_internal(
mut cp: CParams,
src_size: u64,
dict_size: u64,
mode: CParamMode,
) -> CParams {
let adj_src =
if mode == CParamMode::CreateCDict && dict_size != 0 && src_size == CONTENTSIZE_UNKNOWN {
513
} else {
src_size
};
let max_window_resize = 1u64 << (WINDOWLOG_MAX - 1);
if adj_src <= max_window_resize && dict_size <= max_window_resize {
let t_size = (adj_src + dict_size) as u32;
let hash_size_min = 1u32 << HASHLOG_MIN;
let src_log = if t_size < hash_size_min {
HASHLOG_MIN
} else {
highbit32(t_size - 1) + 1
};
if cp.window_log > src_log {
cp.window_log = src_log;
}
}
if adj_src != CONTENTSIZE_UNKNOWN {
let daw_log = dict_and_window_log(cp.window_log, adj_src, dict_size);
let cyc_log = cycle_log(cp.chain_log, cp.strategy);
if cp.hash_log > daw_log + 1 {
cp.hash_log = daw_log + 1;
}
if cyc_log > daw_log {
cp.chain_log -= cyc_log - daw_log;
}
}
if cp.window_log < WINDOWLOG_ABSOLUTEMIN {
cp.window_log = WINDOWLOG_ABSOLUTEMIN;
}
if mode == CParamMode::CreateCDict && matches!(cp.strategy, Strategy::Fast | Strategy::Dfast) {
let max_short_cache_hash_log = 32 - SHORT_CACHE_TAG_BITS;
cp.hash_log = cp.hash_log.min(max_short_cache_hash_log);
cp.chain_log = cp.chain_log.min(max_short_cache_hash_log);
}
if matches!(
cp.strategy,
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2
) {
let row_log = cp.search_log.clamp(4, 6);
let max_hash_log = (32 - 8) + row_log;
if cp.hash_log > max_hash_log {
cp.hash_log = max_hash_log;
}
}
cp
}
#[doc(hidden)]
pub fn cparams_for_testing(level: i32, src_size: u64, dict_size: u64) -> [u32; 7] {
let cp = get_cparams(level, src_size, dict_size);
[
cp.window_log,
cp.chain_log,
cp.hash_log,
cp.search_log,
cp.min_match,
cp.target_length,
cp.strategy as u32,
]
}
#[doc(hidden)]
pub fn cparams_create_cdict_for_testing(level: i32, dict_size: u64) -> [u32; 7] {
let cp = get_cparams_create_cdict(level, dict_size);
[
cp.window_log,
cp.chain_log,
cp.hash_log,
cp.search_log,
cp.min_match,
cp.target_length,
cp.strategy as u32,
]
}
#[inline]
pub(crate) fn read32(data: &[u8], at: usize) -> u32 {
u32::from_le_bytes(data[at..at + 4].try_into().unwrap())
}
#[inline]
pub(crate) fn read64(data: &[u8], at: usize) -> u64 {
u64::from_le_bytes(data[at..at + 8].try_into().unwrap())
}
#[inline]
pub(crate) fn count_eq(data: &[u8], mut a: usize, mut b: usize, limit: usize) -> usize {
let start = a;
while a + 8 <= limit {
let diff = read64(data, a) ^ read64(data, b);
if diff != 0 {
return a - start + (diff.trailing_zeros() / 8) as usize;
}
a += 8;
b += 8;
}
while a < limit && data[a] == data[b] {
a += 1;
b += 1;
}
a - start
}
#[inline]
pub(crate) fn hash_ptr(data: &[u8], at: usize, hlog: u32, mls: u32) -> usize {
const PRIME4: u32 = 2654435761;
const PRIME5: u64 = 889523592379;
const PRIME6: u64 = 227718039650203;
const PRIME7: u64 = 58295818150454627;
const PRIME8: u64 = 0xCF1B_BCDC_B7A5_6463;
match mls {
5 => ((read64(data, at) << (64 - 40)).wrapping_mul(PRIME5) >> (64 - hlog)) as usize,
6 => ((read64(data, at) << (64 - 48)).wrapping_mul(PRIME6) >> (64 - hlog)) as usize,
7 => ((read64(data, at) << (64 - 56)).wrapping_mul(PRIME7) >> (64 - hlog)) as usize,
8 => (read64(data, at).wrapping_mul(PRIME8) >> (64 - hlog)) as usize,
_ => (read32(data, at).wrapping_mul(PRIME4) >> (32 - hlog)) as usize,
}
}
pub(crate) fn index_overlap_check(prefix_lowest_index: u32, rep_index: u32) -> bool {
prefix_lowest_index.wrapping_sub(1).wrapping_sub(rep_index) >= 3
}
pub(crate) struct Window {
pub(crate) seg_bias: u32,
pub(crate) dict_bias: u32,
pub(crate) low_limit: u32,
pub(crate) dict_limit: u32,
next_src_pos: usize,
next_src_idx: u32,
pub(crate) loaded_dict_end: u32,
}
impl Window {
pub(crate) fn new() -> Self {
Window {
seg_bias: WINDOW_START_INDEX as u32,
dict_bias: WINDOW_START_INDEX as u32,
low_limit: WINDOW_START_INDEX as u32,
dict_limit: WINDOW_START_INDEX as u32,
next_src_pos: 0,
next_src_idx: WINDOW_START_INDEX as u32,
loaded_dict_end: 0,
}
}
pub(crate) fn preloaded_ext_dict(dict_len: usize, src_len: usize) -> Self {
let start = WINDOW_START_INDEX as u32;
Window {
seg_bias: start,
dict_bias: start,
low_limit: start,
dict_limit: start + dict_len as u32,
next_src_pos: dict_len + src_len,
next_src_idx: start + (dict_len + src_len) as u32,
loaded_dict_end: start + dict_len as u32,
}
}
pub(crate) fn preloaded_contiguous_prefix(total_len: usize) -> Self {
let start = WINDOW_START_INDEX as u32;
Window {
seg_bias: start,
dict_bias: start,
low_limit: start,
dict_limit: start,
next_src_pos: total_len,
next_src_idx: start + total_len as u32,
loaded_dict_end: 0,
}
}
pub(crate) fn preloaded_attached_dict(content_len: usize, src_len: usize) -> Self {
let start = WINDOW_START_INDEX as u32;
let src_start = start + content_len as u32;
Window {
seg_bias: start,
dict_bias: start,
low_limit: src_start,
dict_limit: src_start,
next_src_pos: content_len + src_len,
next_src_idx: start + (content_len + src_len) as u32,
loaded_dict_end: 0,
}
}
pub(crate) fn streaming_attached_dict(content_len: usize) -> Self {
let start = WINDOW_START_INDEX as u32;
let src_start = start + content_len as u32;
Window {
seg_bias: start,
dict_bias: start,
low_limit: src_start,
dict_limit: src_start,
next_src_pos: content_len,
next_src_idx: src_start,
loaded_dict_end: 0,
}
}
pub(crate) fn streaming_ext_dict(content_len: usize) -> Self {
let start = WINDOW_START_INDEX as u32;
Window {
seg_bias: start,
dict_bias: start,
low_limit: start,
dict_limit: start + content_len as u32,
next_src_pos: content_len,
next_src_idx: start + content_len as u32,
loaded_dict_end: start + content_len as u32,
}
}
pub(crate) fn update(&mut self, start: usize, end: usize) -> bool {
if start == end {
return true;
}
let mut contiguous = true;
if start != self.next_src_pos {
self.low_limit = self.dict_limit;
self.dict_limit = self.next_src_idx;
self.dict_bias = self.seg_bias;
self.seg_bias = self.next_src_idx.wrapping_sub(start as u32);
if self.dict_limit - self.low_limit < HASH_READ_SIZE as u32 {
self.low_limit = self.dict_limit;
}
contiguous = false;
}
self.next_src_pos = end;
self.next_src_idx = self.seg_bias.wrapping_add(end as u32);
let dict_bias = i64::from(self.dict_bias);
if (end as i64 > i64::from(self.low_limit) - dict_bias)
&& ((start as i64) < i64::from(self.dict_limit) - dict_bias)
{
let high_input_idx = end as i64 + dict_bias;
self.low_limit = if high_input_idx > i64::from(self.dict_limit) {
self.dict_limit
} else {
high_input_idx as u32
};
}
contiguous
}
pub(crate) fn enforce_max_dist(&mut self, idx: u32, max_dist: u32) {
if idx > max_dist.wrapping_add(self.loaded_dict_end) {
let new_low_limit = idx - max_dist;
if self.low_limit < new_low_limit {
self.low_limit = new_low_limit;
}
if self.dict_limit < self.low_limit {
self.dict_limit = self.low_limit;
}
self.loaded_dict_end = 0;
}
}
pub(crate) fn check_dict_validity(&mut self, block_end_idx: u32, max_dist: u32) {
if self.loaded_dict_end != 0
&& (block_end_idx > self.loaded_dict_end.wrapping_add(max_dist)
|| self.loaded_dict_end != self.dict_limit)
{
self.loaded_dict_end = 0;
}
}
pub(crate) fn has_ext_dict(&self) -> bool {
self.low_limit < self.dict_limit
}
pub(crate) fn slide_for_init_stats(&mut self, src_size: usize) {
self.seg_bias += src_size as u32;
self.dict_limit += src_size as u32;
self.low_limit = self.dict_limit;
self.next_src_idx += src_size as u32;
}
pub(crate) fn needs_overflow_correction(&self, block_end_pos: usize) -> bool {
const CURRENT_MAX: u32 = 3500 * (1 << 20);
(block_end_pos as u32).wrapping_add(self.seg_bias) > CURRENT_MAX
}
pub(crate) fn correct_overflow(
&mut self,
cycle_log: u32,
max_dist: u32,
block_start_pos: usize,
) -> u32 {
let cycle_size = 1u32 << cycle_log;
let cycle_mask = cycle_size - 1;
let curr = block_start_pos as u32 + self.seg_bias;
let current_cycle = curr & cycle_mask;
let current_cycle_correction = if current_cycle < WINDOW_START_INDEX as u32 {
cycle_size.max(WINDOW_START_INDEX as u32)
} else {
0
};
let new_current = current_cycle + current_cycle_correction + max_dist.max(cycle_size);
let correction = curr - new_current;
self.seg_bias -= correction;
self.dict_bias -= correction;
self.next_src_idx -= correction;
let floor = correction + WINDOW_START_INDEX as u32;
self.low_limit = if self.low_limit < floor {
WINDOW_START_INDEX as u32
} else {
self.low_limit - correction
};
self.dict_limit = if self.dict_limit < floor {
WINDOW_START_INDEX as u32
} else {
self.dict_limit - correction
};
correction
}
}
pub(crate) fn count_2segments(
buf: &[u8],
ip: usize,
matched: usize,
iend: usize,
mend: usize,
istart: usize,
) -> usize {
let v_end = (ip + (mend - matched)).min(iend);
let match_length = count_eq(buf, ip, matched, v_end);
if matched + match_length != mend {
return match_length;
}
match_length + count_eq(buf, ip + match_length, istart, iend)
}
pub(crate) fn reduce_table(table: &mut [u32], correction: u32, preserve_mark: bool) {
const DUBT_UNSORTED_MARK: u32 = 1;
let threshold = correction + WINDOW_START_INDEX as u32;
for v in table.iter_mut() {
if preserve_mark && *v == DUBT_UNSORTED_MARK {
} else if *v < threshold {
*v = 0;
} else {
*v -= correction;
}
}
}
struct FastCtx {
hash_table: Vec<u32>,
hlog: u32,
mls: u32,
step_size: usize,
window_log: u32,
}
impl FastCtx {
fn new(cparams: &CParams) -> Self {
FastCtx {
hash_table: vec![0u32; 1usize << cparams.hash_log],
hlog: cparams.hash_log,
step_size: cparams.target_length as usize + (cparams.target_length == 0) as usize + 1,
mls: cparams.min_match.clamp(4, 7),
window_log: cparams.window_log,
}
}
fn reduce_indices(&mut self, correction: u32) {
reduce_table(&mut self.hash_table, correction, false);
}
}
fn fill_fast_hash_table_for_cctx(ctx: &mut FastCtx, data: &[u8], dict_len: usize) {
let hlog = ctx.hlog;
let mls = ctx.mls;
let mut ip = 0usize;
while ip + 9 < dict_len {
let h = hash_ptr(data, ip, hlog, mls);
ctx.hash_table[h] = (ip + WINDOW_START_INDEX) as u32;
ip += 3;
}
}
fn write_tagged_index(table: &mut [u32], hash_and_tag: usize, index: u32) {
let hash = hash_and_tag >> SHORT_CACHE_TAG_BITS;
let tag = (hash_and_tag as u32) & ((1 << SHORT_CACHE_TAG_BITS) - 1);
table[hash] = (index << SHORT_CACHE_TAG_BITS) | tag;
}
fn fill_fast_hash_table_for_cdict(
table: &mut [u32],
data: &[u8],
dict_len: usize,
hlog: u32,
mls: u32,
) {
let h_bits = hlog + SHORT_CACHE_TAG_BITS;
let mut ip = 0usize;
while ip + 9 < dict_len {
let curr = (ip + WINDOW_START_INDEX) as u32;
write_tagged_index(table, hash_ptr(data, ip, h_bits, mls), curr);
for p in 1..3usize {
let hash_and_tag = hash_ptr(data, ip + p, h_bits, mls);
if table[hash_and_tag >> SHORT_CACHE_TAG_BITS] == 0 {
write_tagged_index(table, hash_and_tag, curr + p as u32);
}
}
ip += 3;
}
}
fn fill_fast_hash_table_for_cctx_full(ctx: &mut FastCtx, data: &[u8], dict_len: usize) {
let hlog = ctx.hlog;
let mls = ctx.mls;
let mut ip = 0usize;
while ip + 9 < dict_len {
let curr = (ip + WINDOW_START_INDEX) as u32;
ctx.hash_table[hash_ptr(data, ip, hlog, mls)] = curr;
for p in 1..3usize {
let h = hash_ptr(data, ip + p, hlog, mls);
if ctx.hash_table[h] == 0 {
ctx.hash_table[h] = curr + p as u32;
}
}
ip += 3;
}
}
#[allow(clippy::too_many_arguments)]
fn compress_block_fast_dict_match_state(
ctx: &mut FastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
content_len: usize,
dict_hash_table: &[u32],
dict_hlog: u32,
) -> usize {
let bias = WINDOW_START_INDEX;
let hlog = ctx.hlog;
let mls = ctx.mls;
let step_size = ctx.step_size - 1;
let k_step_incr = 1usize << K_SEARCH_STRENGTH;
let dict_h_bits = dict_hlog + SHORT_CACHE_TAG_BITS;
let tag_mask = (1u32 << SHORT_CACHE_TAG_BITS) - 1;
let prefix_start_index = (bias + content_len) as u32; let dict_start_index = bias as u32; let prefix_start_pos = content_len; let dict_end_pos = content_len; let iend_pos = block_end;
let iend = block_end + bias;
if block_end - block_start < HASH_READ_SIZE {
return block_end - block_start;
}
let ilimit = iend - HASH_READ_SIZE;
let istart = block_start + bias;
let dict_and_prefix_length = (istart - prefix_start_index as usize) + content_len;
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let mut anchor = istart;
let mut ip0 = istart + (dict_and_prefix_length == 0) as usize;
let mut ip1 = ip0 + step_size;
while ip1 <= ilimit {
let mut hash0 = hash_ptr(data, ip0 - bias, hlog, mls);
let dict_hat0 = hash_ptr(data, ip0 - bias, dict_h_bits, mls);
let mut dict_idx_tag = dict_hash_table[dict_hat0 >> SHORT_CACHE_TAG_BITS];
let mut dict_tags_match = (dict_idx_tag & tag_mask) == (dict_hat0 as u32 & tag_mask);
let mut match_index = ctx.hash_table[hash0];
let mut curr = ip0 as u32;
let mut step = step_size;
let mut next_step = ip0 + k_step_incr;
let m_length = loop {
let rep_index = curr.wrapping_add(1).wrapping_sub(offset_1);
let hash1 = hash_ptr(data, ip1 - bias, hlog, mls);
let dict_hat1 = hash_ptr(data, ip1 - bias, dict_h_bits, mls);
ctx.hash_table[hash0] = curr;
if index_overlap_check(prefix_start_index, rep_index)
&& read32(data, rep_index as usize - bias) == read32(data, (ip0 + 1) - bias)
{
let mend = if rep_index < prefix_start_index {
dict_end_pos
} else {
iend_pos
};
let ml = 4 + count_2segments(
data,
(ip0 + 1 - bias) + 4,
(rep_index as usize - bias) + 4,
iend_pos,
mend,
prefix_start_pos,
);
ip0 += 1;
store.store_seq(&data[anchor - bias..ip0 - bias], 1, ml as u32);
break ml;
}
if dict_tags_match {
let dict_match_index = dict_idx_tag >> SHORT_CACHE_TAG_BITS;
if dict_match_index > dict_start_index
&& read32(data, dict_match_index as usize - bias) == read32(data, ip0 - bias)
&& match_index <= prefix_start_index
{
let offset = curr - dict_match_index; let mut ml = 4 + count_2segments(
data,
(ip0 - bias) + 4,
(dict_match_index as usize - bias) + 4,
iend_pos,
dict_end_pos,
prefix_start_pos,
);
let mut dm = dict_match_index as usize;
while ip0 > anchor
&& dm > dict_start_index as usize
&& data[(ip0 - bias) - 1] == data[(dm - bias) - 1]
{
ip0 -= 1;
dm -= 1;
ml += 1;
}
offset_2 = offset_1;
offset_1 = offset;
store.store_seq(&data[anchor - bias..ip0 - bias], offset + 3, ml as u32);
break ml;
}
}
if match_index >= prefix_start_index
&& read32(data, match_index as usize - bias) == read32(data, ip0 - bias)
{
let offset = curr - match_index;
let mut ml = 4 + count_eq(
data,
(ip0 - bias) + 4,
(match_index as usize - bias) + 4,
iend_pos,
);
let mut m = match_index as usize;
while ip0 > anchor
&& m > prefix_start_index as usize
&& data[(ip0 - bias) - 1] == data[(m - bias) - 1]
{
ip0 -= 1;
m -= 1;
ml += 1;
}
offset_2 = offset_1;
offset_1 = offset;
store.store_seq(&data[anchor - bias..ip0 - bias], offset + 3, ml as u32);
break ml;
}
dict_idx_tag = dict_hash_table[dict_hat1 >> SHORT_CACHE_TAG_BITS];
dict_tags_match = (dict_idx_tag & tag_mask) == (dict_hat1 as u32 & tag_mask);
match_index = ctx.hash_table[hash1];
if ip1 >= next_step {
step += 1;
next_step += k_step_incr;
}
ip0 = ip1;
ip1 += step;
if ip1 > ilimit {
rep[0] = offset_1;
rep[1] = offset_2;
return iend_pos - (anchor - bias);
}
curr = ip0 as u32;
hash0 = hash1;
};
ip0 += m_length;
anchor = ip0;
if ip0 <= ilimit {
ctx.hash_table[hash_ptr(data, (curr as usize + 2) - bias, hlog, mls)] = curr + 2;
ctx.hash_table[hash_ptr(data, (ip0 - 2) - bias, hlog, mls)] = (ip0 - 2) as u32;
while ip0 <= ilimit {
let current2 = ip0 as u32;
let rep_index2 = current2.wrapping_sub(offset_2);
if index_overlap_check(prefix_start_index, rep_index2)
&& read32(data, rep_index2 as usize - bias) == read32(data, ip0 - bias)
{
let rep_end2 = if rep_index2 < prefix_start_index {
dict_end_pos
} else {
iend_pos
};
let rep_length2 = 4 + count_2segments(
data,
(ip0 - bias) + 4,
(rep_index2 as usize - bias) + 4,
iend_pos,
rep_end2,
prefix_start_pos,
);
std::mem::swap(&mut offset_1, &mut offset_2);
store.store_seq(&data[anchor - bias..ip0 - bias], 1, rep_length2 as u32);
ctx.hash_table[hash_ptr(data, ip0 - bias, hlog, mls)] = current2;
ip0 += rep_length2;
anchor = ip0;
} else {
break;
}
}
}
ip1 = ip0 + step_size;
}
rep[0] = offset_1;
rep[1] = offset_2;
iend_pos - (anchor - bias)
}
#[allow(clippy::too_many_arguments)]
fn compress_block_fast(
ctx: &mut FastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
seg_bias: usize,
lowest_valid: usize,
) -> usize {
let src_size = block_end - block_start;
let hlog = ctx.hlog;
let mls = ctx.mls;
let step_size = ctx.step_size;
let max_distance = 1usize << ctx.window_log;
let end_index = block_end + seg_bias;
let prefix_start_index = if end_index - lowest_valid > max_distance {
end_index - max_distance
} else {
lowest_valid
};
let k_step_incr: usize = 1 << (K_SEARCH_STRENGTH - 1);
let bias = seg_bias;
let to_pos = |idx: usize| idx - bias; let istart = block_start + bias;
let iend = block_end + bias;
if src_size < HASH_READ_SIZE {
return src_size;
}
let ilimit = iend - HASH_READ_SIZE;
let mut anchor = istart;
let mut ip0 = istart;
let mut ip1: usize;
let mut ip2: usize;
let mut ip3: usize;
let mut current0: u32;
let mut rep_offset1 = rep[0];
let mut rep_offset2 = rep[1];
let mut offset_saved1 = 0u32;
let mut offset_saved2 = 0u32;
let mut hash0: usize;
let mut hash1: usize;
let mut match_idx: u32;
let mut step: usize;
let mut next_step: usize;
let found = |data: &[u8], cur: usize, idx: u32| -> bool {
idx as usize >= prefix_start_index
&& read32(data, to_pos(cur)) == read32(data, to_pos(idx as usize))
};
ip0 += (ip0 == prefix_start_index) as usize; {
let curr = ip0;
let window_low = if curr - lowest_valid > max_distance {
curr - max_distance
} else {
lowest_valid
};
let max_rep = (curr - window_low) as u32;
if rep_offset2 > max_rep {
offset_saved2 = rep_offset2;
rep_offset2 = 0;
}
if rep_offset1 > max_rep {
offset_saved1 = rep_offset1;
rep_offset1 = 0;
}
}
'outer: loop {
step = step_size;
next_step = ip0 + k_step_incr;
ip1 = ip0 + 1;
ip2 = ip0 + step;
ip3 = ip2 + 1;
if ip3 >= ilimit {
break 'outer;
}
hash0 = hash_ptr(data, to_pos(ip0), hlog, mls);
hash1 = hash_ptr(data, to_pos(ip1), hlog, mls);
match_idx = ctx.hash_table[hash0];
loop {
let rval = if rep_offset1 > 0 {
read32(data, to_pos(ip2 - rep_offset1 as usize))
} else {
0
};
current0 = ip0 as u32;
ctx.hash_table[hash0] = current0;
if rep_offset1 > 0 && read32(data, to_pos(ip2)) == rval {
ip0 = ip2;
let mut match0 = ip0 - rep_offset1 as usize;
let ext = (data[to_pos(ip0) - 1] == data[to_pos(match0) - 1]) as usize;
let mut m_length = ext;
ip0 -= ext;
match0 -= ext;
let offcode = 1u32; m_length += 4;
ctx.hash_table[hash1] = ip1 as u32;
m_length += count_eq(
data,
to_pos(ip0) + m_length,
to_pos(match0) + m_length,
to_pos(iend),
);
store.store_seq(&data[to_pos(anchor)..to_pos(ip0)], offcode, m_length as u32);
ip0 += m_length;
anchor = ip0;
post_match(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut rep_offset1,
&mut rep_offset2,
ilimit,
iend,
hlog,
mls,
bias,
);
continue 'outer;
}
if found(data, ip0, match_idx) {
ctx.hash_table[hash1] = ip1 as u32;
offset_and_match(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut rep_offset1,
&mut rep_offset2,
match_idx,
prefix_start_index,
ilimit,
iend,
hlog,
mls,
bias,
);
continue 'outer;
}
match_idx = ctx.hash_table[hash1];
hash0 = hash1;
hash1 = hash_ptr(data, to_pos(ip2), hlog, mls);
ip0 = ip1;
ip1 = ip2;
ip2 = ip3;
current0 = ip0 as u32;
ctx.hash_table[hash0] = current0;
if found(data, ip0, match_idx) {
if step <= 4 {
ctx.hash_table[hash1] = ip1 as u32;
}
offset_and_match(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut rep_offset1,
&mut rep_offset2,
match_idx,
prefix_start_index,
ilimit,
iend,
hlog,
mls,
bias,
);
continue 'outer;
}
match_idx = ctx.hash_table[hash1];
hash0 = hash1;
hash1 = hash_ptr(data, to_pos(ip2), hlog, mls);
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 'outer;
}
}
}
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
};
to_pos(iend) - to_pos(anchor)
}
#[allow(clippy::too_many_arguments)]
fn offset_and_match(
ctx: &mut FastCtx,
store: &mut SeqStore,
data: &[u8],
ip0: &mut usize,
anchor: &mut usize,
current0: u32,
rep_offset1: &mut u32,
rep_offset2: &mut u32,
match_idx: u32,
prefix_start_index: usize,
ilimit: usize,
iend: usize,
hlog: u32,
mls: u32,
bias: usize,
) {
let to_pos = |idx: usize| idx - bias;
let mut match0 = match_idx as usize;
*rep_offset2 = *rep_offset1;
*rep_offset1 = (*ip0 - match0) as u32;
let offcode = *rep_offset1 + 3; let mut m_length = 4usize;
while *ip0 > *anchor
&& match0 > prefix_start_index
&& data[to_pos(*ip0) - 1] == data[to_pos(match0) - 1]
{
*ip0 -= 1;
match0 -= 1;
m_length += 1;
}
m_length += count_eq(
data,
to_pos(*ip0) + m_length,
to_pos(match0) + m_length,
to_pos(iend),
);
store.store_seq(
&data[to_pos(*anchor)..to_pos(*ip0)],
offcode,
m_length as u32,
);
*ip0 += m_length;
*anchor = *ip0;
post_match(
ctx,
store,
data,
ip0,
anchor,
current0,
rep_offset1,
rep_offset2,
ilimit,
iend,
hlog,
mls,
bias,
);
}
#[allow(clippy::too_many_arguments)]
fn post_match(
ctx: &mut FastCtx,
store: &mut SeqStore,
data: &[u8],
ip0: &mut usize,
anchor: &mut usize,
current0: u32,
rep_offset1: &mut u32,
rep_offset2: &mut u32,
ilimit: usize,
iend: usize,
hlog: u32,
mls: u32,
bias: usize,
) {
let to_pos = |idx: usize| idx - bias;
if *ip0 <= ilimit {
let c02 = current0 as usize + 2;
let h = hash_ptr(data, to_pos(c02), hlog, mls);
ctx.hash_table[h] = c02 as u32;
let h2 = hash_ptr(data, to_pos(*ip0 - 2), hlog, mls);
ctx.hash_table[h2] = (*ip0 - 2) as u32;
if *rep_offset2 > 0 {
while *ip0 <= ilimit
&& read32(data, to_pos(*ip0)) == read32(data, to_pos(*ip0 - *rep_offset2 as usize))
{
let r_length = count_eq(
data,
to_pos(*ip0) + 4,
to_pos(*ip0 - *rep_offset2 as usize) + 4,
to_pos(iend),
) + 4;
std::mem::swap(rep_offset1, rep_offset2);
let h = hash_ptr(data, to_pos(*ip0), hlog, mls);
ctx.hash_table[h] = *ip0 as u32;
*ip0 += r_length;
store.store_seq(&[], 1, r_length as u32); *anchor = *ip0;
}
}
}
}
struct ExtDictView {
seg_bias: usize,
dict_bias: usize,
prefix_start_index: usize,
dict_start_pos: usize,
dict_end_pos: usize,
prefix_start_pos: usize,
iend_pos: usize,
ilimit: usize,
}
impl ExtDictView {
fn pos_p(&self, idx: usize) -> usize {
idx - self.seg_bias
}
fn pos_seg(&self, idx: usize) -> usize {
if idx < self.prefix_start_index {
idx - self.dict_bias
} else {
idx - self.seg_bias
}
}
fn match_end_pos(&self, idx: usize) -> usize {
if idx < self.prefix_start_index {
self.dict_end_pos
} else {
self.iend_pos
}
}
}
fn compress_block_fast_extdict(
ctx: &mut FastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
win: &Window,
) -> usize {
let src_size = block_end - block_start;
let hlog = ctx.hlog;
let mls = ctx.mls;
let step_size = ctx.step_size;
let k_step_incr: usize = 1 << (K_SEARCH_STRENGTH - 1);
let seg_bias = win.seg_bias as usize;
let istart = block_start + seg_bias;
let iend = block_end + seg_bias;
let end_index = iend;
let max_distance = 1usize << ctx.window_log;
let lowest_valid = win.low_limit as usize;
let dict_start_index = if win.loaded_dict_end != 0 {
lowest_valid
} else if end_index - lowest_valid > max_distance {
end_index - max_distance
} else {
lowest_valid
};
let dict_limit = win.dict_limit as usize;
let prefix_start_index = dict_start_index.max(dict_limit);
if prefix_start_index == dict_start_index {
return compress_block_fast(
ctx,
store,
rep,
data,
block_start,
block_end,
seg_bias,
dict_limit,
);
}
if src_size < HASH_READ_SIZE {
return src_size;
}
let w = ExtDictView {
seg_bias,
dict_bias: win.dict_bias as usize,
prefix_start_index,
dict_start_pos: dict_start_index - win.dict_bias as usize,
dict_end_pos: prefix_start_index - win.dict_bias as usize,
prefix_start_pos: prefix_start_index - seg_bias,
iend_pos: block_end,
ilimit: iend - HASH_READ_SIZE,
};
let mut anchor = istart;
let mut ip0 = istart;
let mut ip1: usize;
let mut ip2: usize;
let mut ip3: usize;
let mut current0: u32;
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let mut offset_saved1 = 0u32;
let mut offset_saved2 = 0u32;
{
let curr = ip0 as u32;
let max_rep = curr - dict_start_index as u32;
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 mut hash0: usize;
let mut hash1: usize;
let mut idx: u32;
let mut step: usize;
let mut next_step: usize;
'outer: loop {
step = step_size;
next_step = ip0 + k_step_incr;
ip1 = ip0 + 1;
ip2 = ip0 + step;
ip3 = ip2 + 1;
if ip3 >= w.ilimit {
break 'outer;
}
hash0 = hash_ptr(data, w.pos_p(ip0), hlog, mls);
hash1 = hash_ptr(data, w.pos_p(ip1), hlog, mls);
idx = ctx.hash_table[hash0];
loop {
let current2 = ip2;
let rep_index = current2.wrapping_sub(offset_1 as usize);
let rval = if (prefix_start_index as u32).wrapping_sub(rep_index as u32) >= 4
&& offset_1 > 0
{
read32(data, w.pos_seg(rep_index))
} else {
read32(data, w.pos_p(ip2)) ^ 1 };
current0 = ip0 as u32;
ctx.hash_table[hash0] = current0;
if read32(data, w.pos_p(ip2)) == rval {
ip0 = ip2;
let mut match0 = rep_index;
let match_seg_pos = w.pos_seg(match0);
let ext = (data[w.pos_p(ip0) - 1] == data[match_seg_pos - 1]) as usize;
let mut m_length = ext;
ip0 -= ext;
match0 -= ext;
m_length += 4;
extdict_match_tail(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut offset_1,
&mut offset_2,
m_length,
1, match0,
hash1,
ip1,
&w,
);
continue 'outer;
}
{
let mval = if idx as usize >= dict_start_index {
read32(data, w.pos_seg(idx as usize))
} else {
read32(data, w.pos_p(ip0)) ^ 1
};
if read32(data, w.pos_p(ip0)) == mval {
extdict_offset_and_match(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut offset_1,
&mut offset_2,
idx,
hash1,
ip1,
&w,
);
continue 'outer;
}
}
idx = ctx.hash_table[hash1];
hash0 = hash1;
hash1 = hash_ptr(data, w.pos_p(ip2), hlog, mls);
ip0 = ip1;
ip1 = ip2;
ip2 = ip3;
current0 = ip0 as u32;
ctx.hash_table[hash0] = current0;
{
let mval = if idx as usize >= dict_start_index {
read32(data, w.pos_seg(idx as usize))
} else {
read32(data, w.pos_p(ip0)) ^ 1
};
if read32(data, w.pos_p(ip0)) == mval {
extdict_offset_and_match(
ctx,
store,
data,
&mut ip0,
&mut anchor,
current0,
&mut offset_1,
&mut offset_2,
idx,
hash1,
ip1,
&w,
);
continue 'outer;
}
}
idx = ctx.hash_table[hash1];
hash0 = hash1;
hash1 = hash_ptr(data, w.pos_p(ip2), hlog, mls);
ip0 = ip1;
ip1 = ip2;
ip2 = ip0 + step;
ip3 = ip1 + step;
if ip2 >= next_step {
step += 1;
next_step += k_step_incr;
}
if ip3 >= w.ilimit {
break 'outer;
}
}
}
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
}
#[allow(clippy::too_many_arguments)]
fn extdict_offset_and_match(
ctx: &mut FastCtx,
store: &mut SeqStore,
data: &[u8],
ip0: &mut usize,
anchor: &mut usize,
current0: u32,
offset_1: &mut u32,
offset_2: &mut u32,
idx: u32,
hash1: usize,
ip1: usize,
w: &ExtDictView,
) {
let offset = current0 - idx;
let mut match0 = idx as usize;
let in_dict = match0 < w.prefix_start_index;
let low_match_pos = if in_dict {
w.dict_start_pos
} else {
w.prefix_start_pos
};
let match_bias = if in_dict { w.dict_bias } else { w.seg_bias };
*offset_2 = *offset_1;
*offset_1 = offset;
let offcode = offset + 3; let mut m_length = 4usize;
while *ip0 > *anchor
&& (match0 - match_bias) > low_match_pos
&& data[w.pos_p(*ip0) - 1] == data[match0 - match_bias - 1]
{
*ip0 -= 1;
match0 -= 1;
m_length += 1;
}
extdict_match_tail(
ctx, store, data, ip0, anchor, current0, offset_1, offset_2, m_length, offcode, match0,
hash1, ip1, w,
);
}
#[allow(clippy::too_many_arguments)]
fn extdict_match_tail(
ctx: &mut FastCtx,
store: &mut SeqStore,
data: &[u8],
ip0: &mut usize,
anchor: &mut usize,
current0: u32,
offset_1: &mut u32,
offset_2: &mut u32,
mut m_length: usize,
offcode: u32,
match0: usize,
hash1: usize,
ip1: usize,
w: &ExtDictView,
) {
let hlog = ctx.hlog;
let mls = ctx.mls;
m_length += count_2segments(
data,
w.pos_p(*ip0) + m_length,
w.pos_seg(match0) + m_length,
w.iend_pos,
w.match_end_pos(match0),
w.prefix_start_pos,
);
store.store_seq(
&data[w.pos_p(*anchor)..w.pos_p(*ip0)],
offcode,
m_length as u32,
);
*ip0 += m_length;
*anchor = *ip0;
if ip1 < *ip0 {
ctx.hash_table[hash1] = ip1 as u32;
}
if *ip0 <= w.ilimit {
let c02 = current0 as usize + 2;
let h = hash_ptr(data, w.pos_p(c02), hlog, mls);
ctx.hash_table[h] = c02 as u32;
let h2 = hash_ptr(data, w.pos_p(*ip0 - 2), hlog, mls);
ctx.hash_table[h2] = (*ip0 - 2) as u32;
while *ip0 <= w.ilimit {
let rep_index2 = ip0.wrapping_sub(*offset_2 as usize);
let no_overlap = (w.prefix_start_index as u32)
.wrapping_sub(1)
.wrapping_sub(rep_index2 as u32)
>= 3;
if !(no_overlap && *offset_2 > 0) {
break;
}
let rep_match2_pos = w.pos_seg(rep_index2);
if read32(data, rep_match2_pos) != read32(data, w.pos_p(*ip0)) {
break;
}
let rep_length2 = count_2segments(
data,
w.pos_p(*ip0) + 4,
rep_match2_pos + 4,
w.iend_pos,
w.match_end_pos(rep_index2),
w.prefix_start_pos,
) + 4;
std::mem::swap(offset_1, offset_2);
store.store_seq(&[], 1, rep_length2 as u32);
let h = hash_ptr(data, w.pos_p(*ip0), hlog, mls);
ctx.hash_table[h] = *ip0 as u32;
*ip0 += rep_length2;
*anchor = *ip0;
}
}
}
struct DfastCtx {
hash_long: Vec<u32>,
hash_small: Vec<u32>,
hlog_l: u32,
hlog_s: u32,
mls: u32,
window_log: u32,
}
impl DfastCtx {
fn new(cparams: &CParams) -> Self {
DfastCtx {
hash_long: vec![0u32; 1usize << cparams.hash_log],
hash_small: vec![0u32; 1usize << cparams.chain_log],
hlog_l: cparams.hash_log,
hlog_s: cparams.chain_log,
mls: cparams.min_match.clamp(4, 7),
window_log: cparams.window_log,
}
}
fn reduce_indices(&mut self, correction: u32) {
reduce_table(&mut self.hash_long, correction, false);
reduce_table(&mut self.hash_small, correction, false);
}
}
fn fill_dfast_hash_tables_for_cctx(ctx: &mut DfastCtx, data: &[u8], dict_len: usize) {
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
let mut ip = 0usize;
while ip + 9 < dict_len {
let curr = (ip + WINDOW_START_INDEX) as u32;
ctx.hash_small[hash_ptr(data, ip, hlog_s, mls)] = curr;
ctx.hash_long[hash_ptr(data, ip, hlog_l, 8)] = curr;
ip += 3;
}
}
#[allow(clippy::too_many_arguments)]
fn fill_dfast_hash_tables_for_cdict(
hash_long: &mut [u32],
hash_small: &mut [u32],
data: &[u8],
dict_len: usize,
hlog_l: u32,
hlog_s: u32,
mls: u32,
) {
let h_bits_l = hlog_l + SHORT_CACHE_TAG_BITS;
let h_bits_s = hlog_s + SHORT_CACHE_TAG_BITS;
let mut ip = 0usize;
while ip + 9 < dict_len {
let curr = (ip + WINDOW_START_INDEX) as u32;
for i in 0..3usize {
let sm = hash_ptr(data, ip + i, h_bits_s, mls);
let lg = hash_ptr(data, ip + i, h_bits_l, 8);
if i == 0 {
write_tagged_index(hash_small, sm, curr + i as u32);
}
if i == 0 || hash_long[lg >> SHORT_CACHE_TAG_BITS] == 0 {
write_tagged_index(hash_long, lg, curr + i as u32);
}
}
ip += 3;
}
}
fn fill_dfast_hash_tables_for_cctx_full(ctx: &mut DfastCtx, data: &[u8], dict_len: usize) {
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
let mut ip = 0usize;
while ip + 9 < dict_len {
let curr = (ip + WINDOW_START_INDEX) as u32;
for i in 0..3usize {
let sm = hash_ptr(data, ip + i, hlog_s, mls);
let lg = hash_ptr(data, ip + i, hlog_l, 8);
if i == 0 {
ctx.hash_small[sm] = curr + i as u32;
}
if i == 0 || ctx.hash_long[lg] == 0 {
ctx.hash_long[lg] = curr + i as u32;
}
}
ip += 3;
}
}
#[allow(clippy::too_many_arguments)]
fn compress_block_dfast_dict_match_state(
ctx: &mut DfastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
content_len: usize,
dict_hash_long: &[u32],
dict_hash_small: &[u32],
dict_hlog_l: u32,
dict_hlog_s: u32,
) -> usize {
let bias = WINDOW_START_INDEX;
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
let dict_h_bits_l = dict_hlog_l + SHORT_CACHE_TAG_BITS;
let dict_h_bits_s = dict_hlog_s + SHORT_CACHE_TAG_BITS;
let tag_mask = (1u32 << SHORT_CACHE_TAG_BITS) - 1;
let prefix_lowest_index = (bias + content_len) as u32;
let dict_start_index = bias as u32;
let prefix_start_pos = content_len;
let dict_end_pos = content_len;
let iend_pos = block_end;
let iend = block_end + bias;
if block_end - block_start < HASH_READ_SIZE {
return block_end - block_start;
}
let ilimit = iend - HASH_READ_SIZE;
let istart = block_start + bias;
let dict_and_prefix_length = (istart - prefix_lowest_index as usize) + content_len;
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let mut anchor = istart;
let mut ip = istart + (dict_and_prefix_length == 0) as usize;
'outer: while ip < ilimit {
let curr = ip as u32;
let h2 = hash_ptr(data, ip - bias, hlog_l, 8);
let h = hash_ptr(data, ip - bias, hlog_s, mls);
let dict_hat_l = hash_ptr(data, ip - bias, dict_h_bits_l, 8);
let dict_hat_s = hash_ptr(data, ip - bias, dict_h_bits_s, mls);
let dict_idx_tag_l = dict_hash_long[dict_hat_l >> SHORT_CACHE_TAG_BITS];
let dict_idx_tag_s = dict_hash_small[dict_hat_s >> SHORT_CACHE_TAG_BITS];
let dict_tags_match_l = (dict_idx_tag_l & tag_mask) == (dict_hat_l as u32 & tag_mask);
let dict_tags_match_s = (dict_idx_tag_s & tag_mask) == (dict_hat_s as u32 & tag_mask);
let match_index_l = ctx.hash_long[h2];
let mut match_index_s = ctx.hash_small[h];
let rep_index = curr + 1 - offset_1;
ctx.hash_long[h2] = curr;
ctx.hash_small[h] = curr;
let m_length: usize;
if index_overlap_check(prefix_lowest_index, rep_index)
&& read32(data, rep_index as usize - bias) == read32(data, (ip + 1) - bias)
{
let mend = if rep_index < prefix_lowest_index {
dict_end_pos
} else {
iend_pos
};
m_length = 4 + count_2segments(
data,
(ip + 1 - bias) + 4,
(rep_index as usize - bias) + 4,
iend_pos,
mend,
prefix_start_pos,
);
ip += 1;
store.store_seq(&data[anchor - bias..ip - bias], 1, m_length as u32);
} else {
let (offset, ml) = 'found: {
if match_index_l >= prefix_lowest_index
&& read64(data, match_index_l as usize - bias) == read64(data, ip - bias)
{
let mut ml = 8 + count_eq(
data,
(ip - bias) + 8,
(match_index_l as usize - bias) + 8,
iend_pos,
);
let off = curr - match_index_l;
let mut m = match_index_l as usize;
while ip > anchor
&& m > prefix_lowest_index as usize
&& data[(ip - bias) - 1] == data[(m - bias) - 1]
{
ip -= 1;
m -= 1;
ml += 1;
}
break 'found (off, ml);
}
if dict_tags_match_l {
let dml = dict_idx_tag_l >> SHORT_CACHE_TAG_BITS;
if dml > dict_start_index
&& read64(data, dml as usize - bias) == read64(data, ip - bias)
{
let mut ml = 8 + count_2segments(
data,
(ip - bias) + 8,
(dml as usize - bias) + 8,
iend_pos,
dict_end_pos,
prefix_start_pos,
);
let off = curr - dml;
let mut dm = dml as usize;
while ip > anchor
&& dm > dict_start_index as usize
&& data[(ip - bias) - 1] == data[(dm - bias) - 1]
{
ip -= 1;
dm -= 1;
ml += 1;
}
break 'found (off, ml);
}
}
let short_found = if match_index_s > prefix_lowest_index {
read32(data, match_index_s as usize - bias) == read32(data, ip - bias)
} else if dict_tags_match_s {
let dms = dict_idx_tag_s >> SHORT_CACHE_TAG_BITS;
match_index_s = dms;
dms > dict_start_index
&& read32(data, dms as usize - bias) == read32(data, ip - bias)
} else {
false
};
if !short_found {
ip += ((ip - anchor) >> K_SEARCH_STRENGTH) + 1;
continue 'outer;
}
let hl3 = hash_ptr(data, (ip + 1) - bias, hlog_l, 8);
let dict_hat_l3 = hash_ptr(data, (ip + 1) - bias, dict_h_bits_l, 8);
let match_index_l3 = ctx.hash_long[hl3];
let dict_idx_tag_l3 = dict_hash_long[dict_hat_l3 >> SHORT_CACHE_TAG_BITS];
let dict_tags_match_l3 =
(dict_idx_tag_l3 & tag_mask) == (dict_hat_l3 as u32 & tag_mask);
ctx.hash_long[hl3] = curr + 1;
if match_index_l3 >= prefix_lowest_index
&& read64(data, match_index_l3 as usize - bias) == read64(data, (ip + 1) - bias)
{
let mut ml = 8 + count_eq(
data,
(ip + 1 - bias) + 8,
(match_index_l3 as usize - bias) + 8,
iend_pos,
);
ip += 1;
let off = (curr + 1) - match_index_l3;
let mut m = match_index_l3 as usize;
while ip > anchor
&& m > prefix_lowest_index as usize
&& data[(ip - bias) - 1] == data[(m - bias) - 1]
{
ip -= 1;
m -= 1;
ml += 1;
}
break 'found (off, ml);
}
if dict_tags_match_l3 {
let dml3 = dict_idx_tag_l3 >> SHORT_CACHE_TAG_BITS;
if dml3 > dict_start_index
&& read64(data, dml3 as usize - bias) == read64(data, (ip + 1) - bias)
{
let mut ml = 8 + count_2segments(
data,
(ip + 1 - bias) + 8,
(dml3 as usize - bias) + 8,
iend_pos,
dict_end_pos,
prefix_start_pos,
);
ip += 1;
let off = (curr + 1) - dml3;
let mut dm = dml3 as usize;
while ip > anchor
&& dm > dict_start_index as usize
&& data[(ip - bias) - 1] == data[(dm - bias) - 1]
{
ip -= 1;
dm -= 1;
ml += 1;
}
break 'found (off, ml);
}
}
if match_index_s < prefix_lowest_index {
let mut ml = 4 + count_2segments(
data,
(ip - bias) + 4,
(match_index_s as usize - bias) + 4,
iend_pos,
dict_end_pos,
prefix_start_pos,
);
let off = curr - match_index_s;
let mut m = match_index_s as usize;
while ip > anchor
&& m > dict_start_index as usize
&& data[(ip - bias) - 1] == data[(m - bias) - 1]
{
ip -= 1;
m -= 1;
ml += 1;
}
(off, ml)
} else {
let mut ml = 4 + count_eq(
data,
(ip - bias) + 4,
(match_index_s as usize - bias) + 4,
iend_pos,
);
let off = curr - match_index_s;
let mut m = match_index_s as usize;
while ip > anchor
&& m > prefix_lowest_index as usize
&& data[(ip - bias) - 1] == data[(m - bias) - 1]
{
ip -= 1;
m -= 1;
ml += 1;
}
(off, ml)
}
};
offset_2 = offset_1;
offset_1 = offset;
store.store_seq(&data[anchor - bias..ip - bias], offset + 3, ml as u32);
m_length = ml;
}
ip += m_length;
anchor = ip;
if ip <= ilimit {
let index_to_insert = curr + 2;
ctx.hash_long[hash_ptr(data, index_to_insert as usize - bias, hlog_l, 8)] =
index_to_insert;
ctx.hash_long[hash_ptr(data, (ip - 2) - bias, hlog_l, 8)] = (ip - 2) as u32;
ctx.hash_small[hash_ptr(data, index_to_insert as usize - bias, hlog_s, mls)] =
index_to_insert;
ctx.hash_small[hash_ptr(data, (ip - 1) - bias, hlog_s, mls)] = (ip - 1) as u32;
while ip <= ilimit {
let current2 = ip as u32;
let rep_index2 = current2.wrapping_sub(offset_2);
if index_overlap_check(prefix_lowest_index, rep_index2)
&& read32(data, rep_index2 as usize - bias) == read32(data, ip - bias)
{
let rep_end2 = if rep_index2 < prefix_lowest_index {
dict_end_pos
} else {
iend_pos
};
let rep_length2 = 4 + count_2segments(
data,
(ip - bias) + 4,
(rep_index2 as usize - bias) + 4,
iend_pos,
rep_end2,
prefix_start_pos,
);
std::mem::swap(&mut offset_1, &mut offset_2);
store.store_seq(&data[anchor - bias..ip - bias], 1, rep_length2 as u32);
ctx.hash_small[hash_ptr(data, ip - bias, hlog_s, mls)] = current2;
ctx.hash_long[hash_ptr(data, ip - bias, hlog_l, 8)] = current2;
ip += rep_length2;
anchor = ip;
} else {
break;
}
}
}
}
rep[0] = offset_1;
rep[1] = offset_2;
iend_pos - (anchor - bias)
}
#[allow(clippy::too_many_arguments)]
fn compress_block_dfast(
ctx: &mut DfastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
seg_bias: usize,
lowest_valid: usize,
) -> usize {
let src_size = block_end - block_start;
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
let max_distance = 1usize << ctx.window_log;
let end_index = block_end + seg_bias;
let prefix_start_index = if end_index - lowest_valid > max_distance {
end_index - max_distance
} else {
lowest_valid
};
let k_step_incr: usize = 1 << K_SEARCH_STRENGTH;
let bias = seg_bias;
let to_pos = |idx: usize| idx - bias;
let istart = block_start + bias;
let iend = block_end + bias;
if src_size < HASH_READ_SIZE {
return src_size;
}
let ilimit = iend - HASH_READ_SIZE;
let mut anchor = istart;
let mut ip = istart;
let mut ip1: usize;
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let mut offset_saved1 = 0u32;
let mut offset_saved2 = 0u32;
ip += (ip == prefix_start_index) as usize;
{
let window_low = if ip - lowest_valid > max_distance {
ip - max_distance
} else {
lowest_valid
};
let max_rep = (ip - window_low) as u32;
if offset_2 > max_rep {
offset_saved2 = offset_2;
offset_2 = 0;
}
if offset_1 > max_rep {
offset_saved1 = offset_1;
offset_1 = 0;
}
}
'outer: loop {
let mut step = 1usize;
let mut next_step = ip + k_step_incr;
ip1 = ip + step;
if ip1 > ilimit {
break 'outer;
}
let mut hl0 = hash_ptr(data, to_pos(ip), hlog_l, 8);
let mut idxl0 = ctx.hash_long[hl0];
loop {
let hs0 = hash_ptr(data, to_pos(ip), hlog_s, mls);
let idxs0 = ctx.hash_small[hs0];
let curr = ip;
ctx.hash_long[hl0] = curr as u32;
ctx.hash_small[hs0] = curr as u32;
let m_length: usize;
let m_ip: usize;
let hl1_for_writeback: Option<(usize, usize)>;
if offset_1 > 0
&& read32(data, to_pos(ip + 1 - offset_1 as usize)) == read32(data, to_pos(ip + 1))
{
let len = count_eq(
data,
to_pos(ip + 1) + 4,
to_pos(ip + 1 - offset_1 as usize) + 4,
to_pos(iend),
) + 4;
let seq_ip = ip + 1;
store.store_seq(&data[to_pos(anchor)..to_pos(seq_ip)], 1, len as u32);
m_length = len;
m_ip = seq_ip;
ip = m_ip + m_length;
anchor = ip;
dfast_post_match(
ctx,
store,
data,
&mut ip,
&mut anchor,
curr,
&mut offset_1,
&mut offset_2,
ilimit,
iend,
bias,
);
continue 'outer;
}
let hl1 = hash_ptr(data, to_pos(ip1), hlog_l, 8);
if idxl0 as usize >= prefix_start_index
&& read64(data, to_pos(idxl0 as usize)) == read64(data, to_pos(ip))
{
let mut matchl0 = idxl0 as usize;
let mut len = count_eq(data, to_pos(ip) + 8, to_pos(matchl0) + 8, to_pos(iend)) + 8;
let offset = (ip - matchl0) as u32;
while ip > anchor
&& matchl0 > prefix_start_index
&& data[to_pos(ip) - 1] == data[to_pos(matchl0) - 1]
{
ip -= 1;
matchl0 -= 1;
len += 1;
}
m_length = len;
m_ip = ip;
hl1_for_writeback = if step < 4 { Some((hl1, ip1)) } else { None };
dfast_match_found(
ctx,
store,
data,
&mut ip,
&mut anchor,
curr,
&mut offset_1,
&mut offset_2,
offset,
m_ip,
m_length,
hl1_for_writeback,
ilimit,
iend,
bias,
);
continue 'outer;
}
let idxl1 = ctx.hash_long[hl1];
if idxs0 as usize >= prefix_start_index
&& read32(data, to_pos(idxs0 as usize)) == read32(data, to_pos(ip))
{
let mut matchs0 = idxs0 as usize;
let mut len = count_eq(data, to_pos(ip) + 4, to_pos(matchs0) + 4, to_pos(iend)) + 4;
let mut offset = (ip - matchs0) as u32;
if idxl1 as usize > prefix_start_index
&& read64(data, to_pos(idxl1 as usize)) == read64(data, to_pos(ip1))
{
let l1len = count_eq(
data,
to_pos(ip1) + 8,
to_pos(idxl1 as usize) + 8,
to_pos(iend),
) + 8;
if l1len > len {
ip = ip1;
len = l1len;
offset = (ip - idxl1 as usize) as u32;
matchs0 = idxl1 as usize;
}
}
while ip > anchor
&& matchs0 > prefix_start_index
&& data[to_pos(ip) - 1] == data[to_pos(matchs0) - 1]
{
ip -= 1;
matchs0 -= 1;
len += 1;
}
m_length = len;
m_ip = ip;
hl1_for_writeback = if step < 4 { Some((hl1, ip1)) } else { None };
dfast_match_found(
ctx,
store,
data,
&mut ip,
&mut anchor,
curr,
&mut offset_1,
&mut offset_2,
offset,
m_ip,
m_length,
hl1_for_writeback,
ilimit,
iend,
bias,
);
continue 'outer;
}
if ip1 >= next_step {
step += 1;
next_step += k_step_incr;
}
ip = ip1;
ip1 += step;
hl0 = hl1;
idxl0 = idxl1;
if ip1 > ilimit {
break 'outer;
}
}
}
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
};
to_pos(iend) - to_pos(anchor)
}
#[allow(clippy::too_many_arguments)]
fn dfast_match_found(
ctx: &mut DfastCtx,
store: &mut SeqStore,
data: &[u8],
ip: &mut usize,
anchor: &mut usize,
curr: usize,
offset_1: &mut u32,
offset_2: &mut u32,
offset: u32,
m_ip: usize,
m_length: usize,
hl1_writeback: Option<(usize, usize)>,
ilimit: usize,
iend: usize,
bias: usize,
) {
let to_pos = |idx: usize| idx - bias;
*offset_2 = *offset_1;
*offset_1 = offset;
if let Some((hl1, ip1)) = hl1_writeback {
ctx.hash_long[hl1] = ip1 as u32;
}
store.store_seq(
&data[to_pos(*anchor)..to_pos(m_ip)],
offset + 3, m_length as u32,
);
*ip = m_ip + m_length;
*anchor = *ip;
dfast_post_match(
ctx, store, data, ip, anchor, curr, offset_1, offset_2, ilimit, iend, bias,
);
}
#[allow(clippy::too_many_arguments)]
fn dfast_post_match(
ctx: &mut DfastCtx,
store: &mut SeqStore,
data: &[u8],
ip: &mut usize,
anchor: &mut usize,
curr: usize,
offset_1: &mut u32,
offset_2: &mut u32,
ilimit: usize,
iend: usize,
bias: usize,
) {
let to_pos = |idx: usize| idx - bias;
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
if *ip <= ilimit {
let index_to_insert = curr + 2;
let h = hash_ptr(data, to_pos(index_to_insert), hlog_l, 8);
ctx.hash_long[h] = index_to_insert as u32;
let h = hash_ptr(data, to_pos(*ip - 2), hlog_l, 8);
ctx.hash_long[h] = (*ip - 2) as u32;
let h = hash_ptr(data, to_pos(index_to_insert), hlog_s, mls);
ctx.hash_small[h] = index_to_insert as u32;
let h = hash_ptr(data, to_pos(*ip - 1), hlog_s, mls);
ctx.hash_small[h] = (*ip - 1) as u32;
while *ip <= ilimit
&& *offset_2 > 0
&& read32(data, to_pos(*ip)) == read32(data, to_pos(*ip - *offset_2 as usize))
{
let r_length = count_eq(
data,
to_pos(*ip) + 4,
to_pos(*ip - *offset_2 as usize) + 4,
to_pos(iend),
) + 4;
std::mem::swap(offset_1, offset_2);
let h = hash_ptr(data, to_pos(*ip), hlog_s, mls);
ctx.hash_small[h] = *ip as u32;
let h = hash_ptr(data, to_pos(*ip), hlog_l, 8);
ctx.hash_long[h] = *ip as u32;
store.store_seq(&[], 1, r_length as u32);
*ip += r_length;
*anchor = *ip;
}
}
}
fn compress_block_dfast_extdict(
ctx: &mut DfastCtx,
store: &mut SeqStore,
rep: &mut [u32; 3],
data: &[u8],
block_start: usize,
block_end: usize,
win: &Window,
) -> usize {
let src_size = block_end - block_start;
let hlog_l = ctx.hlog_l;
let hlog_s = ctx.hlog_s;
let mls = ctx.mls;
let seg_bias = win.seg_bias as usize;
let istart = block_start + seg_bias;
let iend = block_end + seg_bias;
let end_index = iend;
let max_distance = 1usize << ctx.window_log;
let lowest_valid = win.low_limit as usize;
let dict_start_index = if win.loaded_dict_end != 0 {
lowest_valid
} else if end_index - lowest_valid > max_distance {
end_index - max_distance
} else {
lowest_valid
};
let dict_limit = win.dict_limit as usize;
let prefix_start_index = dict_start_index.max(dict_limit);
if prefix_start_index == dict_start_index {
return compress_block_dfast(
ctx,
store,
rep,
data,
block_start,
block_end,
seg_bias,
dict_limit,
);
}
if src_size < HASH_READ_SIZE {
return src_size;
}
let ilimit = iend - HASH_READ_SIZE;
let w = ExtDictView {
seg_bias,
dict_bias: win.dict_bias as usize,
prefix_start_index,
dict_start_pos: dict_start_index - win.dict_bias as usize,
dict_end_pos: prefix_start_index - win.dict_bias as usize,
prefix_start_pos: prefix_start_index - seg_bias,
iend_pos: block_end,
ilimit,
};
let mut anchor = istart;
let mut ip = istart;
let mut offset_1 = rep[0];
let mut offset_2 = rep[1];
let overlap_ok = |rep_index: u32| {
(prefix_start_index as u32)
.wrapping_sub(1)
.wrapping_sub(rep_index)
>= 3
};
while ip < ilimit {
let h_small = hash_ptr(data, w.pos_p(ip), hlog_s, mls);
let match_index = ctx.hash_small[h_small] as usize;
let h_long = hash_ptr(data, w.pos_p(ip), hlog_l, 8);
let match_long_index = ctx.hash_long[h_long] as usize;
let curr = ip;
let rep_index = (curr as u32 + 1).wrapping_sub(offset_1);
ctx.hash_small[h_small] = curr as u32;
ctx.hash_long[h_long] = curr as u32;
let m_length: usize;
if overlap_ok(rep_index)
&& offset_1 <= (curr + 1 - dict_start_index) as u32
&& read32(data, w.pos_seg(rep_index as usize)) == read32(data, w.pos_p(ip + 1))
{
let rep_idx = rep_index as usize;
m_length = count_2segments(
data,
w.pos_p(ip + 1) + 4,
w.pos_seg(rep_idx) + 4,
w.iend_pos,
w.match_end_pos(rep_idx),
w.prefix_start_pos,
) + 4;
ip += 1;
store.store_seq(&data[w.pos_p(anchor)..w.pos_p(ip)], 1, m_length as u32);
} else if match_long_index > dict_start_index
&& read64(data, w.pos_seg(match_long_index)) == read64(data, w.pos_p(ip))
{
let mut match_pos = w.pos_seg(match_long_index);
let low_match_pos = if match_long_index < prefix_start_index {
w.dict_start_pos
} else {
w.prefix_start_pos
};
let mut len = count_2segments(
data,
w.pos_p(ip) + 8,
match_pos + 8,
w.iend_pos,
w.match_end_pos(match_long_index),
w.prefix_start_pos,
) + 8;
let offset = (curr - match_long_index) as u32;
while ip > anchor
&& match_pos > low_match_pos
&& data[w.pos_p(ip) - 1] == data[match_pos - 1]
{
ip -= 1;
match_pos -= 1;
len += 1;
}
offset_2 = offset_1;
offset_1 = offset;
store.store_seq(
&data[w.pos_p(anchor)..w.pos_p(ip)],
offset + 3, len as u32,
);
m_length = len;
} else if match_index > dict_start_index
&& read32(data, w.pos_seg(match_index)) == read32(data, w.pos_p(ip))
{
let h3 = hash_ptr(data, w.pos_p(ip + 1), hlog_l, 8);
let match_index3 = ctx.hash_long[h3] as usize;
ctx.hash_long[h3] = (curr + 1) as u32;
let offset: u32;
let mut len: usize;
if match_index3 > dict_start_index
&& read64(data, w.pos_seg(match_index3)) == read64(data, w.pos_p(ip + 1))
{
let mut match_pos = w.pos_seg(match_index3);
let low_match_pos = if match_index3 < prefix_start_index {
w.dict_start_pos
} else {
w.prefix_start_pos
};
len = count_2segments(
data,
w.pos_p(ip + 1) + 8,
match_pos + 8,
w.iend_pos,
w.match_end_pos(match_index3),
w.prefix_start_pos,
) + 8;
ip += 1;
offset = (curr + 1 - match_index3) as u32;
while ip > anchor
&& match_pos > low_match_pos
&& data[w.pos_p(ip) - 1] == data[match_pos - 1]
{
ip -= 1;
match_pos -= 1;
len += 1;
}
} else {
let mut match_pos = w.pos_seg(match_index);
let low_match_pos = if match_index < prefix_start_index {
w.dict_start_pos
} else {
w.prefix_start_pos
};
len = count_2segments(
data,
w.pos_p(ip) + 4,
match_pos + 4,
w.iend_pos,
w.match_end_pos(match_index),
w.prefix_start_pos,
) + 4;
offset = (curr - match_index) as u32;
while ip > anchor
&& match_pos > low_match_pos
&& data[w.pos_p(ip) - 1] == data[match_pos - 1]
{
ip -= 1;
match_pos -= 1;
len += 1;
}
}
offset_2 = offset_1;
offset_1 = offset;
store.store_seq(
&data[w.pos_p(anchor)..w.pos_p(ip)],
offset + 3, len as u32,
);
m_length = len;
} else {
ip += ((ip - anchor) >> K_SEARCH_STRENGTH) + 1;
continue;
}
ip += m_length;
anchor = ip;
if ip <= ilimit {
let index_to_insert = curr + 2;
let h = hash_ptr(data, w.pos_p(index_to_insert), hlog_l, 8);
ctx.hash_long[h] = index_to_insert as u32;
let h = hash_ptr(data, w.pos_p(ip - 2), hlog_l, 8);
ctx.hash_long[h] = (ip - 2) as u32;
let h = hash_ptr(data, w.pos_p(index_to_insert), hlog_s, mls);
ctx.hash_small[h] = index_to_insert as u32;
let h = hash_ptr(data, w.pos_p(ip - 1), hlog_s, mls);
ctx.hash_small[h] = (ip - 1) as u32;
while ip <= ilimit {
let current2 = ip as u32;
let rep_index2 = current2.wrapping_sub(offset_2);
if !(overlap_ok(rep_index2) && offset_2 <= current2 - dict_start_index as u32) {
break;
}
let rep2 = rep_index2 as usize;
if read32(data, w.pos_seg(rep2)) != read32(data, w.pos_p(ip)) {
break;
}
let rep_length2 = count_2segments(
data,
w.pos_p(ip) + 4,
w.pos_seg(rep2) + 4,
w.iend_pos,
w.match_end_pos(rep2),
w.prefix_start_pos,
) + 4;
std::mem::swap(&mut offset_1, &mut offset_2);
store.store_seq(&[], 1, rep_length2 as u32); let h = hash_ptr(data, w.pos_p(ip), hlog_s, mls);
ctx.hash_small[h] = current2;
let h = hash_ptr(data, w.pos_p(ip), hlog_l, 8);
ctx.hash_long[h] = current2;
ip += rep_length2;
anchor = ip;
}
}
}
rep[0] = offset_1;
rep[1] = offset_2;
iend - anchor
}
pub(crate) fn is_rle(src: &[u8]) -> bool {
src.iter().all(|&b| b == src[0])
}
fn write_frame_header(
out: &mut Vec<u8>,
cparams: &CParams,
pledged: Option<u64>,
checksum: bool,
dict_id: u32,
) {
let window_size = 1u64 << cparams.window_log;
let content_size_flag = pledged.is_some();
let pledged_src_size = pledged.unwrap_or(0);
let single_segment = content_size_flag && window_size >= pledged_src_size;
let fcs_code = if content_size_flag {
(pledged_src_size >= 256) as u32
+ (pledged_src_size >= 65536 + 256) as u32
+ (pledged_src_size >= 0xFFFF_FFFF) as u32
} else {
0
};
let did_size_code = (dict_id > 0) as u32 + (dict_id >= 256) as u32 + (dict_id >= 65536) as u32;
let descriptor = (fcs_code << 6) as u8
| ((single_segment as u8) << 5)
| ((checksum as u8) << 2)
| did_size_code as u8;
out.extend_from_slice(&ZSTD_MAGIC.to_le_bytes());
out.push(descriptor);
if !single_segment {
out.push(((cparams.window_log - WINDOWLOG_ABSOLUTEMIN) << 3) as u8);
}
match did_size_code {
0 => {}
1 => out.push(dict_id as u8),
2 => out.extend_from_slice(&(dict_id as u16).to_le_bytes()),
_ => out.extend_from_slice(&dict_id.to_le_bytes()),
}
match fcs_code {
0 => {
if single_segment {
out.push(pledged_src_size as u8);
}
}
1 => out.extend_from_slice(&((pledged_src_size - 256) as u16).to_le_bytes()),
2 => out.extend_from_slice(&(pledged_src_size as u32).to_le_bytes()),
_ => out.extend_from_slice(&pledged_src_size.to_le_bytes()),
}
}
pub(crate) fn push_block_header(out: &mut Vec<u8>, last: bool, block_type: u32, size: usize) {
let v = (last as u32) | (block_type << 1) | ((size as u32) << 3);
out.extend_from_slice(&v.to_le_bytes()[..3]);
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum Stage {
Init,
Ongoing,
Ending,
}
struct MtExtSeqs {
seqs: Vec<crate::ldm::RawSeq>,
cursor: crate::opt::LdmCursor,
}
fn advance_ext_seqs(ext: &mut Option<MtExtSeqs>, block_size: usize) {
if let Some(ext) = ext {
ext.cursor.skip_bytes(&ext.seqs, block_size);
}
}
pub(crate) struct FrameCompressor {
cparams: CParams,
matcher: Matcher,
rep: [u32; 3],
entropy: FseEntropyState,
is_first_block: bool,
consumed: u64,
produced: u64,
window_size: usize,
block_size_max: usize,
pledged: Option<u64>,
checksum: bool,
xxh: crate::xxhash::Xxh64,
stage: Stage,
disable_literal_compression: bool,
window: Window,
ldm: Option<crate::ldm::LdmState>,
ext_seqs: Option<MtExtSeqs>,
window_preloaded: bool,
dict_id: u32,
dict_match_state: Option<DictMatchState>,
post_block_splitter: bool,
}
enum DictMatchState {
Fast(FastDictMatchState),
Dfast(DfastDictMatchState),
Lazy(LazyDictMatchState),
Opt(OptDictMatchState),
}
struct FastDictMatchState {
hash_table: Vec<u32>,
hlog: u32,
content_len: usize,
}
struct DfastDictMatchState {
hash_long: Vec<u32>,
hash_small: Vec<u32>,
hlog_l: u32,
hlog_s: u32,
content_len: usize,
}
struct LazyDictMatchState {
ms: Box<crate::lazy::LazyCtx>,
content_len: usize,
}
struct OptDictMatchState {
ms: Box<crate::opt::OptCtx>,
content_len: usize,
}
impl FrameCompressor {
pub(crate) fn new(level: i32, pledged: Option<u64>, checksum: bool) -> Self {
let cparams = get_cparams(level, pledged.unwrap_or(CONTENTSIZE_UNKNOWN), 0);
Self::from_cparams(cparams, pledged, checksum)
}
pub(crate) fn from_cparams(cparams: CParams, pledged: Option<u64>, checksum: bool) -> Self {
let window_size_u64 = match pledged {
Some(n) => (1u64 << cparams.window_log).min(n).max(1),
None => 1u64 << cparams.window_log,
};
let block_size_max = (BLOCK_SIZE_MAX as u64).min(window_size_u64) as usize;
let matcher = match cparams.strategy {
Strategy::Dfast => Matcher::Dfast(DfastCtx::new(&cparams)),
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2 | Strategy::Btlazy2 => {
Matcher::Lazy(crate::lazy::LazyCtx::new(&cparams))
}
Strategy::Btopt | Strategy::Btultra | Strategy::Btultra2 => {
Matcher::Opt(Box::new(crate::opt::OptCtx::new(&cparams)))
}
_ => Matcher::Fast(FastCtx::new(&cparams)),
};
let disable_literal_compression =
cparams.strategy == Strategy::Fast && cparams.target_length > 0;
FrameCompressor {
cparams,
matcher,
rep: [1, 4, 8],
entropy: FseEntropyState::new(),
is_first_block: true,
consumed: 0,
produced: 0,
window_size: window_size_u64 as usize,
block_size_max,
pledged,
checksum,
xxh: crate::xxhash::Xxh64::new(0),
stage: Stage::Init,
disable_literal_compression,
window: Window::new(),
ldm: crate::ldm::LdmParams::auto(&cparams).map(crate::ldm::LdmState::new),
ext_seqs: None,
window_preloaded: false,
dict_id: 0,
dict_match_state: None,
post_block_splitter: crate::post_split::block_splitter_enabled(&cparams),
}
}
pub(crate) fn window_size(&self) -> usize {
self.window_size
}
pub(crate) fn block_size_max(&self) -> usize {
self.block_size_max
}
pub(crate) fn cdict_attach_overflow(&self, block_end: usize) -> bool {
self.dict_match_state.is_some()
&& (block_end + self.window.seg_bias as usize) as u64
> (1u64 << self.cparams.window_log)
}
pub(crate) fn compress_continue(
&mut self,
out: &mut Vec<u8>,
data: &[u8],
chunk_start: usize,
chunk_end: usize,
last_frame_chunk: bool,
) -> Result<(), Error> {
let out_start = out.len();
if self.stage == Stage::Init {
write_frame_header(
out,
&self.cparams,
self.pledged,
self.checksum,
self.dict_id,
);
self.stage = Stage::Ongoing;
}
if chunk_start == chunk_end {
self.produced += (out.len() - out_start) as u64;
return Ok(());
}
if self.window_preloaded {
self.window_preloaded = false;
} else if !self.window.update(chunk_start, chunk_end) {
match &mut self.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = self.window.dict_limit as usize,
Matcher::Opt(ctx) => ctx.next_to_update = self.window.dict_limit as usize,
_ => {}
}
}
if let Some(ldm) = &mut self.ldm {
ldm.window.update(chunk_start, chunk_end);
}
if self.checksum {
self.xxh.update(&data[chunk_start..chunk_end]);
}
let cparams = self.cparams;
let mut savings: i64 = self.consumed as i64 - self.produced as i64;
let mut pos = chunk_start;
while pos < chunk_end {
let remaining = chunk_end - pos;
let block_size = if remaining < BLOCK_SIZE_MAX || self.block_size_max < BLOCK_SIZE_MAX {
remaining.min(self.block_size_max)
} else if savings < 3 {
BLOCK_SIZE_MAX
} else {
const SPLIT_LEVELS: [usize; 10] = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4];
let split_level = SPLIT_LEVELS[cparams.strategy as usize];
pre_split::split_block(&data[pos..pos + BLOCK_SIZE_MAX], split_level)
};
let last_block = last_frame_chunk && block_size == remaining;
let block = &data[pos..pos + block_size];
let max_dist = 1u32 << cparams.window_log;
if self.window.needs_overflow_correction(pos + block_size) {
let cycle_log = cycle_log(cparams.chain_log, cparams.strategy);
let correction = self.window.correct_overflow(cycle_log, max_dist, pos);
match &mut self.matcher {
Matcher::Fast(ctx) => ctx.reduce_indices(correction),
Matcher::Dfast(ctx) => ctx.reduce_indices(correction),
Matcher::Lazy(ctx) => ctx.reduce_indices(correction),
Matcher::Opt(ctx) => ctx.reduce_indices(correction),
}
}
let block_end_idx = (pos + block_size) as u32 + self.window.seg_bias;
self.window.check_dict_validity(block_end_idx, max_dist);
let block_start_idx = pos as u32 + self.window.seg_bias;
self.window.enforce_max_dist(block_start_idx, max_dist);
match &mut self.matcher {
Matcher::Lazy(ctx) => {
ctx.next_to_update = ctx.next_to_update.max(self.window.low_limit as usize)
}
Matcher::Opt(ctx) => {
ctx.next_to_update = ctx.next_to_update.max(self.window.low_limit as usize)
}
_ => {}
}
let mut c_size_kind: BlockKind;
let mut body: Vec<u8> = Vec::new();
if block_size < MIN_CBLOCK_SIZE + BLOCK_HEADER_SIZE + 1 + 1 {
c_size_kind = BlockKind::Raw;
} else {
match &mut self.matcher {
Matcher::Lazy(ctx) => ctx.limit_update(pos + self.window.seg_bias as usize),
Matcher::Opt(ctx) => ctx.limit_update(pos + self.window.seg_bias as usize),
_ => {}
}
let mut store = SeqStore::new();
let mut next_rep = self.rep;
let last_ll_size = match &mut self.matcher {
Matcher::Fast(ctx) => {
if let Some(DictMatchState::Fast(dms)) = &self.dict_match_state {
compress_block_fast_dict_match_state(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
dms.content_len,
&dms.hash_table,
dms.hlog,
)
} else if self.window.has_ext_dict() {
compress_block_fast_extdict(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&self.window,
)
} else {
compress_block_fast(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
self.window.seg_bias as usize,
self.window.dict_limit as usize,
)
}
}
Matcher::Dfast(ctx) => {
if let Some(DictMatchState::Dfast(dms)) = &self.dict_match_state {
compress_block_dfast_dict_match_state(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
dms.content_len,
&dms.hash_long,
&dms.hash_small,
dms.hlog_l,
dms.hlog_s,
)
} else if self.window.has_ext_dict() {
compress_block_dfast_extdict(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&self.window,
)
} else {
compress_block_dfast(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
self.window.seg_bias as usize,
self.window.dict_limit as usize,
)
}
}
Matcher::Lazy(ctx) => {
if let Some(DictMatchState::Lazy(dms)) = &self.dict_match_state {
crate::lazy::compress_block_lazy_dict_match_state(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&self.window,
&dms.ms,
dms.content_len,
)
} else if self.window.has_ext_dict() {
crate::lazy::compress_block_lazy_extdict(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&self.window,
)
} else {
crate::lazy::compress_block_lazy(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&self.window,
)
}
}
Matcher::Opt(ctx) => {
let ext_dict = self.window.has_ext_dict();
let opt_dms = match &self.dict_match_state {
Some(DictMatchState::Opt(d)) => Some(crate::opt::OptDms {
ms: d.ms.as_ref(),
content_len: d.content_len,
}),
_ => None,
};
let mut ldm_seqs: Vec<crate::ldm::RawSeq> = Vec::new();
if let Some(ldm) = &mut self.ldm {
let capacity =
self.block_size_max / ldm.params.min_match_length as usize;
crate::ldm::generate_sequences(
ldm,
&mut ldm_seqs,
capacity,
data,
pos,
pos + block_size,
)?;
}
let (ldm_input, ldm_cursor): (
Option<&[crate::ldm::RawSeq]>,
crate::opt::LdmCursor,
) = if self.ldm.is_some() {
(Some(&ldm_seqs), crate::opt::LdmCursor::default())
} else if let Some(ext) = &self.ext_seqs {
(Some(&ext.seqs), ext.cursor)
} else {
(None, crate::opt::LdmCursor::default())
};
crate::opt::compress_block_opt(
ctx,
&mut store,
&mut next_rep,
data,
pos,
pos + block_size,
&mut self.window,
ext_dict,
ldm_input,
ldm_cursor,
Some(&self.entropy),
opt_dms.as_ref(),
)
}
};
let lits_from = block_size - last_ll_size;
store.store_last_literals(&block[lits_from..]);
if self.post_block_splitter {
let c_size = crate::post_split::compress_block_split(
out,
&mut store,
&mut self.entropy,
&mut self.rep,
next_rep,
cparams.strategy as i32,
block,
last_block,
self.is_first_block,
)?;
savings += block_size as i64 - c_size as i64;
advance_ext_seqs(&mut self.ext_seqs, block_size);
pos += block_size;
self.is_first_block = false;
continue;
}
match sequences_encode::entropy_compress_seq_store(
&store,
&self.entropy,
cparams.strategy as i32,
self.disable_literal_compression,
block_size,
)? {
None => c_size_kind = BlockKind::Raw,
Some((b, next_entropy)) => {
body = b;
c_size_kind = BlockKind::Compressed;
if !self.is_first_block && body.len() < 25 && is_rle(block) {
c_size_kind = BlockKind::Rle;
}
if c_size_kind == BlockKind::Compressed {
self.rep = next_rep;
self.entropy = next_entropy;
}
}
}
}
let c_size = match c_size_kind {
BlockKind::Raw => {
push_block_header(out, last_block, 0, block_size);
out.extend_from_slice(block);
BLOCK_HEADER_SIZE + block_size
}
BlockKind::Rle => {
push_block_header(out, last_block, 1, block_size);
out.push(block[0]);
BLOCK_HEADER_SIZE + 1
}
BlockKind::Compressed => {
push_block_header(out, last_block, 2, body.len());
out.extend_from_slice(&body);
BLOCK_HEADER_SIZE + body.len()
}
};
savings += block_size as i64 - c_size as i64;
advance_ext_seqs(&mut self.ext_seqs, block_size);
pos += block_size;
self.is_first_block = false;
}
if last_frame_chunk {
self.stage = Stage::Ending;
}
self.consumed += (chunk_end - chunk_start) as u64;
self.produced += (out.len() - out_start) as u64;
if self.pledged.is_some_and(|n| self.consumed > n) {
return Err(Error::Encode("pledged source size exceeded"));
}
Ok(())
}
pub(crate) fn compress_end(
&mut self,
out: &mut Vec<u8>,
data: &[u8],
chunk_start: usize,
chunk_end: usize,
) -> Result<(), Error> {
self.compress_continue(out, data, chunk_start, chunk_end, true)?;
debug_assert!(self.stage != Stage::Init, "header is written above");
if self.stage != Stage::Ending {
push_block_header(out, true, 0, 0);
}
if self.checksum {
out.extend_from_slice(&(self.xxh.digest() as u32).to_le_bytes());
}
if self.pledged.is_some_and(|n| self.consumed != n) {
return Err(Error::Encode("pledged source size not honored"));
}
Ok(())
}
}
pub fn compress(src: &[u8], level: i32) -> Result<Vec<u8>, Error> {
compress_maybe_checksum(src, level, false)
}
pub fn compress_with_dict(src: &[u8], dict: &[u8], level: i32) -> Result<Vec<u8>, Error> {
if dict.len() as u64 + src.len() as u64 >= u64::from(u32::MAX) - 2 {
return Err(Error::Encode("inputs >= 4 GiB are not supported yet"));
}
let cparams = get_cparams(level, src.len() as u64, dict.len() as u64);
let pledged = Some(src.len() as u64);
let mut fc = FrameCompressor::from_cparams(cparams, pledged, false);
let content: &[u8] = if dict.len() >= 8 && read32(dict, 0) == MAGIC_DICTIONARY {
let seed = crate::dict_encode::load_c_entropy(dict)?;
fc.entropy = seed.entropy;
fc.rep = seed.rep;
fc.dict_id = seed.dict_id;
&dict[seed.entropy_size..]
} else if dict.len() < 8 {
let mut out = Vec::with_capacity(src.len() + (src.len() >> 8) + 64);
fc.compress_end(&mut out, src, 0, src.len())?;
return Ok(out);
} else {
dict
};
if fc.ldm.is_some() {
return Err(Error::Encode(
"long-distance matching with a dictionary is not supported yet",
));
}
let content_len = content.len();
let src_len = src.len();
let mut data = Vec::with_capacity(content_len + src_len);
data.extend_from_slice(content);
data.extend_from_slice(src);
prime_raw_prefix(&mut fc, &data, content_len);
let mut out = Vec::with_capacity(src_len + (src_len >> 8) + 64);
fc.compress_end(&mut out, &data, content_len, content_len + src_len)?;
Ok(out)
}
fn prime_raw_prefix(fc: &mut FrameCompressor, buf: &[u8], prefix_len: usize) {
if prefix_len > HASH_READ_SIZE {
match &mut fc.matcher {
Matcher::Fast(ctx) => fill_fast_hash_table_for_cctx(ctx, buf, prefix_len),
Matcher::Dfast(ctx) => fill_dfast_hash_tables_for_cctx(ctx, buf, prefix_len),
Matcher::Lazy(ctx) => ctx.load_dictionary(buf, prefix_len),
Matcher::Opt(ctx) => ctx.load_dictionary(buf, prefix_len),
}
}
fc.window = Window::preloaded_ext_dict(prefix_len, buf.len() - prefix_len);
fc.window_preloaded = true;
match &mut fc.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
Matcher::Opt(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
_ => {}
}
}
fn prime_contiguous_prefix(fc: &mut FrameCompressor, buf: &[u8], prefix_len: usize) {
if prefix_len > HASH_READ_SIZE {
match &mut fc.matcher {
Matcher::Fast(ctx) => fill_fast_hash_table_for_cctx(ctx, buf, prefix_len),
Matcher::Dfast(ctx) => fill_dfast_hash_tables_for_cctx(ctx, buf, prefix_len),
Matcher::Lazy(ctx) => ctx.load_dictionary(buf, prefix_len),
Matcher::Opt(ctx) => ctx.load_dictionary(buf, prefix_len),
}
}
fc.window = Window::preloaded_contiguous_prefix(buf.len());
fc.window_preloaded = true;
let seg_start_idx = WINDOW_START_INDEX + prefix_len;
match &mut fc.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = seg_start_idx,
Matcher::Opt(ctx) => ctx.next_to_update = seg_start_idx,
_ => {}
}
}
pub(crate) const ZSTDMT_JOBSIZE_MIN: u64 = 512 * 1024;
const ZSTDMT_JOBSIZE_MAX: u64 = 1024 * 1024 * 1024;
const ZSTDMT_JOBLOG_MAX: u32 = 30;
fn mt_target_job_log(cp: &CParams, ldm: bool) -> u32 {
let job_log = if ldm {
21.max(cycle_log(cp.chain_log, cp.strategy) + 3)
} else {
20.max(cp.window_log + 2)
};
job_log.min(ZSTDMT_JOBLOG_MAX)
}
fn mt_overlap_size(cp: &CParams, overlap_log: i32, ldm: bool) -> usize {
let default_overlap_log = match cp.strategy {
Strategy::Btultra2 => 9,
Strategy::Btultra | Strategy::Btopt => 8,
Strategy::Btlazy2 | Strategy::Lazy2 => 7,
_ => 6,
};
let eff = if overlap_log == 0 {
default_overlap_log
} else {
overlap_log
};
let overlap_rlog = 9 - eff;
let ov_log = if ldm {
(cp.window_log as i32).min(mt_target_job_log(cp, true) as i32 - 2) - overlap_rlog
} else if overlap_rlog >= 8 {
0
} else {
cp.window_log as i32 - overlap_rlog
};
if ov_log <= 0 { 0 } else { 1usize << ov_log }
}
fn mt_target_section_size(cp: &CParams, job_size: u64, overlap_size: usize, ldm: bool) -> usize {
let mut tss = if job_size != 0 {
job_size.clamp(ZSTDMT_JOBSIZE_MIN, ZSTDMT_JOBSIZE_MAX) as usize
} else {
1usize << mt_target_job_log(cp, ldm)
};
if tss < overlap_size {
tss = overlap_size;
}
tss
}
#[allow(clippy::too_many_arguments)]
fn compress_mt_job(
out: &mut Vec<u8>,
cparams: CParams,
buf: &[u8],
prefix_len: usize,
pledged: Option<u64>,
write_header: bool,
is_last: bool,
checksum: bool,
ext_seqs: Option<Vec<crate::ldm::RawSeq>>,
) -> Result<(), Error> {
let mut fc = FrameCompressor::from_cparams(cparams, pledged, checksum);
fc.ldm = None;
fc.ext_seqs = ext_seqs.map(|seqs| MtExtSeqs {
seqs,
cursor: crate::opt::LdmCursor::default(),
});
if prefix_len > 0 {
prime_contiguous_prefix(&mut fc, buf, prefix_len);
}
if !write_header {
fc.stage = Stage::Ongoing;
}
let seg_start = prefix_len;
let seg_end = buf.len();
if is_last {
fc.compress_end(out, buf, seg_start, seg_end)
} else {
fc.compress_continue(out, buf, seg_start, seg_end, false)
}
}
pub fn compress_mt(
src: &[u8],
level: i32,
nb_workers: u32,
job_size: u64,
overlap_log: i32,
checksum: bool,
) -> Result<Vec<u8>, Error> {
if src.len() as u64 >= u64::from(u32::MAX) - 2 {
return Err(Error::Encode("inputs >= 4 GiB are not supported yet"));
}
let src_len = src.len();
if nb_workers == 0 || src_len as u64 <= ZSTDMT_JOBSIZE_MIN {
return compress_maybe_checksum(src, level, checksum);
}
let cparams = get_cparams(level, src_len as u64, 0);
let ldm_params = crate::ldm::LdmParams::auto(&cparams);
let ldm_enabled = ldm_params.is_some();
let overlap_size = mt_overlap_size(&cparams, overlap_log, ldm_enabled);
let section_size = mt_target_section_size(&cparams, job_size, overlap_size, ldm_enabled);
let max_dict_size = 1u64 << ((cparams.hash_log + 3).max(cparams.chain_log + 1)).min(31);
if overlap_size as u64 > max_dict_size {
return Err(Error::Encode(
"multithreaded overlap larger than the indexable dictionary size \
is not supported yet",
));
}
if src_len < section_size {
return compress_maybe_checksum(src, level, checksum);
}
let mut out = Vec::with_capacity(src_len + (src_len >> 8) + 64);
let mut ldm_state = ldm_params.map(crate::ldm::LdmState::new);
let mut seg_start = 0usize;
let mut first = true;
let mut job_count = 0usize;
loop {
let seg_end = (seg_start + section_size).min(src_len);
let seg_len = seg_end - seg_start;
let more_after = seg_end < src_len;
let is_last = !more_after;
let prefix_len = if first {
0
} else {
overlap_size.min(seg_start)
};
let buf = &src[seg_start - prefix_len..seg_end];
let pledged = Some(if first {
src_len as u64
} else {
seg_len as u64
});
let ext_seqs = if let Some(ldm) = &mut ldm_state {
ldm.window.update(seg_start, seg_end);
let cap = section_size / ldm.params.min_match_length as usize;
let mut seqs = Vec::new();
crate::ldm::generate_sequences(ldm, &mut seqs, cap, src, seg_start, seg_end)?;
Some(seqs)
} else {
None
};
compress_mt_job(
&mut out,
cparams,
buf,
prefix_len,
pledged,
first,
is_last,
checksum && first,
ext_seqs,
)?;
job_count += 1;
if !more_after {
break;
}
seg_start = seg_end;
first = false;
}
if checksum && job_count > 1 {
let mut xxh = crate::xxhash::Xxh64::new(0);
xxh.update(src);
out.extend_from_slice(&(xxh.digest() as u32).to_le_bytes());
}
Ok(out)
}
fn compress_maybe_checksum(src: &[u8], level: i32, checksum: bool) -> Result<Vec<u8>, Error> {
if src.len() as u64 >= u64::from(u32::MAX) - 2 {
return Err(Error::Encode("inputs >= 4 GiB are not supported yet"));
}
let mut fc = FrameCompressor::new(level, Some(src.len() as u64), checksum);
let mut out = Vec::with_capacity(src.len() + (src.len() >> 8) + 64);
fc.compress_end(&mut out, src, 0, src.len())?;
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub fn compress_mt_with_dict(
src: &[u8],
dict: &[u8],
level: i32,
nb_workers: u32,
job_size: u64,
overlap_log: i32,
checksum: bool,
) -> Result<Vec<u8>, Error> {
if dict.len() as u64 + src.len() as u64 >= u64::from(u32::MAX) - 2 {
return Err(Error::Encode("inputs >= 4 GiB are not supported yet"));
}
if nb_workers == 0 || src.len() as u64 <= ZSTDMT_JOBSIZE_MIN {
return compress_with_cdict_checksum(src, dict, level, checksum);
}
let mut state = MtStreamState::new(
level,
job_size,
overlap_log,
checksum,
Some(src.len() as u64),
Some(dict),
)?;
let mut out = Vec::with_capacity(src.len() + (src.len() >> 8) + 64);
state.end(src, &mut out)?;
Ok(out)
}
pub(crate) struct MtStreamState {
cparams: CParams,
section_size: usize,
overlap_size: usize,
buf: Vec<u8>,
prefix_len: usize,
filled: usize,
first: bool,
checksum: bool,
xxh: crate::xxhash::Xxh64,
job_count: usize,
frame_pledged: Option<u64>,
dict: Option<MtDictJob0>,
ldm_state: Option<crate::ldm::LdmState>,
ldm_history: Vec<u8>,
ldm_base: usize,
input_pos: usize,
}
struct MtDictJob0 {
fc0: FrameCompressor,
content: Vec<u8>,
}
impl MtStreamState {
pub(crate) fn new(
level: i32,
job_size: u64,
overlap_log: i32,
checksum: bool,
frame_pledged: Option<u64>,
dict: Option<&[u8]>,
) -> Result<Self, Error> {
let attach = match dict {
Some(d) => {
let cdict_strategy = get_cparams_create_cdict(level, d.len() as u64).strategy;
let cutoff = cdict_attach_cutoff(cdict_strategy);
match frame_pledged {
Some(p) => p as usize <= cutoff,
None => true, }
}
None => true, };
let frame_dict_size = match dict {
Some(d) if !attach => d.len() as u64,
_ => 0,
};
let cparams = get_cparams(
level,
frame_pledged.unwrap_or(CONTENTSIZE_UNKNOWN),
frame_dict_size,
);
let ldm_params = crate::ldm::LdmParams::auto(&cparams);
let ldm_enabled = ldm_params.is_some();
let overlap_size = mt_overlap_size(&cparams, overlap_log, ldm_enabled);
let section_size = mt_target_section_size(&cparams, job_size, overlap_size, ldm_enabled);
let max_dict_size = 1u64 << ((cparams.hash_log + 3).max(cparams.chain_log + 1)).min(31);
if overlap_size as u64 > max_dict_size {
return Err(Error::Encode(
"multithreaded overlap larger than the indexable dictionary size \
is not supported yet",
));
}
let dict_job0 = match dict {
None => None,
Some(dict) => Some(Self::build_dict_job0(
dict,
level,
cparams.window_log,
frame_pledged,
checksum,
attach,
crate::post_split::block_splitter_enabled(&cparams),
)?),
};
Ok(MtStreamState {
cparams,
section_size,
overlap_size,
buf: vec![0u8; overlap_size + section_size],
prefix_len: 0,
filled: 0,
first: true,
checksum,
xxh: crate::xxhash::Xxh64::new(0),
job_count: 0,
frame_pledged,
dict: dict_job0,
ldm_state: ldm_params.map(crate::ldm::LdmState::new),
ldm_history: Vec::new(),
ldm_base: 0,
input_pos: 0,
})
}
fn build_dict_job0(
dict: &[u8],
level: i32,
frame_window_log: u32,
frame_pledged: Option<u64>,
checksum: bool,
attach: bool,
post_block_splitter: bool,
) -> Result<MtDictJob0, Error> {
let (content, entropy, rep, dict_id) = parse_cdict(dict)?;
if content.len() <= HASH_READ_SIZE {
return Err(Error::Encode(
"multithreaded streaming with a <= 8-byte dictionary is not supported yet",
));
}
let content_len = content.len();
let cdict_cparams = get_cparams_create_cdict(level, dict.len() as u64);
let mut fc0 = if attach {
let mut fc0 = attach_cdict_compressor(
content,
cdict_cparams,
frame_pledged,
checksum,
frame_window_log,
);
fc0.window = Window::streaming_attached_dict(content_len);
fc0
} else {
let mut working = cdict_cparams;
working.window_log = frame_window_log;
let mut fc0 = FrameCompressor::from_cparams(working, frame_pledged, checksum);
match cdict_cparams.strategy {
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2 | Strategy::Btlazy2 => {
let cdict_uses_row = crate::lazy::use_row_match_finder(&cdict_cparams);
let mut ctx =
crate::lazy::LazyCtx::with_row_match_finder(&working, cdict_uses_row);
ctx.use_cdict_hash_salt();
ctx.load_dictionary(content, content_len);
fc0.matcher = Matcher::Lazy(ctx);
}
Strategy::Btopt | Strategy::Btultra | Strategy::Btultra2 => {
if let Matcher::Opt(ctx) = &mut fc0.matcher {
ctx.load_dictionary(content, content_len);
}
}
_ => match &mut fc0.matcher {
Matcher::Fast(ctx) => {
fill_fast_hash_table_for_cctx_full(ctx, content, content_len)
}
Matcher::Dfast(ctx) => {
fill_dfast_hash_tables_for_cctx_full(ctx, content, content_len)
}
_ => {}
},
}
fc0.window = Window::streaming_ext_dict(content_len);
fc0
};
fc0.entropy = entropy;
fc0.rep = rep;
fc0.dict_id = dict_id;
fc0.post_block_splitter = post_block_splitter;
fc0.ldm = None;
match &mut fc0.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = fc0.window.dict_limit as usize,
Matcher::Opt(ctx) => ctx.next_to_update = fc0.window.dict_limit as usize,
_ => {}
}
Ok(MtDictJob0 {
fc0,
content: content.to_vec(),
})
}
pub(crate) fn push(&mut self, mut input: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
while !input.is_empty() {
let n = (self.section_size - self.filled).min(input.len());
let dst = self.prefix_len + self.filled;
self.buf[dst..dst + n].copy_from_slice(&input[..n]);
self.filled += n;
input = &input[n..];
if self.filled == self.section_size {
self.emit(out, false)?;
}
}
Ok(())
}
pub(crate) fn end(&mut self, mut input: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
loop {
let n = (self.section_size - self.filled).min(input.len());
let dst = self.prefix_len + self.filled;
self.buf[dst..dst + n].copy_from_slice(&input[..n]);
self.filled += n;
input = &input[n..];
if input.is_empty() {
self.emit(out, true)?;
break;
}
self.emit(out, false)?;
}
if self.checksum && self.job_count > 1 {
out.extend_from_slice(&(self.xxh.digest() as u32).to_le_bytes());
}
Ok(())
}
pub(crate) fn flush(&mut self, out: &mut Vec<u8>) -> Result<(), Error> {
if self.filled > 0 {
self.emit(out, false)?;
}
Ok(())
}
fn emit(&mut self, out: &mut Vec<u8>, is_last: bool) -> Result<(), Error> {
if self.checksum {
let seg = &self.buf[self.prefix_len..self.prefix_len + self.filled];
self.xxh.update(seg);
}
if self.filled == 0 && !self.first {
debug_assert!(is_last);
push_block_header(out, true, 0, 0);
self.job_count += 1;
return Ok(());
}
let job_filled = self.filled;
let ext_seqs = if let Some(ldm) = &mut self.ldm_state {
let max_dist = 1usize << self.cparams.window_log;
let keep_from = self.input_pos.saturating_sub(max_dist);
if keep_from > self.ldm_base {
self.ldm_history.drain(..keep_from - self.ldm_base);
self.ldm_base = keep_from;
}
self.ldm_history
.extend_from_slice(&self.buf[self.prefix_len..self.prefix_len + job_filled]);
let seg_bias = (self.ldm_base + WINDOW_START_INDEX) as u32;
ldm.window.seg_bias = seg_bias;
ldm.window.dict_bias = seg_bias;
let chunk_start = self.input_pos - self.ldm_base;
let chunk_end = chunk_start + job_filled;
let cap = self.section_size / ldm.params.min_match_length as usize;
let mut seqs = Vec::new();
crate::ldm::generate_sequences(
ldm,
&mut seqs,
cap,
&self.ldm_history,
chunk_start,
chunk_end,
)?;
Some(seqs)
} else {
None
};
if self.first && self.dict.is_some() {
let d = self.dict.take().expect("dict present");
let cl = d.content.len();
let mut data = Vec::with_capacity(cl + job_filled);
data.extend_from_slice(&d.content);
data.extend_from_slice(&self.buf[..job_filled]);
let mut fc0 = d.fc0;
fc0.ext_seqs = ext_seqs.map(|seqs| MtExtSeqs {
seqs,
cursor: crate::opt::LdmCursor::default(),
});
if is_last {
fc0.compress_end(out, &data, cl, cl + job_filled)?;
} else {
fc0.compress_continue(out, &data, cl, cl + job_filled, false)?;
}
} else {
let total = self.prefix_len + job_filled;
let pledged = if self.first {
self.frame_pledged
} else {
Some(job_filled as u64)
};
compress_mt_job(
out,
self.cparams,
&self.buf[..total],
self.prefix_len,
pledged,
self.first,
is_last,
self.checksum && self.first,
ext_seqs,
)?;
}
self.input_pos += job_filled;
self.job_count += 1;
if !is_last {
let new_prefix = self.overlap_size.min(job_filled);
let seg_end = self.prefix_len + job_filled;
self.buf.copy_within(seg_end - new_prefix..seg_end, 0);
self.prefix_len = new_prefix;
}
self.filled = 0;
self.first = false;
Ok(())
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn parse_cdict(dict: &[u8]) -> Result<(&[u8], FseEntropyState, [u32; 3], u32), Error> {
if dict.len() >= 8 && read32(dict, 0) == MAGIC_DICTIONARY {
let seed = crate::dict_encode::load_c_entropy(dict)?;
Ok((
&dict[seed.entropy_size..],
seed.entropy,
seed.rep,
seed.dict_id,
))
} else {
Ok((dict, FseEntropyState::new(), [1, 4, 8], 0))
}
}
pub(crate) fn attach_cdict_compressor(
content: &[u8],
cdict_cparams: CParams,
pledged: Option<u64>,
checksum: bool,
working_window_log: u32,
) -> FrameCompressor {
let content_len = content.len();
let src_size = pledged.unwrap_or(CONTENTSIZE_UNKNOWN);
let mut working = adjust_cparams_internal(cdict_cparams, src_size, 0, CParamMode::NoAttachDict);
working.window_log = working_window_log;
let mut fc = FrameCompressor::from_cparams(working, pledged, checksum);
let mls = cdict_cparams.min_match.clamp(4, 7);
fc.dict_match_state = Some(match cdict_cparams.strategy {
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2 | Strategy::Btlazy2 => {
let cdict_uses_row = crate::lazy::use_row_match_finder(&cdict_cparams);
let mut dms =
crate::lazy::LazyCtx::with_row_match_finder(&cdict_cparams, cdict_uses_row);
dms.use_cdict_hash_salt();
dms.load_dictionary(content, content_len);
fc.matcher = Matcher::Lazy(crate::lazy::LazyCtx::with_row_match_finder(
&working,
cdict_uses_row,
));
DictMatchState::Lazy(LazyDictMatchState {
ms: Box::new(dms),
content_len,
})
}
Strategy::Btopt | Strategy::Btultra | Strategy::Btultra2 => {
let mut dms = crate::opt::OptCtx::new(&cdict_cparams);
dms.load_dictionary(content, content_len);
DictMatchState::Opt(OptDictMatchState {
ms: Box::new(dms),
content_len,
})
}
Strategy::Dfast => {
let mut hash_long = vec![0u32; 1usize << cdict_cparams.hash_log];
let mut hash_small = vec![0u32; 1usize << cdict_cparams.chain_log];
fill_dfast_hash_tables_for_cdict(
&mut hash_long,
&mut hash_small,
content,
content_len,
cdict_cparams.hash_log,
cdict_cparams.chain_log,
mls,
);
DictMatchState::Dfast(DfastDictMatchState {
hash_long,
hash_small,
hlog_l: cdict_cparams.hash_log,
hlog_s: cdict_cparams.chain_log,
content_len,
})
}
_ => {
let mut hash_table = vec![0u32; 1usize << cdict_cparams.hash_log];
fill_fast_hash_table_for_cdict(
&mut hash_table,
content,
content_len,
cdict_cparams.hash_log,
mls,
);
DictMatchState::Fast(FastDictMatchState {
hash_table,
hlog: cdict_cparams.hash_log,
content_len,
})
}
});
fc
}
pub(crate) fn cdict_attach_cutoff(strategy: Strategy) -> usize {
match strategy {
Strategy::Greedy
| Strategy::Lazy
| Strategy::Lazy2
| Strategy::Btlazy2
| Strategy::Btopt => 32 * 1024,
Strategy::Dfast => 16 * 1024,
_ => 8 * 1024,
}
}
pub(crate) struct StreamCdictInit {
pub(crate) fc: FrameCompressor,
pub(crate) in_buff: Vec<u8>,
pub(crate) content_len: usize,
}
pub(crate) fn streaming_cdict_init(
dict: &[u8],
level: i32,
pledged: Option<u64>,
checksum: bool,
) -> Result<StreamCdictInit, Error> {
let (content, entropy, rep, dict_id) = parse_cdict(dict)?;
let content_len = content.len();
if content_len <= HASH_READ_SIZE {
return Err(Error::Encode(
"CDict (Path B): dictionaries with <= 8 bytes of content are not supported yet",
));
}
let cdict_cparams = get_cparams_create_cdict(level, dict.len() as u64);
if pledged.is_some_and(|p| p as usize > cdict_attach_cutoff(cdict_cparams.strategy)) {
return Err(Error::Encode(
"streaming with a CDict above the attach cutoff (copy path) is not supported yet",
));
}
let working_window_log =
get_cparams(level, pledged.unwrap_or(CONTENTSIZE_UNKNOWN), 0).window_log;
let mut fc = attach_cdict_compressor(
content,
cdict_cparams,
pledged,
checksum,
working_window_log,
);
fc.window = Window::streaming_attached_dict(content_len);
fc.entropy = entropy;
fc.rep = rep;
fc.dict_id = dict_id;
fc.post_block_splitter = crate::post_split::block_splitter_enabled(&get_cparams(
level,
pledged.unwrap_or(CONTENTSIZE_UNKNOWN),
0,
));
match &mut fc.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
Matcher::Opt(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
_ => {}
}
let block_size = fc.block_size_max();
let window_size = fc.window_size();
let mut in_buff = vec![0u8; content_len + window_size + block_size];
in_buff[..content_len].copy_from_slice(content);
Ok(StreamCdictInit {
fc,
in_buff,
content_len,
})
}
pub fn compress_with_cdict(src: &[u8], dict: &[u8], level: i32) -> Result<Vec<u8>, Error> {
compress_with_cdict_checksum(src, dict, level, false)
}
fn compress_with_cdict_checksum(
src: &[u8],
dict: &[u8],
level: i32,
checksum: bool,
) -> Result<Vec<u8>, Error> {
if dict.len() as u64 + src.len() as u64 >= u64::from(u32::MAX) - 2 {
return Err(Error::Encode("inputs >= 4 GiB are not supported yet"));
}
let cdict_cparams = get_cparams_create_cdict(level, dict.len() as u64);
let (content, entropy, rep, dict_id) = parse_cdict(dict)?;
let content_len = content.len();
let src_len = src.len();
let src_size = src_len as u64;
if content_len <= HASH_READ_SIZE {
return Err(Error::Encode(
"CDict (Path B): dictionaries with <= 8 bytes of content are not supported yet",
));
}
let attach = src_len <= cdict_attach_cutoff(cdict_cparams.strategy);
let pledged = Some(src_size);
let mut data = Vec::with_capacity(content_len + src_len);
data.extend_from_slice(content);
data.extend_from_slice(src);
let mut fc = if attach {
let working_window_log = get_cparams(level, src_size, 0).window_log;
let mut fc = attach_cdict_compressor(
content,
cdict_cparams,
pledged,
checksum,
working_window_log,
);
fc.window = Window::preloaded_attached_dict(content_len, src_len);
fc.window_preloaded = true;
fc
} else {
let mut working = cdict_cparams;
working.window_log = get_cparams(level, src_size, dict.len() as u64).window_log;
let mut fc = FrameCompressor::from_cparams(working, pledged, checksum);
match cdict_cparams.strategy {
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2 | Strategy::Btlazy2 => {
let cdict_uses_row = crate::lazy::use_row_match_finder(&cdict_cparams);
let mut ctx = crate::lazy::LazyCtx::with_row_match_finder(&working, cdict_uses_row);
ctx.use_cdict_hash_salt();
ctx.load_dictionary(&data, content_len);
fc.matcher = Matcher::Lazy(ctx);
}
Strategy::Btopt | Strategy::Btultra | Strategy::Btultra2 => {
if let Matcher::Opt(ctx) = &mut fc.matcher {
ctx.load_dictionary(&data, content_len);
}
}
_ => match &mut fc.matcher {
Matcher::Fast(ctx) => fill_fast_hash_table_for_cctx_full(ctx, &data, content_len),
Matcher::Dfast(ctx) => {
fill_dfast_hash_tables_for_cctx_full(ctx, &data, content_len)
}
_ => {}
},
}
fc.window = Window::preloaded_ext_dict(content_len, src_len);
fc.window_preloaded = true;
fc
};
if fc.ldm.is_some() {
return Err(Error::Encode(
"long-distance matching with a CDict is not supported yet",
));
}
fc.entropy = entropy;
fc.rep = rep;
fc.dict_id = dict_id;
match &mut fc.matcher {
Matcher::Lazy(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
Matcher::Opt(ctx) => ctx.next_to_update = fc.window.dict_limit as usize,
_ => {}
}
let frame_dict_size = if attach { 0 } else { dict.len() as u64 };
fc.post_block_splitter =
crate::post_split::block_splitter_enabled(&get_cparams(level, src_size, frame_dict_size));
let mut out = Vec::with_capacity(src_len + (src_len >> 8) + 64);
fc.compress_end(&mut out, &data, content_len, content_len + src_len)?;
Ok(out)
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum BlockKind {
Raw,
Rle,
Compressed,
}
enum Matcher {
Fast(FastCtx),
Dfast(DfastCtx),
Lazy(crate::lazy::LazyCtx),
Opt(Box<crate::opt::OptCtx>),
}