use crate::bit::BitCStream;
use crate::block::BlockType;
use crate::compressed::{ll_code, ml_code, of_code, offset_value_for, resolve_offset};
use crate::dict::Dictionary;
use crate::error::Error;
use crate::frame::{BLOCKSIZE_MAX, MAGIC};
use crate::fse::{self, FseCTable};
use crate::huffman::{self, HuffCTable, HuffUpdate};
use crate::params::{compression_params, CompressionParameters, Strategy};
use crate::xxh64::{content_checksum, Xxh64};
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
pub fn compress(src: &[u8], level: i32) -> Result<Vec<u8>, Error> {
compress_with(
src,
CompressOptions {
level,
checksum: true,
},
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompressOptions {
pub level: i32,
pub checksum: bool,
}
impl Default for CompressOptions {
fn default() -> Self {
Self {
level: crate::DEFAULT_CLEVEL,
checksum: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AdvancedOptions {
pub ldm: crate::ldm::LdmParams,
pub rsyncable: bool,
pub target_cblock_size: u32,
pub nb_workers: u32,
pub job_size: usize,
pub overlap_log: u32,
pub prime_only: bool,
}
#[cfg(feature = "alloc")]
pub fn compress_with(src: &[u8], opts: CompressOptions) -> Result<Vec<u8>, Error> {
let params = compression_params(opts.level, Some(src.len() as u64))?;
encode_oneshot(
src,
params,
opts.checksum,
Some(src.len() as u64),
None,
&[],
true,
AdvancedOptions::default(),
)
}
fn params_with_history(
level: i32,
src_len: usize,
hist_len: usize,
) -> Result<CompressionParameters, Error> {
let hint = if prefix_window_enabled() {
(src_len as u64).saturating_add(hist_len as u64)
} else {
src_len as u64
};
compression_params(level, Some(hint))
}
static PREFIX_WINDOW_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_prefix_window_arm(on: bool) {
PREFIX_WINDOW_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prefix_window_enabled() -> bool {
!matches!(
PREFIX_WINDOW_ARM.load(core::sync::atomic::Ordering::Relaxed),
1
)
}
pub fn compress_using_dict(src: &[u8], dict: &Dictionary, level: i32) -> Result<Vec<u8>, Error> {
compress_using_dict_with(
src,
dict,
CompressOptions {
level,
checksum: true,
},
true,
)
}
pub fn compress_using_dict_with(
src: &[u8],
dict: &Dictionary,
opts: CompressOptions,
write_dict_id: bool,
) -> Result<Vec<u8>, Error> {
let params = params_with_history(opts.level, src.len(), dict.content().len())?;
encode_oneshot(
src,
params,
opts.checksum,
Some(src.len() as u64),
Some(dict),
&[],
write_dict_id,
AdvancedOptions::default(),
)
}
pub fn compress_using_prefix(src: &[u8], prefix: &[u8], level: i32) -> Result<Vec<u8>, Error> {
let params = params_with_history(level, src.len(), prefix.len())?;
encode_oneshot(
src,
params,
true,
Some(src.len() as u64),
None,
prefix,
false,
AdvancedOptions::default(),
)
}
#[cfg(feature = "alloc")]
pub fn compress_with_params(
src: &[u8],
params: CompressionParameters,
checksum: bool,
) -> Result<Vec<u8>, Error> {
encode_oneshot(
src,
params,
checksum,
Some(src.len() as u64),
None,
&[],
true,
AdvancedOptions::default(),
)
}
pub fn compress_with_history(
src: &[u8],
params: CompressionParameters,
checksum: bool,
dict: Option<&Dictionary>,
prefix: &[u8],
write_dict_id: bool,
) -> Result<Vec<u8>, Error> {
compress_with_advanced(
src,
params,
checksum,
dict,
prefix,
write_dict_id,
AdvancedOptions::default(),
)
}
#[allow(clippy::too_many_arguments)]
pub fn compress_with_advanced(
src: &[u8],
params: CompressionParameters,
checksum: bool,
dict: Option<&Dictionary>,
prefix: &[u8],
write_dict_id: bool,
adv: AdvancedOptions,
) -> Result<Vec<u8>, Error> {
if adv.nb_workers > 0 {
#[cfg(feature = "std")]
{
return crate::mt::compress_mt(src, params, checksum, dict, prefix, write_dict_id, adv);
}
}
encode_oneshot(
src,
params,
checksum,
Some(src.len() as u64),
dict,
prefix,
write_dict_id,
adv,
)
}
#[derive(Clone, Copy, Debug)]
struct Seq {
litlen: u32,
matchlen: u32,
offset: u32,
}
#[derive(Clone)]
pub(crate) struct MatchTables {
hash: Vec<u32>,
hash_long: Vec<u32>,
ltags: Vec<u8>,
rep_yield: f32,
hash_log: u32,
chain: Vec<u32>,
frame_start: usize,
last_nseq: usize,
step_pick: u8,
step_used: u8,
route_force: u8,
step_probed: u32,
step_sum1: f64,
step_sum2: f64,
step_reprobe: u32,
last_search_per_byte: f32,
walk_first_share: f32,
walk_probe: u32,
walk_share_meas: bool,
wide_ok_blocks: u32,
chain_pack: bool,
ctags: Vec<u8>,
coded_scratch: Vec<CodedSeq>,
bits_scratch: Vec<u8>,
chain_wide: bool,
blocks_done: u32,
payload_scratch: Vec<u8>,
seq_scratch: Vec<Seq>,
lit_scratch: Vec<u8>,
opt_ops: Vec<(u32, u32, u32)>,
opt_price: Vec<u32>,
opt_prev: Vec<u32>,
opt_om: Vec<u64>,
next_long_yield: f32,
nl_off_worse: f32,
nl_band_meas: u32,
nl_band_probe: u32,
lit_short_share: f32,
lit_mid_share: f32,
rep_len_ratio: f32,
rep_probe: u32,
raw_run: u32,
raw_probe: u32,
opt_rep_rate: f32,
opt_rep_probe: u32,
opt_rep_peak: f32,
opt_rep_meas: u32,
opt_rep_seen: u32,
opt_lit_price: u32,
dfast_mean_ml: f32,
dfast_spec_yield: f32,
dfast_probe: u32,
pair_gain: f32,
pair_probe: u32,
pair_route: u8,
tags: alloc::vec::Vec<u8>,
pack_tags: bool,
fast_hash_legacy: bool,
tag_yield: f32,
rep_run: u32,
}
impl MatchTables {
pub(crate) fn new(params: CompressionParameters) -> Self {
let hash_log = params.hash_log.clamp(6, 24);
let hsz = 1usize << hash_log;
let csz = 1usize << params.chain_log.min(24);
let use_long = matches!(params.strategy, Strategy::DFast);
let use_tags = false;
let _ = tag_alloc_enabled;
let use_chain = !matches!(params.strategy, Strategy::Fast | Strategy::DFast);
let hash_b = (hsz as u64).saturating_mul(4);
let long_b = if use_long { hash_b } else { 0 };
let chain_b = if use_chain {
(csz as u64).saturating_mul(4)
} else {
0
};
crate::prof::note_tables(hash_b, long_b, chain_b);
Self {
rep_yield: 1.0,
hash_log,
hash: vec![0; hsz],
hash_long: if use_long { vec![0; hsz] } else { Vec::new() },
ltags: Vec::new(),
chain: if use_chain { vec![0; csz] } else { Vec::new() },
frame_start: 0,
last_nseq: 0,
step_pick: 0,
step_used: 0,
route_force: 0,
step_probed: 0,
step_sum1: 0.0,
step_sum2: 0.0,
step_reprobe: 0,
last_search_per_byte: 1.0,
walk_first_share: 0.0,
walk_probe: 0,
walk_share_meas: false,
wide_ok_blocks: 0,
chain_pack: false,
ctags: Vec::new(),
chain_wide: false,
coded_scratch: Vec::new(),
bits_scratch: Vec::new(),
blocks_done: 0,
payload_scratch: Vec::new(),
seq_scratch: Vec::new(),
lit_scratch: Vec::new(),
opt_ops: Vec::new(),
opt_price: Vec::new(),
opt_prev: Vec::new(),
opt_om: Vec::new(),
rep_run: 0,
next_long_yield: 1.0,
nl_off_worse: 0.0,
nl_band_meas: 0,
nl_band_probe: 0,
lit_short_share: 1.0,
lit_mid_share: 0.0,
rep_len_ratio: 1.0,
rep_probe: 0,
raw_run: 0,
raw_probe: 0,
opt_rep_rate: f32::MAX,
opt_rep_probe: 0,
opt_rep_peak: 0.0,
opt_rep_meas: 0,
opt_rep_seen: 0,
opt_lit_price: 0,
dfast_mean_ml: 0.0,
dfast_spec_yield: 1.0,
dfast_probe: 0,
pair_gain: 1.0,
pair_probe: 0,
pair_route: 2,
tags: if use_tags {
alloc::vec![0u8; hsz]
} else {
alloc::vec::Vec::new()
},
pack_tags: false,
fast_hash_legacy: false,
tag_yield: 1.0,
}
}
pub(crate) fn reset(&mut self) {
self.tags.fill(0);
self.hash.fill(0);
self.hash_long.fill(0);
self.ltags.fill(0);
self.chain.fill(0);
self.ctags.fill(0);
}
#[inline(always)]
#[allow(unsafe_code)]
fn store_fast(&mut self, h: usize, pos: usize, tag: u8, packed: bool) {
debug_assert_eq!(packed, self.pack_tags);
debug_assert!(h < self.hash.len());
if packed {
*unsafe { self.hash.get_unchecked_mut(h) } =
(((pos as u32).wrapping_add(1)) & 0x00FF_FFFF) | (u32::from(tag) << 24);
return;
}
if !self.tags.is_empty() {
debug_assert_eq!(self.tags.len(), self.hash.len());
*unsafe { self.tags.get_unchecked_mut(h) } = tag;
}
*unsafe { self.hash.get_unchecked_mut(h) } = {
(pos as u32).wrapping_add(1)
};
}
#[inline(always)]
fn raw_fast(&self, h: usize) -> u32 {
let e = self.hash[h];
if self.pack_tags {
e & 0x00FF_FFFF
} else {
e
}
}
#[inline(always)]
#[allow(unsafe_code)]
fn put_h(&mut self, h: usize, pos: usize) {
debug_assert!(h < self.hash.len());
debug_assert!(pos < u32::MAX as usize);
*unsafe { self.hash.get_unchecked_mut(h) } = (pos as u32) + 1;
}
#[inline(always)]
#[allow(unsafe_code)]
fn put_h_tag(&mut self, h: usize, pos: usize, tag: u8, packed: bool, live: bool) {
debug_assert_eq!(packed, self.pack_tags);
debug_assert_eq!(live, !self.tags.is_empty());
if packed {
debug_assert!(h < self.hash.len());
debug_assert!(pos + 1 < 0x00FF_FFFF);
*unsafe { self.hash.get_unchecked_mut(h) } =
(((pos as u32) + 1) & 0x00FF_FFFF) | (u32::from(tag) << 24);
return;
}
if live {
debug_assert!(!self.tags.is_empty() && self.tags.len() == self.hash.len());
*unsafe { self.tags.get_unchecked_mut(h) } = tag;
}
debug_assert!(h < self.hash.len());
debug_assert!(pos < u32::MAX as usize);
*unsafe { self.hash.get_unchecked_mut(h) } = (pos as u32) + 1;
}
pub(crate) fn alloc_fast_tags(&mut self, params: CompressionParameters) {
if ((params.strategy == Strategy::Fast && tag_alloc_enabled())
|| (params.strategy == Strategy::DFast && dfast_tag_enabled()))
&& self.tags.is_empty()
{
self.tags = alloc::vec![0u8; self.hash.len()];
}
if params.strategy == Strategy::DFast
&& dfast_tag_enabled()
&& long_tag_enabled()
&& !self.hash_long.is_empty()
&& self.ltags.is_empty()
{
self.ltags = alloc::vec![0u8; self.hash_long.len()];
}
if matches!(
params.strategy,
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2
) && chain_tag_enabled()
&& (params.min_match.max(3) as usize) < 8
&& !self.chain.is_empty()
{
if self.ctags.is_empty() {
self.ctags = alloc::vec![0u8; self.chain.len()];
}
if self.tags.is_empty() {
self.tags = alloc::vec![0u8; self.hash.len()];
}
}
}
#[inline]
fn enable_packed_tags(&mut self, on: bool, len: usize) {
self.pack_tags = on && len < 0x00FF_FFFF;
}
#[inline(always)]
#[allow(unsafe_code)]
fn get_h_tag(&self, h: usize, tag: u8, on: bool, packed: bool) -> Option<usize> {
debug_assert_eq!(packed, self.pack_tags);
debug_assert!(h < self.hash.len());
let v = *unsafe { self.hash.get_unchecked(h) };
if v == 0 {
return None;
}
if packed {
if (v >> 24) as u8 != tag {
return None;
}
return Some(((v & 0x00FF_FFFF) as usize) - 1);
}
if on && !self.tags.is_empty() {
debug_assert_eq!(self.tags.len(), self.hash.len());
#[allow(unsafe_code)]
let t = *unsafe { self.tags.get_unchecked(h) };
if t != tag {
return None;
}
}
Some((v as usize) - 1)
}
#[inline(always)]
#[allow(unsafe_code)]
fn get_h(&self, h: usize) -> Option<usize> {
debug_assert!(h < self.hash.len());
let v = *unsafe { self.hash.get_unchecked(h) };
if v == 0 {
None
} else {
Some((v as usize) - 1)
}
}
#[inline(always)]
#[allow(unsafe_code)]
fn lz_head_raw(&self, h: usize) -> u32 {
debug_assert!(h < self.hash.len());
*unsafe { self.hash.get_unchecked(h) }
}
#[inline(always)]
#[allow(unsafe_code)]
fn lz_head_put(&mut self, h: usize, pos: usize, tag: u8, cp: bool) {
debug_assert!(h < self.hash.len());
debug_assert!(pos < u32::MAX as usize);
let v = if cp {
debug_assert!(pos + 1 < 0x00FF_FFFF);
(((pos as u32) + 1) & 0x00FF_FFFF) | (u32::from(tag) << 24)
} else {
(pos as u32) + 1
};
*unsafe { self.hash.get_unchecked_mut(h) } = v;
}
#[inline(always)]
fn lz_head_pos(raw: u32, cp: bool) -> Option<usize> {
let p = if cp { raw & 0x00FF_FFFF } else { raw };
if p == 0 {
None
} else {
Some((p as usize) - 1)
}
}
#[inline(always)]
fn lz_head_tag(raw: u32) -> u8 {
(raw >> 24) as u8
}
#[inline(always)]
fn lz_link_from_head(raw: u32, cp: bool) -> u32 {
if cp {
let p = raw & 0x00FF_FFFF;
if p == 0 {
0
} else {
(p - 1) | (raw & 0xFF00_0000)
}
} else if raw == 0 {
0
} else {
raw - 1
}
}
#[inline(always)]
#[allow(unsafe_code)]
fn chain_masked(&self, i: usize) -> u32 {
debug_assert!(i < self.chain.len());
*unsafe { self.chain.get_unchecked(i) }
}
#[inline(always)]
#[allow(unsafe_code)]
fn chain_masked_set(&mut self, i: usize, v: u32) {
debug_assert!(i < self.chain.len());
*unsafe { self.chain.get_unchecked_mut(i) } = v;
}
#[inline(always)]
#[allow(unsafe_code)]
fn ctags_masked(&self, i: usize) -> u8 {
debug_assert!(i < self.ctags.len());
*unsafe { self.ctags.get_unchecked(i) }
}
#[inline(always)]
fn lz_insert(
&mut self,
h: usize,
ip: usize,
gtag: u8,
cp: bool,
ca: bool,
chain_mask: usize,
) -> (Option<usize>, u8) {
let raw = self.lz_head_raw(h);
let old_tag = if cp {
Self::lz_head_tag(raw)
} else if ca {
debug_assert!(h < self.tags.len());
#[allow(unsafe_code)]
*unsafe { self.tags.get_unchecked(h) }
} else {
0
};
self.chain_masked_set(ip & chain_mask, Self::lz_link_from_head(raw, cp));
if ca {
debug_assert!((ip & chain_mask) < self.ctags.len() && h < self.tags.len());
#[allow(unsafe_code)]
unsafe {
*self.ctags.get_unchecked_mut(ip & chain_mask) = old_tag;
*self.tags.get_unchecked_mut(h) = gtag;
}
}
self.lz_head_put(h, ip, gtag, cp);
(Self::lz_head_pos(raw, cp), old_tag)
}
#[inline(always)]
#[allow(unsafe_code)]
fn lz_insert_only(
&mut self,
h: usize,
ip: usize,
gtag: u8,
cp: bool,
ca: bool,
chain_mask: usize,
) {
let raw = self.lz_head_raw(h);
let old_tag = if cp {
Self::lz_head_tag(raw)
} else if ca {
debug_assert!(h < self.tags.len());
*unsafe { self.tags.get_unchecked(h) }
} else {
0
};
self.chain_masked_set(ip & chain_mask, Self::lz_link_from_head(raw, cp));
if ca {
debug_assert!((ip & chain_mask) < self.ctags.len() && h < self.tags.len());
unsafe {
*self.ctags.get_unchecked_mut(ip & chain_mask) = old_tag;
*self.tags.get_unchecked_mut(h) = gtag;
}
}
self.lz_head_put(h, ip, gtag, cp);
}
#[inline(always)]
#[allow(unsafe_code)]
fn chain_at(&self, i: usize) -> u32 {
debug_assert!(i < self.chain.len());
*unsafe { self.chain.get_unchecked(i) }
}
#[inline(always)]
#[allow(unsafe_code)]
fn chain_set(&mut self, i: usize, v: u32) {
debug_assert!(i < self.chain.len());
*unsafe { self.chain.get_unchecked_mut(i) } = v;
}
#[allow(unsafe_code)]
fn put_hl(&mut self, h: usize, pos: usize) {
debug_assert!(h < self.hash_long.len());
debug_assert!(pos < u32::MAX as usize);
*unsafe { self.hash_long.get_unchecked_mut(h) } = (pos as u32) + 1;
}
#[inline(always)]
#[allow(unsafe_code)]
fn put_hl_tag(&mut self, h: usize, pos: usize, tag: u8, packed: bool, live: bool) {
debug_assert!(h < self.hash_long.len());
debug_assert_eq!(packed, self.pack_tags);
debug_assert_eq!(live, !self.ltags.is_empty());
if packed {
debug_assert!(pos + 1 < 0x00FF_FFFF);
*unsafe { self.hash_long.get_unchecked_mut(h) } =
(((pos as u32) + 1) & 0x00FF_FFFF) | (u32::from(tag) << 24);
return;
}
if live {
debug_assert!(!self.ltags.is_empty() && self.ltags.len() == self.hash_long.len());
*unsafe { self.ltags.get_unchecked_mut(h) } = tag;
}
debug_assert!(pos < u32::MAX as usize);
*unsafe { self.hash_long.get_unchecked_mut(h) } = (pos as u32) + 1;
}
#[inline(always)]
#[allow(unsafe_code)]
fn get_hl_tag(&self, h: usize, tag: u8, on: bool, packed: bool) -> Option<usize> {
debug_assert!(h < self.hash_long.len());
debug_assert_eq!(packed, self.pack_tags);
let v = *unsafe { self.hash_long.get_unchecked(h) };
if v == 0 {
return None;
}
if packed {
if on && (v >> 24) as u8 != tag {
return None;
}
return Some(((v & 0x00FF_FFFF) as usize) - 1);
}
if on && !self.ltags.is_empty() {
debug_assert_eq!(self.ltags.len(), self.hash_long.len());
#[allow(unsafe_code)]
let t = *unsafe { self.ltags.get_unchecked(h) };
if t != tag {
return None;
}
}
Some((v as usize) - 1)
}
#[cfg(feature = "profile")]
#[inline(always)]
fn raw_hl(&self, h: usize) -> u32 {
let e = self.hash_long[h];
if self.pack_tags {
e & 0x00FF_FFFF
} else {
e
}
}
}
#[derive(Clone)]
pub(crate) enum RetainedTable {
Static(&'static FseCTable),
Own(FseCTable),
}
impl core::ops::Deref for RetainedTable {
type Target = FseCTable;
#[inline(always)]
fn deref(&self) -> &FseCTable {
match self {
RetainedTable::Static(t) => t,
RetainedTable::Own(t) => t,
}
}
}
#[derive(Clone, Default)]
pub(crate) struct EntropyState {
huff: Option<alloc::sync::Arc<HuffCTable>>,
ll: Option<alloc::sync::Arc<RetainedTable>>,
of: Option<alloc::sync::Arc<RetainedTable>>,
ml: Option<alloc::sync::Arc<RetainedTable>>,
}
impl EntropyState {
pub(crate) fn seed_from_dict(&mut self, e: &crate::dict::DictEntropy) {
self.huff = Some(alloc::sync::Arc::new(e.huff_c.clone()));
self.ll = Some(alloc::sync::Arc::new(RetainedTable::Own(e.ll_c.clone())));
self.of = Some(alloc::sync::Arc::new(RetainedTable::Own(e.of_c.clone())));
self.ml = Some(alloc::sync::Arc::new(RetainedTable::Own(e.ml_c.clone())));
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_oneshot(
src: &[u8],
params: CompressionParameters,
checksum: bool,
pledged: Option<u64>,
dict: Option<&Dictionary>,
prefix: &[u8],
write_dict_id: bool,
adv: AdvancedOptions,
) -> Result<Vec<u8>, Error> {
let _enc = crate::prof::scope(crate::prof::Stage::EncodeTotal);
let hist_prefix = dict.map(Dictionary::content).unwrap_or(prefix);
let dict_id = if write_dict_id {
dict.map(Dictionary::id).filter(|&id| id != 0)
} else {
None
};
let mut tables = {
let _t = crate::prof::scope(crate::prof::Stage::EncodeTables);
MatchTables::new(params)
};
tables.enable_packed_tags(
(params.strategy == Strategy::DFast && dfast_tag_enabled())
|| (params.strategy == Strategy::Fast && tag_alloc_enabled() && fast_pack_enabled()),
hist_prefix.len() + src.len(),
);
if !tables.pack_tags
&& ((params.strategy == Strategy::Fast && tag_alloc_enabled())
|| (params.strategy == Strategy::DFast && dfast_tag_enabled()))
{
tables.tags = alloc::vec![0u8; tables.hash.len()];
}
tables.chain_pack = matches!(
params.strategy,
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2
) && chain_tag_enabled()
&& (params.min_match.max(3) as usize) < 8
&& (hist_prefix.len() + src.len()) < 0x00FF_FFFF;
tables.chain_wide = false;
if matches!(
params.strategy,
Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2
) && chain_tag_enabled()
&& (params.min_match.max(3) as usize) < 8
&& !tables.chain_pack
&& !tables.chain.is_empty()
{
if tables.ctags.is_empty() {
tables.ctags = alloc::vec![0u8; tables.chain.len()];
}
if tables.tags.is_empty() {
tables.tags = alloc::vec![0u8; tables.hash.len()];
}
}
if params.strategy == Strategy::DFast
&& dfast_tag_enabled()
&& long_tag_enabled()
&& !tables.pack_tags
&& !tables.hash_long.is_empty()
&& tables.ltags.is_empty()
{
tables.ltags = alloc::vec![0u8; tables.hash_long.len()];
}
let mut reps = [1u32, 4, 8];
let mut entropy = EntropyState::default();
if let Some(d) = dict {
if let Some(e) = d.entropy() {
entropy.seed_from_dict(e);
reps = e.reps;
}
}
let mut out = Vec::with_capacity(crate::compress_bound(src.len()));
write_frame_header(
&mut out,
src.len() as u64,
params.window_log,
checksum,
pledged,
dict_id,
!hist_prefix.is_empty() && !adv.prime_only,
);
if src.is_empty() {
write_block_header(&mut out, true, BlockType::Raw, 0);
if checksum {
out.extend_from_slice(&content_checksum(src).to_le_bytes());
}
return Ok(out);
}
let window = 1usize << params.window_log.min(31);
let mut block_max = (window.min(BLOCKSIZE_MAX as usize)).max(1);
if let Ok(v) = crate::env_knob("RZSTD_BLOCK_KB") {
if let Ok(kb) = v.trim().parse::<usize>() {
if kb > 0 {
block_max = block_max.min(kb * 1024);
}
}
}
if adv.target_cblock_size > 0 {
let t = adv.target_cblock_size as usize;
block_max = block_max.min(t.saturating_mul(4).max(256));
}
let ldm_res = if adv.ldm.enable {
Some(adv.ldm.resolved(params.window_log))
} else {
None
};
let mut ldm_tables = ldm_res.map(crate::ldm::LdmTables::new);
let mut owned = Vec::new();
let (workspace, payload_off): (&[u8], usize) = if hist_prefix.is_empty() {
(src, 0)
} else {
let keep = window.saturating_add(BLOCKSIZE_MAX as usize);
let cut = if prefix_bound_enabled() {
hist_prefix.len().saturating_sub(keep)
} else {
0
};
let hp = &hist_prefix[cut..];
owned.reserve(hp.len() + src.len());
owned.extend_from_slice(hp);
owned.extend_from_slice(src);
(owned.as_slice(), hp.len())
};
if adv.prime_only {
tables.frame_start = payload_off;
}
prime_tables(&mut tables, workspace, payload_off, window, params);
if let (Some(lt), Some(rp)) = (ldm_tables.as_mut(), ldm_res) {
crate::ldm::prime_ldm(lt, workspace, payload_off, window, rp);
}
let rbits = if adv.rsyncable {
crate::ldm::rsync_bits(params.window_log)
} else {
0
};
let mut off = payload_off;
let mut xxh = if checksum { Some(Xxh64::new()) } else { None };
{
let _b = crate::prof::scope(crate::prof::Stage::EncodeBlocks);
let mut r_prev: f32 = -1.0;
let mut r_prev2: f32 = -1.0;
while off < workspace.len() {
let bmax = adaptive_block_max(
block_max,
r_prev,
r_prev2,
tables.rep_yield,
params.strategy,
workspace.len(),
);
let mut end = (off + bmax).min(workspace.len());
if adv.rsyncable && end > off + 64 {
if let Some(cut) = crate::ldm::rsync_cut(&workspace[off..end], rbits) {
if cut > 32 && off + cut < workspace.len() {
end = off + cut;
}
}
}
let last = end == workspace.len();
let before_block = out.len();
encode_block(
&mut out,
workspace,
off,
end,
window,
params,
&mut tables,
&mut reps,
&mut entropy,
last,
ldm_tables.as_mut(),
adv.ldm,
)?;
if let Some(h) = xxh.as_mut() {
h.update(&workspace[off..end]);
}
let produced = out.len() - before_block;
r_prev2 = r_prev;
r_prev = produced as f32 / (end - off).max(1) as f32;
off = end;
}
}
if let Some(h) = xxh {
let _c = crate::prof::scope(crate::prof::Stage::EncodeChecksum);
crate::prof::note_checksum_bytes(src.len() as u64);
out.extend_from_slice(&(h.digest() as u32).to_le_bytes());
}
Ok(out)
}
static PRIME_BT_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_prime_bt_arm(keep: bool) {
PRIME_BT_ARM.store(u8::from(keep) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prime_bt_chain_write() -> bool {
match PRIME_BT_ARM.load(core::sync::atomic::Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
#[cfg(feature = "std")]
{
let keep = std::env::var("RZSTD_PRIME_BT")
.map(|v| v.trim() == "1")
.unwrap_or(false);
PRIME_BT_ARM.store(u8::from(keep) + 1, core::sync::atomic::Ordering::Relaxed);
keep
}
#[cfg(not(feature = "std"))]
false
}
}
}
static PRIME_STRIDE_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub fn set_prime_stride_arm(n: usize) {
PRIME_STRIDE_ARM.store(n.max(1) as u32 + 1, core::sync::atomic::Ordering::Relaxed);
}
pub static PRIME_ITERS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static N9_BASIC: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static ENT_SAVE: [core::sync::atomic::AtomicU64; 2] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
pub fn take_ent_save() -> [u64; 2] {
use core::sync::atomic::Ordering;
[
ENT_SAVE[0].swap(0, Ordering::Relaxed),
ENT_SAVE[1].swap(0, Ordering::Relaxed),
]
}
pub fn take_n9_basic() -> u64 {
N9_BASIC.swap(0, core::sync::atomic::Ordering::Relaxed)
}
pub fn take_prime_iters() -> u64 {
PRIME_ITERS.swap(0, core::sync::atomic::Ordering::Relaxed)
}
#[inline]
fn prime_stride() -> usize {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let v = PRIME_STRIDE_ARM.load(Ordering::Relaxed);
if v != 0 {
return (v - 1) as usize;
}
let n: usize = std::env::var("RZSTD_PRIME_STRIDE")
.ok()
.and_then(|x| x.trim().parse().ok())
.filter(|x| *x >= 1)
.unwrap_or(1);
PRIME_STRIDE_ARM.store(n as u32 + 1, Ordering::Relaxed);
n
}
#[cfg(not(feature = "std"))]
1
}
static PREFIX_BOUND_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_prefix_bound_arm(bound: bool) {
PREFIX_BOUND_ARM.store(u8::from(bound) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prefix_bound_enabled() -> bool {
PREFIX_BOUND_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static PRIME_BT_EXTENT_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(16);
pub fn set_prime_bt_extent_arm(n: u32) {
PRIME_BT_EXTENT_ARM.store(n.max(1), core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prime_bt_extent() -> usize {
PRIME_BT_EXTENT_ARM
.load(core::sync::atomic::Ordering::Relaxed)
.max(1) as usize
}
static PRIME_BT_TREE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_prime_bt_tree_arm(build: bool) {
PRIME_BT_TREE_ARM.store(u8::from(build) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prime_bt_tree_enabled() -> bool {
match PRIME_BT_TREE_ARM.load(core::sync::atomic::Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
#[cfg(feature = "std")]
{
let on = std::env::var("RZSTD_PRIME_BT_TREE")
.map(|v| v.trim() == "1")
.unwrap_or(false);
PRIME_BT_TREE_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
on
}
#[cfg(not(feature = "std"))]
true
}
}
}
static PRIME_BT_DEPTH_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
const PRIME_BT_DEPTH_DEFAULT: u32 = 5;
pub fn set_prime_bt_depth_arm(d: u32) {
PRIME_BT_DEPTH_ARM.store(d, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn prime_bt_depth() -> u32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let v = PRIME_BT_DEPTH_ARM.load(Ordering::Relaxed);
if v != u32::MAX {
return v;
}
let d: u32 = std::env::var("RZSTD_PRIME_BT_DEPTH")
.ok()
.and_then(|x| x.trim().parse().ok())
.unwrap_or(PRIME_BT_DEPTH_DEFAULT);
PRIME_BT_DEPTH_ARM.store(d, Ordering::Relaxed);
d
}
#[cfg(not(feature = "std"))]
PRIME_BT_DEPTH_DEFAULT
}
#[inline]
fn adaptive_block_max(
base: usize,
r_prev: f32,
r_prev2: f32,
rep_yield: f32,
strategy: Strategy,
input_len: usize,
) -> usize {
if strategy == Strategy::Fast && input_len > g5_fast_max_len() {
return base;
}
let (rep_min, ratio_min, drift_min) = match strategy {
Strategy::Fast => (g5_rep_min_fast(), g5_ratio_min_fast(), g5_drift_min_fast()),
Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2 => {
(g5_rep_min_opt(), g5_ratio_min_opt(), g5_drift_min_opt())
}
_ => (g5_rep_min(), g5_ratio_min(), g5_drift_min()),
};
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
G5_CALLS.fetch_add(1, Relaxed);
if r_prev >= 0.0 {
G5_RPREV.fetch_add((r_prev.clamp(0.0, 10.0) * 10000.0) as u64, Relaxed);
G5_RPREV_N.fetch_add(1, Relaxed);
if r_prev2 >= 0.0 {
let d = (r_prev - r_prev2).abs() / r_prev.max(1e-6);
G5_DRIFTSUM.fetch_add((d.clamp(0.0, 100.0) * 10000.0) as u64, Relaxed);
G5_DRIFT_N.fetch_add(1, Relaxed);
}
}
}
if r_prev < 0.0 {
return base;
}
if r_prev < g5_tiny_max() {
return base;
}
if r_prev < G5_RLE_MAX {
return base.min(g5_band());
}
if rep_yield >= rep_min {
return base;
}
if r_prev >= ratio_min {
#[cfg(feature = "profile")]
G5_HIT_RATIO.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
return base.min(G5_SMALL);
}
if r_prev2 >= 0.0 {
let drift = (r_prev - r_prev2).abs() / r_prev.max(1e-6);
if drift >= drift_min {
#[cfg(feature = "profile")]
G5_HIT_DRIFT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
return base.min(G5_SMALL);
}
}
base
}
pub static G5_RPREV: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static G5_RPREV_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static G5_DRIFTSUM: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static G5_DRIFT_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_g5_inputs() -> (f64, f64) {
use core::sync::atomic::Ordering::Relaxed;
let a = G5_RPREV_N.swap(0, Relaxed).max(1) as f64;
let b = G5_DRIFT_N.swap(0, Relaxed).max(1) as f64;
(
G5_RPREV.swap(0, Relaxed) as f64 / (10000.0 * a),
G5_DRIFTSUM.swap(0, Relaxed) as f64 / (10000.0 * b),
)
}
pub static G5_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static G5_HIT_RATIO: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static G5_HIT_DRIFT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_g5() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
G5_CALLS.swap(0, Relaxed),
G5_HIT_RATIO.swap(0, Relaxed),
G5_HIT_DRIFT.swap(0, Relaxed),
)
}
const G5_FAST_MAX_LEN: usize = 2 << 20;
static G5_FAST_LEN_A: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[inline(always)]
fn g5_fast_max_len() -> usize {
let v = G5_FAST_LEN_A.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
G5_FAST_MAX_LEN
} else {
v
}
}
pub fn set_g5_fast_len_arm(v: usize) {
G5_FAST_LEN_A.store(v, core::sync::atomic::Ordering::Relaxed);
}
const G5_TINY_MAX: f32 = 0.0005;
const G5_BAND: usize = usize::MAX;
static G5_TINY_A: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_BAND_A: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[inline(always)]
fn g5_tiny_max() -> f32 {
let v = G5_TINY_A.load(core::sync::atomic::Ordering::Relaxed);
if v == u32::MAX {
G5_TINY_MAX
} else {
f32::from_bits(v)
}
}
#[inline(always)]
fn g5_band() -> usize {
let v = G5_BAND_A.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
G5_BAND
} else {
v
}
}
pub fn set_g5_tiny_arm(v: f32) {
G5_TINY_A.store(
if v.is_nan() { u32::MAX } else { v.to_bits() },
core::sync::atomic::Ordering::Relaxed,
);
}
pub fn set_g5_band_arm(v: usize) {
G5_BAND_A.store(v, core::sync::atomic::Ordering::Relaxed);
}
const G5_RLE_MAX: f32 = 0.01;
const G5_SMALL: usize = 64 << 10;
const G5_REP_MIN: f32 = 0.30;
const G5_RATIO_MIN: f32 = 0.70;
const G5_DRIFT_MIN: f32 = 1.50;
const G5_REP_MIN_FAST: f32 = 2.00;
const G5_RATIO_MIN_FAST: f32 = 0.70;
const G5_DRIFT_MIN_FAST: f32 = 2.00;
const G5_REP_MIN_OPT: f32 = 2.00;
const G5_RATIO_MIN_OPT: f32 = 0.50;
const G5_DRIFT_MIN_OPT: f32 = 1.50;
static G5_REP_O: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_RATIO_O: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_DRIFT_O: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_g5_opt_arms(rep: f32, ratio: f32, drift: f32) {
use core::sync::atomic::Ordering::Relaxed;
G5_REP_O.store(rep.to_bits(), Relaxed);
G5_RATIO_O.store(ratio.to_bits(), Relaxed);
G5_DRIFT_O.store(drift.to_bits(), Relaxed);
}
#[inline]
fn g5_rep_min_opt() -> f32 {
let b = G5_REP_O.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_REP_MIN_OPT
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_ratio_min_opt() -> f32 {
let b = G5_RATIO_O.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_RATIO_MIN_OPT
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_drift_min_opt() -> f32 {
let b = G5_DRIFT_O.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_DRIFT_MIN_OPT
} else {
f32::from_bits(b)
}
}
static G5_REP_F: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_RATIO_F: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_DRIFT_F: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_g5_fast_arms(rep: f32, ratio: f32, drift: f32) {
use core::sync::atomic::Ordering::Relaxed;
G5_REP_F.store(rep.to_bits(), Relaxed);
G5_RATIO_F.store(ratio.to_bits(), Relaxed);
G5_DRIFT_F.store(drift.to_bits(), Relaxed);
}
#[inline]
fn g5_rep_min_fast() -> f32 {
let b = G5_REP_F.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_REP_MIN_FAST
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_ratio_min_fast() -> f32 {
let b = G5_RATIO_F.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_RATIO_MIN_FAST
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_drift_min_fast() -> f32 {
let b = G5_DRIFT_F.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_DRIFT_MIN_FAST
} else {
f32::from_bits(b)
}
}
static G5_REP: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_RATIO: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static G5_DRIFT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_g5_arms(rep: f32, ratio: f32, drift: f32) {
use core::sync::atomic::Ordering::Relaxed;
G5_REP.store(rep.to_bits(), Relaxed);
G5_RATIO.store(ratio.to_bits(), Relaxed);
G5_DRIFT.store(drift.to_bits(), Relaxed);
}
#[inline]
fn g5_rep_min() -> f32 {
let b = G5_REP.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_REP_MIN
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_ratio_min() -> f32 {
let b = G5_RATIO.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_RATIO_MIN
} else {
f32::from_bits(b)
}
}
#[inline]
fn g5_drift_min() -> f32 {
let b = G5_DRIFT.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
G5_DRIFT_MIN
} else {
f32::from_bits(b)
}
}
#[inline(always)]
pub(crate) fn prime_tables(
tables: &mut MatchTables,
src: &[u8],
payload_off: usize,
window: usize,
params: CompressionParameters,
) {
if payload_off == 0 {
return;
}
let packed = tables.pack_tags;
let stag_live = !tables.tags.is_empty();
let ltag_live = !tables.ltags.is_empty();
let mls = params.min_match.max(3) as usize;
let from = payload_off.saturating_sub(window);
let ilimit = payload_off.saturating_sub(8);
if from >= ilimit || src.len() < mls {
return;
}
let hash_log = tables.hash_log;
let chain_mask = tables.chain.len().saturating_sub(1);
let uses_bt = matches!(
params.strategy,
Strategy::BtLazy2 | Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2
);
let write_chain = (!uses_bt || prime_bt_chain_write()) && !tables.chain.is_empty();
let do_long = !tables.hash_long.is_empty();
let stride = prime_stride();
let is_fast =
params.strategy == Strategy::Fast && (tables.pack_tags || !tables.tags.is_empty());
let mut iters = 0u64;
if uses_bt && prime_bt_tree_enabled() && !tables.chain.is_empty() {
let d = prime_bt_depth();
let pparams = if d == 0 {
params
} else {
CompressionParameters {
search_log: d,
..params
}
};
let prime_attempts = bt_depth_apply(search_attempts(pparams), pparams, tables.opt_rep_rate);
let btf = bt_resolve_ins(tables.hash_log, pparams.chain_log.min(24));
let prime_ctx = BtCtx {
src,
block_start: payload_off,
block_end: payload_off,
window,
mls,
attempts: prime_attempts,
chain_log: pparams.chain_log.min(24),
bt_lowest: payload_off.saturating_sub(window).max(tables.frame_start),
chain_len: tables.chain.len(),
wide_hash: mls >= 8,
};
let ext = prime_bt_extent();
let range = ilimit.saturating_sub(from);
let tree_from = if ext <= 1 {
from
} else {
ilimit.saturating_sub(range / ext).max(from)
};
let mut p = from;
while p < tree_from && p + 8 <= src.len() {
let h = hash_mls(src, p, mls, hash_log);
tables.put_h(h, p);
if do_long {
let hl = hash8(src, p, hash_log);
tables.put_hl(hl, p);
}
iters += 1;
p += stride;
}
while p <= ilimit && p + 8 <= src.len() {
btf(&prime_ctx, p, tables);
iters += 1;
p += stride;
}
#[cfg(feature = "profile")]
PRIME_ITERS.fetch_add(iters, core::sync::atomic::Ordering::Relaxed);
#[cfg(not(feature = "profile"))]
let _ = iters;
return;
}
let mut p = from;
while p <= ilimit && p + 8 <= src.len() {
if is_fast {
let fhp = fast_hash_spec(mls, hash_log);
let (h, tag) = fast_hash_tag::<true>(src, p, fhp.wide, fhp.mask, fhp.shift);
tables.store_fast(h, p, tag, packed);
} else {
if tables.chain_pack || !tables.ctags.is_empty() {
let cp = tables.chain_pack;
let ca = !tables.ctags.is_empty();
let smask = if mls >= 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
};
let (hh, gt) = if tables.chain_wide {
hash_wide_link_tag(src, p, hash_log, smask)
} else {
hash4_link_tag(src, p, hash_log, smask)
};
if write_chain {
let _ = tables.lz_insert(hh, p, gt, cp, ca, chain_mask);
} else {
let raw = tables.lz_head_raw(hh);
let _ = raw;
if ca {
tables.tags[hh] = gt;
}
tables.lz_head_put(hh, p, gt, cp);
}
if do_long {
let hl = hash8(src, p, hash_log);
tables.put_hl(hl, p);
}
iters += 1;
p += stride;
continue;
}
let h = hash_mls(src, p, mls, hash_log);
if write_chain {
tables.chain[p & chain_mask] = tables.get_h(h).map(|x| x as u32).unwrap_or(0);
}
if tables.tags.is_empty() && !tables.pack_tags {
tables.put_h(h, p);
if do_long {
let hl = hash8(src, p, hash_log);
tables.put_hl(hl, p);
}
} else {
let sk = 8.min(mls);
let smask = if sk == 8 {
u64::MAX
} else {
(1u64 << (8 * sk)) - 1
};
let tv = (load_u64le(src, p) & smask).wrapping_mul(FAST_HASH_PRIME64);
let g = (tv ^ (tv >> 29)) as u8;
tables.put_h_tag(h, p, g, packed, stag_live);
if do_long {
let hl = hash8(src, p, hash_log);
tables.put_hl_tag(hl, p, g, packed, ltag_live);
}
}
}
iters += 1;
p += stride;
}
#[cfg(feature = "profile")]
PRIME_ITERS.fetch_add(iters, core::sync::atomic::Ordering::Relaxed);
#[cfg(not(feature = "profile"))]
let _ = iters;
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_block(
out: &mut Vec<u8>,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: &mut [u32; 3],
entropy: &mut EntropyState,
last: bool,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
) -> Result<(), Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
encode_block_bmi2(
out,
src,
block_start,
block_end,
window,
params,
tables,
reps,
entropy,
last,
ldm,
ldm_p,
)
};
}
encode_block_inner(
out,
src,
block_start,
block_end,
window,
params,
tables,
reps,
entropy,
last,
ldm,
ldm_p,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn encode_block_bmi2(
out: &mut Vec<u8>,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: &mut [u32; 3],
entropy: &mut EntropyState,
last: bool,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
) -> Result<(), Error> {
encode_block_inner(
out,
src,
block_start,
block_end,
window,
params,
tables,
reps,
entropy,
last,
ldm,
ldm_p,
)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn encode_block_inner(
out: &mut Vec<u8>,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: &mut [u32; 3],
entropy: &mut EntropyState,
last: bool,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
) -> Result<(), Error> {
let block = &src[block_start..block_end];
if block.is_empty() {
crate::prof::note_raw_block();
write_block_header(out, last, BlockType::Raw, 0);
return Ok(());
}
if let Some(b) = rle_byte(block) {
crate::prof::note_rle_block();
tap_block(
block.len(),
0,
block.len(),
0,
1000,
params.strategy,
false,
1,
tables.rep_yield,
0,
0,
);
write_block_header(out, last, BlockType::Rle, block.len() as u32);
out.push(b);
return Ok(());
}
let skip_search = raw_skip_on() && tables.raw_run >= raw_run_min() && tables.raw_probe != 0;
let probing = params.strategy == Strategy::Fast
&& params.target_length == 0
&& tables.pair_route == 1
&& ldm.is_none()
&& step_probe_on()
&& (tables.step_pick == 0 || tables.step_reprobe == 0);
let (seqs, literals) = if skip_search {
(Vec::new(), block.to_vec())
} else if probing {
let mut probe = tables.clone();
probe.route_force = 2;
let (s2, l2) = find_sequences(
src,
block_start,
block_end,
window,
params,
&mut probe,
None,
ldm_p,
*reps,
);
let _m = crate::prof::scope(crate::prof::Stage::EncodeMatchFind);
let r = find_sequences(
src,
block_start,
block_end,
window,
params,
tables,
ldm,
ldm_p,
*reps,
);
note_step_probe(tables, &r.0, r.1.len(), &s2, l2.len());
r
} else {
let _m = crate::prof::scope(crate::prof::Stage::EncodeMatchFind);
find_sequences(
src,
block_start,
block_end,
window,
params,
tables,
ldm,
ldm_p,
*reps,
)
};
let (off_coll, off_bkt) = if cfg!(feature = "profile") {
offset_stats(&seqs)
} else {
(0, 0)
};
if seqs.is_empty() && !huffman::literals_worth_huffman(block) {
#[cfg(feature = "profile")]
RAW_EXIT[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
note_raw_outcome(tables, true);
crate::prof::note_raw_block();
tap_block(
block.len(),
0,
0,
block.len(),
huffman::lit_sample_peak(block),
params.strategy,
false,
block.len(),
tables.rep_yield,
off_coll,
off_bkt,
);
write_block_header(out, last, BlockType::Raw, block.len() as u32);
out.extend_from_slice(block);
if finder_scratch_enabled() {
tables.seq_scratch = seqs;
tables.lit_scratch = literals;
}
return Ok(());
}
let match_b: usize = seqs.iter().map(|s| s.matchlen as usize).sum();
let lit_b = literals.len();
let mg = min_gain(block.len(), params.strategy);
let peak = huffman::lit_sample_peak(if seqs.is_empty() { block } else { &literals });
if early_raw_skip(match_b, block.len(), params) {
#[cfg(feature = "profile")]
RAW_EXIT[1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
note_raw_outcome(tables, true);
crate::prof::note_raw_block();
crate::prof::note_early_raw();
tap_block(
block.len(),
seqs.len(),
match_b,
lit_b,
peak,
params.strategy,
true,
block.len(),
tables.rep_yield,
off_coll,
off_bkt,
);
write_block_header(out, last, BlockType::Raw, block.len() as u32);
out.extend_from_slice(block);
if finder_scratch_enabled() {
tables.seq_scratch = seqs;
tables.lit_scratch = literals;
}
return Ok(());
}
let saved_reps = *reps;
#[cfg(feature = "profile")]
ENT_SAVE[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let saved_ent = entropy.clone();
crate::prof::note_scratch(1);
let mut payload = core::mem::take(&mut tables.payload_scratch);
payload.clear();
if payload_reserve_enabled() && payload.capacity() < block.len() {
payload = Vec::with_capacity(block.len());
}
{
let _e = crate::prof::scope(crate::prof::Stage::EncodeEntropy);
if seqs.is_empty() {
let _ = write_literals(&mut payload, block, entropy)?;
crate::prof::note_emit_lit(payload.len() as u64);
payload.push(0);
} else {
let lit_reused = write_literals(&mut payload, &literals, entropy)?;
let lit_end = payload.len();
let _ = lit_reused;
if !literals.is_empty() {
tables.opt_lit_price = measured_lit_bits(lit_end, literals.len());
}
crate::prof::note_emit_lit(lit_end as u64);
write_sequences(&mut payload, &seqs, reps, entropy, params.strategy, tables)?;
crate::prof::note_emit_seq((payload.len() - lit_end) as u64);
}
}
let raw_limit = if incomp_skip_on(params) {
block.len().saturating_sub(mg)
} else {
block.len()
};
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
let ratio = (payload.len() as f64 / raw_limit.max(1) as f64 * 1000.0) as u64;
if payload.len() >= raw_limit {
RAW_MARGIN_SUM.fetch_add(ratio.min(4000), Relaxed);
RAW_MARGIN_N.fetch_add(1, Relaxed);
let b = match ratio {
0..=1010 => 0,
1011..=1050 => 1,
1051..=1200 => 2,
_ => 3,
};
RAW_MARGIN_HIST[b].fetch_add(1, Relaxed);
}
}
if payload.len() >= raw_limit {
#[cfg(feature = "profile")]
RAW_EXIT[2].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
*reps = saved_reps;
#[cfg(feature = "profile")]
ENT_SAVE[1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
*entropy = saved_ent;
note_raw_outcome(tables, true);
crate::prof::note_raw_block();
tap_block(
block.len(),
seqs.len(),
match_b,
lit_b,
peak,
params.strategy,
false,
block.len(),
tables.rep_yield,
off_coll,
off_bkt,
);
write_block_header(out, last, BlockType::Raw, block.len() as u32);
out.extend_from_slice(block);
tables.payload_scratch = payload;
if finder_scratch_enabled() {
tables.seq_scratch = seqs;
tables.lit_scratch = literals;
}
return Ok(());
}
crate::prof::note_comp_block();
tap_block(
block.len(),
seqs.len(),
match_b,
lit_b,
peak,
params.strategy,
false,
payload.len(),
tables.rep_yield,
off_coll,
off_bkt,
);
note_raw_outcome(tables, false);
note_step_outcome(tables, payload.len(), block.len());
write_block_header(out, last, BlockType::Compressed, payload.len() as u32);
out.extend_from_slice(&payload);
tables.payload_scratch = payload;
if finder_scratch_enabled() {
tables.seq_scratch = seqs;
tables.lit_scratch = literals;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_block_from_scratch(
out: &mut Vec<u8>,
src: &[u8],
block_start: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: &mut [u32; 3],
entropy: &mut EntropyState,
last: bool,
) -> Result<(), Error> {
let window = 1usize << params.window_log.min(31);
encode_block(
out,
src,
block_start,
src.len(),
window,
params,
tables,
reps,
entropy,
last,
None,
crate::ldm::LdmParams::default(),
)
}
fn rle_byte(block: &[u8]) -> Option<u8> {
let first = *block.first()?;
if block.len() < 2 {
return None;
}
let splat = u64::from(first) * 0x0101_0101_0101_0101;
let mut i = 0usize;
while i + 8 <= block.len() {
if load_u64le(block, i) != splat {
return None;
}
i += 8;
}
while i < block.len() {
if block[i] != first {
return None;
}
i += 1;
}
Some(first)
}
pub(crate) fn min_gain(src_size: usize, strategy: Strategy) -> usize {
let minlog = if strategy.id() >= 8 {
u32::from(strategy.id()) - 1
} else {
6
};
(src_size >> minlog) + 2
}
#[cfg(test)]
thread_local! {
static SKIP_OVERRIDE: core::cell::Cell<Option<bool>> =
const { core::cell::Cell::new(None) };
}
static INCOMP_SKIP_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_incomp_skip_arm(on: Option<bool>) {
let v = match on {
Some(false) => 1,
Some(true) => 2,
None => 3,
};
INCOMP_SKIP_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
static RAW_SKIP_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_raw_skip_arm(on: bool) {
RAW_SKIP_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn raw_skip_on() -> bool {
RAW_SKIP_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static RAW_RUN_MIN_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
static RAW_PROBE_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub fn set_raw_run_min_arm(v: u32) {
RAW_RUN_MIN_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
pub fn set_raw_probe_arm(v: u32) {
RAW_PROBE_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn raw_run_min() -> u32 {
let v = RAW_RUN_MIN_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
RAW_RUN_MIN
} else {
v
}
}
#[inline(always)]
fn raw_probe_period() -> u32 {
let v = RAW_PROBE_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
RAW_PROBE_PERIOD
} else {
v
}
}
#[cfg(feature = "profile")]
pub static RAW_EXIT: [core::sync::atomic::AtomicU64; 3] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_raw_exits() -> [u64; 3] {
use core::sync::atomic::Ordering::Relaxed;
let mut o = [0u64; 3];
for (i, v) in RAW_EXIT.iter().enumerate() {
o[i] = v.swap(0, Relaxed);
}
o
}
#[cfg(feature = "profile")]
pub static RAW_MARGIN_SUM: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static RAW_MARGIN_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static RAW_MARGIN_HIST: [core::sync::atomic::AtomicU64; 4] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_raw_margin() -> (u64, u64, [u64; 4]) {
use core::sync::atomic::Ordering::Relaxed;
let mut h = [0u64; 4];
for (i, v) in RAW_MARGIN_HIST.iter().enumerate() {
h[i] = v.swap(0, Relaxed);
}
(
RAW_MARGIN_SUM.swap(0, Relaxed),
RAW_MARGIN_N.swap(0, Relaxed),
h,
)
}
const RAW_RUN_MIN: u32 = 2;
const RAW_PROBE_PERIOD: u32 = 16;
fn note_raw_outcome(tables: &mut MatchTables, raw: bool) {
if raw {
tables.raw_run = tables.raw_run.saturating_add(1);
} else {
tables.raw_run = 0;
}
tables.raw_probe = if tables.raw_probe == 0 {
raw_probe_period()
} else {
tables.raw_probe - 1
};
}
fn incomp_skip_on(params: CompressionParameters) -> bool {
#[cfg(test)]
{
if let Some(v) = SKIP_OVERRIDE.with(|c| c.get()) {
return v;
}
}
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let mut v = INCOMP_SKIP_ARM.load(Ordering::Relaxed);
if v == 0 {
v = match std::env::var("RZSTD_INCOMP_SKIP") {
Ok(x) if x.trim() == "0" || x.trim().eq_ignore_ascii_case("off") => 1,
Ok(x) if x.trim() == "1" || x.trim().eq_ignore_ascii_case("on") => 2,
_ => 3,
};
INCOMP_SKIP_ARM.store(v, Ordering::Relaxed);
}
if v == 1 {
return false;
}
if v == 2 {
return true;
}
}
params.strategy == Strategy::Fast && params.target_length >= 1 && params.target_length <= 7
}
fn early_raw_skip(match_bytes: usize, block_len: usize, params: CompressionParameters) -> bool {
if !incomp_skip_on(params) {
return false;
}
match_bytes < min_gain(block_len, params.strategy)
}
#[allow(clippy::too_many_arguments)]
fn offset_stats(seqs: &[Seq]) -> (u32, u8) {
if seqs.is_empty() {
return (0, 0);
}
let mut bins = [0u32; 32];
for s in seqs {
let b = (31 - s.offset.max(1).leading_zeros()) as usize;
bins[b.min(31)] += 1;
}
let n = seqs.len() as u64;
let sum_sq: u64 = bins.iter().map(|&c| u64::from(c) * u64::from(c)).sum();
let used = bins.iter().filter(|&&c| c != 0).count() as u8;
(((sum_sq * 1000) / (n * n)) as u32, used)
}
fn tap_block(
block_len: usize,
nseq: usize,
match_bytes: usize,
lit_bytes: usize,
lit_peak: u32,
strategy: Strategy,
early_raw: bool,
csize: usize,
rep_yield: f32,
off_collision_x1000: u32,
off_buckets: u8,
) {
let c = crate::prof::encode_counts();
crate::prof::note_block_tap(crate::prof::BlockTap {
block_len: block_len as u32,
nseq: nseq as u32,
match_bytes: match_bytes as u32,
lit_bytes: lit_bytes as u32,
min_gain: min_gain(block_len, strategy) as u32,
lit_peak,
early_raw: u8::from(early_raw),
csize: csize as u32,
probes: c.hash_probes,
hits: c.probe_hits,
rep_yield_x1000: (rep_yield * 1000.0) as u32,
off_collision_x1000,
off_buckets,
mf_ns: crate::prof::stage_ns(crate::prof::Stage::EncodeMatchFind),
});
}
fn write_block_header(out: &mut Vec<u8>, last: bool, ty: BlockType, size: u32) {
let t = match ty {
BlockType::Raw => 0u32,
BlockType::Rle => 1,
BlockType::Compressed => 2,
};
let n = u32::from(last) | (t << 1) | (size << 3);
out.push(n as u8);
out.push((n >> 8) as u8);
out.push((n >> 16) as u8);
}
pub(crate) fn write_frame_header(
out: &mut Vec<u8>,
src_len: u64,
window_log: u32,
checksum: bool,
pledged: Option<u64>,
dict_id: Option<u32>,
ext_hist: bool,
) {
out.extend_from_slice(&MAGIC.to_le_bytes());
let window = 1u64 << window_log.min(31);
let size = pledged.unwrap_or(src_len);
let known = pledged.is_some();
let single = known && size <= window && !ext_hist;
let (fcs_flag, fcs_bytes) = if !known {
(0u8, Vec::new())
} else if single && size < 256 {
(0, vec![size as u8])
} else if size < 256 + 65536 {
let v = (size as u16).wrapping_sub(256);
(1, v.to_le_bytes().to_vec())
} else if size < 1 << 32 {
(2, (size as u32).to_le_bytes().to_vec())
} else {
(3, size.to_le_bytes().to_vec())
};
let (fcs_flag, fcs_bytes) = if known && !single && size < 256 {
(2u8, (size as u32).to_le_bytes().to_vec())
} else {
(fcs_flag, fcs_bytes)
};
let (dict_flag, dict_bytes): (u8, Vec<u8>) = match dict_id.filter(|&id| id != 0) {
None => (0, Vec::new()),
Some(id) if id < 256 => (1, vec![id as u8]),
Some(id) if id < 65536 => (2, (id as u16).to_le_bytes().to_vec()),
Some(id) => (3, id.to_le_bytes().to_vec()),
};
let mut desc = fcs_flag << 6;
if single {
desc |= 0x20;
}
if checksum {
desc |= 0x04;
}
desc |= dict_flag;
out.push(desc);
if !single {
let exp = window_log.saturating_sub(10).min(31);
out.push((exp << 3) as u8);
}
out.extend_from_slice(&dict_bytes);
out.extend_from_slice(&fcs_bytes);
}
static ENC_AVX2_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
pub fn set_enc_avx2_arm(on: bool) {
ENC_AVX2_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline(always)]
fn enc_avx2_on() -> bool {
ENC_AVX2_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
fn write_literals(
dst: &mut Vec<u8>,
lits: &[u8],
entropy: &mut EntropyState,
) -> Result<bool, Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { write_literals_bmi2(dst, lits, entropy) };
}
write_literals_inner(dst, lits, entropy)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn write_literals_bmi2(
dst: &mut Vec<u8>,
lits: &[u8],
entropy: &mut EntropyState,
) -> Result<bool, Error> {
write_literals_inner(dst, lits, entropy)
}
#[inline(always)]
fn write_literals_inner(
dst: &mut Vec<u8>,
lits: &[u8],
entropy: &mut EntropyState,
) -> Result<bool, Error> {
let _h = crate::prof::scope(crate::prof::Stage::EncodeHuff);
let (sec, upd) = huffman::encode_literals_section(lits, entropy.huff.as_deref())?;
let reused = matches!(upd, HuffUpdate::Unchanged);
match upd {
HuffUpdate::New(ct) => entropy.huff = Some(alloc::sync::Arc::new(ct)),
HuffUpdate::Unchanged => {}
}
dst.extend_from_slice(&sec);
huffman::sec_pool_give(sec);
Ok(reused)
}
fn write_nseq(dst: &mut Vec<u8>, n: u32) {
if n == 0 {
dst.push(0);
} else if n < 128 {
dst.push(n as u8);
} else if n < 0x7F00 {
dst.push(((n >> 8) + 128) as u8);
dst.push(n as u8);
} else {
dst.push(255);
let v = n - 0x7F00;
dst.push(v as u8);
dst.push((v >> 8) as u8);
}
}
fn write_sequences(
dst: &mut Vec<u8>,
seqs: &[Seq],
reps: &mut [u32; 3],
entropy: &mut EntropyState,
strategy: Strategy,
tables: &mut MatchTables,
) -> Result<(), Error> {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if enc_avx2_on() && crate::simd::has_avx2() && crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { write_sequences_avx2(dst, seqs, reps, entropy, strategy, tables) };
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe { write_sequences_bmi2(dst, seqs, reps, entropy, strategy, tables) };
}
write_sequences_inner(dst, seqs, reps, entropy, strategy, tables)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "avx2,bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn write_sequences_avx2(
dst: &mut Vec<u8>,
seqs: &[Seq],
reps: &mut [u32; 3],
entropy: &mut EntropyState,
strategy: Strategy,
tables: &mut MatchTables,
) -> Result<(), Error> {
write_sequences_inner(dst, seqs, reps, entropy, strategy, tables)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
unsafe fn write_sequences_bmi2(
dst: &mut Vec<u8>,
seqs: &[Seq],
reps: &mut [u32; 3],
entropy: &mut EntropyState,
strategy: Strategy,
tables: &mut MatchTables,
) -> Result<(), Error> {
write_sequences_inner(dst, seqs, reps, entropy, strategy, tables)
}
#[inline(always)]
fn write_sequences_inner(
dst: &mut Vec<u8>,
seqs: &[Seq],
reps: &mut [u32; 3],
entropy: &mut EntropyState,
strategy: Strategy,
tables: &mut MatchTables,
) -> Result<(), Error> {
write_nseq(dst, seqs.len() as u32);
if seqs.is_empty() {
return Ok(());
}
let (coded, ll_count, of_count, ml_count, of_max) = {
let _sc = crate::prof::scope(crate::prof::Stage::EncodeSeqCode);
let lut_arm = crate::compressed::lut_on();
let mut coded: Vec<CodedSeq> = core::mem::take(&mut tables.coded_scratch);
coded.clear();
if coded.capacity() < seqs.len() {
coded = Vec::with_capacity(seqs.len());
}
let mut ll_count = [0u32; 36];
let mut of_count = [0u32; 32];
let mut ml_count = [0u32; 53];
let mut of_max = 0u8;
for s in seqs {
let ov = offset_value_for(s.offset, s.litlen, reps);
let is_new = ov > 3 || (ov == 3 && s.litlen == 0);
if is_new {
reps[2] = reps[1];
reps[1] = reps[0];
reps[0] = s.offset;
} else {
let which = if s.litlen == 0 { ov + 1 } else { ov };
match which {
2 => reps.swap(0, 1),
3 => reps.rotate_right(1),
_ => {}
}
}
let (llc, llx, llb) = ll_code(s.litlen, lut_arm);
let (mlc, mlx, mlb) = ml_code(s.matchlen, lut_arm);
let (ofc, ofx) = of_code(ov);
if ofc > 31 {
return Err(Error::Corruption);
}
ll_count[llc as usize] += 1;
of_count[ofc as usize] += 1;
ml_count[mlc as usize] += 1;
of_max = of_max.max(ofc);
coded.push(CodedSeq {
llc,
mlc,
ofc,
llx,
mlx,
ofx,
llb,
mlb,
});
}
(coded, ll_count, of_count, ml_count, of_max)
};
let use_low = strategy.id() >= Strategy::Lazy.id();
let last_i = coded.len() - 1;
let (ll_mode, ll_t, ll_hdr, of_mode, of_t, of_hdr, ml_mode, ml_t, ml_hdr) = {
let _t = crate::prof::scope(crate::prof::Stage::EncodeTableSelect);
let (ll_mode, ll_t, ll_hdr) = select_seq_table(
&ll_count,
36,
9,
&fse::DEFAULT_LL_NORM,
6,
entropy.ll.as_deref().map(|r| &**r),
use_low,
false,
coded[last_i].llc as usize,
)?;
let of_needs_comp = of_max as usize >= fse::DEFAULT_OF_NORM.len();
let (of_mode, of_t, of_hdr) = select_seq_table(
&of_count,
32,
8,
&fse::DEFAULT_OF_NORM,
5,
entropy.of.as_deref().map(|r| &**r),
use_low,
of_needs_comp,
coded[last_i].ofc as usize,
)?;
let (ml_mode, ml_t, ml_hdr) = select_seq_table(
&ml_count,
53,
9,
&fse::DEFAULT_ML_NORM,
6,
entropy.ml.as_deref().map(|r| &**r),
use_low,
false,
coded[last_i].mlc as usize,
)?;
(
ll_mode, ll_t, ll_hdr, of_mode, of_t, of_hdr, ml_mode, ml_t, ml_hdr,
)
};
crate::prof::note_seq_mode(ll_mode);
crate::prof::note_seq_mode(of_mode);
crate::prof::note_seq_mode(ml_mode);
dst.push((ll_mode << 6) | (of_mode << 4) | (ml_mode << 2));
dst.extend_from_slice(&ll_hdr);
dst.extend_from_slice(&of_hdr);
dst.extend_from_slice(&ml_hdr);
fse::give_ncount_buf(ll_hdr);
fse::give_ncount_buf(of_hdr);
fse::give_ncount_buf(ml_hdr);
let last = last_i;
let _fs = crate::prof::scope(crate::prof::Stage::EncodeFseSeq);
let mut ml_s = ml_t.init_state2(coded[last].mlc as usize);
let mut of_s = of_t.init_state2(coded[last].ofc as usize);
let mut ll_s = ll_t.init_state2(coded[last].llc as usize);
let mut bits = BitCStream::from_vec(
core::mem::take(&mut tables.bits_scratch),
coded.len() * 4 + 16,
);
bits.add_bits(u64::from(coded[last].llx), u32::from(coded[last].llb));
bits.add_bits(u64::from(coded[last].mlx), u32::from(coded[last].mlb));
bits.add_bits(u64::from(coded[last].ofx), u32::from(coded[last].ofc));
bits.flush();
if coded.len() >= 2 {
for n in (0..coded.len() - 1).rev() {
let c = &coded[n];
of_t.encode(&mut of_s, &mut bits, c.ofc as usize);
ml_t.encode(&mut ml_s, &mut bits, c.mlc as usize);
ll_t.encode(&mut ll_s, &mut bits, c.llc as usize);
bits.add_bits(u64::from(c.llx), u32::from(c.llb));
bits.add_bits(u64::from(c.mlx), u32::from(c.mlb));
bits.add_bits(u64::from(c.ofx), u32::from(c.ofc));
}
}
ml_t.flush(ml_s, &mut bits);
of_t.flush(of_s, &mut bits);
ll_t.flush(ll_s, &mut bits);
let out = bits.close();
dst.extend_from_slice(&out);
tables.bits_scratch = out;
tables.coded_scratch = coded;
let ll_new = match ll_t {
SeqTable::Own(t) => Some(RetainedTable::Own(t)),
SeqTable::Static(t) => Some(RetainedTable::Static(t)),
SeqTable::Ref(_) => None,
};
let of_new = match of_t {
SeqTable::Own(t) => Some(RetainedTable::Own(t)),
SeqTable::Static(t) => Some(RetainedTable::Static(t)),
SeqTable::Ref(_) => None,
};
let ml_new = match ml_t {
SeqTable::Own(t) => Some(RetainedTable::Own(t)),
SeqTable::Static(t) => Some(RetainedTable::Static(t)),
SeqTable::Ref(_) => None,
};
if let Some(t) = ll_new {
entropy.ll = Some(alloc::sync::Arc::new(t));
}
if let Some(t) = of_new {
entropy.of = Some(alloc::sync::Arc::new(t));
}
if let Some(t) = ml_new {
entropy.ml = Some(alloc::sync::Arc::new(t));
}
Ok(())
}
#[inline(always)]
fn ncount_seq_table(
counts: &[u32],
last_sym: usize,
max_log: u8,
use_low_prob: bool,
) -> Result<(Vec<u8>, FseCTable), Error> {
let mut buf = counts.to_vec();
if last_sym < buf.len() && buf[last_sym] > 1 {
buf[last_sym] -= 1;
}
fse::ncount_and_ctable(&buf, max_log, use_low_prob)
}
#[cfg(feature = "alloc")]
enum SeqTable<'a> {
Ref(&'a FseCTable),
Static(&'static FseCTable),
Own(FseCTable),
}
#[cfg(feature = "alloc")]
impl core::ops::Deref for SeqTable<'_> {
type Target = FseCTable;
#[inline(always)]
fn deref(&self) -> &FseCTable {
match self {
SeqTable::Ref(t) => t,
SeqTable::Static(t) => t,
SeqTable::Own(t) => t,
}
}
}
#[allow(clippy::too_many_arguments)]
#[inline(never)]
fn select_seq_table<'a>(
counts: &[u32],
_alphabet: usize,
max_log: u8,
default_norm: &[i16],
default_log: u8,
prev: Option<&'a FseCTable>,
use_low_prob: bool,
force_compressed: bool,
last_sym: usize,
) -> Result<(u8, SeqTable<'a>, Vec<u8>), Error> {
let total: u32 = counts.iter().sum();
let most = counts.iter().copied().max().unwrap_or(0);
if total > 0 && most == total {
let sym = counts.iter().position(|&c| c == total).unwrap_or(0) as u8;
return Ok((1, SeqTable::Own(FseCTable::rle(u16::from(sym))), vec![sym]));
}
let basic_owned;
#[cfg(all(feature = "std", feature = "alloc"))]
let cached = fse::default_ctable_cached(default_norm, default_log);
#[cfg(not(all(feature = "std", feature = "alloc")))]
let cached: Option<&fse::FseCTable> = None;
let basic: &fse::FseCTable = match cached {
Some(t) => t,
None => {
#[cfg(feature = "profile")]
N9_BASIC.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
basic_owned = fse::FseCTable::from_norm(default_norm, default_log)?;
&basic_owned
}
};
let mut best_mode = 0u8;
let mut best_table: Option<SeqTable<'a>> = None;
let mut best_hdr = Vec::new();
let mut best_cost = basic.bit_cost(counts);
if let Some(p) = prev {
let c = p.bit_cost(counts);
if c <= best_cost {
best_mode = 3;
best_table = Some(SeqTable::Ref(p));
best_hdr = Vec::new();
best_cost = c;
}
}
if total >= 8 {
if let Ok((hdr, ct)) = ncount_seq_table(counts, last_sym, max_log, use_low_prob) {
let c = ct.bit_cost(counts) + (hdr.len() as u64) * 8;
if c < best_cost || force_compressed {
best_mode = 2;
best_table = Some(SeqTable::Own(ct));
best_hdr = hdr;
best_cost = c;
} else {
fse::give_ncount_buf(hdr);
}
}
}
if force_compressed && best_mode == 0 {
if let Ok((hdr, ct)) = ncount_seq_table(counts, last_sym, max_log, use_low_prob) {
return Ok((2, SeqTable::Own(ct), hdr));
}
return Err(Error::Corruption);
}
let _ = best_cost;
Ok((
best_mode,
best_table.unwrap_or_else(|| match cached {
Some(t) => SeqTable::Static(t),
None => SeqTable::Own(basic.clone()),
}),
best_hdr,
))
}
#[derive(Clone, Copy)]
struct CodedSeq {
llc: u8,
mlc: u8,
ofc: u8,
llx: u32,
mlx: u32,
ofx: u32,
llb: u8,
mlb: u8,
}
#[allow(clippy::too_many_arguments)]
fn find_sequences(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
find_sequences_bmi2(
src,
block_start,
block_end,
window,
params,
tables,
ldm,
ldm_p,
reps,
)
};
}
find_sequences_inner(
src,
block_start,
block_end,
window,
params,
tables,
ldm,
ldm_p,
reps,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn find_sequences_bmi2(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_sequences_inner(
src,
block_start,
block_end,
window,
params,
tables,
ldm,
ldm_p,
reps,
)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn find_sequences_inner(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
ldm: Option<&mut crate::ldm::LdmTables>,
ldm_p: crate::ldm::LdmParams,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let hits = if let Some(lt) = ldm {
if ldm_p.enable {
let rp = ldm_p.resolved(params.window_log);
crate::ldm::collect_ldm(
lt,
src,
block_start,
block_end,
window,
rp,
tables.frame_start,
)
} else {
Vec::new()
}
} else {
Vec::new()
};
if hits.is_empty() {
return find_sequences_strategy(src, block_start, block_end, window, params, tables, reps);
}
let mut seqs = Vec::new();
let mut lits = Vec::new();
let mut pos = block_start;
for h in hits {
if h.ip < pos || h.ip >= block_end {
continue;
}
let (s, lit) = find_sequences_strategy(src, pos, h.ip, window, params, tables, reps);
seqs.extend(s.iter().copied());
lits.extend_from_slice(&lit);
let consumed: u32 = s.iter().map(|x| x.litlen).sum();
let leftover = (lit.len() as u32).saturating_sub(consumed);
seqs.push(Seq {
litlen: leftover,
matchlen: h.matchlen,
offset: h.offset,
});
pos = h.ip + h.matchlen as usize;
if pos > block_end {
pos = block_end;
}
}
let (s, lit) = find_sequences_strategy(src, pos, block_end, window, params, tables, reps);
seqs.extend(s);
lits.extend_from_slice(&lit);
(seqs, lits)
}
fn find_sequences_strategy(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
find_sequences_strategy_bmi2(src, block_start, block_end, window, params, tables, reps)
};
}
find_sequences_strategy_sel(src, block_start, block_end, window, params, tables, reps)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn find_sequences_strategy_bmi2(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_sequences_strategy_sel(src, block_start, block_end, window, params, tables, reps)
}
#[inline(always)]
fn find_sequences_strategy_sel(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
match params.strategy {
Strategy::DFast => find_dfast(src, block_start, block_end, window, params, tables, reps),
Strategy::Greedy => find_greedy(src, block_start, block_end, window, params, tables, reps),
Strategy::Lazy => find_lazy(src, block_start, block_end, window, params, tables, 1, reps),
Strategy::Lazy2 => find_lazy(src, block_start, block_end, window, params, tables, 2, reps),
Strategy::BtLazy2 => {
find_bt_lazy(src, block_start, block_end, window, params, tables, 2, reps)
}
Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2 => {
find_opt(src, block_start, block_end, window, params, tables, reps)
}
Strategy::Fast => {
if tables.blocks_done > 0 && tables.rep_yield > fast_lazy_threshold() {
tables.rep_run = tables.rep_run.saturating_add(1);
} else {
tables.rep_run = 0;
}
if fast_lazy_enabled() && tables.rep_run >= FAST_LAZY_RUN {
#[cfg(feature = "profile")]
FF_LAZY_FIRES.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if !tables.fast_hash_legacy
&& fast_hash_wide_enabled()
&& (5..=8).contains(&(params.min_match.max(3) as usize))
{
fast_hash_relatch(tables, src, block_start, window);
}
if tables.pack_tags {
for e in tables.hash.iter_mut() {
*e &= 0x00FF_FFFF;
}
tables.pack_tags = false;
}
if tables.chain.is_empty() {
tables.chain = alloc::vec![0u32; 1usize << params.chain_log.min(24)];
}
let r = find_lazy(src, block_start, block_end, window, params, tables, 1, reps);
tables.blocks_done += 1;
return r;
}
let r = find_fast(src, block_start, block_end, window, params, tables, reps);
tables.blocks_done += 1;
r
}
}
}
static FAST_LAZY_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_fast_lazy_arm(on: bool) {
FAST_LAZY_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn fast_lazy_enabled() -> bool {
use core::sync::atomic::Ordering;
match FAST_LAZY_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
#[cfg(feature = "std")]
{
let on = std::env::var("RZSTD_FASTLAZY")
.map(|v| v.trim() != "0")
.unwrap_or(true);
FAST_LAZY_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
#[cfg(not(feature = "std"))]
true
}
}
}
const FAST_LAZY_RUN: u32 = 4;
fn fast_lazy_threshold() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = FASTLAZY_T_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_FASTLAZY_T")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.7);
FASTLAZY_T_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.7
}
static FASTLAZY_T_CACHE: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
const REP_YIELD_MIN_DEFAULT: f32 = 0.125;
const REP_PROBE_PERIOD: u32 = 16;
fn rep_len_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[1].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = REPLEN_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_REPLEN")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(1.0);
REPLEN_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
1.0
}
static REPLEN_CACHE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
fn rep_decay() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[2].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = REP_DECAY_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_REP_DECAY")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.0);
REP_DECAY_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.0
}
#[cfg(feature = "std")]
static REP_DECAY_CACHE: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
#[cfg(feature = "std")]
static REPMIN_OVR: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
fn rep_yield_min_for(strategy: Strategy) -> f32 {
#[cfg(feature = "profile")]
ENVHIT[3].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let mut c = REPMIN_OVR.load(Ordering::Relaxed);
if c == u32::MAX {
c = std::env::var("RZSTD_REPMIN")
.ok()
.and_then(|v| v.trim().parse::<f32>().ok())
.map(f32::to_bits)
.unwrap_or(u32::MAX - 1);
REPMIN_OVR.store(c, Ordering::Relaxed);
}
if c != u32::MAX - 1 {
return f32::from_bits(c);
}
}
match strategy {
Strategy::DFast => 0.005,
Strategy::Fast => 0.20,
_ => REP_YIELD_MIN_DEFAULT,
}
}
#[allow(dead_code)]
fn rep_yield_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[4].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
std::env::var("RZSTD_REPMIN")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(REP_YIELD_MIN_DEFAULT)
}
#[cfg(not(feature = "std"))]
REP_YIELD_MIN_DEFAULT
}
fn find_fast(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let pipe_on = pipe_enabled();
#[allow(clippy::if_same_then_else)]
let route = if tables.route_force != 0 {
tables.route_force
} else if tables.step_pick == 2 && tables.step_reprobe > 0 && step_probe_on() {
2
} else if !pair_enabled() {
0
} else if params.target_length != 0 {
2
} else if tables.pair_probe == 0 {
2
} else if tables.pair_gain < pair_gain_min() {
0
} else if tables.pair_gain >= pair_rate_hi() {
2
} else if tables.rep_yield > pair_rep_max() {
0
} else if tables.pair_gain < pair_gain_lo() {
2
} else {
1
};
tables.pair_route = route;
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering;
ROUTE_HIST[tables.pair_route.min(2) as usize].fetch_add(1, Ordering::Relaxed);
ROUTE_GAIN.fetch_add((tables.pair_gain * 1000.0) as u64, Ordering::Relaxed);
ROUTE_REP.fetch_add((tables.rep_yield * 1000.0) as u64, Ordering::Relaxed);
ROUTE_N.fetch_add(1, Ordering::Relaxed);
let q = |v: f32| -> u64 { (v.max(0.0).min(1.0e6) * 1000.0) as u64 };
SIG_GAIN.fetch_add(q(tables.pair_gain), Ordering::Relaxed);
SIG_REP.fetch_add(q(tables.rep_yield), Ordering::Relaxed);
SIG_TAG.fetch_add(q(tables.tag_yield), Ordering::Relaxed);
SIG_REPLEN.fetch_add(q(tables.rep_len_ratio), Ordering::Relaxed);
SIG_NSEQ.fetch_add(tables.last_nseq as u64, Ordering::Relaxed);
SIG_OPTREP.fetch_add(q(tables.opt_rep_rate), Ordering::Relaxed);
SIG_N.fetch_add(1, Ordering::Relaxed);
}
let s0 = if params.target_length == 0 {
if tables.pair_route == 1 {
1
} else {
step0_default()
}
} else {
tables.step_used = 0;
params.target_length as usize + 1
};
let wide_block = fast_hash_wide_enabled()
&& (5..=8).contains(&(params.min_match.max(3) as usize))
&& !tables.fast_hash_legacy
&& tables.pack_tags;
macro_rules! go {
($p:expr, $r:expr, $h:expr, $s:expr, $pi:expr) => {{
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[allow(unsafe_code)]
let out = if crate::simd::has_bmi2() {
if wide_block {
unsafe {
find_fast_impl_bmi2::<$p, $r, 0, 0, true>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
}
} else {
unsafe {
find_fast_impl_bmi2::<$p, $r, 0, 0, false>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
}
}
} else if wide_block {
find_fast_impl::<$p, $r, $h, 0, true>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
} else {
find_fast_impl::<$p, $r, $h, 0, false>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
};
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let out = if wide_block {
find_fast_impl::<$p, $r, $h, 0, true>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
} else {
find_fast_impl::<$p, $r, $h, 0, false>(
s0,
pipe_on,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
};
out
}};
}
#[cfg(feature = "profile")]
FAST_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let rep_on = rep_search_on(tables.rep_yield, params.strategy)
|| tables.rep_probe == 0
|| tables.rep_len_ratio >= rep_len_min();
tables.rep_probe = if tables.rep_probe == 0 {
REP_PROBE_PERIOD
} else {
tables.rep_probe - 1
};
let ut = (tables.pack_tags || !tables.tags.is_empty())
&& tag_enabled()
&& tables.tag_yield >= tag_min();
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
let spec = fast_spec_enabled()
&& pipe_on
&& ((!ut && !rep_on && (1..=4).contains(&s0))
|| ((ut || rep_on) && (1..=2).contains(&s0)));
let idx = if spec {
0usize
} else {
match (ut, rep_on, pipe_on) {
(true, false, true) => 1,
(_, true, true) => 2,
_ => 3,
}
};
FF_ARM[idx].fetch_add(1, Relaxed);
}
let r = match (ut, rep_on, pipe_on, s0) {
(false, false, true, 2) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, false, 12, 2, true),
13 => go!(false, false, 13, 2, true),
14 => go!(false, false, 14, 2, true),
15 => go!(false, false, 15, 2, true),
16 => go!(false, false, 16, 2, true),
_ => go!(false, false, 0, 2, true),
},
(false, false, true, 1) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, false, 12, 1, true),
13 => go!(false, false, 13, 1, true),
14 => go!(false, false, 14, 1, true),
15 => go!(false, false, 15, 1, true),
16 => go!(false, false, 16, 1, true),
_ => go!(false, false, 0, 1, true),
},
(false, false, true, 3) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, false, 12, 3, true),
13 => go!(false, false, 13, 3, true),
14 => go!(false, false, 14, 3, true),
15 => go!(false, false, 15, 3, true),
16 => go!(false, false, 16, 3, true),
_ => go!(false, false, 0, 3, true),
},
(false, false, true, 4) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, false, 12, 4, true),
13 => go!(false, false, 13, 4, true),
14 => go!(false, false, 14, 4, true),
15 => go!(false, false, 15, 4, true),
16 => go!(false, false, 16, 4, true),
_ => go!(false, false, 0, 4, true),
},
(true, false, true, 2) if fast_spec_enabled() => match tables.hash_log {
12 => go!(true, false, 12, 2, true),
13 => go!(true, false, 13, 2, true),
14 => go!(true, false, 14, 2, true),
15 => go!(true, false, 15, 2, true),
16 => go!(true, false, 16, 2, true),
_ => go!(true, false, 0, 2, true),
},
(true, false, true, 1) if fast_spec_enabled() => match tables.hash_log {
12 => go!(true, false, 12, 1, true),
13 => go!(true, false, 13, 1, true),
14 => go!(true, false, 14, 1, true),
15 => go!(true, false, 15, 1, true),
16 => go!(true, false, 16, 1, true),
_ => go!(true, false, 0, 1, true),
},
(false, true, true, 2) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, true, 12, 2, true),
13 => go!(false, true, 13, 2, true),
14 => go!(false, true, 14, 2, true),
15 => go!(false, true, 15, 2, true),
16 => go!(false, true, 16, 2, true),
_ => go!(false, true, 0, 2, true),
},
(false, true, true, 1) if fast_spec_enabled() => match tables.hash_log {
12 => go!(false, true, 12, 1, true),
13 => go!(false, true, 13, 1, true),
14 => go!(false, true, 14, 1, true),
15 => go!(false, true, 15, 1, true),
16 => go!(false, true, 16, 1, true),
_ => go!(false, true, 0, 1, true),
},
(true, true, true, 2) if fast_spec_enabled() => match tables.hash_log {
12 => go!(true, true, 12, 2, true),
13 => go!(true, true, 13, 2, true),
14 => go!(true, true, 14, 2, true),
15 => go!(true, true, 15, 2, true),
16 => go!(true, true, 16, 2, true),
_ => go!(true, true, 0, 2, true),
},
(true, true, true, 1) if fast_spec_enabled() => match tables.hash_log {
12 => go!(true, true, 12, 1, true),
13 => go!(true, true, 13, 1, true),
14 => go!(true, true, 14, 1, true),
15 => go!(true, true, 15, 1, true),
16 => go!(true, true, 16, 1, true),
_ => go!(true, true, 0, 1, true),
},
(false, false, true, _) => go!(false, false, 0, 0, true),
(false, false, false, 2) => go!(false, false, 0, 2, false),
(false, false, false, 1) => go!(false, false, 0, 1, false),
(false, false, false, _) => go!(false, false, 0, 0, false),
(false, true, true, _) => go!(false, true, 0, 0, true),
(true, true, true, _) => go!(true, true, 0, 0, true),
(true, true, false, _) => go!(true, true, 0, 0, false),
(true, false, true, _) => go!(true, false, 0, 0, true),
(true, false, false, _) => go!(true, false, 0, 0, false),
(false, true, false, _) => go!(false, true, 0, 0, false),
};
tables.pair_probe = if tables.pair_probe == 0 {
PAIR_PROBE_PERIOD
} else {
tables.pair_probe - 1
};
r
}
#[inline(never)]
fn find_fast_impl<
const PACKED: bool,
const REP: bool,
const HLOG: u32,
const STEP: usize,
const WIDE: bool,
>(
step_rt: usize,
pipe_rt: bool,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_fast_impl_inner::<PACKED, REP, HLOG, STEP, WIDE, false>(
step_rt,
pipe_rt,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
#[inline(never)]
unsafe fn find_fast_impl_bmi2<
const PACKED: bool,
const REP: bool,
const HLOG: u32,
const STEP: usize,
const WIDE: bool,
>(
step_rt: usize,
pipe_rt: bool,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_fast_impl_inner::<PACKED, REP, HLOG, STEP, WIDE, true>(
step_rt,
pipe_rt,
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn find_fast_impl_inner<
const PACKED: bool,
const REP: bool,
const HLOG: u32,
const STEP: usize,
const WIDE: bool,
const BMI2: bool,
>(
step_rt: usize,
pipe_rt: bool,
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let mls = params.min_match.max(3) as usize;
debug_assert!(block_start <= block_end && block_end <= src.len());
let block_end = block_end.max(block_start).min(src.len());
crate::prof::note_scratch(2);
let block_len = block_end.saturating_sub(block_start);
let reserve = lit_push_enabled();
let lp_copy =
if reserve && (tables.blocks_done == 0 || tables.lit_short_share >= lit_short_min()) {
lit_width_for(tables)
} else {
0
};
let seq_guess = (tables.last_nseq + tables.last_nseq / 4 + 64).min(block_len / mls + 16);
let keep = finder_scratch_enabled();
let mut seqs = if keep {
core::mem::take(&mut tables.seq_scratch)
} else {
Vec::new()
};
seqs.clear();
if reserve && seqs.capacity() < seq_guess {
seqs = Vec::with_capacity(seq_guess);
}
let mut lits = if keep {
core::mem::take(&mut tables.lit_scratch)
} else {
Vec::new()
};
lits.clear();
if reserve && lits.capacity() < block_len + LIT_PUSH_WIDTH_MAX {
lits = Vec::with_capacity(block_len + LIT_PUSH_WIDTH_MAX);
}
let mut anchor = block_start;
let ilimit = block_end.saturating_sub(8);
if block_start >= ilimit {
crate::prof::note_huff_path(10);
push_lits_range(&mut lits, src, block_start, block_end);
crate::prof::note_search(0, 0, 0, 0, lits.len() as u64);
tables.last_nseq = 0;
return (seqs, lits);
}
let mut ip = block_start;
let mut hash_v = core::mem::take(&mut tables.hash);
let mut tags_v = core::mem::take(&mut tables.tags);
let pack = tables.pack_tags;
debug_assert!(!WIDE || pack);
let pack_eff = if WIDE { true } else { pack };
let tags_live = !tags_v.is_empty();
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut hits = 0u64;
let mut rep_hits = 0u64;
let mut rep_probes = 0u64;
let mut rep_bytes = 0u64;
let mut cand = (0u64, 0u64);
let step0 = if STEP != 0 { STEP } else { step_rt };
let route = tables.pair_route;
let maintain_rep1 = pipe_rep1_enabled() && tables.rep_yield <= fast_lazy_threshold();
let pair = step0 > 2 || (route == 2 && tables.rep_yield <= pair_rep_max());
let mut pair_bytes = 0u64;
let mut pair_probes = 0u64;
let lowest = block_start.saturating_sub(window).max(tables.frame_start);
let frame_start = tables.frame_start;
let mut rep1 = reps[0] as usize;
debug_assert!(!WIDE || !tables.fast_hash_legacy);
let f_wide = WIDE;
let f_mask = if WIDE {
fast_hash_spec(mls, if HLOG != 0 { HLOG } else { tables.hash_log }).mask
} else {
0
};
let hlog_eff = if HLOG != 0 { HLOG } else { tables.hash_log };
let f_shift = if WIDE {
64u32.saturating_sub(hlog_eff)
} else {
32u32.saturating_sub(hlog_eff)
};
#[cfg(feature = "profile")]
let bar_all = std::env::var("RZSTD_FFBAR_ALL")
.map(|v| v == "1")
.unwrap_or(false);
#[cfg(not(feature = "profile"))]
let bar_all = false;
let veto_block = (WIDE || tables.fast_hash_legacy)
&& (bar_all || (tables.blocks_done > 0 && tables.rep_yield > fast_lazy_threshold()));
let accept_ml = if veto_block {
mls.max(ff_anchor_ml())
} else {
mls
};
let accel = if cfg!(feature = "profile") {
accel_shift_for(params.strategy)
} else {
7
};
let f_ends = dfast_fill_ends();
let ectx = FastEmitCtx {
src,
pack: pack_eff,
f_wide,
f_mask,
f_shift,
ilimit,
frame_start,
w: lp_copy,
tags_live,
ends: f_ends,
};
if pipe_rt && !pair && ip <= ilimit {
if COUNT {
FF_PIPE_BLOCKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let mut pipe_pos = 0u64;
let (mut ff_made, mut ff_used) = (0u64, 0u64);
let (mut h0, mut g0) = fast_hash_tag::<true>(src, ip, WIDE, f_mask, f_shift);
let mut m0 = fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h0, g0);
loop {
if COUNT {
pipe_pos += 1;
}
if COUNT {
probes += 1;
}
if COUNT && PACKED {
let raw = fast_slot_raw(&hash_v, pack_eff, h0);
if m0 == 0 && raw != 0 {
if fast_probe(&mut (0, 0), src, raw, ip, window, lowest, mls, block_end)
.is_some()
{
TAG_FALSE_REJECT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
TAG_REJECT_TOTAL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
fast_slot_store(&mut hash_v, &mut tags_v, pack_eff, tags_live, h0, ip, g0);
if REP {
if COUNT {
rep_probes += 1;
}
if let Some(ml) = try_rep1(src, ip, rep1, lowest, block_end, ilimit) {
rep_hits += 1;
rep_bytes += ml as u64;
if COUNT {
hits += 1;
}
let mstart = ip + 1;
crate::prof::note_huff_path(11);
push_literals(&mut lits, src, anchor, mstart, lp_copy);
crate::prof::note_huff_path(13);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
if ip > ilimit {
break;
}
let (nh, ng) = fast_hash_tag::<true>(src, ip, WIDE, f_mask, f_shift);
h0 = nh;
g0 = ng;
m0 = fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h0, g0);
continue;
}
}
let nip = ip + step0 + ((ip - anchor) >> accel);
if COUNT && nip <= ilimit {
ff_made += 1;
}
let (h1, g1, m1) = if nip <= ilimit {
let (h, g) = fast_hash_tag::<true>(src, nip, WIDE, f_mask, f_shift);
let v = if h == h0 {
if !PACKED || g == g0 {
(ip as u32).wrapping_add(1)
} else {
0
}
} else {
fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h, g)
};
(h, g, v)
} else {
(0usize, 0u8, 0u32)
};
if let Some((m, ml)) = if WIDE {
fast_probe_wide::<true>(
&mut cand, src, m0, ip, window, lowest, accept_ml, f_mask, block_end,
)
} else {
fast_probe(&mut cand, src, m0, ip, window, lowest, accept_ml, block_end)
} {
if COUNT {
hits += 1;
}
ip = emit_fast_seq::<PACKED, BMI2>(
&ectx,
&mut hash_v,
&mut tags_v,
&mut seqs,
&mut lits,
anchor,
ip,
m,
ml,
);
anchor = ip;
if maintain_rep1 {
if let Some(sq) = seqs.last() {
rep1 = sq.offset as usize;
}
}
if ip > ilimit {
break;
}
let (nh, ng) = fast_hash_tag::<true>(src, ip, WIDE, f_mask, f_shift);
h0 = nh;
g0 = ng;
m0 = fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h0, g0);
continue;
}
if nip > ilimit {
break;
}
if COUNT {
ff_used += 1;
}
ip = nip;
h0 = h1;
g0 = g1;
m0 = m1;
}
crate::prof::note_huff_path(12);
push_lits_range(&mut lits, src, anchor, block_end);
let match_bytes: u64 = if cfg!(feature = "profile") {
seqs.iter().map(|s| u64::from(s.matchlen)).sum()
} else {
0
};
crate::prof::note_search(
probes,
hits,
seqs.len() as u64,
match_bytes,
lits.len() as u64,
);
let y = if seqs.is_empty() {
0.0
} else {
rep_hits as f32 / seqs.len() as f32
};
tables.rep_yield = y.max(tables.rep_yield * 0.5);
tables.tag_yield = cand_yield(cand);
let (ls, lm) = lit_shares(&seqs);
tables.lit_short_share = ls;
tables.lit_mid_share = lm;
tables.last_nseq = seqs.len();
if REP && replen_pipe_fixed() && rep_hits > 0 && !seqs.is_empty() {
let all_bytes: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
let num = rep_bytes as f32 * seqs.len() as f32;
let den = rep_hits as f32 * all_bytes as f32;
if den > 0.0 {
tables.rep_len_ratio = 0.75 * tables.rep_len_ratio + 0.25 * (num / den);
}
}
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
MM_TOTAL.fetch_add(pipe_pos, Relaxed);
REP_PROBES.fetch_add(rep_probes, Relaxed);
REP_BYTES.fetch_add(rep_bytes, Relaxed);
REP_HITS_G.fetch_add(rep_hits, Relaxed);
let mb: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
ALL_MATCH_BYTES.fetch_add(mb, Relaxed);
ALL_SEQS.fetch_add(seqs.len() as u64, Relaxed);
FF_SPEC_MADE.fetch_add(ff_made, Relaxed);
FF_SPEC_USED.fetch_add(ff_used, Relaxed);
}
tables.hash = hash_v;
tables.tags = tags_v;
return (seqs, lits);
}
let (mut mm_total, mut mm_miss) = (0u64, 0u64);
while ip <= ilimit {
if COUNT {
mm_total += 1;
}
if COUNT {
probes += 1;
}
let (h0, g0) = fast_hash_tag::<true>(src, ip, WIDE, f_mask, f_shift);
let m0 =
fast_slot_swap::<PACKED>(&mut hash_v, &mut tags_v, pack_eff, tags_live, h0, ip, g0);
if COUNT && PACKED {
let raw = fast_slot_raw(&hash_v, pack_eff, h0);
if m0 == 0 && raw != 0 {
if fast_probe(&mut (0, 0), src, raw, ip, window, lowest, mls, block_end).is_some() {
TAG_FALSE_REJECT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
TAG_REJECT_TOTAL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
let pair_pre = if pair && ip < ilimit {
let (h1, g1) = fast_hash_tag::<false>(src, ip + 1, WIDE, f_mask, f_shift);
Some((
h1,
g1,
fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h1, g1),
))
} else {
None
};
if REP {
if COUNT {
rep_probes += 1;
}
if let Some(ml) = try_rep1(src, ip, rep1, lowest, block_end, ilimit) {
rep_hits += 1;
rep_bytes += ml as u64;
if COUNT {
hits += 1;
}
let mstart = ip + 1;
crate::prof::note_huff_path(11);
push_literals(&mut lits, src, anchor, mstart, lp_copy);
crate::prof::note_huff_path(13);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
continue;
}
}
if let Some((m, ml)) = if WIDE {
fast_probe_wide::<true>(
&mut cand, src, m0, ip, window, lowest, accept_ml, f_mask, block_end,
)
} else {
fast_probe(&mut cand, src, m0, ip, window, lowest, accept_ml, block_end)
} {
if COUNT {
hits += 1;
}
ip = emit_fast_seq::<PACKED, BMI2>(
&ectx,
&mut hash_v,
&mut tags_v,
&mut seqs,
&mut lits,
anchor,
ip,
m,
ml,
);
anchor = ip;
if maintain_rep1 {
if let Some(sq) = seqs.last() {
rep1 = sq.offset as usize;
}
}
continue;
}
if pair {
let ip1 = ip + 1;
if ip1 <= ilimit {
if COUNT {
probes += 1;
}
pair_probes += 1;
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
if m0 == 0 {
PAIR_M0_EMPTY.fetch_add(1, Relaxed);
} else {
PAIR_M0_LIVE.fetch_add(1, Relaxed);
}
}
if COUNT {
PAIR_PROBES.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let (h1, g1, m1) = match pair_pre {
Some(v) => v,
None => {
let (h, g) = fast_hash_tag::<false>(src, ip1, WIDE, f_mask, f_shift);
(
h,
g,
fast_slot_load::<PACKED>(&hash_v, &tags_v, pack_eff, tags_live, h, g),
)
}
};
if COUNT && PACKED {
let raw = fast_slot_raw(&hash_v, pack_eff, h1);
if m1 == 0 && raw != 0 {
if fast_probe(&mut (0, 0), src, raw, ip1, window, lowest, mls, block_end)
.is_some()
{
TAG_FALSE_REJECT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
TAG_REJECT_TOTAL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
fast_slot_store(&mut hash_v, &mut tags_v, pack_eff, tags_live, h1, ip1, g1);
if let Some((m, ml)) = (if WIDE {
fast_probe_wide::<false>(
&mut cand, src, m1, ip1, window, lowest, accept_ml, f_mask, block_end,
)
} else {
fast_probe(
&mut cand, src, m1, ip1, window, lowest, accept_ml, block_end,
)
})
.filter(|&(_, ml)| !veto_block || ml >= ff_anchor_ml())
{
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
if m0 == 0 {
PAIR_HIT_EMPTY.fetch_add(1, Relaxed);
PAIR_BYTES_EMPTY.fetch_add(ml as u64, Relaxed);
} else {
PAIR_HIT_LIVE.fetch_add(1, Relaxed);
PAIR_BYTES_LIVE.fetch_add(ml as u64, Relaxed);
}
PAIR_HITS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
PAIR_BYTES.fetch_add(ml as u64, core::sync::atomic::Ordering::Relaxed);
}
if COUNT {
hits += 1;
}
pair_bytes += ml as u64;
ip = emit_fast_seq::<PACKED, BMI2>(
&ectx,
&mut hash_v,
&mut tags_v,
&mut seqs,
&mut lits,
anchor,
ip1,
m,
ml,
);
anchor = ip;
continue;
}
}
}
if COUNT {
mm_miss += 1;
}
ip += step0 + ((ip - anchor) >> accel);
}
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
MM_TOTAL.fetch_add(mm_total, Relaxed);
MM_MISS.fetch_add(mm_miss, Relaxed);
}
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
REP_PROBES.fetch_add(rep_probes, Relaxed);
REP_BYTES.fetch_add(rep_bytes, Relaxed);
REP_HITS_G.fetch_add(rep_hits, Relaxed);
let mb: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
ALL_MATCH_BYTES.fetch_add(mb, Relaxed);
ALL_SEQS.fetch_add(seqs.len() as u64, Relaxed);
}
if REP && rep_hits > 0 && !seqs.is_empty() {
let all_bytes: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
let num = rep_bytes as f32 * seqs.len() as f32;
let den = rep_hits as f32 * all_bytes as f32;
if den > 0.0 {
tables.rep_len_ratio = 0.75 * tables.rep_len_ratio + 0.25 * (num / den);
}
}
tables.tag_yield = cand_yield(cand);
let (ls, lm) = lit_shares(&seqs);
tables.lit_short_share = ls;
tables.lit_mid_share = lm;
if pair {
let now = pair_bytes as f32 / pair_probes.max(1) as f32;
tables.pair_gain = 0.75 * tables.pair_gain + 0.25 * now;
}
crate::prof::note_huff_path(12);
push_lits_range(&mut lits, src, anchor, block_end);
let match_bytes: u64 = if cfg!(feature = "profile") {
seqs.iter().map(|s| u64::from(s.matchlen)).sum()
} else {
0
};
crate::prof::note_search(
probes,
hits,
seqs.len() as u64,
match_bytes,
lits.len() as u64,
);
let y = if seqs.is_empty() {
0.0
} else {
rep_hits as f32 / seqs.len() as f32
};
tables.rep_yield = y.max(tables.rep_yield * 0.5);
tables.last_nseq = seqs.len();
tables.hash = hash_v;
tables.tags = tags_v;
(seqs, lits)
}
#[inline(always)]
fn fast_probe_wide<const SAFE: bool>(
cand: &mut (u64, u64),
src: &[u8],
match_slot: u32,
ip: usize,
window: usize,
lowest: usize,
accept_ml: usize,
mask: u64,
block_end: usize,
) -> Option<(usize, usize)> {
if match_slot == 0 {
return None;
}
let m = (match_slot as usize) - 1;
if m < lowest || m >= ip || ip - m > window {
return None;
}
let a = if SAFE {
debug_assert!(ip + 8 <= src.len());
crate::simd::load_u64_le(src, ip)
} else {
load_u64le_tail(src, ip)
};
let b = if SAFE {
debug_assert!(m + 8 <= src.len());
crate::simd::load_u64_le(src, m)
} else {
load_u64le_tail(src, m)
};
let x = a ^ b;
if x & mask != 0 {
if cfg!(feature = "profile") {
cand.0 += 1;
}
return None;
}
if cfg!(feature = "profile") {
cand.1 += 1;
}
#[cfg(feature = "profile")]
FF_CAND4.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "profile")]
FF_ACCEPT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let ml = if x != 0 {
(x.trailing_zeros() as usize) >> 3
} else {
8 + count_match_fast(src, m + 8, ip + 8, block_end)
};
if ml >= accept_ml {
Some((m, ml))
} else {
None
}
}
fn fast_probe(
cand: &mut (u64, u64),
src: &[u8],
match_slot: u32,
ip: usize,
window: usize,
lowest: usize,
accept_ml: usize,
block_end: usize,
) -> Option<(usize, usize)> {
if match_slot == 0 {
return None;
}
let m = (match_slot as usize) - 1;
if m < lowest || m >= ip || ip - m > window {
return None;
}
let ml = if ip + 8 <= block_end {
let x = load_u64le(src, m) ^ load_u64le(src, ip);
if x as u32 != 0 {
if cfg!(feature = "profile") {
cand.0 += 1;
}
return None;
}
if cfg!(feature = "profile") {
cand.1 += 1;
}
#[cfg(feature = "profile")]
FF_CAND4.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if x != 0 {
(x.trailing_zeros() as usize) >> 3
} else {
8 + count_match(src, m + 8, ip + 8, block_end)
}
} else {
if load_u32le(src, m) != load_u32le(src, ip) {
if cfg!(feature = "profile") {
cand.0 += 1;
}
return None;
}
if cfg!(feature = "profile") {
cand.1 += 1;
}
#[cfg(feature = "profile")]
FF_CAND4.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
4 + count_match(src, m + 4, ip + 4, block_end)
};
if ml >= accept_ml {
#[cfg(feature = "profile")]
FF_ACCEPT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
Some((m, ml))
} else {
None
}
}
static LAZY_FILL_ENABLED_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_lazy_fill_arm(on: bool) {
LAZY_FILL_ENABLED_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn lazy_fill_enabled() -> bool {
use core::sync::atomic::Ordering;
match LAZY_FILL_ENABLED_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_LAZY_FILL")
.map(|v| v != "0")
.unwrap_or(true);
LAZY_FILL_ENABLED_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
fn lazy_fill_threshold() -> f32 {
use core::sync::atomic::Ordering;
let v = LAZY_FILL_T_ARM.load(Ordering::Relaxed);
if v != u32::MAX {
return f32::from_bits(v);
}
let t: f32 = crate::env_knob("RZSTD_LAZY_FILL_T")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0.0);
LAZY_FILL_T_ARM.store(t.to_bits(), Ordering::Relaxed);
t
}
static LAZY_FILL_T_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_lazy_fill_threshold_arm(v: f32) {
LAZY_FILL_T_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
static BT_FILL_S_C: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(usize::MAX);
#[inline(always)]
fn bt_fill_stride() -> usize {
use core::sync::atomic::Ordering::Relaxed;
let c = BT_FILL_S_C.load(Relaxed);
if c != usize::MAX && bt_depth_cached() {
return c;
}
#[cfg(feature = "std")]
{
let v = std::env::var("RZSTD_BT_FILL_S")
.ok()
.and_then(|v| v.trim().parse().ok())
.filter(|v| *v >= 1)
.unwrap_or(1);
BT_FILL_S_C.store(v, Relaxed);
v
}
#[cfg(not(feature = "std"))]
1
}
pub static LF_FILLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static LF_NONEMPTY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static LF_INSERTS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_lazy_fill() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
LF_FILLS.swap(0, Relaxed),
LF_NONEMPTY.swap(0, Relaxed),
LF_INSERTS.swap(0, Relaxed),
)
}
fn lazy_fill_stride() -> usize {
use core::sync::atomic::Ordering;
let v = LAZY_FILL_S_ARM.load(Ordering::Relaxed);
if v != 0 {
return v;
}
let s: usize = crate::env_knob("RZSTD_LAZY_FILL_S")
.ok()
.and_then(|v| v.parse().ok())
.filter(|&v: &usize| v >= 1)
.unwrap_or(1);
LAZY_FILL_S_ARM.store(s, Ordering::Relaxed);
s
}
static LAZY_FILL_S_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
pub static NL_PROBES_G: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_HITS_G: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_GAIN_G: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_BAND_HITS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_BAND_GAIN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_BAND_OLD: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_OFF_NEW: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_OFF_OLD: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static NL_OFF_WORSE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_nl_off() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
NL_OFF_NEW.swap(0, Relaxed),
NL_OFF_OLD.swap(0, Relaxed),
NL_OFF_WORSE.swap(0, Relaxed),
)
}
pub fn take_nl_band() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
NL_BAND_HITS.swap(0, Relaxed),
NL_BAND_GAIN.swap(0, Relaxed),
NL_BAND_OLD.swap(0, Relaxed),
)
}
pub fn take_next_long() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
NL_PROBES_G.swap(0, Relaxed),
NL_HITS_G.swap(0, Relaxed),
NL_GAIN_G.swap(0, Relaxed),
)
}
const NL_BAND_WARMUP: u32 = 2;
const NL_BAND_PERIOD: u32 = 16;
static NL_OFF_WORSE_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_nl_off_worse_arm(v: f32) {
NL_OFF_WORSE_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn nl_off_worse_max() -> f32 {
let v = NL_OFF_WORSE_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == u32::MAX {
0.60
} else {
f32::from_bits(v)
}
}
static NL_DISPATCH_ON: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_nl_dispatch_arm(on: bool) {
NL_DISPATCH_ON.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn nl_cut_for(tables: &MatchTables) -> usize {
if NL_DISPATCH_ON.load(core::sync::atomic::Ordering::Relaxed) != 2 {
return 8;
}
if tables.nl_band_meas < NL_BAND_WARMUP
|| tables.nl_band_probe == 0
|| tables.nl_off_worse <= nl_off_worse_max()
{
dfast_good_ml_raised()
} else {
8
}
}
#[inline(always)]
fn dfast_good_ml_raised() -> usize {
let v = DFAST_GOOD_ML_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
24
} else {
v
}
}
pub static SIG_REP_RATE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub static SIG_REP_PEAK: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub static SIG_SPB: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub fn take_opt_signals() -> (f32, f32, f32) {
use core::sync::atomic::Ordering::Relaxed;
(
f32::from_bits(SIG_REP_RATE.load(Relaxed)),
f32::from_bits(SIG_REP_PEAK.load(Relaxed)),
f32::from_bits(SIG_SPB.load(Relaxed)),
)
}
static DFAST_GOOD_ML_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
pub fn set_dfast_good_ml_arm(v: usize) {
DFAST_GOOD_ML_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
static DFAST_GOOD_ML2_ARM: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
pub fn set_dfast_good_ml2_arm(v: usize) {
DFAST_GOOD_ML2_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn dfast_good_ml2() -> usize {
let v = DFAST_GOOD_ML2_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
8
} else {
v
}
}
static DFAST_FILL_S_ARM: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(usize::MAX);
pub fn set_dfast_fill_stride_arm(v: usize) {
DFAST_FILL_S_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_fill_stride() -> usize {
let v = DFAST_FILL_S_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v != usize::MAX {
return v;
}
let s: usize = crate::env_knob("RZSTD_DFAST_FILL_S")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0);
DFAST_FILL_S_ARM.store(s, core::sync::atomic::Ordering::Relaxed);
s
}
pub static DF_ENDFILL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_endfill() -> u64 {
DF_ENDFILL.swap(0, core::sync::atomic::Ordering::Relaxed)
}
static DFAST_FILL_N_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_dfast_fill_n_arm(n: u8) {
DFAST_FILL_N_ARM.store(n + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_fill_ends() -> (bool, bool) {
match DFAST_FILL_N_ARM.load(core::sync::atomic::Ordering::Relaxed) {
1 => (false, false),
2 => (true, false),
4 => (false, true),
_ => (true, true),
}
}
static DFAST_FILL_A_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_dfast_fill_anchor_arm(c: bool) {
DFAST_FILL_A_ARM.store(u8::from(c) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_fill_anchor_c() -> bool {
DFAST_FILL_A_ARM.load(core::sync::atomic::Ordering::Relaxed) == 2
}
pub static DF_FILL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_fill() -> u64 {
DF_FILL.swap(0, core::sync::atomic::Ordering::Relaxed)
}
pub fn set_lazy_fill_stride_arm(v: usize) {
LAZY_FILL_S_ARM.store(v.max(1), core::sync::atomic::Ordering::Relaxed);
}
static REP1_MODE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_rep1_mode(m: Option<bool>) {
REP1_MODE_ARM.store(
match m {
None => 0,
Some(false) => 1,
Some(true) => 2,
},
core::sync::atomic::Ordering::Relaxed,
);
}
static REPLEN_PIPE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_replen_pipe_arm(fixed: bool) {
REPLEN_PIPE_ARM.store(u8::from(fixed) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn replen_pipe_fixed() -> bool {
REPLEN_PIPE_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static ACCEL_SHIFT_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_accel_shift_arm(n: u32) {
ACCEL_SHIFT_ARM.store(n, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn accel_shift_base() -> u32 {
let v = ACCEL_SHIFT_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v != u32::MAX {
return v;
}
let n: u32 = crate::env_knob("RZSTD_ACCEL")
.ok()
.and_then(|v| v.trim().parse().ok())
.filter(|&n| (1..=24).contains(&n))
.unwrap_or(0);
ACCEL_SHIFT_ARM.store(n, core::sync::atomic::Ordering::Relaxed);
n
}
#[inline(always)]
fn accel_shift_for(strategy: Strategy) -> u32 {
let pinned = accel_shift_base();
if pinned != 0 {
return pinned;
}
if strategy == Strategy::Fast {
7
} else {
8
}
}
#[inline]
fn rep_search_on(rep_yield: f32, strategy: Strategy) -> bool {
match REP1_MODE_ARM.load(core::sync::atomic::Ordering::Relaxed) {
1 => false,
2 => true,
_ => rep_yield >= rep_yield_min_for(strategy),
}
}
#[inline(always)]
fn try_rep1(
src: &[u8],
ip: usize,
rep1: usize,
lowest: usize,
block_end: usize,
ilimit: usize,
) -> Option<usize> {
let at = ip + 1;
if rep1 == 0 || ip > ilimit || at < rep1 {
return None;
}
debug_assert!(at + 4 <= block_end);
let back = at - rep1;
if back < lowest {
return None;
}
if at + 8 <= block_end {
let x = load_u64le(src, back) ^ load_u64le(src, at);
if x as u32 != 0 {
return None;
}
return Some(if x != 0 {
(x.trailing_zeros() as usize) >> 3
} else {
8 + count_match(src, back + 8, at + 8, block_end)
});
}
if load_u32le(src, back) != load_u32le(src, at) {
return None;
}
Some(4 + count_match_fast(src, back + 4, at + 4, block_end))
}
static STEP0_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
pub fn set_step0_arm(step: usize) {
STEP0_ARM.store(step.max(1) + 1, core::sync::atomic::Ordering::Relaxed);
}
const STEP_PROBE_BLOCKS: u32 = 1;
const SEQ_BYTES_EST: f64 = 3.0;
const STEP_REPROBE_PERIOD: u32 = 256;
static STEP_PROBE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_step_probe_arm(on: bool) {
STEP_PROBE_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn step_probe_on() -> bool {
STEP_PROBE_ARM.load(core::sync::atomic::Ordering::Relaxed) == 2
}
#[cfg(feature = "profile")]
pub static STEP_FORFEIT_SUM: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static STEP_FORFEIT_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static STEP_SEQ_SUM: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_step_forfeit() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
STEP_FORFEIT_SUM.swap(0, Relaxed),
STEP_FORFEIT_N.swap(0, Relaxed),
STEP_SEQ_SUM.swap(0, Relaxed),
)
}
fn note_step_probe(
tables: &mut MatchTables,
seqs1: &[Seq],
lits1: usize,
seqs2: &[Seq],
lits2: usize,
) {
if seqs1.is_empty() {
return;
}
let est1 = lits1 as f64 + seqs1.len() as f64 * SEQ_BYTES_EST;
let est2 = lits2 as f64 + seqs2.len() as f64 * SEQ_BYTES_EST;
let forfeit = est2 / est1.max(1.0) - 1.0;
let seq_ratio = 0.0;
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
STEP_SEQ_SUM.fetch_add((seq_ratio * 10000.0) as u64, Relaxed);
}
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
STEP_FORFEIT_SUM.fetch_add((forfeit.max(0.0) * 10000.0) as u64, Relaxed);
STEP_FORFEIT_N.fetch_add(1, Relaxed);
}
let _ = seq_ratio;
tables.step_sum1 += forfeit;
tables.step_sum2 += 1.0;
tables.step_probed = tables.step_probed.saturating_add(1);
if tables.step_probed >= STEP_PROBE_BLOCKS {
let n = f64::from(tables.step_probed);
let mean_forfeit = tables.step_sum1 / n;
let mean_seq = 0.0f64;
let _ = mean_seq;
tables.step_pick = if mean_forfeit < step_forfeit_max() {
2
} else {
1
};
tables.step_reprobe = STEP_REPROBE_PERIOD;
tables.step_probed = 0;
tables.step_sum1 = 0.0;
tables.step_sum2 = 0.0;
}
}
fn note_step_outcome(tables: &mut MatchTables, _payload: usize, _block_len: usize) {
if tables.step_pick != 0 && tables.step_reprobe > 0 {
tables.step_reprobe -= 1;
}
}
static STEP_FORFEIT_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_step_forfeit_arm(v: f32) {
STEP_FORFEIT_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn step_forfeit_max() -> f64 {
let v = STEP_FORFEIT_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == u32::MAX {
0.002
} else {
f64::from(f32::from_bits(v))
}
}
static STEP_SEQ_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_step_seq_arm(v: f32) {
STEP_SEQ_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
fn step0_default() -> usize {
use core::sync::atomic::Ordering;
let v = STEP0_ARM.load(Ordering::Relaxed);
if v != 0 {
return v - 1;
}
let on = crate::env_knob("RZSTD_STEP0")
.ok()
.and_then(|v| v.parse().ok())
.filter(|&v: &usize| v >= 1)
.unwrap_or(2);
STEP0_ARM.store(on + 1, Ordering::Relaxed);
on
}
#[derive(Clone, Copy)]
struct FastHash {
wide: bool,
mask: u64,
shift: u32,
}
const FAST_HASH_PRIME64: u64 = 0x9E37_79B1_85EB_CA87;
#[inline(always)]
fn fast_hash_spec(mls: usize, hash_log: u32) -> FastHash {
if fast_hash_wide_enabled() && (5..=8).contains(&mls) {
FastHash {
wide: true,
mask: if mls == 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
},
shift: 64u32.saturating_sub(hash_log),
}
} else {
FastHash {
wide: false,
mask: 0,
shift: 32u32.saturating_sub(hash_log),
}
}
}
#[inline(always)]
fn load_u64le_tail(src: &[u8], pos: usize) -> u64 {
if pos + 8 <= src.len() {
return crate::simd::load_u64_le(src, pos);
}
let len = src.len();
if len >= 8 && pos < len {
let over = (pos + 8 - len) as u32;
return crate::simd::load_u64_le(src, len - 8) >> (8 * over);
}
let mut v = 0u64;
let mut i = 0;
while pos + i < len {
v |= u64::from(src[pos + i]) << (8 * i);
i += 1;
}
v
}
#[inline(always)]
fn fast_hash_tag<const SAFE: bool>(
src: &[u8],
pos: usize,
wide: bool,
mask: u64,
shift: u32,
) -> (usize, u8) {
if wide {
let v = if SAFE {
debug_assert!(pos + 8 <= src.len());
crate::simd::load_u64_le(src, pos)
} else {
load_u64le_tail(src, pos)
} & mask;
let hv = v.wrapping_mul(FAST_HASH_PRIME64);
((hv >> shift) as usize, (hv ^ (hv >> 29)) as u8)
} else {
let hv = load_u32le(src, pos).wrapping_mul(HASH4_PRIME);
((hv >> shift) as usize, (hv ^ (hv >> 15)) as u8)
}
}
#[inline(always)]
fn hash4_tag_mls(src: &[u8], pos: usize, hash_shift: u32, smask: u64) -> (usize, u8) {
let v = load_u64le(src, pos);
let hv = (v as u32).wrapping_mul(HASH4_PRIME);
let tv = (v & smask).wrapping_mul(FAST_HASH_PRIME64);
((hv >> hash_shift) as usize, (tv ^ (tv >> 29)) as u8)
}
pub static TAG_FALSE_REJECT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static TAG_REJECT_TOTAL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_tag_rejects() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
TAG_FALSE_REJECT.swap(0, Relaxed),
TAG_REJECT_TOTAL.swap(0, Relaxed),
)
}
pub static REP_PROBES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static REP_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static REP_HITS_G: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static ALL_MATCH_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static ALL_SEQS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_rep_rate() -> (u64, u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
REP_PROBES.swap(0, Relaxed),
REP_BYTES.swap(0, Relaxed),
REP_HITS_G.swap(0, Relaxed),
ALL_MATCH_BYTES.swap(0, Relaxed),
ALL_SEQS.swap(0, Relaxed),
)
}
pub static MM_TOTAL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static MM_MISS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_mm() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(MM_TOTAL.swap(0, Relaxed), MM_MISS.swap(0, Relaxed))
}
#[cfg(feature = "profile")]
pub static FF_ARM: [core::sync::atomic::AtomicU64; 4] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_ff_arms() -> [u64; 4] {
let mut o = [0u64; 4];
for i in 0..4 {
o[i] = FF_ARM[i].swap(0, core::sync::atomic::Ordering::Relaxed);
}
o
}
pub static FF_PIPE_BLOCKS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static FF_SPEC_MADE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static FF_SPEC_USED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_ff_pipe() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
FF_PIPE_BLOCKS.swap(0, Relaxed),
FF_SPEC_MADE.swap(0, Relaxed),
FF_SPEC_USED.swap(0, Relaxed),
)
}
static PIPE_REP1_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_pipe_rep1_arm(on: bool) {
PIPE_REP1_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn pipe_rep1_enabled() -> bool {
PIPE_REP1_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static PIPE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_pipe_arm(on: bool) {
PIPE_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn pipe_enabled() -> bool {
use core::sync::atomic::Ordering;
match PIPE_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_MF_PIPE")
.map(|v| v != "0")
.unwrap_or(true);
PIPE_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static HUFF_FAST_ENABLED_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_huff_fast_arm(on: bool) {
HUFF_FAST_ENABLED_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
pub(crate) fn huff_fast_enabled() -> bool {
use core::sync::atomic::Ordering;
match HUFF_FAST_ENABLED_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_HUFF_FAST")
.map(|v| v != "0")
.unwrap_or(true);
HUFF_FAST_ENABLED_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static PAYLOAD_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_payload_arm(on: bool) {
PAYLOAD_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn payload_reserve_enabled() -> bool {
use core::sync::atomic::Ordering;
match PAYLOAD_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_PAYLOAD_RES")
.map(|v| v != "0")
.unwrap_or(true);
PAYLOAD_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static LITPUSH_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
static LITPUSH_HOIST_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_litpush_hoist_arm(on: bool) {
LITPUSH_HOIST_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn litpush_hoist_enabled() -> bool {
LITPUSH_HOIST_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
pub fn set_litpush_arm(on: bool) {
LITPUSH_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn lit_push_enabled() -> bool {
use core::sync::atomic::Ordering;
match LITPUSH_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_LIT_PUSH")
.map(|v| v != "0")
.unwrap_or(true);
LITPUSH_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
pub(crate) const LIT_PUSH_WIDTH: usize = 16;
pub(crate) const LIT_PUSH_WIDTH_WIDE: usize = 32;
const WIDEN_RATIO: f32 = 0.0526;
pub(crate) const LIT_PUSH_WIDTH_MAX: usize = 64;
pub(crate) const LIT_PUSH_TIER2: usize = 32;
pub(crate) const LIT_PUSH_TIER3: usize = 64;
static LIT_PUSH_TIERS_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_lit_push_tiers_arm(t: u8) {
LIT_PUSH_TIERS_ARM.store(t, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn lit_push_tiers() -> u8 {
LIT_PUSH_TIERS_ARM.load(core::sync::atomic::Ordering::Relaxed)
}
static DFAST_LITPUSH_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_dfast_litpush_arm(on: bool) {
DFAST_LITPUSH_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_litpush_enabled() -> bool {
DFAST_LITPUSH_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
#[inline]
fn lit_shares(seqs: &[Seq]) -> (f32, f32) {
if seqs.is_empty() {
return (1.0, 0.0);
}
let (mut short, mut mid) = (0usize, 0usize);
for q in seqs {
let l = q.litlen as usize;
if l <= LIT_PUSH_WIDTH {
short += 1;
} else if l <= LIT_PUSH_WIDTH_WIDE {
mid += 1;
}
}
let n = seqs.len() as f32;
let inv = 1.0 / n;
(short as f32 * inv, mid as f32 * inv)
}
#[inline]
fn lit_width_for(tables: &MatchTables) -> usize {
if tables.blocks_done == 0 {
return LIT_PUSH_WIDTH;
}
if tables.lit_mid_share > tables.lit_short_share * WIDEN_RATIO {
LIT_PUSH_WIDTH_WIDE
} else {
LIT_PUSH_WIDTH
}
}
const LIT_SHORT_MIN: f32 = 0.25;
static LIT_SHORT_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_lit_short_arm(v: f32) {
LIT_SHORT_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn lit_short_min() -> f32 {
let b = LIT_SHORT_ARM.load(core::sync::atomic::Ordering::Relaxed);
if b == u32::MAX {
LIT_SHORT_MIN
} else {
f32::from_bits(b)
}
}
pub static LP_GUARD_FAIL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static LP_GUARD_SKIP: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_lp_guard() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
LP_GUARD_FAIL.swap(0, Ordering::Relaxed),
LP_GUARD_SKIP.swap(0, Ordering::Relaxed),
)
}
#[allow(unsafe_code)]
#[inline]
fn push_literals(lits: &mut Vec<u8>, src: &[u8], from: usize, to: usize, w: usize) {
let n = to - from;
let arm = w != 0;
#[cfg(feature = "profile")]
{
let b = match n {
0..=4 => 0,
5..=8 => 1,
9..=16 => 2,
17..=32 => 3,
33..=64 => 4,
_ => 5,
};
LP_HIST[b].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if n <= w && from + w <= src.len() && lits.capacity() - lits.len() >= w && arm {
#[cfg(feature = "profile")]
LP_FAST.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let len = lits.len();
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr().add(from), lits.as_mut_ptr().add(len), w);
lits.set_len(len + n);
}
return;
}
push_literals_tiers(lits, src, from, to, n, arm);
}
#[allow(unsafe_code)]
#[inline(never)]
#[cold]
fn push_literals_tiers(
lits: &mut Vec<u8>,
src: &[u8],
from: usize,
to: usize,
n: usize,
arm: bool,
) {
let tiers = lit_push_tiers();
if tiers != 1 && arm {
if n <= LIT_PUSH_TIER2
&& from + LIT_PUSH_TIER2 <= src.len()
&& lits.capacity() - lits.len() >= LIT_PUSH_TIER2
{
#[cfg(feature = "profile")]
LP_FAST2.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let len = lits.len();
unsafe {
core::ptr::copy_nonoverlapping(
src.as_ptr().add(from),
lits.as_mut_ptr().add(len),
LIT_PUSH_TIER2,
);
lits.set_len(len + n);
}
return;
}
if tiers == 0
&& n <= LIT_PUSH_TIER3
&& from + LIT_PUSH_TIER3 <= src.len()
&& lits.capacity() - lits.len() >= LIT_PUSH_TIER3
{
#[cfg(feature = "profile")]
LP_FAST3.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let len = lits.len();
unsafe {
core::ptr::copy_nonoverlapping(
src.as_ptr().add(from),
lits.as_mut_ptr().add(len),
LIT_PUSH_TIER3,
);
lits.set_len(len + n);
}
return;
}
}
#[cfg(feature = "profile")]
{
LP_SLOW.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if arm {
LP_GUARD_FAIL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
} else {
LP_GUARD_SKIP.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
lits.extend_from_slice(&src[from..to]);
}
pub static LP_HIST: [core::sync::atomic::AtomicU64; 6] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
pub fn take_lit_hist() -> [u64; 6] {
use core::sync::atomic::Ordering::Relaxed;
let mut o = [0u64; 6];
for (i, v) in LP_HIST.iter().enumerate() {
o[i] = v.swap(0, Relaxed);
}
o
}
pub static LP_FAST2: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static LP_FAST3: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_lit_tiers() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(LP_FAST2.swap(0, Relaxed), LP_FAST3.swap(0, Relaxed))
}
pub fn take_lp_stats() -> ([u64; 6], u64, u64) {
use core::sync::atomic::Ordering;
let mut h = [0u64; 6];
for (i, c) in LP_HIST.iter().enumerate() {
h[i] = c.swap(0, Ordering::Relaxed);
}
(
h,
LP_FAST.swap(0, Ordering::Relaxed),
LP_SLOW.swap(0, Ordering::Relaxed),
)
}
pub static LP_FAST: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static LP_SLOW: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_lit_push() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(LP_FAST.swap(0, Relaxed), LP_SLOW.swap(0, Relaxed))
}
#[inline(always)]
#[allow(unsafe_code)]
fn fast_slot_store(
hash: &mut [u32],
tags: &mut [u8],
pack: bool,
tags_live: bool,
h: usize,
pos: usize,
tag: u8,
) {
debug_assert_eq!(tags_live, !tags.is_empty());
debug_assert!(h < hash.len());
if pack {
*unsafe { hash.get_unchecked_mut(h) } =
(((pos as u32).wrapping_add(1)) & 0x00FF_FFFF) | (u32::from(tag) << 24);
return;
}
if tags_live {
debug_assert!(tags.len() == hash.len());
*unsafe { tags.get_unchecked_mut(h) } = tag;
}
*unsafe { hash.get_unchecked_mut(h) } = (pos as u32).wrapping_add(1);
}
#[inline(always)]
#[allow(unsafe_code)]
fn fast_slot_swap<const PACKED: bool>(
hash: &mut [u32],
tags: &mut [u8],
pack: bool,
tags_live: bool,
h: usize,
pos: usize,
tag: u8,
) -> u32 {
debug_assert_eq!(tags_live, !tags.is_empty());
debug_assert!(h < hash.len());
let slot = unsafe { hash.get_unchecked_mut(h) };
let e = *slot;
if pack {
*slot = (((pos as u32).wrapping_add(1)) & 0x00FF_FFFF) | (u32::from(tag) << 24);
if e == 0 {
return 0;
}
#[cfg(feature = "profile")]
PACKED_TAG_READS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if PACKED && (e >> 24) as u8 != tag {
return 0;
}
return e & 0x00FF_FFFF;
}
*slot = (pos as u32).wrapping_add(1);
if tags_live {
debug_assert!(!tags.is_empty() && tags.len() == hash.len());
let t = unsafe { *tags.get_unchecked(h) };
unsafe { *tags.get_unchecked_mut(h) = tag };
if e == 0 {
return 0;
}
if PACKED {
#[cfg(feature = "profile")]
TAGARR_READS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if t != tag {
return 0;
}
}
return e;
}
if e == 0 {
0
} else {
e
}
}
#[inline(always)]
#[allow(unsafe_code)]
fn fast_slot_load<const PACKED: bool>(
hash: &[u32],
tags: &[u8],
pack: bool,
tags_live: bool,
h: usize,
tag: u8,
) -> u32 {
debug_assert_eq!(tags_live, !tags.is_empty());
debug_assert!(h < hash.len());
let e = *unsafe { hash.get_unchecked(h) };
if e == 0 {
return 0;
}
if pack {
#[cfg(feature = "profile")]
PACKED_TAG_READS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if PACKED && (e >> 24) as u8 != tag {
return 0;
}
return e & 0x00FF_FFFF;
}
if !PACKED {
return e;
}
if tags_live {
debug_assert!(tags.len() == hash.len());
#[allow(unsafe_code)]
let t = *unsafe { tags.get_unchecked(h) };
#[cfg(feature = "profile")]
TAGARR_READS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if t != tag {
return 0;
}
}
e
}
#[allow(dead_code)] const FF_NEAR_MAX: usize = 1 << 16;
fn ff_anchor_ml() -> usize {
#[cfg(feature = "profile")]
{
if let Ok(v) = std::env::var("RZSTD_FF_ML") {
if let Ok(n) = v.parse() {
return n;
}
}
}
16
}
#[inline(always)]
fn fast_hash_relatch(tables: &mut MatchTables, src: &[u8], block_start: usize, window: usize) {
let shift = 32u32.saturating_sub(tables.hash_log);
let from = block_start.saturating_sub(window).max(tables.frame_start);
let to = block_start.saturating_sub(8);
let mut p = from;
while p <= to && p + 8 <= src.len() {
let h = (load_u32le(src, p).wrapping_mul(HASH4_PRIME) >> shift) as usize;
tables.put_h(h, p);
p += 1;
}
tables.pack_tags = false;
tables.fast_hash_legacy = true;
#[cfg(feature = "profile")]
FF_LATCH.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn fast_slot_raw(hash: &[u32], pack: bool, h: usize) -> u32 {
let e = hash[h];
if pack {
e & 0x00FF_FFFF
} else {
e
}
}
pub(crate) struct FastEmitCtx<'a> {
src: &'a [u8],
pack: bool,
f_wide: bool,
f_mask: u64,
f_shift: u32,
ilimit: usize,
frame_start: usize,
w: usize,
tags_live: bool,
ends: (bool, bool),
}
#[inline(always)]
fn fill_fast_after_match<const PACKED: bool>(
hash: &mut [u32],
tags: &mut [u8],
pack: bool,
f_wide: bool,
f_mask: u64,
f_shift: u32,
src: &[u8],
match_ip: usize,
match_end: usize,
ilimit: usize,
tags_live: bool,
ends: (bool, bool),
) {
let (do_a, do_b) = ends;
let mut n = 0u64;
debug_assert!(match_ip < usize::MAX - 2);
let a = match_ip + 2;
if do_a && a <= ilimit {
let (h, g) = fast_hash_tag::<true>(src, a, f_wide, f_mask, f_shift);
fast_slot_store(hash, tags, pack, tags_live, h, a, g);
n += 1;
}
debug_assert!(match_end >= 2);
if do_b {
let b = match_end - 2;
if b <= ilimit && b != a {
let (h, g) = fast_hash_tag::<true>(src, b, f_wide, f_mask, f_shift);
fast_slot_store(hash, tags, pack, tags_live, h, b, g);
n += 1;
}
}
#[cfg(feature = "profile")]
DF_ENDFILL.fetch_add(n, core::sync::atomic::Ordering::Relaxed);
crate::prof::note_hash_fill(n);
}
#[inline(never)]
fn emit_fast_seq_plain<const PACKED: bool>(
ctx: &FastEmitCtx,
hash: &mut [u32],
tags: &mut [u8],
seqs: &mut Vec<Seq>,
lits: &mut Vec<u8>,
anchor: usize,
found_ip: usize,
m: usize,
ml: usize,
) -> usize {
emit_fast_seq_body::<PACKED>(ctx, hash, tags, seqs, lits, anchor, found_ip, m, ml)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
#[inline(never)]
unsafe fn emit_fast_seq_bmi2<const PACKED: bool>(
ctx: &FastEmitCtx,
hash: &mut [u32],
tags: &mut [u8],
seqs: &mut Vec<Seq>,
lits: &mut Vec<u8>,
anchor: usize,
found_ip: usize,
m: usize,
ml: usize,
) -> usize {
emit_fast_seq_body::<PACKED>(ctx, hash, tags, seqs, lits, anchor, found_ip, m, ml)
}
#[inline(always)]
fn emit_fast_seq<const PACKED: bool, const BMI2: bool>(
ctx: &FastEmitCtx,
hash: &mut [u32],
tags: &mut [u8],
seqs: &mut Vec<Seq>,
lits: &mut Vec<u8>,
anchor: usize,
found_ip: usize,
m: usize,
ml: usize,
) -> usize {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if BMI2 {
#[allow(unsafe_code)]
return unsafe {
emit_fast_seq_bmi2::<PACKED>(ctx, hash, tags, seqs, lits, anchor, found_ip, m, ml)
};
}
emit_fast_seq_plain::<PACKED>(ctx, hash, tags, seqs, lits, anchor, found_ip, m, ml)
}
#[inline(always)]
fn emit_fast_seq_body<const PACKED: bool>(
ctx: &FastEmitCtx,
hash: &mut [u32],
tags: &mut [u8],
seqs: &mut Vec<Seq>,
lits: &mut Vec<u8>,
anchor: usize,
found_ip: usize,
m: usize,
ml: usize,
) -> usize {
let &FastEmitCtx {
src,
pack,
f_wide,
f_mask,
f_shift,
ilimit,
frame_start,
w,
tags_live,
ends,
} = ctx;
let mut ip = found_ip;
let mut mm = m;
let mut n = ml;
let back_from = ip;
#[cfg(feature = "profile")]
let bext_from = ip;
while ip > anchor && mm > frame_start && back_eq(src, ip, mm) {
ip -= 1;
mm -= 1;
n += 1;
}
#[cfg(feature = "profile")]
note_bext((bext_from - ip) as u64);
crate::prof::note_back_ext((back_from - ip) as u64);
push_literals(lits, src, anchor, ip, w);
seqs.push(Seq {
litlen: (ip - anchor) as u32,
matchlen: n as u32,
offset: (ip - mm) as u32,
});
let end = ip + n;
fill_fast_after_match::<PACKED>(
hash, tags, pack, f_wide, f_mask, f_shift, src, found_ip, end, ilimit, tags_live, ends,
);
end
}
#[inline]
fn fill_hash_after_match(
tables: &mut MatchTables,
src: &[u8],
match_ip: usize,
match_end: usize,
ends: (bool, bool),
smask: u64,
hash_shift: u32,
ilimit: usize,
) {
let packed = tables.pack_tags;
let stag_live = !tables.tags.is_empty();
let (do_a, do_b) = ends;
let mut n = 0u64;
debug_assert!(match_ip < usize::MAX - 2);
let a = match_ip + 2;
if do_a && a <= ilimit {
let (h, g) = hash4_tag_mls(src, a, hash_shift, smask);
tables.put_h_tag(h, a, g, packed, stag_live);
n += 1;
}
debug_assert!(match_end >= 2);
if do_b {
let b = match_end - 2;
if b <= ilimit && b != a {
let (h, g) = hash4_tag_mls(src, b, hash_shift, smask);
tables.put_h_tag(h, b, g, packed, stag_live);
n += 1;
}
}
#[cfg(feature = "profile")]
DF_ENDFILL.fetch_add(n, core::sync::atomic::Ordering::Relaxed);
crate::prof::note_hash_fill(n);
}
#[inline]
fn fill_hash_long_after_match(
tables: &mut MatchTables,
src: &[u8],
match_ip: usize,
match_end: usize,
hash_log: u32,
ends: (bool, bool),
smask: u64,
hash_shift: u32,
ilimit: usize,
) {
let packed = tables.pack_tags;
let ltag_live = !tables.ltags.is_empty();
let (do_a, do_b) = ends;
let mut n = 0u64;
debug_assert!(match_ip < usize::MAX - 2);
let a = match_ip + 2;
let ltag_wanted = packed || ltag_live;
if do_a && a <= ilimit {
let g = if ltag_wanted {
hash4_tag_mls(src, a, hash_shift, smask).1
} else {
0
};
tables.put_hl_tag(hash8(src, a, hash_log), a, g, packed, ltag_live);
n += 1;
}
debug_assert!(match_end >= 2);
if do_b {
let b = match_end - 2;
if b <= ilimit && b != a {
let g = if ltag_wanted {
hash4_tag_mls(src, b, hash_shift, smask).1
} else {
0
};
tables.put_hl_tag(hash8(src, b, hash_log), b, g, packed, ltag_live);
n += 1;
}
}
#[cfg(feature = "profile")]
DF_ENDFILL.fetch_add(n, core::sync::atomic::Ordering::Relaxed);
#[cfg(not(feature = "profile"))]
let _ = n;
}
#[inline(never)]
fn find_dfast(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
macro_rules! go {
($h:expr) => {{
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[allow(unsafe_code)]
let out = if crate::simd::has_bmi2() {
unsafe {
find_dfast_impl_bmi2::<0>(
src,
block_start,
block_end,
window,
params,
tables,
reps,
)
}
} else {
find_dfast_impl::<$h>(src, block_start, block_end, window, params, tables, reps)
};
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let out =
find_dfast_impl::<$h>(src, block_start, block_end, window, params, tables, reps);
out
}};
}
if !dfast_spec_enabled() {
return go!(0);
}
match tables.hash_log {
14 => go!(14),
15 => go!(15),
16 => go!(16),
17 => go!(17),
18 => go!(18),
_ => go!(0),
}
}
fn find_dfast_impl<const HLOG: u32>(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_dfast_impl_inner::<HLOG>(src, block_start, block_end, window, params, tables, reps)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn find_dfast_impl_bmi2<const HLOG: u32>(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_dfast_impl_inner::<HLOG>(src, block_start, block_end, window, params, tables, reps)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn find_dfast_impl_inner<const HLOG: u32>(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(feature = "profile")]
if HLOG != 0 {
DFAST_SPEC_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let hlog = if HLOG == 0 { tables.hash_log } else { HLOG };
#[cfg(feature = "profile")]
if HLOG == 0 {
DFAST_RUNTIME_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let good_ml = nl_cut_for(tables);
let good_ml2 = dfast_good_ml2();
let mut band_hits = 0u64;
let mut band_worse = 0u64;
let mls = params.min_match.max(3) as usize;
let lp = if litpush_hoist_enabled() {
dfast_litpush_enabled()
} else {
lit_push_enabled()
};
let block_len = block_end - block_start;
let seq_guess = (tables.last_nseq + tables.last_nseq / 4 + 64).min(block_len / mls + 16);
let keep = finder_scratch_enabled();
let mut seqs = if keep {
let mut v = core::mem::take(&mut tables.seq_scratch);
v.clear();
v
} else {
Vec::new()
};
if lp && seqs.capacity() < seq_guess {
seqs = Vec::with_capacity(seq_guess);
}
let mut lits = if keep {
let mut v = core::mem::take(&mut tables.lit_scratch);
v.clear();
v
} else {
Vec::new()
};
if lp && lits.capacity() < block_len + LIT_PUSH_WIDTH_MAX {
lits = Vec::with_capacity(block_len + LIT_PUSH_WIDTH_MAX);
}
let mut anchor = block_start;
let ilimit = block_end.saturating_sub(8);
if block_start >= ilimit {
lits.extend_from_slice(&src[block_start..block_end]);
return (seqs, lits);
}
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut hits = 0u64;
let use_rep = rep_search_on(tables.rep_yield, params.strategy) || tables.rep_probe == 0;
let mut rep1 = reps[0] as usize;
let mut rep_hits = 0u64;
let fstart_c = tables.frame_start;
let lowest_rep = block_start.saturating_sub(window).max(fstart_c);
let frame_start_c = tables.frame_start;
let mlx_c = 8.min(mls).max(4);
let lp_w = if lp { LIT_PUSH_WIDTH } else { 0 };
let nl_on = next_long_enabled() && tables.next_long_yield >= next_long_min();
let accel = if cfg!(feature = "profile") {
accel_shift_for(params.strategy)
} else {
8
};
#[cfg(feature = "profile")]
let mut mm_total = 0u64;
let mut nl_probes = 0u64;
let mut nl_hits = 0u64;
let mut d_rep_bytes = 0u64;
let mut ip = block_start;
let ml = tables.dfast_mean_ml;
let dstep = if dfast_step_forced() != 0 {
dfast_step_forced()
} else if ml == 0.0 || ml >= dfast_ml_min() {
2
} else {
1
};
let dpipe = dfast_pipe_enabled()
&& (tables.dfast_probe == 0 || tables.dfast_spec_yield >= dfast_spec_min());
let (mut spec_made, mut spec_dropped) = (0u64, 0u64);
#[derive(Clone, Copy)]
struct Carried {
h4: u32,
h8: u32,
v4: u32,
v8: u32,
g4: u8,
live: bool,
}
let mut carried = Carried {
h4: 0,
h8: 0,
v4: 0,
v8: 0,
g4: 0,
live: false,
};
#[inline(always)]
fn dec(v: u32) -> Option<usize> {
if v == 0 {
None
} else {
Some((v as usize) - 1)
}
}
#[inline(always)]
fn enc(m: Option<usize>) -> u32 {
match m {
Some(p) => (p as u32) + 1,
None => 0,
}
}
let dtag_on = tables.pack_tags || !tables.tags.is_empty();
let dtag_shift = 32u32.saturating_sub(hlog.min(32));
let lt_on = long_tag_enabled() && (tables.pack_tags || !tables.ltags.is_empty());
let packed = tables.pack_tags;
let stag_live = !tables.tags.is_empty();
let ltag_live = !tables.ltags.is_empty();
let sk = 8.min(mls);
let smask = if sk == 8 {
u64::MAX
} else {
(1u64 << (8 * sk)) - 1
};
let fill_anchor_c = dfast_fill_anchor_c();
let fill_stride = dfast_fill_stride();
let fill_ends = dfast_fill_ends();
while ip <= ilimit {
#[cfg(feature = "profile")]
if COUNT {
mm_total += 1;
}
if use_rep {
if let Some(ml) = try_rep1(src, ip, rep1, lowest_rep, block_end, ilimit) {
rep_hits += 1;
if COUNT {
d_rep_bytes += ml as u64;
}
let mstart = ip + 1;
push_literals(&mut lits, src, anchor, mstart, lp_w);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
spec_dropped += u64::from(carried.live);
carried.live = false;
continue;
}
}
let (h4, g4, h8, m4, m8) = if carried.live {
carried.live = false;
(
carried.h4 as usize,
carried.g4,
carried.h8 as usize,
dec(carried.v4),
dec(carried.v8),
)
} else {
{
let (a, ga, b) = dfast_hash_pair(src, ip, dtag_shift, smask, hlog);
let m = tables.get_h_tag(a, ga, dtag_on, packed);
if COUNT && dtag_on && tables.raw_fast(a) != 0 {
TAG_REJECT_TOTAL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if m.is_none() {
TAG_FALSE_REJECT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
let ml8 = tables.get_hl_tag(b, ga, lt_on, packed);
#[cfg(feature = "profile")]
if COUNT && lt_on {
use core::sync::atomic::Ordering::Relaxed;
let raw = tables.raw_hl(b);
if raw != 0 {
LTAG_NONEMPTY.fetch_add(1, Relaxed);
if ml8.is_none() {
LTAG_REJECT.fetch_add(1, Relaxed);
let mr = (raw as usize) - 1;
if match_ok(src, mr, ip, window, block_start, mlx_c, frame_start_c)
&& count_match(src, mr, ip, block_end) >= mls
{
LTAG_FALSE.fetch_add(1, Relaxed);
}
}
}
}
(a, ga, b, m, ml8)
}
};
tables.put_h_tag(h4, ip, g4, packed, stag_live);
tables.put_hl_tag(h8, ip, g4, packed, ltag_live);
if dpipe {
let nip = ip + dstep + ((ip - anchor) >> accel);
if nip <= ilimit {
let (a, ga, b) = dfast_hash_pair(src, nip, dtag_shift, smask, hlog);
let va = if a == h4 {
if !dtag_on || ga == g4 {
Some(ip)
} else {
None
}
} else {
tables.get_h_tag(a, ga, dtag_on, packed)
};
let vb = if b == h8 {
if !lt_on || ga == g4 {
Some(ip)
} else {
None
}
} else {
tables.get_hl_tag(b, ga, lt_on, packed)
};
spec_made += 1;
carried = Carried {
h4: a as u32,
h8: b as u32,
v4: enc(va),
v8: enc(vb),
g4: ga,
live: true,
};
}
}
let mut best_m = 0usize;
let mut best_ml = 0usize;
if let Some(m8) = m8 {
if COUNT {
probes += 1;
}
let mlx = mlx_c;
if match_ok(src, m8, ip, window, block_start, mlx, frame_start_c) {
let ml = mlx + count_match_fast(src, m8 + mlx, ip + mlx, block_end);
if ml >= mls {
best_m = m8;
best_ml = ml;
}
}
#[cfg(feature = "profile")]
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
if best_ml == 0 {
let mlx = mlx_c;
let lowest = lowest_rep;
let cheap = m8 >= ip
|| ip - m8 > window
|| m8 < lowest
|| ip + mlx > src.len()
|| m8 + mlx > src.len();
if cheap {
LTAG_SURV_WFAIL.fetch_add(1, Relaxed);
} else {
LTAG_SURV_FAIL.fetch_add(1, Relaxed);
}
} else {
LTAG_SURV_ACC.fetch_add(1, Relaxed);
}
}
}
let mut best_ip = ip;
if best_ml < good_ml && nl_on && ip < ilimit {
nl_probes += 1;
let h8b = hash8(src, ip + 1, hlog);
let g8b = if lt_on {
hash4_tag_mls(src, ip + 1, dtag_shift, smask).1
} else {
0
};
if let Some(m8b) = tables.get_hl_tag(h8b, g8b, lt_on, packed) {
if COUNT {
probes += 1;
}
let mlx = mlx_c;
if match_ok(src, m8b, ip + 1, window, block_start, mlx, frame_start_c) {
let ml = mlx + count_match_fast(src, m8b + mlx, ip + 1 + mlx, block_end);
if ml >= mls && ml > best_ml {
if best_ml >= 8 {
band_hits += 1;
if ip + 1 - m8b > ip - best_m {
band_worse += 1;
}
}
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
NL_GAIN_G.fetch_add((ml - best_ml) as u64, Relaxed);
if best_ml >= 8 {
NL_BAND_HITS.fetch_add(1, Relaxed);
NL_BAND_GAIN.fetch_add((ml - best_ml) as u64, Relaxed);
NL_BAND_OLD.fetch_add(best_ml as u64, Relaxed);
let off_new = (ip + 1 - m8b) as u64;
let off_old = (ip - best_m) as u64;
NL_OFF_NEW.fetch_add(off_new, Relaxed);
NL_OFF_OLD.fetch_add(off_old, Relaxed);
if off_new > off_old {
NL_OFF_WORSE.fetch_add(1, Relaxed);
}
}
}
best_m = m8b;
best_ml = ml;
best_ip = ip + 1;
nl_hits += 1;
}
}
}
}
if best_ml < good_ml2 && best_ip == ip {
if let Some(m4) = m4 {
if COUNT {
probes += 1;
}
let mut _acc = false;
if match_ok(src, m4, ip, window, block_start, mls, frame_start_c) {
let ml = mls + count_match_fast(src, m4 + mls, ip + mls, block_end);
_acc = ml >= mls;
if ml >= mls && ml > best_ml {
best_m = m4;
best_ml = ml;
}
}
#[cfg(feature = "profile")]
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
if _acc {
STAG_SURV_ACC.fetch_add(1, Relaxed);
} else {
let lowest = lowest_rep;
let cheap = m4 >= ip
|| ip - m4 > window
|| m4 < lowest
|| ip + mls > src.len()
|| m4 + mls > src.len();
if cheap {
STAG_SURV_WFAIL.fetch_add(1, Relaxed);
} else {
STAG_SURV_FAIL.fetch_add(1, Relaxed);
}
}
}
}
}
if best_ml >= mls {
push_literals(&mut lits, src, anchor, best_ip, lp_w);
seqs.push(Seq {
litlen: (best_ip - anchor) as u32,
matchlen: best_ml as u32,
offset: (best_ip - best_m) as u32,
});
rep1 = best_ip - best_m;
if COUNT {
hits += 1;
}
let end = best_ip + best_ml;
fill_hash_after_match(
tables, src, best_ip, end, fill_ends, smask, dtag_shift, ilimit,
);
let long_anchor = if fill_anchor_c { best_ip } else { ip };
fill_hash_long_after_match(
tables,
src,
long_anchor,
end,
hlog,
fill_ends,
smask,
dtag_shift,
ilimit,
);
let dfs = fill_stride;
if dfs != 0 {
let hash_shift = dtag_shift;
let stop = end.saturating_sub(2).min(ilimit + 1);
let mut p = best_ip + 2 + dfs;
if p < stop {
let hp = tables.hash.as_mut_ptr();
let hlp = tables.hash_long.as_mut_ptr();
let tp = tables.tags.as_mut_ptr();
let ltp = tables.ltags.as_mut_ptr();
while p < stop {
let (h, g) = hash4_tag_mls(src, p, hash_shift, smask);
let h8 = hash8(src, p, hlog);
debug_assert!(h < tables.hash.len() && h8 < tables.hash_long.len());
#[allow(unsafe_code)]
unsafe {
let v = (p as u32) + 1;
if packed {
let w = (v & 0x00FF_FFFF) | (u32::from(g) << 24);
*hp.add(h) = w;
*hlp.add(h8) = w;
} else {
if stag_live {
*tp.add(h) = g;
}
*hp.add(h) = v;
if ltag_live {
*ltp.add(h8) = g;
}
*hlp.add(h8) = v;
}
}
#[cfg(feature = "profile")]
DF_FILL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
p += dfs;
}
}
}
ip = end;
anchor = ip;
spec_dropped += u64::from(carried.live);
carried.live = false;
} else {
ip += dstep + ((ip - anchor) >> accel);
}
}
tables.rep_yield = if seqs.is_empty() {
1.0
} else {
(rep_hits as f32 / seqs.len() as f32).max(tables.rep_yield * rep_decay())
};
tables.rep_probe = if tables.rep_probe == 0 {
REP_PROBE_PERIOD
} else {
tables.rep_probe - 1
};
let spec_used = spec_made
.saturating_sub(spec_dropped)
.saturating_sub(u64::from(carried.live));
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
MM_TOTAL.fetch_add(mm_total, Relaxed);
DFAST_SPEC_MADE.fetch_add(spec_made, Relaxed);
DFAST_SPEC_USED.fetch_add(spec_used, Relaxed);
}
if dpipe && spec_made > 0 {
let now = spec_used as f32 / spec_made as f32;
tables.dfast_spec_yield = 0.75 * tables.dfast_spec_yield + 0.25 * now;
}
tables.dfast_probe = if tables.dfast_probe == 0 {
DFAST_PROBE_PERIOD
} else {
tables.dfast_probe - 1
};
let mb: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
DFAST_MATCH_BYTES.fetch_add(mb, Relaxed);
DFAST_SEQS.fetch_add(seqs.len() as u64, Relaxed);
DFAST_BLOCK_BYTES.fetch_add((block_end - block_start) as u64, Relaxed);
DFAST_REP_BYTES.fetch_add(d_rep_bytes, Relaxed);
DFAST_REP_HITS.fetch_add(rep_hits, Relaxed);
DFAST_BLOCKS.fetch_add(1, Relaxed);
if use_rep {
DFAST_REP_BLOCKS.fetch_add(1, Relaxed);
DFAST_REP_POS.fetch_add((block_end - block_start) as u64, Relaxed);
}
}
#[cfg(not(feature = "profile"))]
let _ = d_rep_bytes;
{
let now = if seqs.is_empty() {
0.0
} else {
mb as f32 / seqs.len() as f32
};
tables.dfast_mean_ml = if tables.dfast_mean_ml == 0.0 && now == 0.0 {
0.0
} else {
0.75 * tables.dfast_mean_ml + 0.25 * now
};
}
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
NL_PROBES_G.fetch_add(nl_probes, Relaxed);
NL_HITS_G.fetch_add(nl_hits, Relaxed);
}
if band_hits > 0 {
let now = band_worse as f32 / band_hits as f32;
tables.nl_off_worse = if tables.nl_band_meas == 0 {
now
} else {
0.75 * tables.nl_off_worse + 0.25 * now
};
tables.nl_band_meas = tables.nl_band_meas.saturating_add(1);
}
tables.nl_band_probe = if tables.nl_band_probe == 0 {
NL_BAND_PERIOD
} else {
tables.nl_band_probe - 1
};
tables.next_long_yield = if nl_probes == 0 {
1.0
} else {
(nl_hits as f32 / nl_probes as f32).max(tables.next_long_yield * 0.5)
};
push_lits_range(&mut lits, src, anchor, block_end);
tables.last_nseq = seqs.len();
note_finder_work(COUNT, probes, hits, &seqs, &lits);
(seqs, lits)
}
fn dfast_ml_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[5].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = DFAST_ML_MIN_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_DFAST_ML")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(14.0);
DFAST_ML_MIN_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
14.0
}
#[cfg(feature = "std")]
static DFAST_ML_MIN_CACHE: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
fn dfast_step_forced() -> usize {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = DFAST_STEP_ARM.load(Ordering::Relaxed);
if c != 0 {
return c as usize;
}
let v: usize = std::env::var("RZSTD_DFAST_STEP")
.ok()
.and_then(|v| v.trim().parse().ok())
.filter(|v| *v >= 1)
.unwrap_or(0);
DFAST_STEP_ARM.store(v as u32, Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
1
}
pub static DFAST_MATCH_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_SEQS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_BLOCK_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_BLOCKS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_REP_BLOCKS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_REP_POS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_rep_blocks() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
DFAST_BLOCKS.swap(0, Relaxed),
DFAST_REP_BLOCKS.swap(0, Relaxed),
DFAST_REP_POS.swap(0, Relaxed),
)
}
pub static DFAST_REP_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_REP_HITS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_match_stats() -> (u64, u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
DFAST_MATCH_BYTES.swap(0, Relaxed),
DFAST_SEQS.swap(0, Relaxed),
DFAST_BLOCK_BYTES.swap(0, Relaxed),
DFAST_REP_BYTES.swap(0, Relaxed),
DFAST_REP_HITS.swap(0, Relaxed),
)
}
static DFAST_STEP_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub fn set_dfast_step_arm(v: usize) {
DFAST_STEP_ARM.store(v as u32, core::sync::atomic::Ordering::Relaxed);
}
const DFAST_PROBE_PERIOD: u32 = 16;
fn dfast_spec_min() -> f32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = DFAST_SPEC_MIN_ARM.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_DFAST_SPECMIN")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.70);
DFAST_SPEC_MIN_ARM.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.70
}
static DFAST_SPEC_MIN_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_dfast_spec_min_arm(v: f32) {
DFAST_SPEC_MIN_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
static DFAST_PIPE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub static DFAST_SPEC_MADE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_SPEC_USED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_spec() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
DFAST_SPEC_MADE.swap(0, Relaxed),
DFAST_SPEC_USED.swap(0, Relaxed),
)
}
pub fn set_dfast_pipe_arm(on: bool) {
DFAST_PIPE_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_pipe_enabled() -> bool {
DFAST_PIPE_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
fn note_finder_work(count: bool, probes: u64, hits: u64, seqs: &[Seq], lits: &[u8]) {
let match_bytes: u64 = if count {
seqs.iter().map(|s| u64::from(s.matchlen)).sum()
} else {
0
};
crate::prof::note_search(
probes,
hits,
seqs.len() as u64,
match_bytes,
lits.len() as u64,
);
}
#[inline(never)]
fn find_greedy(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
find_greedy_bmi2(src, block_start, block_end, window, params, tables, reps)
};
}
find_greedy_sel(src, block_start, block_end, window, params, tables, reps)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn find_greedy_bmi2(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_greedy_sel(src, block_start, block_end, window, params, tables, reps)
}
#[inline(always)]
fn find_greedy_sel(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
if params.min_match.max(3) == 5 {
find_greedy_impl::<5>(src, block_start, block_end, window, params, tables, reps)
} else {
find_greedy_impl::<0>(src, block_start, block_end, window, params, tables, reps)
}
}
#[inline(always)]
fn find_greedy_impl<const MLS: usize>(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let mls = if MLS == 0 {
params.min_match.max(3) as usize
} else {
MLS
};
let hash_log = tables.hash_log;
let chain_mask = tables.chain.len() - 1;
let attempts = search_attempts(params);
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut hits = 0u64;
let keep = finder_scratch_enabled();
let mut seqs = if keep {
let mut v = core::mem::take(&mut tables.seq_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut lits = if keep {
let mut v = core::mem::take(&mut tables.lit_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut anchor = block_start;
let ilimit = block_end.saturating_sub(8);
if block_start >= ilimit {
lits.extend_from_slice(&src[block_start..block_end]);
return (seqs, lits);
}
let block_len = block_end - block_start;
if lits.capacity() < block_len + LIT_PUSH_WIDTH_MAX {
lits = Vec::with_capacity(block_len + LIT_PUSH_WIDTH_MAX);
}
let seq_guess = (tables.last_nseq + tables.last_nseq / 4 + 64).min(block_len / mls + 16);
if seqs.capacity() < seq_guess {
seqs = Vec::with_capacity(seq_guess);
}
let lp_copy = if tables.blocks_done == 0 || tables.lit_short_share >= lit_short_min() {
lit_width_for(tables)
} else {
0
}; let use_rep = rep_search_on(tables.rep_yield, params.strategy)
|| (rep_reprobe_enabled() && tables.rep_probe == 0);
if rep_reprobe_enabled() {
tables.rep_probe = if tables.rep_probe == 0 {
REP_PROBE_PERIOD
} else {
tables.rep_probe - 1
};
}
let mut rep1 = reps[0] as usize;
let mut rep_hits = 0u64;
let fstart_c = tables.frame_start;
let lowest_rep = block_start.saturating_sub(window).max(fstart_c);
let walk_cont = walk_cont_enabled()
&& tables.rep_yield <= walk_rep_max()
&& (tables.walk_first_share <= walk_first_max(attempts) || tables.walk_probe == 0);
tables.walk_probe = if tables.walk_probe == 0 {
WALK_PROBE_PERIOD
} else {
tables.walk_probe - 1
};
let mut wcls = (0u32, 0u32);
maybe_latch_wide_chain(tables, src, block_start, window, mls);
let cp = tables.chain_pack;
let ca = !tables.ctags.is_empty();
let wchain = tables.chain_wide;
let smask = if mls >= 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
};
let tag_filter = cp || ca;
let wide_h = mls >= 8;
let src_len = src.len();
let mut searches = 0u64;
let mut ip = block_start;
while ip <= ilimit {
if use_rep {
if let Some(ml) = try_rep1(src, ip, rep1, lowest_rep, block_end, ilimit) {
rep_hits += 1;
let mstart = ip + 1;
push_literals(&mut lits, src, anchor, mstart, lp_copy);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
continue;
}
}
searches += 1;
let (h, gtag) = if wide_h && ip + 8 <= src_len {
(hash8(src, ip, hash_log), 0u8)
} else if wchain {
hash_wide_link_tag(src, ip, hash_log, smask)
} else {
hash4_link_tag(src, ip, hash_log, smask)
};
let (prev, head_tag) = tables.lz_insert(h, ip, gtag, cp, ca, chain_mask);
let mut best_m = 0usize;
let mut best_ml = 0usize;
let mut bar = mls;
if let Some(mut m) = prev {
let mut mtag = head_tag;
let low = lowest_rep.max(ip.saturating_sub(window));
if m < ip && ip + mls <= src.len() {
let mut missed_before = false;
for _ in 0..attempts {
if m < low {
break;
}
if tag_filter && m != 0 && mtag != gtag {
#[cfg(feature = "profile")]
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
LINK_SKIPS.fetch_add(1, Relaxed);
if mls_eq(src, m, ip, mls, smask) {
LINK_FALSE.fetch_add(1, Relaxed);
}
}
missed_before = true;
if !walk_cont {
break;
}
let slot = m & chain_mask;
let link = tables.chain_masked(slot);
let next = if cp {
(link & 0x00FF_FFFF) as usize
} else {
link as usize
};
if next >= m {
break;
}
mtag = if cp {
(link >> 24) as u8
} else {
tables.ctags_masked(slot)
};
m = next;
continue;
}
if COUNT {
probes += 1;
#[cfg(feature = "profile")]
WALK_EXAM.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if mls_eq(src, m, ip, mls, smask) {
if best_ml == 0 || pre_eq(src, m, ip, best_ml) {
let ml = mls + count_match_fast(src, m + mls, ip + mls, block_end);
if ml >= bar {
if missed_before {
if best_ml == 0 {
wcls.0 += 1;
} else {
wcls.1 += 1;
}
}
best_ml = ml;
bar = ml + 1;
best_m = m;
if ip + best_ml >= block_end {
break;
}
}
}
} else {
missed_before = true;
#[cfg(feature = "profile")]
if COUNT {
WALK_BYTEMISS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if !walk_cont {
break;
}
}
let link = tables.chain_masked(m & chain_mask);
let next = if cp {
(link & 0x00FF_FFFF) as usize
} else {
link as usize
};
if next >= m {
break;
}
mtag = if cp {
(link >> 24) as u8
} else if ca {
tables.ctags_masked(m & chain_mask)
} else {
0
};
m = next;
}
}
}
if best_ml >= mls {
if COUNT {
hits += 1;
}
let mut s = ip;
let mut mm = best_m;
let mut n = best_ml;
#[cfg(feature = "profile")]
let bext_from = s;
while s > anchor && mm > fstart_c && back_eq(src, s, mm) {
s -= 1;
mm -= 1;
n += 1;
}
#[cfg(feature = "profile")]
note_bext((bext_from - s) as u64);
push_literals(&mut lits, src, anchor, s, lp_copy);
seqs.push(Seq {
litlen: (s - anchor) as u32,
matchlen: n as u32,
offset: (s - mm) as u32,
});
rep1 = ip - best_m;
let end = ip + best_ml;
let stop = end.min(ilimit + 1);
let mut p = ip + 1;
while p < stop {
let (hh, gt) = if wide_h && p + 8 <= src_len {
(hash8(src, p, hash_log), 0u8)
} else if wchain {
hash_wide_link_tag(src, p, hash_log, smask)
} else {
hash4_link_tag(src, p, hash_log, smask)
};
tables.lz_insert_only(hh, p, gt, cp, ca, chain_mask);
p += 1;
}
ip = end;
anchor = ip;
} else {
ip += 1;
}
}
tables.rep_yield = if seqs.is_empty() {
1.0
} else {
(rep_hits as f32 / seqs.len() as f32).max(tables.rep_yield * 0.5)
};
update_walk_first_share(tables, walk_cont, wcls, attempts);
let span = (block_end - block_start).max(1) as f32;
tables.last_search_per_byte = searches as f32 / span;
push_lits_range(&mut lits, src, anchor, block_end);
note_finder_work(COUNT, probes, hits, &seqs, &lits);
(seqs, lits)
}
#[allow(clippy::too_many_arguments)]
pub(crate) struct ChainCtx<'a> {
src: &'a [u8],
block_start: usize,
block_end: usize,
window: usize,
mls: usize,
attempts: usize,
hash_log: u32,
chain_mask: usize,
smask: u64,
cp: bool,
ca: bool,
wchain: bool,
wide_hash: bool,
lowest: usize,
tag_filter: bool,
}
type ChainFn =
for<'a> fn(&ChainCtx<'a>, usize, bool, &mut (u32, u32), &mut MatchTables) -> (usize, usize);
#[inline(never)]
fn chain_find_best<const MLS: usize>(
ctx: &ChainCtx,
ip: usize,
walk_cont: bool,
cls: &mut (u32, u32),
tables: &mut MatchTables,
) -> (usize, usize) {
chain_find_best_inner::<MLS>(ctx, ip, walk_cont, cls, tables)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
fn chain_find_best_bmi2_ptr<const MLS: usize>(
ctx: &ChainCtx,
ip: usize,
walk_cont: bool,
cls: &mut (u32, u32),
tables: &mut MatchTables,
) -> (usize, usize) {
#[allow(unsafe_code)]
unsafe {
chain_find_best_bmi2::<MLS>(ctx, ip, walk_cont, cls, tables)
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
#[inline(never)]
unsafe fn chain_find_best_bmi2<const MLS: usize>(
ctx: &ChainCtx,
ip: usize,
walk_cont: bool,
cls: &mut (u32, u32),
tables: &mut MatchTables,
) -> (usize, usize) {
chain_find_best_inner::<MLS>(ctx, ip, walk_cont, cls, tables)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn chain_find_best_inner<const MLS: usize>(
ctx: &ChainCtx,
ip: usize,
walk_cont: bool,
cls: &mut (u32, u32),
tables: &mut MatchTables,
) -> (usize, usize) {
let ChainCtx {
src,
block_start,
block_end,
window,
mls,
attempts,
hash_log,
chain_mask,
smask,
cp,
ca,
wchain,
wide_hash,
lowest,
tag_filter,
} = *ctx;
debug_assert_eq!(tag_filter, cp || ca);
let mls = if MLS == 0 { mls } else { MLS };
debug_assert_eq!(hash_log, tables.hash_log);
debug_assert_eq!(chain_mask, tables.chain.len() - 1);
debug_assert_eq!(cp, tables.chain_pack);
debug_assert_eq!(ca, !tables.ctags.is_empty());
debug_assert_eq!(wchain, tables.chain_wide);
debug_assert_eq!(wide_hash, mls >= 8);
debug_assert_eq!(
smask,
if mls >= 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
}
);
let (h, gtag) = if wide_hash && ip + 8 <= src.len() {
(hash8(src, ip, hash_log), 0u8)
} else if wchain {
hash_wide_link_tag(src, ip, hash_log, smask)
} else {
hash4_link_tag(src, ip, hash_log, smask)
};
let (prev, head_tag) = tables.lz_insert(h, ip, gtag, cp, ca, chain_mask);
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut best_m = 0usize;
let mut best_ml = 0usize;
let mut bar = mls;
let Some(mut m) = prev else {
return (0, 0);
};
let mut mtag = head_tag;
debug_assert_eq!(
lowest,
block_start.saturating_sub(window).max(tables.frame_start)
);
let low = lowest.max(ip.saturating_sub(window));
let mut missed_before = false;
if m < ip && ip + mls <= src.len() {
for _ in 0..attempts {
if m < low {
break;
}
if tag_filter && m != 0 && mtag != gtag {
#[cfg(feature = "profile")]
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
LINK_SKIPS.fetch_add(1, Relaxed);
if mls_eq(src, m, ip, mls, smask) {
LINK_FALSE.fetch_add(1, Relaxed);
}
}
missed_before = true;
if !walk_cont {
break;
}
let link = tables.chain_masked(m & chain_mask);
let next = if cp {
(link & 0x00FF_FFFF) as usize
} else {
link as usize
};
if next >= m {
break;
}
mtag = if cp {
(link >> 24) as u8
} else {
tables.ctags_masked(m & chain_mask)
};
m = next;
continue;
}
if COUNT {
probes += 1;
#[cfg(feature = "profile")]
WALK_EXAM.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if mls_eq(src, m, ip, mls, smask) {
if best_ml == 0 || pre_eq(src, m, ip, best_ml) {
let ml = mls + count_match_fast(src, m + mls, ip + mls, block_end);
if ml >= bar {
if missed_before {
if best_ml == 0 {
cls.0 += 1;
} else {
cls.1 += 1;
}
#[cfg(feature = "profile")]
if COUNT {
use core::sync::atomic::Ordering::Relaxed;
if best_ml == 0 {
WALK_CONT_FIRST.fetch_add(1, Relaxed);
} else {
WALK_CONT_UPGRADE.fetch_add(1, Relaxed);
}
}
}
best_ml = ml;
best_m = m;
bar = ml + 1;
if ip + best_ml >= block_end {
break;
}
}
}
} else {
missed_before = true;
#[cfg(feature = "profile")]
if COUNT {
WALK_BYTEMISS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if !walk_cont {
break;
}
}
let link = tables.chain_masked(m & chain_mask);
let next = if cp {
(link & 0x00FF_FFFF) as usize
} else {
link as usize
};
if next >= m {
break;
}
mtag = if cp {
(link >> 24) as u8
} else if ca {
tables.ctags_masked(m & chain_mask)
} else {
0
};
m = next;
}
}
if COUNT {
crate::prof::note_probes(probes);
}
(best_m, best_ml)
}
#[inline(never)]
fn find_lazy(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
depth: usize,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if crate::simd::has_bmi2() {
#[allow(unsafe_code)]
return unsafe {
find_lazy_bmi2(
src,
block_start,
block_end,
window,
params,
tables,
depth,
reps,
)
};
}
find_lazy_sel(
src,
block_start,
block_end,
window,
params,
tables,
depth,
reps,
)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(clippy::too_many_arguments)]
#[allow(unsafe_code)]
unsafe fn find_lazy_bmi2(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
depth: usize,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
find_lazy_sel(
src,
block_start,
block_end,
window,
params,
tables,
depth,
reps,
)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn find_lazy_sel(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
depth: usize,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
if params.min_match.max(3) == 5 {
find_lazy_impl::<5>(
src,
block_start,
block_end,
window,
params,
tables,
depth,
reps,
)
} else {
find_lazy_impl::<0>(
src,
block_start,
block_end,
window,
params,
tables,
depth,
reps,
)
}
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn find_lazy_impl<const MLS: usize>(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
depth: usize,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let mls = if MLS == 0 {
params.min_match.max(3) as usize
} else {
MLS
};
let hash_log = tables.hash_log;
let chain_mask = tables.chain.len() - 1;
let attempts = search_attempts(params);
#[cfg(all(target_arch = "x86_64", feature = "std"))]
let cfb: ChainFn = if crate::simd::has_bmi2() {
chain_find_best_bmi2_ptr::<MLS>
} else {
chain_find_best::<MLS>
};
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let cfb: ChainFn = chain_find_best::<MLS>;
let scratch = finder_scratch_enabled();
let mut seqs = if scratch {
let mut v = core::mem::take(&mut tables.seq_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut lits = if scratch {
let mut v = core::mem::take(&mut tables.lit_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut anchor = block_start;
let ilimit = block_end.saturating_sub(8);
if block_start >= ilimit {
lits.extend_from_slice(&src[block_start..block_end]);
return (seqs, lits);
}
let use_rep = rep_search_on(tables.rep_yield, params.strategy)
|| (rep_reprobe_enabled() && tables.rep_probe == 0);
if rep_reprobe_enabled() {
tables.rep_probe = if tables.rep_probe == 0 {
REP_PROBE_PERIOD
} else {
tables.rep_probe - 1
};
}
let mut rep1 = reps[0] as usize;
let mut rep_hits = 0u64;
let fstart_c = tables.frame_start;
let lowest_rep = block_start.saturating_sub(window).max(fstart_c);
let mut ip = block_start;
let mut searches = 0u64;
let fill = lazy_fill_enabled()
&& params.strategy != Strategy::Fast
&& tables.last_search_per_byte >= lazy_fill_threshold();
let fill_stride = lazy_fill_stride();
let walk_cont = walk_cont_enabled()
&& params.strategy != Strategy::Fast
&& tables.rep_yield <= walk_rep_max()
&& (tables.walk_first_share <= walk_first_max(attempts) || tables.walk_probe == 0);
tables.walk_probe = if tables.walk_probe == 0 {
WALK_PROBE_PERIOD
} else {
tables.walk_probe - 1
};
let mut wcls = (0u32, 0u32);
maybe_latch_wide_chain(tables, src, block_start, window, mls);
let cp = tables.chain_pack;
let ca = !tables.ctags.is_empty();
let wchain = tables.chain_wide;
let smask = if mls >= 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
};
let wide_h = mls >= 8;
let src_len = src.len();
let chain_ctx = ChainCtx {
src,
block_start,
block_end,
window,
mls,
attempts,
hash_log: tables.hash_log,
chain_mask: tables.chain.len() - 1,
smask,
cp,
ca,
wchain,
wide_hash: mls >= 8,
lowest: lowest_rep,
tag_filter: cp || ca,
};
let lp_copy = if tables.blocks_done == 0 || tables.lit_short_share >= lit_short_min() {
lit_width_for(tables)
} else {
0
};
let gain_cmp = lazy_gain_enabled();
while ip <= ilimit {
if use_rep {
if let Some(ml) = try_rep1(src, ip, rep1, lowest_rep, block_end, ilimit) {
rep_hits += 1;
let mstart = ip + 1;
push_literals(&mut lits, src, anchor, mstart, lp_copy);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
continue;
}
}
searches += 1;
let (mut best_m, mut best_ml) = cfb(&chain_ctx, ip, walk_cont, &mut wcls, tables);
let mut best_gain = if gain_cmp {
lazy_gain(best_ml, ip - best_m)
} else {
0
};
let mut best_ip = ip;
let mut look_hi = ip; if best_ml >= mls {
for d in 1..=depth {
let ip2 = ip + d;
if ip2 > ilimit {
break;
}
look_hi = ip2;
let (m, ml) = cfb(&chain_ctx, ip2, walk_cont, &mut wcls, tables);
let cand_gain = if gain_cmp { lazy_gain(ml, ip2 - m) } else { 0 };
let take = if gain_cmp {
ml != 0 && cand_gain > best_gain + 4
} else {
ml > best_ml
};
if take {
best_ml = ml;
best_m = m;
best_ip = ip2;
best_gain = cand_gain;
}
}
}
debug_assert!(best_ml == 0 || best_ml >= mls);
if best_ml != 0 {
let mut s = best_ip;
let mut mm = best_m;
let mut n = best_ml;
#[cfg(feature = "profile")]
let bext_from = s;
while s > anchor && mm > fstart_c && back_eq(src, s, mm) {
s -= 1;
mm -= 1;
n += 1;
}
#[cfg(feature = "profile")]
note_bext((bext_from - s) as u64);
push_literals(&mut lits, src, anchor, s, lp_copy);
seqs.push(Seq {
litlen: (s - anchor) as u32,
matchlen: n as u32,
offset: (s - mm) as u32,
});
rep1 = best_ip - best_m;
let end = best_ip + best_ml;
if fill {
let stride = fill_stride;
let mut p = (best_ip + 1).max(look_hi + 1);
#[cfg(feature = "profile")]
{
LF_FILLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if p < end && p <= ilimit {
LF_NONEMPTY.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
while p < end && p <= ilimit {
#[cfg(feature = "profile")]
LF_INSERTS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let (hh, gt) = if wide_h && p + 8 <= src_len {
(hash8(src, p, hash_log), 0u8)
} else if wchain {
hash_wide_link_tag(src, p, hash_log, smask)
} else {
hash4_link_tag(src, p, hash_log, smask)
};
let _ = tables.lz_insert(hh, p, gt, cp, ca, chain_mask);
p += stride;
}
}
ip = end;
anchor = ip;
} else {
ip += 1;
}
}
tables.rep_yield = if seqs.is_empty() {
1.0
} else {
(rep_hits as f32 / seqs.len() as f32).max(tables.rep_yield * 0.5)
};
update_walk_first_share(tables, walk_cont, wcls, attempts);
push_lits_range(&mut lits, src, anchor, block_end);
let span = (block_end - block_start).max(1) as f32;
tables.last_search_per_byte = searches as f32 / span;
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
WALK_SIG_FIRST.store(tables.walk_first_share.to_bits(), Relaxed);
WALK_SIG_REP.store(tables.rep_yield.to_bits(), Relaxed);
WALK_SIG_SPB.store(tables.last_search_per_byte.to_bits(), Relaxed);
let mb: u64 = seqs.iter().map(|q| q.matchlen as u64).sum();
let ob: u64 = seqs
.iter()
.map(|q| 64 - u64::from(q.offset.max(1)).leading_zeros() as u64)
.sum();
WALK_SIG_MB.store(mb, Relaxed);
WALK_SIG_NS.store(seqs.len() as u64, Relaxed);
WALK_SIG_OB.store(ob, Relaxed);
}
note_finder_work(
cfg!(feature = "profile"),
0,
seqs.len() as u64,
&seqs,
&lits,
);
(seqs, lits)
}
static SEARCH_LOG_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
pub fn set_search_log_delta(delta: i32) {
SEARCH_LOG_ARM.store(
(delta.clamp(-4, 4) + 8) as u32,
core::sync::atomic::Ordering::Relaxed,
);
}
#[inline]
fn bt_depth_apply(attempts: usize, params: CompressionParameters, opt_rep_rate: f32) -> usize {
if bt_depth_cut(params, opt_rep_rate) == 0 {
attempts
} else {
attempts.min(bt_depth_target_for(opt_rep_rate))
}
}
#[inline(always)]
fn bt_depth_target_for(opt_rep_rate: f32) -> usize {
let base = bt_depth_target();
if opt_rep_rate >= bt_depth_deep_min() {
base.min(bt_depth_deep())
} else {
base
}
}
static BT_DEEP_MIN_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
static BT_DEEP_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
pub fn set_bt_deep_min_arm(v: f32) {
BT_DEEP_MIN_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
pub fn set_bt_deep_arm(v: usize) {
BT_DEEP_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn bt_depth_deep_min() -> f32 {
let v = BT_DEEP_MIN_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == u32::MAX {
2.0
} else {
f32::from_bits(v)
}
}
#[inline(always)]
fn bt_depth_deep() -> usize {
let v = BT_DEEP_ARM.load(core::sync::atomic::Ordering::Relaxed);
if v == 0 {
24
} else {
v
}
}
static BT_DEPTH_ENV_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
static BT_DEPTH_T_C: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(usize::MAX);
static BT_DEPTH_SLOG_C: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
static BT_DEPTH_REP_C: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static BT_DEPTH_STEPS_C: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_bt_depth_target_arm(v: usize) {
BT_DEPTH_T_C.store(
if v == 0 { 32 } else { v },
core::sync::atomic::Ordering::Relaxed,
);
}
pub fn set_bt_depth_cached_arm(cached: bool) {
BT_DEPTH_ENV_ARM.store(u8::from(cached) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline(always)]
fn bt_depth_cached() -> bool {
BT_DEPTH_ENV_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
#[inline(always)]
fn bt_depth_target() -> usize {
use core::sync::atomic::Ordering::Relaxed;
let c = BT_DEPTH_T_C.load(Relaxed);
if c != usize::MAX && bt_depth_cached() {
return c;
}
#[cfg(feature = "std")]
{
let v = std::env::var("RZSTD_BT_DEPTH_TARGET")
.ok()
.and_then(|v| v.trim().parse().ok())
.filter(|v| *v >= 1)
.unwrap_or(32);
BT_DEPTH_T_C.store(v, Relaxed);
v
}
#[cfg(not(feature = "std"))]
32
}
#[inline]
fn bt_depth_cut(params: CompressionParameters, opt_rep_rate: f32) -> u32 {
let opt = matches!(
params.strategy,
Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2
);
if !opt || params.search_log < bt_depth_min_slog() || opt_rep_rate > bt_depth_rep_max() {
0
} else {
bt_depth_steps()
}
}
#[inline(always)]
fn bt_depth_rep_max() -> f32 {
use core::sync::atomic::Ordering::Relaxed;
let c = BT_DEPTH_REP_C.load(Relaxed);
if c != u32::MAX && bt_depth_cached() {
return f32::from_bits(c);
}
#[cfg(feature = "std")]
{
let v: f32 = std::env::var("RZSTD_BT_DEPTH_REP")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(50.0);
BT_DEPTH_REP_C.store(v.to_bits(), Relaxed);
v
}
#[cfg(not(feature = "std"))]
50.0
}
#[inline(always)]
fn bt_depth_min_slog() -> u32 {
use core::sync::atomic::Ordering::Relaxed;
let c = BT_DEPTH_SLOG_C.load(Relaxed);
if c != u32::MAX && bt_depth_cached() {
return c;
}
#[cfg(feature = "std")]
{
let v = std::env::var("RZSTD_BT_DEPTH_SLOG")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(7);
BT_DEPTH_SLOG_C.store(v, Relaxed);
v
}
#[cfg(not(feature = "std"))]
7
}
#[inline(always)]
fn bt_depth_steps() -> u32 {
use core::sync::atomic::Ordering::Relaxed;
let c = BT_DEPTH_STEPS_C.load(Relaxed);
if c != u32::MAX && bt_depth_cached() {
return c;
}
#[cfg(feature = "std")]
{
let v = std::env::var("RZSTD_BT_DEPTH")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(1);
BT_DEPTH_STEPS_C.store(v, Relaxed);
v
}
#[cfg(not(feature = "std"))]
1
}
pub static BT_WALKS2: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static BT_ITERS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static BT_FULL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_bt_iters() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
BT_WALKS2.swap(0, Relaxed),
BT_ITERS.swap(0, Relaxed),
BT_FULL.swap(0, Relaxed),
)
}
fn search_attempts(params: CompressionParameters) -> usize {
let v = SEARCH_LOG_ARM.load(core::sync::atomic::Ordering::Relaxed);
let base = params.search_log.min(12) as i32;
let d = if v == 0 { 0 } else { v as i32 - 8 };
1usize << base.saturating_add(d).clamp(0, 12)
}
macro_rules! bt_spec_list {
($cb:ident) => {
$cb! {
(11, 11) (12, 12) (13, 13) (14, 14) (14, 15) (15, 15) (16, 16)
(17, 17) (17, 18) (19, 18) (19, 19) (20, 20) (21, 21)
(22, 22) (22, 23) (22, 24) (23, 22) (23, 23) (23, 24) (24, 24)
}
};
}
macro_rules! bt_spec_pairs_const {
($( ($h:literal, $c:literal) )*) => {
pub const BT_SPEC_PAIRS: &[(u32, u32)] = &[$( ($h, $c) ),*];
};
}
bt_spec_list!(bt_spec_pairs_const);
pub(crate) struct BtCtx<'a> {
src: &'a [u8],
block_start: usize,
block_end: usize,
window: usize,
mls: usize,
attempts: usize,
chain_log: u32,
bt_lowest: usize,
chain_len: usize,
wide_hash: bool,
}
type BtFn = for<'a> fn(&BtCtx<'a>, usize, &mut MatchTables) -> (usize, usize);
type BtInsFn = for<'a> fn(&BtCtx<'a>, usize, &mut MatchTables);
fn bt_rt_search(ctx: &BtCtx, ip: usize, t: &mut MatchTables) -> (usize, usize) {
bt_find_best_runtime(true, ctx, ip, t)
}
fn bt_rt_insert(ctx: &BtCtx, ip: usize, t: &mut MatchTables) -> (usize, usize) {
bt_find_best_runtime(false, ctx, ip, t)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
fn bt_rt_search_bmi2(ctx: &BtCtx, ip: usize, t: &mut MatchTables) -> (usize, usize) {
#[allow(unsafe_code)]
unsafe {
bt_find_best_runtime_bmi2(true, ctx, ip, t)
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
fn bt_rt_insert_bmi2(ctx: &BtCtx, ip: usize, t: &mut MatchTables) -> (usize, usize) {
#[allow(unsafe_code)]
unsafe {
bt_find_best_runtime_bmi2(false, ctx, ip, t)
}
}
fn bt_resolve_ins(hash_log: u32, chain_log: u32) -> BtInsFn {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
let bmi2 = crate::simd::has_bmi2();
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let bmi2 = false;
#[cfg(all(target_arch = "x86_64", feature = "std"))]
let rt: BtInsFn = if bmi2 {
bt_rt_ins_bmi2
} else {
bt_rt_ins_plain
};
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let rt: BtInsFn = bt_rt_ins_plain;
if !bt_spec_enabled() {
return rt;
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if bmi2 {
return rt;
}
macro_rules! ins_resolve {
($( ($h:literal, $c:literal) )*) => {
match (hash_log, chain_log) {
$( ($h, $c) => bt_ins_spec::<$h, $c>, )*
_ => rt,
}
};
}
bt_spec_list!(ins_resolve)
}
fn bt_resolve<const SEARCH: bool>(hash_log: u32, chain_log: u32) -> BtFn {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
let bmi2 = crate::simd::has_bmi2();
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let bmi2 = false;
#[cfg(all(target_arch = "x86_64", feature = "std"))]
let rt: BtFn = match (bmi2, SEARCH) {
(true, true) => bt_rt_search_bmi2,
(true, false) => bt_rt_insert_bmi2,
(false, true) => bt_rt_search,
(false, false) => bt_rt_insert,
};
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
let rt: BtFn = if SEARCH { bt_rt_search } else { bt_rt_insert };
if !bt_spec_enabled() {
return rt;
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if bmi2 {
return rt;
}
macro_rules! bt_spec_resolve {
($( ($h:literal, $c:literal) )*) => {
match (hash_log, chain_log) {
$( ($h, $c) => bt_find_best_impl::<$h, $c, SEARCH>, )*
_ => rt,
}
};
}
bt_spec_list!(bt_spec_resolve)
}
#[inline(never)]
fn bt_find_best_impl<const HLOG: u32, const CLOG: u32, const SEARCH: bool>(
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
bt_find_best_impl_inner::<HLOG, CLOG, SEARCH>(ctx, ip, tables)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[allow(dead_code)]
fn bt_ins_spec_bmi2<const HLOG: u32, const CLOG: u32>(
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) {
#[allow(unsafe_code)]
unsafe {
bt_find_best_impl_bmi2::<HLOG, CLOG, false>(ctx, ip, tables);
}
}
fn bt_ins_spec<const HLOG: u32, const CLOG: u32>(ctx: &BtCtx, ip: usize, tables: &mut MatchTables) {
bt_find_best_impl_inner::<HLOG, CLOG, false>(ctx, ip, tables);
}
fn bt_rt_ins_plain(ctx: &BtCtx, ip: usize, t: &mut MatchTables) {
bt_find_best_runtime(false, ctx, ip, t);
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
fn bt_rt_ins_bmi2(ctx: &BtCtx, ip: usize, t: &mut MatchTables) {
#[allow(unsafe_code)]
unsafe {
bt_find_best_runtime_bmi2(false, ctx, ip, t);
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[allow(dead_code)]
fn bt_find_best_spec_bmi2<const HLOG: u32, const CLOG: u32, const SEARCH: bool>(
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
#[allow(unsafe_code)]
unsafe {
bt_find_best_impl_bmi2::<HLOG, CLOG, SEARCH>(ctx, ip, tables)
}
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
#[inline(never)]
unsafe fn bt_find_best_impl_bmi2<const HLOG: u32, const CLOG: u32, const SEARCH: bool>(
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
bt_find_best_impl_inner::<HLOG, CLOG, SEARCH>(ctx, ip, tables)
}
#[inline(always)]
fn bt_find_best_impl_inner<const HLOG: u32, const CLOG: u32, const SEARCH: bool>(
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
let BtCtx {
src,
block_start,
block_end,
window,
mls,
attempts,
chain_log,
bt_lowest,
chain_len,
wide_hash,
} = *ctx;
debug_assert_eq!(wide_hash, mls >= 8);
debug_assert_eq!(chain_len, tables.chain.len());
debug_assert_eq!(
bt_lowest,
block_start.saturating_sub(window).max(tables.frame_start)
);
if cfg!(feature = "profile") {
BT_SPEC_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
const fn btlog(c: u32) -> u32 {
let c = if c > 24 { 24 } else { c };
let c = c.saturating_sub(1);
if c < 1 {
1
} else {
c
}
}
let _ = chain_log;
let bt_log = btlog(CLOG);
let bt_mask = (1usize << bt_log) - 1;
if (bt_mask << 1) | 1 >= chain_len {
return (0, 0);
}
let h = if wide_hash && ip + 8 <= src.len() {
hash8(src, ip, HLOG)
} else {
hash4(load_u32le(src, ip), HLOG)
};
debug_assert!(h < tables.hash.len());
let mut match_idx = tables.get_h(h);
tables.put_h(h, ip);
let mut smaller = (ip & bt_mask) << 1;
let mut larger = smaller + 1;
debug_assert!(larger < tables.chain.len());
let win_low = ip.saturating_sub(window);
let low = if win_low > bt_lowest {
win_low
} else {
bt_lowest
};
debug_assert!(block_end <= src.len());
let head_ok = ip + 8 <= block_end;
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut best_ml = 0usize;
let mut best_m = 0usize;
let mut iters = 0u32;
for _ in 0..attempts {
iters += 1;
let Some(m) = match_idx else {
tables.chain_set(smaller, 0);
tables.chain_set(larger, 0);
break;
};
if m >= ip || m < low {
if m >= ip || m < win_low {
tables.chain_set(smaller, 0);
tables.chain_set(larger, 0);
}
break;
}
let bt_idx = (m & bt_mask) << 1;
debug_assert!(bt_idx + 1 < tables.chain.len());
if COUNT {
probes += 1;
}
let c_lo = tables.chain_at(bt_idx);
let c_hi = tables.chain_at(bt_idx + 1);
let (ml, go_smaller) = if head_ok {
let a = load_u64le(src, m);
let b = load_u64le(src, ip);
if a != b {
(
((a ^ b).trailing_zeros() as usize) >> 3,
a.swap_bytes() < b.swap_bytes(),
)
} else {
let ml = 8 + count_match_fast(src, m + 8, ip + 8, block_end);
let mb = src.get(m + ml).copied().unwrap_or(0);
let ib = src.get(ip + ml).copied().unwrap_or(0);
(ml, mb < ib)
}
} else {
let ml = count_match(src, m, ip, block_end);
let mb = src.get(m + ml).copied().unwrap_or(0);
let ib = src.get(ip + ml).copied().unwrap_or(0);
(ml, mb < ib)
};
#[cfg(feature = "profile")]
{
BT_PROBE.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if ml < mls {
BT_SHORT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if ml <= best_ml {
BT_NOGAIN.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
if SEARCH && ml >= mls && ml > best_ml {
best_ml = ml;
best_m = m;
}
if go_smaller {
tables.chain_set(smaller, m as u32);
let v = if smaller == bt_idx + 1 {
m as u32
} else {
c_hi
};
smaller = bt_idx + 1;
match_idx = if v == 0 { None } else { Some(v as usize) };
} else {
tables.chain_set(larger, m as u32);
let v = if larger == bt_idx { m as u32 } else { c_lo };
larger = bt_idx;
match_idx = if v == 0 { None } else { Some(v as usize) };
}
debug_assert!(smaller < tables.chain.len() && larger < tables.chain.len());
}
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
BT_WALKS2.fetch_add(1, Relaxed);
BT_ITERS.fetch_add(iters as u64, Relaxed);
if iters as usize >= attempts {
BT_FULL.fetch_add(1, Relaxed);
}
}
#[cfg(not(feature = "profile"))]
let _ = iters;
if COUNT {
crate::prof::note_probes(probes);
}
(best_m, best_ml)
}
#[inline(never)]
fn bt_find_best_runtime(
search: bool,
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
bt_find_best_runtime_inner(search, ctx, ip, tables)
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[target_feature(enable = "bmi2,lzcnt")]
#[allow(unsafe_code)]
#[inline(never)]
unsafe fn bt_find_best_runtime_bmi2(
search: bool,
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
bt_find_best_runtime_inner(search, ctx, ip, tables)
}
#[inline(always)]
fn bt_find_best_runtime_inner(
search: bool,
ctx: &BtCtx,
ip: usize,
tables: &mut MatchTables,
) -> (usize, usize) {
let BtCtx {
src,
block_start,
block_end,
window,
mls,
attempts,
chain_log,
bt_lowest,
chain_len,
wide_hash,
} = *ctx;
debug_assert_eq!(wide_hash, mls >= 8);
debug_assert_eq!(chain_len, tables.chain.len());
debug_assert_eq!(
bt_lowest,
block_start.saturating_sub(window).max(tables.frame_start)
);
if cfg!(feature = "profile") {
BT_RUNTIME_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
let hash_log = tables.hash_log;
let bt_log = chain_log.min(24).saturating_sub(1).max(1);
let bt_mask = (1usize << bt_log) - 1;
if (bt_mask << 1) | 1 >= chain_len {
return (0, 0);
}
let h = if wide_hash && ip + 8 <= src.len() {
hash8(src, ip, hash_log)
} else {
hash4(load_u32le(src, ip), hash_log)
};
if h >= tables.hash.len() {
return (0, 0);
}
let mut match_idx = tables.get_h(h);
tables.put_h(h, ip);
let mut smaller = (ip & bt_mask) << 1;
let mut larger = smaller + 1;
if larger >= tables.chain.len() {
return (0, 0);
}
let win_low = ip.saturating_sub(window);
let low = if win_low > bt_lowest {
win_low
} else {
bt_lowest
};
debug_assert!(block_end <= src.len());
let head_ok = ip + 8 <= block_end;
const COUNT: bool = cfg!(feature = "profile");
let mut probes = 0u64;
let mut best_ml = 0usize;
let mut best_m = 0usize;
let mut iters = 0u32;
for _ in 0..attempts {
iters += 1;
let Some(m) = match_idx else {
tables.chain_set(smaller, 0);
tables.chain_set(larger, 0);
break;
};
if m >= ip || m < low {
if m >= ip || m < win_low {
tables.chain_set(smaller, 0);
tables.chain_set(larger, 0);
}
break;
}
let bt_idx = (m & bt_mask) << 1;
debug_assert!(bt_idx + 1 < tables.chain.len());
if COUNT {
probes += 1;
}
let c_lo = tables.chain_at(bt_idx);
let c_hi = tables.chain_at(bt_idx + 1);
let (ml, go_smaller) = if head_ok {
let a = load_u64le(src, m);
let b = load_u64le(src, ip);
if a != b {
(
((a ^ b).trailing_zeros() as usize) >> 3,
a.swap_bytes() < b.swap_bytes(),
)
} else {
let ml = 8 + count_match_fast(src, m + 8, ip + 8, block_end);
let mb = src.get(m + ml).copied().unwrap_or(0);
let ib = src.get(ip + ml).copied().unwrap_or(0);
(ml, mb < ib)
}
} else {
let ml = count_match(src, m, ip, block_end);
let mb = src.get(m + ml).copied().unwrap_or(0);
let ib = src.get(ip + ml).copied().unwrap_or(0);
(ml, mb < ib)
};
#[cfg(feature = "profile")]
{
BT_PROBE.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if ml < mls {
BT_SHORT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
if ml <= best_ml {
BT_NOGAIN.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
if search && ml >= mls && ml > best_ml {
best_ml = ml;
best_m = m;
}
if go_smaller {
tables.chain_set(smaller, m as u32);
let v = if smaller == bt_idx + 1 {
m as u32
} else {
c_hi
};
smaller = bt_idx + 1;
match_idx = if v == 0 { None } else { Some(v as usize) };
} else {
tables.chain_set(larger, m as u32);
let v = if larger == bt_idx { m as u32 } else { c_lo };
larger = bt_idx;
match_idx = if v == 0 { None } else { Some(v as usize) };
}
debug_assert!(smaller < tables.chain.len() && larger < tables.chain.len());
}
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
BT_WALKS2.fetch_add(1, Relaxed);
BT_ITERS.fetch_add(iters as u64, Relaxed);
if iters as usize >= attempts {
BT_FULL.fetch_add(1, Relaxed);
}
}
#[cfg(not(feature = "profile"))]
let _ = iters;
if COUNT {
crate::prof::note_probes(probes);
}
(best_m, best_ml)
}
#[inline(always)]
fn find_bt_lazy(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
depth: usize,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
let mls = params.min_match.max(3) as usize;
let keep = finder_scratch_enabled();
let mut seqs = if keep {
let mut v = core::mem::take(&mut tables.seq_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut lits = if keep {
let mut v = core::mem::take(&mut tables.lit_scratch);
v.clear();
v
} else {
Vec::new()
};
let mut anchor = block_start;
let ilimit = block_end.saturating_sub(8);
if block_start >= ilimit {
lits.extend_from_slice(&src[block_start..block_end]);
return (seqs, lits);
}
let block_len = block_end - block_start;
if lits.capacity() < block_len + LIT_PUSH_WIDTH_MAX {
lits = Vec::with_capacity(block_len + LIT_PUSH_WIDTH_MAX);
}
let seq_guess = (tables.last_nseq + tables.last_nseq / 4 + 64).min(block_len / mls + 16);
if seqs.capacity() < seq_guess {
seqs = Vec::with_capacity(seq_guess);
}
let lp_copy = if tables.blocks_done == 0 || tables.lit_short_share >= lit_short_min() {
lit_width_for(tables)
} else {
0
};
let attempts = bt_depth_apply(search_attempts(params), params, tables.opt_rep_rate);
let clog = params.chain_log.min(24);
let btf = bt_resolve::<true>(tables.hash_log, clog);
let btf_ins = bt_resolve_ins(tables.hash_log, clog);
let fstart_c = tables.frame_start;
let lowest_rep = block_start.saturating_sub(window).max(fstart_c);
let bt_ctx = BtCtx {
src,
block_start,
block_end,
window,
mls,
attempts,
chain_log: clog,
bt_lowest: lowest_rep,
chain_len: tables.chain.len(),
wide_hash: mls >= 8,
};
let gain_cmp = lazy_gain_enabled_bt();
let fill_on = lazy_fill_enabled();
let bt_stride = bt_fill_stride();
let use_rep = rep_search_on(tables.rep_yield, params.strategy);
let mut rep1 = reps[0] as usize;
let mut rep_hits = 0u64;
let mut ip = block_start;
while ip <= ilimit {
if use_rep {
if let Some(ml) = try_rep1(src, ip, rep1, lowest_rep, block_end, ilimit) {
rep_hits += 1;
let mstart = ip + 1;
push_literals(&mut lits, src, anchor, mstart, lp_copy);
seqs.push(Seq {
litlen: (mstart - anchor) as u32,
matchlen: ml as u32,
offset: rep1 as u32,
});
ip = mstart + ml;
anchor = ip;
continue;
}
}
let (mut best_m, mut best_ml) = btf(&bt_ctx, ip, tables);
let mut best_ip = ip;
let mut look_hi = ip;
debug_assert!(best_ml == 0 || best_ml >= mls);
if best_ml != 0 {
let mut best_gain = if gain_cmp {
lazy_gain(best_ml, ip - best_m)
} else {
0
};
for d in 1..=depth {
let ip2 = ip + d;
if ip2 > ilimit {
break;
}
look_hi = ip2;
let (m, ml) = btf(&bt_ctx, ip2, tables);
let cand_gain = if gain_cmp { lazy_gain(ml, ip2 - m) } else { 0 };
let take = if gain_cmp {
ml != 0 && cand_gain > best_gain + 4
} else {
ml > best_ml
};
if take {
best_ml = ml;
best_m = m;
best_ip = ip2;
best_gain = cand_gain;
}
}
}
if best_ml != 0 {
let mut s = best_ip;
let mut mm = best_m;
let mut n = best_ml;
#[cfg(feature = "profile")]
let bext_from = s;
while s > anchor && mm > fstart_c && back_eq(src, s, mm) {
s -= 1;
mm -= 1;
n += 1;
}
#[cfg(feature = "profile")]
note_bext((bext_from - s) as u64);
push_literals(&mut lits, src, anchor, s, lp_copy);
seqs.push(Seq {
litlen: (s - anchor) as u32,
matchlen: n as u32,
offset: (s - mm) as u32,
});
rep1 = best_ip - best_m;
let end = best_ip + best_ml;
if fill_on {
let stride = bt_stride;
let stop = end.min(ilimit + 1);
let mut p = (best_ip + 1).max(look_hi + 1);
while p < stop {
btf_ins(&bt_ctx, p, tables);
p += stride;
}
}
ip = end;
anchor = ip;
} else {
ip += 1;
}
}
tables.rep_yield = if seqs.is_empty() {
1.0
} else {
(rep_hits as f32 / seqs.len() as f32).max(tables.rep_yield * 0.5)
};
push_lits_range(&mut lits, src, anchor, block_end);
note_finder_work(
cfg!(feature = "profile"),
0,
seqs.len() as u64,
&seqs,
&lits,
);
(seqs, lits)
}
#[inline]
fn measured_lit_bits(section_bytes: usize, literal_count: usize) -> u32 {
let bits = (section_bytes as u64 * 8) / literal_count.max(1) as u64;
bits.clamp(3, 10) as u32
}
fn opt_lit_cost(tables: &MatchTables) -> u32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
const UNCHECKED: u32 = u32::MAX;
const NO_OVERRIDE: u32 = u32::MAX - 1;
let mut e = OPT_LIT_ARM.load(Ordering::Relaxed);
if e == UNCHECKED {
e = std::env::var("RZSTD_OPT_LIT")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(NO_OVERRIDE);
OPT_LIT_ARM.store(e, Ordering::Relaxed);
}
if e != NO_OVERRIDE {
return e;
}
match tables.opt_lit_price {
0 => 6,
m => m.max(6),
}
}
#[cfg(not(feature = "std"))]
6
}
static OPT_LIT_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static OPT_MLBITS_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_opt_mlbits_arm(on: bool) {
OPT_MLBITS_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn opt_mlbits_enabled() -> bool {
!matches!(
OPT_MLBITS_ARM.load(core::sync::atomic::Ordering::Relaxed),
1
)
}
pub fn set_opt_lit_arm(v: u32) {
OPT_LIT_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
const OPT_REP_PERIOD: u32 = 16;
const OPT_REP_WARMUP: u32 = 4;
fn opt_rep_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[6].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = OPT_REP_MIN_C.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_OPT_REP_MIN")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(50.0);
OPT_REP_MIN_C.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
50.0
}
#[cfg(feature = "std")]
static OPT_REP_MIN_C: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
static OPT_REP_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_opt_rep_arm(on: bool) {
OPT_REP_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn opt_rep_enabled() -> bool {
!matches!(OPT_REP_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
pub static OPT_REP_PROBES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_REP_HITS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_REP_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_POS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_SKIP_INF: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_SKIP_JUMP: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_SKIP_JUMPS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_opt_skips() -> (u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
OPT_POS.swap(0, Relaxed),
OPT_SKIP_INF.swap(0, Relaxed),
OPT_SKIP_JUMP.swap(0, Relaxed),
OPT_SKIP_JUMPS.swap(0, Relaxed),
)
}
pub static OPT_BT_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_BT_DRY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_BT_LEN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_SEQS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_opt_bt() -> (u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
OPT_BT_CALLS.swap(0, Relaxed),
OPT_BT_DRY.swap(0, Relaxed),
OPT_BT_LEN.swap(0, Relaxed),
OPT_SEQS.swap(0, Relaxed),
)
}
pub fn take_opt_rep() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
OPT_REP_PROBES.swap(0, Relaxed),
OPT_REP_HITS.swap(0, Relaxed),
OPT_REP_BYTES.swap(0, Relaxed),
)
}
fn opt_fill_enabled() -> bool {
#[cfg(feature = "profile")]
ENVHIT[7].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = OPT_FILL_C.load(Ordering::Relaxed);
if c != 0 {
return c == 2;
}
let v = std::env::var("RZSTD_OPT_FILL")
.map(|v| v.trim() != "0")
.unwrap_or(true);
OPT_FILL_C.store(if v { 2 } else { 1 }, Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
false
}
#[cfg(feature = "std")]
static OPT_FILL_C: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
fn opt_fill_rep_max() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[8].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = OPT_FILL_REP_C.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_OPT_FILL_REP")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(50.0);
OPT_FILL_REP_C.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
50.0
}
#[cfg(feature = "std")]
static OPT_FILL_REP_C: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
fn opt_fill_max() -> usize {
#[cfg(feature = "profile")]
ENVHIT[9].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let a = OPT_FILL_MAX_ARM.load(core::sync::atomic::Ordering::Relaxed);
if a != 0 {
return a;
}
#[cfg(feature = "std")]
{
std::env::var("RZSTD_OPT_FILL_MAX")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(usize::MAX)
}
#[cfg(not(feature = "std"))]
usize::MAX
}
static OPT_FILL_S_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
static OPT_FILL_MAX_ARM: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
pub fn set_opt_fill_stride_arm(v: usize) {
OPT_FILL_S_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
pub fn set_opt_fill_max_arm(v: usize) {
OPT_FILL_MAX_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
pub static OPT_FILL_INS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_opt_fill_ins() -> u64 {
OPT_FILL_INS.swap(0, core::sync::atomic::Ordering::Relaxed)
}
static OPT_HOIST_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_opt_hoist_arm(hoisted: bool) {
OPT_HOIST_ARM.store(u8::from(hoisted) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn opt_hoisted() -> bool {
OPT_HOIST_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
fn opt_fill_stride() -> usize {
#[cfg(feature = "profile")]
ENVHIT[10].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let a = OPT_FILL_S_ARM.load(core::sync::atomic::Ordering::Relaxed);
if a != 0 {
return a;
}
#[cfg(feature = "std")]
{
std::env::var("RZSTD_OPT_FILL_S")
.ok()
.and_then(|v| v.trim().parse().ok())
.filter(|v| *v >= 1)
.unwrap_or(1)
}
#[cfg(not(feature = "std"))]
1
}
#[inline(always)]
fn find_opt(
src: &[u8],
block_start: usize,
block_end: usize,
window: usize,
params: CompressionParameters,
tables: &mut MatchTables,
reps: [u32; 3],
) -> (Vec<Seq>, Vec<u8>) {
#[cfg(feature = "profile")]
OPT_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let n = block_end - block_start;
let mls = params.min_match.max(3) as usize;
if n < 8 {
return (Vec::new(), src[block_start..block_end].to_vec());
}
let inf = u32::MAX / 4;
let mut price = core::mem::take(&mut tables.opt_price);
let mut prev = core::mem::take(&mut tables.opt_prev);
let mut match_om = core::mem::take(&mut tables.opt_om);
reset_to(&mut price, n + 1, inf);
const OPT_MATCH_BIT: u32 = 1 << 31;
ensure_len(&mut prev, n + 1, 0u32);
ensure_len(&mut match_om, n + 1, 0u64);
debug_assert!(!price.is_empty());
#[allow(unsafe_code)]
unsafe {
*price.get_unchecked_mut(0) = 0;
}
let rep1 = reps[0] as usize;
let fstart_c = tables.frame_start;
let lowest_rep = block_start.saturating_sub(window).max(fstart_c);
let rep_ilimit = block_end.saturating_sub(8);
let bt_attempts = bt_depth_apply(search_attempts(params), params, tables.opt_rep_rate);
let clog = params.chain_log.min(24);
let btf = bt_resolve::<true>(tables.hash_log, clog);
let btf_ins = bt_resolve_ins(tables.hash_log, clog);
let bt_ctx = BtCtx {
src,
block_start,
block_end,
window,
mls,
attempts: bt_attempts,
chain_log: clog,
bt_lowest: block_start.saturating_sub(window).max(tables.frame_start),
chain_len: tables.chain.len(),
wide_hash: mls >= 8,
};
let extra = match params.strategy {
Strategy::BtUltra2 => 2u32,
Strategy::BtUltra => 1,
_ => 0,
};
let rep_cost = 12u32.saturating_sub(extra).saturating_add(2);
const OPT_SKIP_FLOOR: usize = 1024;
let sufficient_len = if params.target_length == 0 {
usize::MAX
} else {
(params.target_length as usize).max(OPT_SKIP_FLOOR)
};
let lit_cost = opt_lit_cost(tables);
let (mut o_rep_probes, mut o_rep_hits, mut o_rep_bytes) = (0u64, 0u64, 0u64);
let (mut o_bt_calls, mut o_bt_dry, mut o_bt_len) = (0u64, 0u64, 0u64);
#[cfg(feature = "profile")]
let (mut o_skip_inf, mut o_skip_jump, mut o_skip_jumps) = (0u64, 0u64, 0u64);
#[cfg(feature = "profile")]
let o_positions = n as u64;
let rep_min = opt_rep_min();
let opt_rep_on = rep1 != 0
&& opt_rep_enabled()
&& (rep_min < 0.0 || tables.opt_rep_seen < OPT_REP_WARMUP
|| tables.opt_rep_probe == 0
|| tables.opt_rep_rate >= rep_min);
let mut i = 0usize;
let fill_on = opt_fill_enabled();
let fill_rep_max = opt_fill_rep_max();
let fill_step = opt_fill_stride();
let fill_span_max = opt_fill_max();
let fill_gate_hoisted =
fill_on && tables.opt_rep_meas >= 2 && tables.opt_rep_peak < fill_rep_max;
let mlb_on = opt_mlbits_enabled();
let ultra2 = params.strategy == Strategy::BtUltra2;
let mlb_over = if mlb_on { 34usize } else { usize::MAX };
let hoisted_arm = opt_hoisted();
while i < n {
debug_assert!(i + 1 < price.len() && price.len() == n + 1);
#[allow(unsafe_code)]
let pi = *unsafe { price.get_unchecked(i) };
if pi >= inf {
#[cfg(feature = "profile")]
{
o_skip_inf += 1;
}
i += 1;
continue;
}
let np = pi + lit_cost;
#[allow(unsafe_code)]
let p_next = unsafe {
let q = price.as_mut_ptr().add(i + 1);
let cur = *q;
if np < cur {
*q = np;
*prev.get_unchecked_mut(i + 1) = i as u32;
np
} else {
cur
}
};
if i + 8 > n {
i += 1;
continue;
}
let ip = block_start + i;
debug_assert!(price[i + 1] < inf);
if opt_rep_on {
o_rep_probes += 1;
if let Some(rml) = try_rep1(src, ip, rep1, lowest_rep, block_end, rep_ilimit) {
o_rep_hits += 1;
o_rep_bytes += rml as u64;
let j = i + 1 + rml;
if j <= n {
#[allow(unsafe_code)]
unsafe {
let np = p_next
+ rep_cost
+ if rml > mlb_over {
27 - ((rml - 3) as u32).leading_zeros()
} else {
0
};
if np < *price.get_unchecked(j) {
*price.get_unchecked_mut(j) = np;
*prev.get_unchecked_mut(j) = (i + 1) as u32 | OPT_MATCH_BIT;
*match_om.get_unchecked_mut(j) = rep1 as u64 | ((rml as u64) << 32);
}
}
}
}
}
let (bm, bml) = btf(&bt_ctx, ip, tables);
o_bt_calls += 1;
if bml < mls {
o_bt_dry += 1;
i += 1;
continue;
}
o_bt_len += bml as u64;
let off_bits = 32 - ((ip - bm) as u32 | 1).leading_zeros();
debug_assert!(extra <= 2);
let seq_cost = (12u32 + off_bits) - extra;
const OPT_MAX_LENGTHS: usize = 64;
debug_assert!(bml >= mls);
let floor_step = ((bml - mls) / OPT_MAX_LENGTHS).max(1);
#[allow(unsafe_code)]
let np_base = unsafe { *price.get_unchecked(i) } + seq_cost;
let lmax = bml.min(n - i);
#[allow(unsafe_code)]
let (pp, pv, pm) = (price.as_mut_ptr(), prev.as_mut_ptr(), match_om.as_mut_ptr());
let mut len = mls;
if len <= lmax {
loop {
let j = i + len;
let np = if len > mlb_over {
np_base + (27 - ((len - 3) as u32).leading_zeros())
} else {
np_base
};
#[allow(unsafe_code)]
unsafe {
let pj = pp.add(j);
if np < *pj {
*pj = np;
*pv.add(j) = i as u32 | OPT_MATCH_BIT;
*pm.add(j) = (ip - bm) as u64 | ((len as u64) << 32);
}
}
if len == lmax {
break;
}
len = if ultra2 {
(len + floor_step).min(lmax)
} else {
(len + (bml - len).clamp(1, 4).max(floor_step)).min(lmax)
};
}
}
if bml >= sufficient_len && i + bml <= n {
#[cfg(feature = "profile")]
{
o_skip_jump += bml as u64;
o_skip_jumps += 1;
}
let hoisted = hoisted_arm;
let (g_on, g_rep) = if hoisted {
(fill_on, fill_rep_max)
} else {
(opt_fill_enabled(), opt_fill_rep_max())
};
let gate_ok = if hoisted {
fill_gate_hoisted
} else {
g_on && tables.opt_rep_meas >= 2 && tables.opt_rep_peak < g_rep
};
if gate_ok {
let step = if hoisted {
fill_step
} else {
opt_fill_stride()
};
let span = bml.min(if hoisted {
fill_span_max
} else {
opt_fill_max()
});
let qp_end = block_end - 8;
let mut qp = block_start + i + 1;
let mut q = i + 1;
while q < i + span && qp <= qp_end {
btf_ins(&bt_ctx, qp, tables);
#[cfg(feature = "profile")]
OPT_FILL_INS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
q += step;
qp += step;
}
}
i += bml;
continue;
}
i += 1;
}
debug_assert!(price[n] < inf);
let mut ops: Vec<(u32, u32, u32)> = core::mem::take(&mut tables.opt_ops);
ops.clear();
let ops_bound = n / mls.max(1) + 1;
if opt_ops_exact() && ops.capacity() < ops_bound {
let mut k = 0usize;
let mut j = n;
while j > 0 {
debug_assert!(j < prev.len());
#[allow(unsafe_code)]
let pr = *unsafe { prev.get_unchecked(j) };
k += usize::from(pr & OPT_MATCH_BIT != 0);
j = (pr & !OPT_MATCH_BIT) as usize;
}
if ops.capacity() < k {
ops = Vec::with_capacity(k);
}
} else if opt_ops_blanket() && ops.capacity() < n + 1 {
ops = Vec::with_capacity(n + 1);
}
let mut i = n;
let (mut w_short, mut w_mid) = (0usize, 0usize);
let mut pending_start = usize::MAX;
let count_run = |run: usize, w_short: &mut usize, w_mid: &mut usize| {
if run <= LIT_PUSH_WIDTH {
*w_short += 1;
} else if run <= LIT_PUSH_WIDTH_WIDE {
*w_mid += 1;
}
};
while i > 0 {
debug_assert!(i < prev.len());
#[allow(unsafe_code)]
let pr = unsafe { *prev.get_unchecked(i) };
let p = (pr & !OPT_MATCH_BIT) as usize;
let m = pr & OPT_MATCH_BIT != 0;
if m {
#[allow(unsafe_code)]
let om = unsafe { *match_om.get_unchecked(i) };
let (off, ml) = (om as u32, (om >> 32) as u32);
if pending_start != usize::MAX {
count_run(pending_start - (p + ml as usize), &mut w_short, &mut w_mid);
}
pending_start = p;
ops.push((p as u32, off, ml));
}
i = p;
}
if pending_start != usize::MAX {
count_run(pending_start, &mut w_short, &mut w_mid);
}
let block_len = block_end - block_start;
let mut seqs = core::mem::take(&mut tables.seq_scratch);
seqs.clear();
let nmatched = ops.len();
if seqs.capacity() < nmatched + 1 {
seqs = Vec::with_capacity(nmatched + 1);
}
let mut lits = core::mem::take(&mut tables.lit_scratch);
lits.clear();
if lits.capacity() < block_len + LIT_PUSH_WIDTH_MAX {
lits = Vec::with_capacity(block_len + LIT_PUSH_WIDTH_MAX);
}
let opt_w = {
let n = nmatched.max(1) as f32;
let inv = 1.0 / n;
let (sh, md) = (w_short as f32 * inv, w_mid as f32 * inv);
if sh < lit_short_min() {
0
} else if md > sh * WIDEN_RATIO {
LIT_PUSH_WIDTH_WIDE
} else {
LIT_PUSH_WIDTH
}
};
let mut anchor = 0usize;
for &(start, off, ml) in ops.iter().rev() {
let start = start as usize;
{
push_literals(
&mut lits,
src,
block_start + anchor,
block_start + start,
opt_w,
);
debug_assert!(seqs.len() < seqs.capacity());
#[allow(unsafe_code)]
unsafe {
let l = seqs.len();
seqs.as_mut_ptr().add(l).write(Seq {
litlen: (start - anchor) as u32,
matchlen: ml,
offset: off,
});
seqs.set_len(l + 1);
}
anchor = start + ml as usize;
}
}
push_lits_range(&mut lits, src, block_start + anchor, block_end);
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
OPT_REP_PROBES.fetch_add(o_rep_probes, Relaxed);
OPT_REP_HITS.fetch_add(o_rep_hits, Relaxed);
OPT_REP_BYTES.fetch_add(o_rep_bytes, Relaxed);
OPT_BT_CALLS.fetch_add(o_bt_calls, Relaxed);
OPT_BT_DRY.fetch_add(o_bt_dry, Relaxed);
OPT_BT_LEN.fetch_add(o_bt_len, Relaxed);
OPT_SEQS.fetch_add(seqs.len() as u64, Relaxed);
OPT_POS.fetch_add(o_positions, Relaxed);
OPT_SKIP_INF.fetch_add(o_skip_inf, Relaxed);
OPT_SKIP_JUMP.fetch_add(o_skip_jump, Relaxed);
OPT_SKIP_JUMPS.fetch_add(o_skip_jumps, Relaxed);
}
#[cfg(not(feature = "profile"))]
let _ = (
o_rep_probes,
o_rep_hits,
o_rep_bytes,
o_bt_calls,
o_bt_dry,
o_bt_len,
);
if opt_rep_on && o_rep_probes > 0 {
let now = o_rep_bytes as f32 / o_rep_probes as f32;
tables.opt_rep_peak = tables.opt_rep_peak.max(now);
#[cfg(feature = "profile")]
{
use core::sync::atomic::Ordering::Relaxed;
SIG_REP_RATE.store(tables.opt_rep_rate.to_bits(), Relaxed);
SIG_REP_PEAK.store(tables.opt_rep_peak.to_bits(), Relaxed);
SIG_SPB.store(tables.last_search_per_byte.to_bits(), Relaxed);
}
tables.opt_rep_meas = tables.opt_rep_meas.saturating_add(1);
tables.opt_rep_seen = tables.opt_rep_seen.saturating_add(1);
tables.opt_rep_rate = if tables.opt_rep_rate == f32::MAX {
now
} else if tables.opt_rep_seen <= OPT_REP_WARMUP {
tables.opt_rep_rate.max(now)
} else {
0.75 * tables.opt_rep_rate + 0.25 * now
};
}
tables.opt_rep_probe = if tables.opt_rep_probe == 0 {
OPT_REP_PERIOD
} else {
tables.opt_rep_probe - 1
};
note_finder_work(
cfg!(feature = "profile"),
0,
seqs.len() as u64,
&seqs,
&lits,
);
tables.opt_ops = ops;
tables.opt_price = price;
tables.opt_prev = prev;
tables.opt_om = match_om;
(seqs, lits)
}
#[inline(always)]
fn mls_eq(src: &[u8], m: usize, ip: usize, mls: usize, smask: u64) -> bool {
if mls <= 8 {
debug_assert!(m < ip && ip + 8 <= src.len());
debug_assert!(
smask
== if mls == 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
}
);
return (load_u64le(src, m) ^ load_u64le(src, ip)) & smask == 0;
}
if load_u32le(src, m) != load_u32le(src, ip) {
return false;
}
src[m + 4..m + mls] == src[ip + 4..ip + mls]
}
static WALK_CONT_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_walk_cont_arm(on: bool) {
WALK_CONT_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn walk_cont_enabled() -> bool {
!matches!(WALK_CONT_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
static WALK_REP_MAX_ARM: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_walk_rep_max_arm(v: f32) {
WALK_REP_MAX_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
fn walk_rep_max() -> f32 {
let c = WALK_REP_MAX_ARM.load(core::sync::atomic::Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
0.10
}
static LAZY_GAIN_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_lazy_gain_arm(on: bool) {
LAZY_GAIN_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn lazy_gain_enabled() -> bool {
matches!(LAZY_GAIN_ARM.load(core::sync::atomic::Ordering::Relaxed), 2)
}
fn lazy_gain_enabled_bt() -> bool {
!matches!(LAZY_GAIN_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
#[inline(always)]
fn lazy_gain(ml: usize, off: usize) -> i64 {
(ml as i64) * 4 - (63 - ((off as u64 + 1).leading_zeros() as i64))
}
static WALK_FIRST_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_walk_first_max_arm(v: f32) {
WALK_FIRST_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
fn walk_first_max(attempts: usize) -> f32 {
let c = WALK_FIRST_ARM.load(core::sync::atomic::Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
if attempts <= 8 {
0.80
} else if attempts <= 16 {
0.70
} else {
0.55
}
}
const WALK_PROBE_PERIOD: u32 = 16;
static REP_REPROBE_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_rep_reprobe_arm(on: bool) {
REP_REPROBE_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn rep_reprobe_enabled() -> bool {
matches!(
REP_REPROBE_ARM.load(core::sync::atomic::Ordering::Relaxed),
2
)
}
static CHAIN_TAG_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_chain_tag_arm(on: bool) {
CHAIN_TAG_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn chain_tag_enabled() -> bool {
!matches!(CHAIN_TAG_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
#[inline(always)]
fn dfast_hash_pair(
src: &[u8],
pos: usize,
dtag_shift: u32,
smask: u64,
hlog: u32,
) -> (usize, u8, usize) {
let v = load_u64le(src, pos);
let hv4 = (v as u32).wrapping_mul(HASH4_PRIME);
let tv = (v & smask).wrapping_mul(FAST_HASH_PRIME64);
let h8 =
(v.wrapping_mul(0xCF1B_BCDC_B7A5_6463) >> (64u32.saturating_sub(hlog.min(32)))) as usize;
((hv4 >> dtag_shift) as usize, (tv ^ (tv >> 29)) as u8, h8)
}
#[inline(always)]
fn hash4_link_tag(src: &[u8], pos: usize, hash_log: u32, smask: u64) -> (usize, u8) {
hash4_tag_mls(src, pos, 32u32.saturating_sub(hash_log.min(32)), smask)
}
static WCHAIN_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_wide_chain_arm(on: bool) {
WCHAIN_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn wide_chain_enabled() -> bool {
!matches!(WCHAIN_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
static WIDE_FIRST_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_wide_first_max_arm(v: f32) {
WIDE_FIRST_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
static WIDE_SPB_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_wide_spb_min_arm(v: f32) {
WIDE_SPB_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
fn wide_spb_min() -> f32 {
let c = WIDE_SPB_ARM.load(core::sync::atomic::Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
0.50
}
fn wide_first_max(attempts: usize) -> f32 {
let c = WIDE_FIRST_ARM.load(core::sync::atomic::Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
if attempts <= 16 {
0.65
} else {
0.60
}
}
#[inline(always)]
fn maybe_latch_wide_chain(
tables: &mut MatchTables,
src: &[u8],
block_start: usize,
window: usize,
mls: usize,
) {
if tables.chain_wide
|| !wide_chain_enabled()
|| mls >= 8
|| !tables.walk_share_meas
|| tables.wide_ok_blocks < 3
{
return;
}
let hash_log = tables.hash_log;
let smask = (1u64 << (8 * mls)) - 1;
let cp = tables.chain_pack;
let ca = !tables.ctags.is_empty();
let from = block_start.saturating_sub(window).max(tables.frame_start);
let to = block_start.saturating_sub(8);
let chain_mask = tables.chain.len() - 1;
let mut p = from;
while p <= to && p + 8 <= src.len() {
let (h, g) = hash_wide_link_tag(src, p, hash_log, smask);
let _ = tables.lz_insert(h, p, g, cp, ca, chain_mask);
p += 1;
}
tables.chain_wide = true;
}
#[inline(always)]
fn hash_wide_link_tag(src: &[u8], pos: usize, hash_log: u32, smask: u64) -> (usize, u8) {
let v = load_u64le(src, pos) & smask;
let hv = v.wrapping_mul(FAST_HASH_PRIME64);
(
(hv >> (64u32.saturating_sub(hash_log.min(32)))) as usize,
(hv ^ (hv >> 29)) as u8,
)
}
#[cfg(feature = "profile")]
pub static LINK_SKIPS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static LINK_FALSE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_link_tag() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(LINK_SKIPS.swap(0, Relaxed), LINK_FALSE.swap(0, Relaxed))
}
#[inline(always)]
#[allow(unsafe_code)]
fn push_lits_range(lits: &mut Vec<u8>, src: &[u8], from: usize, to: usize) {
debug_assert!(from <= to && to <= src.len());
lits.extend_from_slice(unsafe { src.get_unchecked(from..to) });
}
fn update_walk_first_share(
tables: &mut MatchTables,
walked: bool,
cls: (u32, u32),
attempts: usize,
) {
let n = cls.0 + cls.1;
if !walked || n < 64 {
return;
}
let now = cls.0 as f32 / n as f32;
tables.walk_first_share = if tables.walk_share_meas {
0.75 * tables.walk_first_share + 0.25 * now
} else {
now
};
tables.walk_share_meas = true;
if tables.walk_first_share <= wide_first_max(attempts)
|| tables.last_search_per_byte >= wide_spb_min()
{
tables.wide_ok_blocks = tables.wide_ok_blocks.saturating_add(1);
} else {
tables.wide_ok_blocks = 0;
}
}
#[cfg(feature = "profile")]
pub static WALK_SIG_FIRST: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
#[cfg(feature = "profile")]
pub static WALK_SIG_REP: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
#[cfg(feature = "profile")]
pub static WALK_SIG_SPB: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
#[cfg(feature = "profile")]
pub static WALK_SIG_MB: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static WALK_SIG_NS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static WALK_SIG_OB: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_walk_signals() -> (f32, f32, f32, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
f32::from_bits(WALK_SIG_FIRST.load(Relaxed)),
f32::from_bits(WALK_SIG_REP.load(Relaxed)),
f32::from_bits(WALK_SIG_SPB.load(Relaxed)),
WALK_SIG_MB.load(Relaxed),
WALK_SIG_NS.load(Relaxed),
WALK_SIG_OB.load(Relaxed),
)
}
#[cfg(feature = "profile")]
pub static BEXT_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static BEXT_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static BEXT_GE8: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static BEXT_MATCHES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_bext() -> (u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
BEXT_MATCHES.swap(0, Relaxed),
BEXT_N.swap(0, Relaxed),
BEXT_BYTES.swap(0, Relaxed),
BEXT_GE8.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
fn note_bext(ext: u64) {
use core::sync::atomic::Ordering::Relaxed;
BEXT_MATCHES.fetch_add(1, Relaxed);
if ext > 0 {
BEXT_N.fetch_add(1, Relaxed);
BEXT_BYTES.fetch_add(ext, Relaxed);
if ext >= 8 {
BEXT_GE8.fetch_add(1, Relaxed);
}
}
}
#[cfg(feature = "profile")]
pub static WALK_EXAM: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static WALK_BYTEMISS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static WALK_CONT_FIRST: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static WALK_CONT_UPGRADE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_walk_classes() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
WALK_CONT_FIRST.swap(0, Relaxed),
WALK_CONT_UPGRADE.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub fn take_walk_census() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(WALK_EXAM.swap(0, Relaxed), WALK_BYTEMISS.swap(0, Relaxed))
}
#[inline(always)]
fn match_ok(
src: &[u8],
m: usize,
ip: usize,
window: usize,
block_start: usize,
mls: usize,
frame_start: usize,
) -> bool {
if m >= ip || ip - m > window {
return false;
}
let lowest = block_start.saturating_sub(window).max(frame_start);
if m < lowest {
return false;
}
if ip + mls > src.len() || m + mls > src.len() {
return false;
}
if mls <= 8 && ip + 8 <= src.len() {
debug_assert!(m + 8 <= src.len());
let mask = if mls == 8 {
u64::MAX
} else {
(1u64 << (8 * mls)) - 1
};
return (load_u64le(src, m) ^ load_u64le(src, ip)) & mask == 0;
}
match_ok_cold_tail(src, m, ip, mls)
}
#[cold]
#[inline(never)]
fn match_ok_cold_tail(src: &[u8], m: usize, ip: usize, mls: usize) -> bool {
if mls >= 4 {
if load_u32le(src, m) != load_u32le(src, ip) {
return false;
}
return mls == 4 || src[m + 4..m + mls] == src[ip + 4..ip + mls];
}
src[m..m + mls] == src[ip..ip + mls]
}
#[cold]
#[inline(never)]
fn count_match_sub8(src: &[u8], m: usize, ip: usize, max: usize) -> usize {
if ip + 8 <= src.len() {
let x = load_u64le(src, m) ^ load_u64le(src, ip);
let n = if x == 0 {
max
} else {
((x.trailing_zeros() as usize) >> 3).min(max)
};
#[cfg(feature = "profile")]
crate::simd::note_eqlen(n);
return n;
}
let a = &src[m..m + max];
let b = &src[ip..ip + max];
let mut n = 0usize;
while n < max && a[n] == b[n] {
n += 1;
}
#[cfg(feature = "profile")]
crate::simd::note_eqlen(n);
n
}
#[inline(always)]
fn count_match_fast(src: &[u8], m: usize, ip: usize, limit: usize) -> usize {
debug_assert!(limit <= src.len() && m <= ip);
if ip + 8 <= limit {
let a = load_u64le(src, m);
let b = load_u64le(src, ip);
if a != b {
return ((a ^ b).trailing_zeros() as usize) >> 3;
}
8 + count_match(src, m + 8, ip + 8, limit)
} else {
count_match(src, m, ip, limit)
}
}
pub(crate) fn count_match(src: &[u8], m: usize, ip: usize, limit: usize) -> usize {
debug_assert!(limit <= src.len() && m <= ip);
if ip >= limit {
return 0;
}
let max = limit - ip;
let a = &src[m..m + max];
let b = &src[ip..limit];
if max < 8 {
return count_match_sub8(src, m, ip, max);
}
let n = crate::simd::count_eq_len_ge8(a, b, max);
#[cfg(feature = "profile")]
crate::simd::note_eqlen(n);
n
}
const HASH4_PRIME: u32 = 2_654_435_761;
#[inline(always)]
fn load_u32le(src: &[u8], i: usize) -> u32 {
crate::simd::load_u32_le(src, i)
}
#[inline(always)]
fn load_u64le(src: &[u8], i: usize) -> u64 {
crate::simd::load_u64_le(src, i)
}
#[inline(always)]
fn ensure_len<T: Clone>(v: &mut Vec<T>, n: usize, val: T) {
if v.len() < n {
v.resize(n, val);
}
}
fn reset_to<T: Clone>(v: &mut Vec<T>, n: usize, val: T) {
if v.capacity() < n {
*v = Vec::with_capacity(n);
}
v.clear();
v.resize(n, val);
}
#[inline(always)]
#[allow(unsafe_code)]
fn pre_eq(src: &[u8], m: usize, ip: usize, off: usize) -> bool {
debug_assert!(m + off < src.len() && ip + off < src.len());
unsafe { *src.get_unchecked(m + off) == *src.get_unchecked(ip + off) }
}
#[inline(always)]
#[allow(unsafe_code)]
fn back_eq(src: &[u8], s: usize, mm: usize) -> bool {
debug_assert!(s >= 1 && mm >= 1 && s - 1 < src.len() && mm - 1 < src.len());
unsafe { *src.get_unchecked(s - 1) == *src.get_unchecked(mm - 1) }
}
fn hash4(v: u32, hash_log: u32) -> usize {
let shift = 32u32.saturating_sub(hash_log.min(32));
(v.wrapping_mul(HASH4_PRIME) >> shift) as usize
}
#[inline(always)]
fn hash8(src: &[u8], ip: usize, hash_log: u32) -> usize {
let v = load_u64le(src, ip);
let shift = 64u32.saturating_sub(hash_log.min(32));
(v.wrapping_mul(0xCF1B_BCDC_B7A5_6463) >> shift) as usize
}
#[inline(always)]
fn hash_mls(src: &[u8], ip: usize, mls: usize, hash_log: u32) -> usize {
if mls >= 8 && ip + 8 <= src.len() {
hash8(src, ip, hash_log)
} else {
hash4(load_u32le(src, ip), hash_log)
}
}
pub(crate) fn checksum_u32(h: &Xxh64) -> u32 {
h.digest() as u32
}
#[cfg(feature = "std")]
pub(crate) struct HarvestedEntropy {
pub huff: huffman::HuffCTable,
pub of_nc: Vec<u8>,
pub ml_nc: Vec<u8>,
pub ll_nc: Vec<u8>,
pub reps: [u32; 3],
}
#[cfg(feature = "std")]
pub(crate) fn harvest_dict_entropy(
content: &[u8],
samples: &[&[u8]],
) -> Result<HarvestedEntropy, Error> {
let hint = samples.iter().map(|s| s.len() as u64).sum::<u64>().max(1);
let params = compression_params(3, Some(hint))?;
let mut tables = MatchTables::new(params);
let window = 1usize << params.window_log.min(31);
let block_max = (window.min(BLOCKSIZE_MAX as usize)).max(1);
let mut lit_freq = [0u32; 256];
let mut ll_count = [0u32; 36];
let mut of_count = [0u32; 32];
let mut ml_count = [0u32; 53];
let mut reps = [1u32, 4, 8];
for sample in samples {
if sample.is_empty() {
continue;
}
let mut owned = Vec::with_capacity(content.len() + sample.len());
owned.extend_from_slice(content);
owned.extend_from_slice(sample);
tables.reset();
prime_tables(&mut tables, &owned, content.len(), window, params);
let mut off = content.len();
while off < owned.len() {
let end = (off + block_max).min(owned.len());
let (seqs, lits) = find_sequences(
&owned,
off,
end,
window,
params,
&mut tables,
None,
crate::ldm::LdmParams::default(),
[1, 4, 8],
);
for &b in &lits {
lit_freq[b as usize] = lit_freq[b as usize].saturating_add(1);
}
for s in &seqs {
let ov = offset_value_for(s.offset, s.litlen, &reps);
if resolve_offset(ov, s.litlen, &mut reps).is_err() {
continue;
}
let (llc, _, _) = ll_code(s.litlen, true);
let (mlc, _, _) = ml_code(s.matchlen, true);
let (ofc, _) = of_code(ov);
if (llc as usize) < ll_count.len() {
ll_count[llc as usize] = ll_count[llc as usize].saturating_add(1);
}
if (ofc as usize) < of_count.len() {
of_count[ofc as usize] = of_count[ofc as usize].saturating_add(1);
}
if (mlc as usize) < ml_count.len() {
ml_count[mlc as usize] = ml_count[mlc as usize].saturating_add(1);
}
}
off = end;
}
}
let huff = huffman::build_ctable_from_freq(&pad_lit_freq(lit_freq))?;
let of_nc = ncount_or_default(&of_count, 8, &fse::DEFAULT_OF_NORM, 5)?;
let ml_nc = ncount_or_default(&ml_count, 9, &fse::DEFAULT_ML_NORM, 6)?;
let ll_nc = ncount_or_default(&ll_count, 9, &fse::DEFAULT_LL_NORM, 6)?;
let clen = content.len() as u32;
let reps = clamp_reps(reps, clen);
Ok(HarvestedEntropy {
huff,
of_nc,
ml_nc,
ll_nc,
reps,
})
}
#[cfg(feature = "std")]
fn pad_lit_freq(mut freq: [u32; 256]) -> [u32; 256] {
let n = freq.iter().filter(|&&c| c > 0).count();
if n < 2 {
freq[0] = freq[0].saturating_add(1);
freq[1] = freq[1].saturating_add(1);
freq[255] = freq[255].saturating_add(1);
}
freq
}
#[cfg(feature = "std")]
fn ncount_or_default(
count: &[u32],
max_log: u8,
default_norm: &[i16],
default_log: u8,
) -> Result<Vec<u8>, Error> {
let mut buf = count.to_vec();
let total: u32 = buf.iter().sum();
if total == 0 {
return fse::write_ncount(default_norm, default_log);
}
let max_sv = buf.iter().rposition(|&c| c > 0).unwrap_or(0);
if buf[max_sv] == total {
let other = if max_sv == 0 { 1 } else { 0 };
if other < buf.len() {
buf[other] = buf[other].saturating_add(1);
}
}
match fse::ncount_and_ctable(&buf, max_log, false) {
Ok((hdr, _)) => Ok(hdr),
Err(_) => fse::write_ncount(default_norm, default_log),
}
}
#[cfg(feature = "std")]
fn clamp_reps(mut reps: [u32; 3], content_len: u32) -> [u32; 3] {
let cap = content_len.max(1);
for r in &mut reps {
if *r == 0 || *r > cap {
*r = ((*r) % cap).max(1);
}
}
reps
}
pub fn reset_env_arms() {
use core::sync::atomic::Ordering;
STEP0_ARM.store(0, Ordering::Relaxed);
PIPE_ARM.store(0, Ordering::Relaxed);
LAZY_FILL_ENABLED_ARM.store(0, Ordering::Relaxed);
FAST_LAZY_ARM.store(0, Ordering::Relaxed);
PAIR_GAIN_ARM.store(u32::MAX, Ordering::Relaxed);
PAIR_HI_ARM.store(u32::MAX, Ordering::Relaxed);
}
static DFAST_SPEC_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_dfast_spec_arm(on: bool) {
DFAST_SPEC_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn dfast_spec_enabled() -> bool {
use core::sync::atomic::Ordering;
match DFAST_SPEC_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_DFAST_SPEC")
.map(|v| v.trim() != "0")
.unwrap_or(true);
DFAST_SPEC_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static FAST_SPEC_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_fast_spec_arm(on: bool) {
FAST_SPEC_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn fast_spec_enabled() -> bool {
use core::sync::atomic::Ordering;
match FAST_SPEC_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_FAST_SPEC")
.map(|v| v.trim() != "0")
.unwrap_or(true);
FAST_SPEC_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
pub static DFAST_SPEC_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static DFAST_RUNTIME_CALLS: core::sync::atomic::AtomicU64 =
core::sync::atomic::AtomicU64::new(0);
pub fn take_dfast_calls() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
DFAST_SPEC_CALLS.swap(0, Ordering::Relaxed),
DFAST_RUNTIME_CALLS.swap(0, Ordering::Relaxed),
)
}
pub static FAST_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static OPT_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_finder_calls() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
FAST_CALLS.swap(0, Ordering::Relaxed),
OPT_CALLS.swap(0, Ordering::Relaxed),
)
}
pub static BT_SPEC_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static BT_RUNTIME_CALLS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_bt_calls() -> (u64, u64) {
use core::sync::atomic::Ordering;
(
BT_SPEC_CALLS.swap(0, Ordering::Relaxed),
BT_RUNTIME_CALLS.swap(0, Ordering::Relaxed),
)
}
static BT_SPEC_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_bt_spec_arm(on: bool) {
BT_SPEC_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn bt_spec_enabled() -> bool {
use core::sync::atomic::Ordering;
match BT_SPEC_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_BT_SPEC")
.map(|v| v.trim() != "0")
.unwrap_or(true);
BT_SPEC_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static NEXT_LONG_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_next_long_arm(on: bool) {
NEXT_LONG_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn next_long_enabled() -> bool {
use core::sync::atomic::Ordering;
match NEXT_LONG_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_NEXT_LONG")
.map(|v| v.trim() != "0")
.unwrap_or(true);
NEXT_LONG_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
fn next_long_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[11].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = NEXT_LONG_MIN_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_NEXT_LONG_T")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.10);
NEXT_LONG_MIN_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.10
}
#[cfg(feature = "std")]
static NEXT_LONG_MIN_CACHE: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(u32::MAX);
static PAIR_ON_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_pair_on_arm(on: bool) {
PAIR_ON_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn pair_enabled() -> bool {
use core::sync::atomic::Ordering;
match PAIR_ON_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_PAIR")
.map(|v| v.trim() != "0")
.unwrap_or(true);
PAIR_ON_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
#[inline(always)]
fn pair_gain_lo() -> f32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = PAIR_LO_ARM.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_PAIR_LO")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.71);
PAIR_LO_ARM.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.71
}
static PAIR_LO_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_pair_lo_arm(v: f32) {
use core::sync::atomic::Ordering;
PAIR_LO_ARM.store(
if v.is_nan() { u32::MAX } else { v.to_bits() },
Ordering::Relaxed,
);
}
fn pair_rep_max() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[12].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = PAIR_T_CACHE.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_PAIR_T")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.7);
PAIR_T_CACHE.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.7
}
static PAIR_T_CACHE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
#[cfg(feature = "profile")]
pub static ENVHIT: [core::sync::atomic::AtomicU64; 14] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
#[cfg(feature = "profile")]
pub fn take_envhits() -> [u64; 14] {
let mut o = [0u64; 14];
for i in 0..14 {
o[i] = ENVHIT[i].swap(0, core::sync::atomic::Ordering::Relaxed);
}
o
}
pub static PAIR_PROBES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_HITS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_M0_EMPTY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_M0_LIVE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_HIT_EMPTY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_HIT_LIVE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_BYTES_EMPTY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static PAIR_BYTES_LIVE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_pair_split() -> (u64, u64, u64, u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
PAIR_M0_EMPTY.swap(0, Relaxed),
PAIR_M0_LIVE.swap(0, Relaxed),
PAIR_HIT_EMPTY.swap(0, Relaxed),
PAIR_HIT_LIVE.swap(0, Relaxed),
PAIR_BYTES_EMPTY.swap(0, Relaxed),
PAIR_BYTES_LIVE.swap(0, Relaxed),
)
}
pub static MAIN_BYTES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static ROUTE_HIST: [core::sync::atomic::AtomicU64; 3] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
static ROUTE_GAIN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static ROUTE_REP: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static ROUTE_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_GAIN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_REP: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_N: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_TAG: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_REPLEN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_NSEQ: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
static SIG_OPTREP: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_content_signals() -> (f64, f64, f64, f64, f64, f64) {
use core::sync::atomic::Ordering;
let n = SIG_N.swap(0, Ordering::Relaxed).max(1) as f64;
let g = SIG_GAIN.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n;
let y = SIG_REP.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n;
let t = SIG_TAG.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n;
let r = SIG_REPLEN.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n;
let q = SIG_NSEQ.swap(0, Ordering::Relaxed) as f64 / n;
let o = SIG_OPTREP.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n;
(g, y, t, r, q, o)
}
pub fn take_route_hist() -> (u64, u64, u64, f64, f64) {
use core::sync::atomic::Ordering;
let n = ROUTE_N.swap(0, Ordering::Relaxed).max(1);
(
ROUTE_HIST[0].swap(0, Ordering::Relaxed),
ROUTE_HIST[1].swap(0, Ordering::Relaxed),
ROUTE_HIST[2].swap(0, Ordering::Relaxed),
ROUTE_GAIN.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n as f64,
ROUTE_REP.swap(0, Ordering::Relaxed) as f64 / 1000.0 / n as f64,
)
}
pub fn take_pair_stats() -> (u64, u64, u64, u64) {
use core::sync::atomic::Ordering;
(
PAIR_PROBES.swap(0, Ordering::Relaxed),
PAIR_HITS.swap(0, Ordering::Relaxed),
PAIR_BYTES.swap(0, Ordering::Relaxed),
MAIN_BYTES.swap(0, Ordering::Relaxed),
)
}
static TAG_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_tag_arm(on: bool) {
TAG_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn tag_enabled() -> bool {
use core::sync::atomic::Ordering;
match TAG_ARM.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = crate::env_knob("RZSTD_TAG")
.map(|v| v.trim() != "0")
.unwrap_or(true);
TAG_ARM.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
}
}
static TAG_MIN_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
fn tag_min() -> f32 {
#[cfg(feature = "profile")]
ENVHIT[13].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = TAG_MIN_ARM.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_TAG_T")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.0);
TAG_MIN_ARM.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.0
}
#[inline]
fn cand_yield((f, t): (u64, u64)) -> f32 {
if f + t == 0 {
1.0
} else {
f as f32 / (f + t) as f32
}
}
pub static BT_PROBE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static BT_SHORT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub static BT_NOGAIN: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
pub fn take_bt_probe_stats() -> (u64, u64, u64) {
use core::sync::atomic::Ordering;
(
BT_PROBE.swap(0, Ordering::Relaxed),
BT_SHORT.swap(0, Ordering::Relaxed),
BT_NOGAIN.swap(0, Ordering::Relaxed),
)
}
const PAIR_PROBE_PERIOD: u32 = 16;
fn pair_rate_hi() -> f32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = PAIR_HI_ARM.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_PAIR_HI")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(1.0);
PAIR_HI_ARM.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
1.0
}
static PAIR_HI_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_pair_hi_arm(v: f32) {
PAIR_HI_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
fn pair_gain_min() -> f32 {
#[cfg(feature = "std")]
{
use core::sync::atomic::Ordering;
let c = PAIR_GAIN_ARM.load(Ordering::Relaxed);
if c != u32::MAX {
return f32::from_bits(c);
}
let v: f32 = std::env::var("RZSTD_PAIR_G")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.20);
PAIR_GAIN_ARM.store(v.to_bits(), Ordering::Relaxed);
v
}
#[cfg(not(feature = "std"))]
0.20
}
static TAG_ALLOC_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_tag_alloc_arm(on: bool) {
TAG_ALLOC_ARM.store(u8::from(on) + 1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn tag_alloc_enabled() -> bool {
TAG_ALLOC_ARM.load(core::sync::atomic::Ordering::Relaxed) != 1
}
static PAIR_GAIN_ARM: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_pair_gain_arm(v: f32) {
PAIR_GAIN_ARM.store(v.to_bits(), core::sync::atomic::Ordering::Relaxed);
}
static OPT_OPS_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_opt_ops_arm(v: u8) {
OPT_OPS_ARM.store(v + 1, core::sync::atomic::Ordering::Relaxed);
}
fn opt_ops_exact() -> bool {
matches!(
OPT_OPS_ARM.load(core::sync::atomic::Ordering::Relaxed),
0 | 2
)
}
fn opt_ops_blanket() -> bool {
OPT_OPS_ARM.load(core::sync::atomic::Ordering::Relaxed) == 3
}
static FINDER_SCRATCH_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_finder_scratch_arm(on: bool) {
FINDER_SCRATCH_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn finder_scratch_enabled() -> bool {
!matches!(
FINDER_SCRATCH_ARM.load(core::sync::atomic::Ordering::Relaxed),
1
)
}
static DFAST_TAG_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_dfast_tag_arm(on: bool) {
DFAST_TAG_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn dfast_tag_enabled() -> bool {
!matches!(DFAST_TAG_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
static LONG_TAG_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_long_tag_arm(on: bool) {
LONG_TAG_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn long_tag_enabled() -> bool {
!matches!(LONG_TAG_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
#[cfg(feature = "profile")]
pub static LTAG_NONEMPTY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static LTAG_REJECT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static LTAG_FALSE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_long_tag() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
LTAG_NONEMPTY.swap(0, Relaxed),
LTAG_REJECT.swap(0, Relaxed),
LTAG_FALSE.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub static LTAG_SURV_FAIL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static LTAG_SURV_WFAIL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static LTAG_SURV_ACC: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static STAG_SURV_FAIL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static STAG_SURV_WFAIL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static STAG_SURV_ACC: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_short_tag_residual() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
STAG_SURV_FAIL.swap(0, Relaxed),
STAG_SURV_WFAIL.swap(0, Relaxed),
STAG_SURV_ACC.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub fn take_long_tag_residual() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
LTAG_SURV_FAIL.swap(0, Relaxed),
LTAG_SURV_WFAIL.swap(0, Relaxed),
LTAG_SURV_ACC.swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub static TAGARR_READS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static PACKED_TAG_READS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_tag_reads() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(
TAGARR_READS.swap(0, Relaxed),
PACKED_TAG_READS.swap(0, Relaxed),
)
}
static FAST_PACK_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_fast_pack_arm(on: bool) {
FAST_PACK_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn fast_pack_enabled() -> bool {
!matches!(FAST_PACK_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
#[cfg(feature = "profile")]
pub static FF_LAZY_FIRES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static FF_LATCH: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static FF_CAND4: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub static FF_ACCEPT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "profile")]
pub fn take_ff_waste() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(FF_CAND4.swap(0, Relaxed), FF_ACCEPT.swap(0, Relaxed))
}
static FAST_HASH_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
pub fn set_fast_hash_arm(on: bool) {
FAST_HASH_ARM.store(
if on { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
}
fn fast_hash_wide_enabled() -> bool {
!matches!(FAST_HASH_ARM.load(core::sync::atomic::Ordering::Relaxed), 1)
}
#[cfg(test)]
mod tests {
#[test]
fn push_literals_matches_extend_from_slice() {
let src: Vec<u8> = (0..512u32).map(|i| (i % 251) as u8).collect();
for from in [0usize, 1, 7, 100, 495, 500, 511] {
for n in 0usize..=40 {
if from + n > src.len() {
continue;
}
for spare in [0usize, 1, 15, 16, 31, 32, 1024] {
for w in [0usize, super::LIT_PUSH_WIDTH, super::LIT_PUSH_WIDTH_WIDE] {
let mut fast = Vec::with_capacity(4 + spare);
fast.extend_from_slice(b"HEAD");
let mut want = fast.clone();
want.extend_from_slice(&src[from..from + n]);
super::push_literals(&mut fast, &src, from, from + n, w);
assert_eq!(fast, want, "from={from} n={n} spare={spare} w={w}");
}
}
}
}
}
use super::*;
use crate::{decompress, frame_block_census};
fn rt(src: &[u8], level: i32) {
let zst = compress(src, level).expect("compress");
let got = decompress(&zst).unwrap_or_else(|e| {
panic!(
"decompress our frame L{level} src={} zst={}: {e:?}",
src.len(),
zst.len()
)
});
assert_eq!(got.len(), src.len(), "len level={level}");
if got != src {
let pos = got
.iter()
.zip(src.iter())
.position(|(a, b)| a != b)
.unwrap_or(got.len());
panic!(
"mismatch L{level} at {pos}/{} got={:02x} want={:02x} zst={}",
src.len(),
got.get(pos).copied().unwrap_or(0),
src.get(pos).copied().unwrap_or(0),
zst.len()
);
}
}
#[test]
fn silesia_mr_prefix_finder_recon() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("corpora/data/silesia/mr");
if !path.is_file() {
return;
}
let mut src = std::fs::read(&path).expect("read mr");
src.truncate(277_521);
let params = crate::compression_params(1, Some(src.len() as u64)).expect("params");
let mut tables = MatchTables::new(params);
let window = 1usize << params.window_log.min(31);
let mut off = 0usize;
let mut recon = Vec::new();
let mut oracle_reps = [1u32, 4, 8];
while off < src.len() {
let end = (off + crate::BLOCKSIZE_MAX as usize).min(src.len());
let (seqs, lits) = find_fast(&src, off, end, window, params, &mut tables, oracle_reps);
for sq in &seqs {
let ov = crate::compressed::offset_value_for(sq.offset, sq.litlen, &oracle_reps);
let _ = crate::compressed::resolve_offset(ov, sq.litlen, &mut oracle_reps);
}
let mut lit_at = 0usize;
for s in &seqs {
let n = s.litlen as usize;
recon.extend_from_slice(&lits[lit_at..lit_at + n]);
lit_at += n;
let start = recon
.len()
.checked_sub(s.offset as usize)
.unwrap_or_else(|| {
panic!(
"offset {} > recon {} off={off} ml={} ll={}",
s.offset,
recon.len(),
s.matchlen,
s.litlen
)
});
for k in 0..s.matchlen as usize {
recon.push(recon[start + k]);
}
}
recon.extend_from_slice(&lits[lit_at..]);
off = end;
}
assert_eq!(recon.as_slice(), src.as_slice(), "finder recon vs src");
}
#[test]
fn silesia_mr_prefix_entropy_oracle() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("corpora/data/silesia/mr");
if !path.is_file() {
return;
}
let mut src = std::fs::read(&path).expect("read mr");
src.truncate(277_521);
let params = crate::compression_params(1, Some(src.len() as u64)).expect("params");
let mut tables = MatchTables::new(params);
tables.enable_packed_tags(
params.strategy == Strategy::Fast && tag_alloc_enabled() && fast_pack_enabled(),
src.len(),
);
let window = 1usize << params.window_log.min(31);
let zst = compress_with(
&src,
CompressOptions {
level: 1,
checksum: false,
},
)
.expect("nocheck");
use crate::block::{parse_block_header, BlockType};
use crate::compressed::BlockState;
use crate::frame::parse_kind;
use crate::reader::Reader;
let mut r = Reader::new(&zst);
parse_kind(&mut r).expect("frame");
let mut state = BlockState::new();
let mut off = 0usize;
let mut block_i = 0u32;
let mut decoded = Vec::new();
let mut oracle_reps = [1u32, 4, 8];
loop {
let bh = parse_block_header(&mut r).expect("bh");
let payload = r.take(bh.payload_len() as usize).expect("payload");
let end = (off + crate::BLOCKSIZE_MAX as usize).min(src.len());
let (seqs, lits) = if bh.ty == BlockType::Rle {
(Vec::new(), Vec::new())
} else {
find_fast(&src, off, end, window, params, &mut tables, oracle_reps)
};
for sq in &seqs {
let ov = crate::compressed::offset_value_for(sq.offset, sq.litlen, &oracle_reps);
let _ = crate::compressed::resolve_offset(ov, sq.litlen, &mut oracle_reps);
}
match bh.ty {
BlockType::Compressed => {
let mut lr = Reader::new(payload);
let got_lits =
crate::compressed::decode_literals(Vec::new(), &mut lr, &mut state)
.unwrap_or_else(|e| panic!("block {block_i} literals: {e:?}"));
let lit_pos = got_lits
.iter()
.zip(lits.iter())
.position(|(a, b)| a != b)
.unwrap_or(got_lits.len().min(lits.len()));
assert_eq!(
got_lits.as_slice(),
lits.as_slice(),
"block {block_i} Huffman lits mismatch at {lit_pos}/enc={} dec={} nseq={}",
lits.len(),
got_lits.len(),
seqs.len()
);
let seq_bytes = lr.take(lr.remaining()).expect("seq bytes");
let (nseq_d, modes, got_codes) =
crate::compressed::debug_seq_codes(seq_bytes, &state)
.unwrap_or_else(|e| panic!("block {block_i} seq codes: {e:?}"));
let mut reps = state.reps;
let mut want_codes = Vec::new();
for s in &seqs {
let ov = offset_value_for(s.offset, s.litlen, &reps);
let _ = resolve_offset(ov, s.litlen, &mut reps).expect("ov");
let (llc, _, _) = ll_code(s.litlen, true);
let (mlc, _, _) = ml_code(s.matchlen, true);
let (ofc, _) = of_code(ov);
want_codes.push((s.litlen, s.matchlen, ov, llc, mlc, ofc));
}
if got_codes != want_codes {
let i = got_codes
.iter()
.zip(want_codes.iter())
.position(|(a, b)| a != b)
.unwrap_or(got_codes.len().min(want_codes.len()));
panic!(
"block {block_i} seq codes mismatch at {i}/enc={} dec={} nseq_d={nseq_d} modes={modes:#04x}\n got={:?}\n want={:?}\n last_got={:?}\n last_want={:?}",
want_codes.len(),
got_codes.len(),
got_codes.get(i),
want_codes.get(i),
got_codes.last(),
want_codes.last()
);
}
crate::compressed::decode_sequences(
seq_bytes,
&got_lits,
&mut decoded,
1u64 << params.window_log.min(31),
crate::BLOCKSIZE_MAX,
&mut state,
&[],
0,
0,
)
.unwrap_or_else(|e| panic!("block {block_i} seqs: {e:?}"));
let got = &decoded[off..decoded.len().min(end)];
let want = &src[off..end];
if got != want {
let pos = got
.iter()
.zip(want.iter())
.position(|(a, b)| a != b)
.unwrap_or(got.len().min(want.len()));
panic!(
"block {block_i} FSE/exec mismatch at {pos}/{} nseq={} last={:?} trail={}",
end - off,
seqs.len(),
seqs.last(),
lits.len() as u32 - seqs.iter().map(|s| s.litlen).sum::<u32>()
);
}
}
BlockType::Raw => {
assert_eq!(payload, &src[off..end], "block {block_i} raw");
decoded.extend_from_slice(payload);
}
BlockType::Rle => {
decoded.resize(decoded.len() + (end - off), payload[0]);
}
}
off = end;
block_i += 1;
if bh.last {
break;
}
}
assert_eq!(off, src.len());
}
#[test]
fn silesia_mr_prefix_roundtrip() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("corpora/data/silesia/mr");
if !path.is_file() {
return;
}
let mut src = std::fs::read(&path).expect("read mr");
src.truncate(277_521);
let z_off = compress_with(
&src,
CompressOptions {
level: 1,
checksum: false,
},
)
.expect("nocheck");
let got = crate::decompress(&z_off).expect("decompress");
if got.as_slice() != src.as_slice() {
let pos = got
.iter()
.zip(src.iter())
.position(|(a, b)| a != b)
.unwrap_or(got.len().min(src.len()));
panic!(
"first mismatch at {pos}/{} got_len={} zst={}",
src.len(),
got.len(),
z_off.len()
);
}
}
#[test]
fn silesia_all_oneshot_roundtrip_l1() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("corpora/data/silesia");
if !dir.is_dir() {
return;
}
let mut files: Vec<_> = std::fs::read_dir(&dir)
.expect("silesia dir")
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.is_file())
.collect();
files.sort();
assert!(
!files.is_empty(),
"silesia dir exists but has no files: {}",
dir.display()
);
for path in files {
let src =
std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let zst =
compress(&src, 1).unwrap_or_else(|e| panic!("compress {}: {e:?}", path.display()));
let got = crate::decompress(&zst)
.unwrap_or_else(|e| panic!("decompress {}: {e:?}", path.display()));
if got.as_slice() != src.as_slice() {
let pos = got
.iter()
.zip(src.iter())
.position(|(a, b)| a != b)
.unwrap_or(got.len().min(src.len()));
panic!(
"{} mismatch at {pos}/{} zst={}",
path.file_name().unwrap().to_string_lossy(),
src.len(),
zst.len()
);
}
}
}
#[test]
fn higher_level_never_larger_osdb() {
let Ok(src) = std::fs::read("../../corpora/data/silesia/osdb") else {
return; };
let mut prev = usize::MAX;
for lvl in [1, 3, 5, 7, 9, 13, 16, 19] {
let n = crate::compress(&src, lvl).unwrap().len();
assert!(
n <= prev,
"level {lvl} emitted {n} bytes, more than the previous level's {prev}"
);
prev = n;
}
}
#[ignore]
#[test]
fn probe_density_truth_table() {
const FILES: &[&str] = &[
"dickens", "mozilla", "mr", "nci", "ooffice", "osdb", "reymont", "samba", "sao",
"webster", "x-ray", "xml",
];
println!(
"TT {:<9} {:>10} {:>9} {:>9} {:>10} {:>9}",
"file", "probes/B", "hit_rate", "matchfrac", "lit_share", "seqs/B"
);
for f in FILES {
let Ok(src) = std::fs::read(format!("../../corpora/data/silesia/{f}")) else {
continue;
};
crate::prof::reset();
let n = crate::compress(&src, 1).unwrap().len();
let c = crate::prof::encode_counts();
let b = src.len() as f64;
println!(
"TT {f:<9} {:>10.4} {:>9.4} {:>9.4} {:>10.4} {:>9.5} size={n}",
c.hash_probes as f64 / b,
c.probe_hits as f64 / (c.hash_probes.max(1)) as f64,
c.match_bytes as f64 / b,
c.lit_bytes as f64 / b,
c.seqs as f64 / b,
);
}
}
#[cfg(feature = "profile")]
#[test]
fn work_counter_covers_every_strategy() {
let Ok(src) = std::fs::read("../../corpora/data/silesia/xml") else {
return; };
for (lvl, strat) in [
(1, "fast"),
(3, "dfast"),
(5, "greedy"),
(7, "lazy"),
(9, "lazy2"),
(13, "btlazy2"),
(17, "btopt"),
(19, "btultra"),
] {
crate::prof::reset();
let _ = crate::compress(&src, lvl).unwrap();
let c = crate::prof::encode_counts();
println!(
"WORK L{lvl:<2} {strat:<8} probes={:<12} hits={:<10} seqs={:<10}",
c.hash_probes, c.probe_hits, c.seqs
);
assert!(
c.hash_probes > 0,
"L{lvl} ({strat}) reported ZERO probes -- the work counter is \
missing for this strategy, so no gate on it is bankable"
);
assert!(c.seqs > 0, "L{lvl} ({strat}) reported zero sequences");
}
}
#[ignore]
#[test]
fn size_table_silesia() {
const FILES: &[&str] = &[
"dickens", "mozilla", "mr", "nci", "ooffice", "osdb", "reymont", "samba", "sao",
"webster", "x-ray", "xml",
];
for f in FILES {
let Ok(src) = std::fs::read(format!("../../corpora/data/silesia/{f}")) else {
continue;
};
let mut row = format!("{f:<8}");
for lvl in [5, 7, 9, 13, 19] {
let n = crate::compress(&src, lvl).unwrap().len();
row.push_str(&format!(" L{lvl}={n}"));
}
println!("SIZETABLE {row}");
}
}
#[test]
fn census_zeros_all_rle() {
let src = vec![0u8; 128 * 1024 * 2];
let zst = compress(&src, 1).expect("compress");
let c = crate::frame_block_census(&zst).expect("census");
assert_eq!(c.compressed, 0);
assert_eq!(c.raw, 0);
assert_eq!(c.rle, 2);
assert_eq!(c.rle_regen, src.len() as u64);
}
#[test]
fn frame_checksum_matches_oneshot_xxh64() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut text = Vec::new();
while text.len() < 200_000 {
text.extend_from_slice(fox);
}
for src in [&b""[..], b"a", &[0u8; 128 * 1024 + 7][..], text.as_slice()] {
let zst = compress(src, 1).expect("compress");
assert!(zst.len() >= 4);
let got = u32::from_le_bytes(zst[zst.len() - 4..].try_into().unwrap());
assert_eq!(got, content_checksum(src), "len {}", src.len());
}
}
#[test]
fn streaming_xxh64_matches_oneshot_at_every_boundary_phase() {
let mut data = Vec::new();
for i in 0usize..(3 * 1024 + 37) {
data.push((i.wrapping_mul(2654435761) >> 11) as u8);
}
const CHUNKS: &[usize] = &[
1, 7, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1024,
];
for &len in &[
0usize,
1,
31,
32,
33,
255,
256,
257,
511,
512,
1000,
3072,
3 * 1024 + 37,
] {
let src = &data[..len];
let want = content_checksum(src);
for &c in CHUNKS {
let mut h = Xxh64::new();
for part in src.chunks(c) {
h.update(part);
}
assert_eq!(
checksum_u32(&h),
want,
"streaming digest diverged: len {len}, chunk {c}"
);
}
for &split in &[1usize, 31, 32, 33, 255, 256, 257] {
if split >= len {
continue;
}
let mut h = Xxh64::new();
h.update(&src[..split]);
h.update(&src[split..split + 1]);
h.update(&src[split + 1..]);
assert_eq!(
checksum_u32(&h),
want,
"streaming digest diverged: len {len}, split {split}"
);
}
}
}
#[test]
fn roundtrip_small_all_fast_levels() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut text = Vec::new();
while text.len() < 8192 {
text.extend_from_slice(fox);
}
for level in -7i32..=3 {
rt(b"", level);
rt(b"a", level);
rt(b"hello", level);
rt(&[0u8; 16], level);
rt(&[0u8; 256], level);
rt(&[0u8; 4096], level);
rt(&text, level);
rt(&xorshift(0xA5A5_5A5A, 1024), level);
rt(&xorshift(0xA5A5_5A5A, 64 * 1024), level);
}
}
#[test]
fn roundtrip_mid_and_high_levels() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut text = Vec::new();
while text.len() < 8192 {
text.extend_from_slice(fox);
}
let noise = xorshift(0xA5A5_5A5A, 8192);
for level in [4, 5, 6, 8, 9, 13, 16, 19] {
rt(&text, level);
rt(&noise, level);
rt(&[0u8; 1024], level);
}
}
#[test]
fn huffman_literals_emitted_on_text() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut text = Vec::new();
while text.len() < 224 {
text.extend_from_slice(fox);
}
text.truncate(224);
let (sec, upd) = crate::huffman::encode_literals_section(&text, None).unwrap();
assert_eq!(sec[0] & 3, 2, "expected Huffman Compressed literals");
match upd {
crate::huffman::HuffUpdate::New(_) => {}
crate::huffman::HuffUpdate::Unchanged => panic!("expected a new Huffman table"),
}
let zst = compress(&text, 1).unwrap();
assert_eq!(decompress(&zst).unwrap(), text);
}
#[test]
fn roundtrip_greedy_explicit() {
let opts = CompressOptions {
level: 5,
..CompressOptions::default()
};
let src = xorshift(0x1111_2222, 32 * 1024);
let zst = compress_with(&src, opts).unwrap();
assert_eq!(decompress(&zst).unwrap(), src);
}
#[test]
fn zeros_and_text_shrink() {
let zeros = vec![0u8; 4096];
let zst = compress(&zeros, 1).unwrap();
assert!(
zst.len() < zeros.len(),
"zeros L1 {} vs {}",
zst.len(),
zeros.len()
);
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n".repeat(64);
let zst = compress(&fox, 3).unwrap();
assert!(
zst.len() < fox.len(),
"text L3 {} vs {}",
zst.len(),
fox.len()
);
}
#[test]
fn rle_byte_word_matches_byte_all() {
assert_eq!(rle_byte(&[7, 7, 7, 7, 7, 7, 7, 7, 7]), Some(7));
assert_eq!(rle_byte(&[7, 7, 7, 7, 7, 7, 7, 8]), None);
assert_eq!(rle_byte(&[1]), None);
let mut v = vec![0xAAu8; 1024];
assert_eq!(rle_byte(&v), Some(0xAA));
v[1000] = 0xAB;
assert_eq!(rle_byte(&v), None);
}
#[test]
fn empty_frame_has_checksum() {
let zst = compress(b"", 3).unwrap();
assert!(zst.len() >= 13);
assert_eq!(decompress(&zst).unwrap(), b"");
}
#[test]
fn roundtrip_all_strategies() {
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n".repeat(32);
let noise = xorshift(0x3333_4444, 4096);
for id in 1i32..=9 {
let mut params = crate::compression_params(3, Some(fox.len() as u64)).unwrap();
params.apply_zstd_kv("strategy", id).unwrap();
let zeros = [0u8; 2048];
for src in [fox.as_slice(), noise.as_slice(), zeros.as_slice()] {
let zst = compress_with_params(src, params, true).expect("compress");
let got = decompress(&zst)
.unwrap_or_else(|e| panic!("strategy {id} src={}: {e:?}", src.len()));
assert_eq!(got, src, "strategy {id}");
}
}
}
#[test]
fn roundtrip_ultra_levels() {
let src = b"The quick brown fox jumps over the lazy dog. 0123456789.\n".repeat(48);
for level in [20, 21, 22] {
rt(&src, level);
rt(&xorshift(0xABCDu64, 2048), level);
}
}
#[test]
fn rle_fse_large_match() {
let chunk: Vec<u8> = (0..32_768).map(|i| (i % 251) as u8).collect();
let mut src = chunk.clone();
src.extend_from_slice(&chunk);
let zst = compress(&src, 1).expect("compress");
let got = decompress(&zst).unwrap_or_else(|e| panic!("zst={} err={e:?}", zst.len()));
assert_eq!(got, src);
}
#[test]
fn literals_and_sequence_modes_coverage() {
let mut seen_lit = [false; 4];
let mut seen_seq = [false; 4];
let mut seen_4stream = false;
let mut seen_huff_direct = false;
let mut seen_huff_fse = false;
let fox = b"The quick brown fox jumps over the lazy dog. 0123456789.\n";
let mut text_block = Vec::new();
while text_block.len() < 64 * 1024 {
text_block.extend_from_slice(fox);
}
let mut text_two_blocks = text_block.clone();
while text_two_blocks.len() < 130 * 1024 {
text_two_blocks.extend_from_slice(fox);
}
let mut skip = crate::compression_params(1, Some(400)).unwrap();
skip.target_length = 1 << 16;
skip.min_match = 7;
skip.strategy = crate::Strategy::Fast;
let mut huff_src = Vec::new();
while huff_src.len() < 400 {
huff_src.extend_from_slice(fox);
}
huff_src.truncate(400);
let mut huff_two = Vec::new();
while huff_two.len() < 130 * 1024 {
huff_two.extend_from_slice(fox);
}
let mut two_sym = Vec::new();
while two_sym.len() < 400 {
two_sym.extend_from_slice(&[0u8, 0, 0, 1]);
}
two_sym.truncate(400);
let mut rle_lits = fox.repeat(20);
rle_lits.truncate(1024);
for _ in 0..30 {
rle_lits.push(0xA5);
rle_lits.push(0xA5);
rle_lits.extend_from_slice(&fox[..20]);
}
let mut rle_win = crate::compression_params(1, Some(rle_lits.len() as u64)).unwrap();
rle_win.window_log = 10;
let mut frames: Vec<Vec<u8>> = Vec::new();
let mut blocks_log: Vec<(u8, Option<u8>)> = Vec::new();
let zst_rl = compress_with_params(&rle_lits, rle_win, true).expect("rle lits");
assert_eq!(decompress(&zst_rl).unwrap(), rle_lits);
frames.push(zst_rl);
for (src, p) in [
(huff_src.as_slice(), skip),
(huff_two.as_slice(), skip),
(two_sym.as_slice(), skip),
] {
let zst = compress_with_params(src, p, true).expect("huff params");
assert_eq!(decompress(&zst).unwrap(), src);
frames.push(zst);
}
let mut mixed = Vec::new();
let mut n = 0u32;
while mixed.len() < 4096 {
mixed.extend_from_slice(b"block ");
mixed.push(b'0' + (n % 10) as u8);
mixed.extend_from_slice(b" extra words for matches ");
mixed.extend_from_slice(&(n.to_le_bytes()));
mixed.push(b'\n');
n += 1;
}
frames.push({
let z = compress(&mixed, 1).expect("mixed L1");
assert_eq!(decompress(&z).unwrap(), mixed);
z
});
let mut mix_win = crate::compression_params(1, Some(8192)).unwrap();
mix_win.window_log = 12;
let mixed2 = mixed.repeat(2);
frames.push({
let z = compress_with_params(&mixed2, mix_win, true).expect("mixed2");
assert_eq!(decompress(&z).unwrap(), mixed2);
z
});
let mut fox_win = crate::compression_params(3, Some(3000)).unwrap();
fox_win.window_log = 10;
let fox_multi = fox.repeat(60);
let zst_rep = compress_with_params(&fox_multi, fox_win, true).expect("repeat seq");
assert_eq!(decompress(&zst_rep).unwrap(), fox_multi);
frames.push(zst_rep);
let mut small_win = crate::compression_params(1, Some(2048)).unwrap();
small_win.window_log = 10;
let repeated = b"TheQuickBrownFox0123456789ABCD".repeat(80);
let zst_rle = compress_with_params(&repeated, small_win, true).expect("rle seq");
assert_eq!(decompress(&zst_rle).unwrap(), repeated);
frames.push(zst_rle);
let mut rle_prefix = Vec::new();
let mut rle_body = Vec::new();
for i in 0..24u8 {
let pat: Vec<u8> = (0..24u8).map(|j| b'A' + ((i * 7 + j * 3) % 26)).collect();
rle_prefix.extend_from_slice(&pat);
rle_body.push(b'q');
rle_body.extend_from_slice(&pat);
}
let zst_rle_lits =
compress_using_prefix(&rle_body, &rle_prefix, 1).expect("rle lits prefix");
assert_eq!(
crate::decode::decompress_using_prefix(&zst_rle_lits, &rle_prefix).unwrap(),
rle_body
);
frames.push(zst_rle_lits);
let mut stationary = Vec::with_capacity(400 * 1024);
{
let mut st = 0x2545_F491_4F6C_DD1Du64;
while stationary.len() < 400 * 1024 {
st ^= st << 13;
st ^= st >> 7;
st ^= st << 17;
for k in 0..8 {
stationary.push(b'a' + ((st >> (k * 8)) & 0x0F) as u8);
}
}
}
let zst_stat = compress(&stationary, 1).expect("stationary");
assert_eq!(decompress(&zst_stat).unwrap(), stationary);
frames.push(zst_stat);
let mut stationary = Vec::new();
let mut rng = 0x1234_5678_9abc_def0u64;
while stationary.len() < 400 * 1024 {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
let take = 12 + (rng >> 40) as usize % (fox.len() - 12);
stationary.extend_from_slice(&fox[..take]);
}
let samples: Vec<(Vec<u8>, i32)> = vec![
(fox[..20].to_vec(), 1),
(b"TheQuickBrownFox0123456789ABCD".repeat(2), 1),
(fox.repeat(4), 1),
(fox.repeat(8), 3),
(text_block.clone(), 3),
(text_two_blocks, 3),
(stationary, 3),
(xorshift(0xF00Du64, 4096), 1),
(vec![0u8; 8192], 1),
(vec![b'a'; 1024], 5),
(xorshift(0xF00Du64, 8192), 9),
];
for (src, level) in samples {
let zst = compress(&src, level)
.unwrap_or_else(|e| panic!("compress L{level} src={}: {e:?}", src.len()));
let got = decompress(&zst).unwrap_or_else(|e| {
panic!(
"decompress L{level} src={} zst={}: {e:?}",
src.len(),
zst.len()
)
});
assert_eq!(got, src, "L{level} src={}", src.len());
frames.push(zst);
}
for zst in &frames {
for b in inspect_compressed_blocks(zst) {
seen_lit[b.lit as usize] = true;
if let Some(m) = b.seq {
seen_seq[((m >> 6) & 3) as usize] = true;
seen_seq[((m >> 4) & 3) as usize] = true;
seen_seq[((m >> 2) & 3) as usize] = true;
}
if b.four_stream {
seen_4stream = true;
}
match b.huff_tree {
Some(true) => seen_huff_fse = true,
Some(false) => seen_huff_direct = true,
None => {}
}
blocks_log.push((b.lit, b.seq));
}
}
assert!(
seen_lit[0],
"missing Raw literals (type 0); lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_lit[2],
"missing Huffman Compressed literals (type 2); lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_lit[3],
"missing Treeless Huffman literals (type 3); lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_seq[0],
"missing Predefined FSE mode; lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_seq[1],
"missing RLE FSE mode; lit={seen_lit:?} seq={seen_seq:?} blocks={blocks_log:?}"
);
assert!(
seen_seq[2],
"missing Compressed FSE mode; lit={seen_lit:?} seq={seen_seq:?} blocks={blocks_log:?}"
);
assert!(
seen_seq[3],
"missing Repeat FSE mode; lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_4stream,
"missing 4-stream Huffman; lit={seen_lit:?} seq={seen_seq:?}"
);
assert!(
seen_huff_direct,
"missing direct Huffman tree (header>=128); lit={seen_lit:?}"
);
assert!(
seen_huff_fse,
"missing FSE Huffman tree (header<128); lit={seen_lit:?}"
);
}
struct InspectedBlock {
lit: u8,
seq: Option<u8>,
four_stream: bool,
huff_tree: Option<bool>,
}
fn inspect_compressed_blocks(zst: &[u8]) -> Vec<InspectedBlock> {
use crate::block::{parse_block_header, BlockType};
use crate::frame::parse_kind;
use crate::reader::Reader;
let mut r = Reader::new(zst);
parse_kind(&mut r).expect("frame header");
let mut out = Vec::new();
loop {
let bh = parse_block_header(&mut r).expect("block header");
let payload = r.take(bh.payload_len() as usize).expect("payload");
if bh.ty == BlockType::Compressed {
let lit = payload[0] & 3;
let size_fmt = (payload[0] >> 2) & 3;
let four_stream = matches!(lit, 2 | 3) && size_fmt != 0;
let huff_tree = if lit == 2 {
let hlen = match size_fmt {
0 | 1 => 3usize,
2 => 4,
3 => 5,
_ => 0,
};
payload.get(hlen).map(|&b| b < 128)
} else {
None
};
let after = skip_literals_section(payload).expect("literals");
let (nseq, rest) = read_nseq(after);
let mode = if nseq == 0 {
None
} else {
rest.first().copied()
};
out.push(InspectedBlock {
lit,
seq: mode,
four_stream,
huff_tree,
});
}
if bh.last {
break;
}
}
out
}
fn skip_literals_section(payload: &[u8]) -> Option<&[u8]> {
let first = *payload.first()?;
let lit_type = first & 3;
let size_fmt = (first >> 2) & 3;
match lit_type {
0 | 1 => {
let (regen, hdr) = match size_fmt {
0 | 2 => (u32::from(first >> 3), 1usize),
1 => {
let b1 = *payload.get(1)?;
(u32::from(first >> 4) + (u32::from(b1) << 4), 2)
}
3 => {
let b1 = *payload.get(1)?;
let b2 = *payload.get(2)?;
(
u32::from(first >> 4) + (u32::from(b1) << 4) + (u32::from(b2) << 12),
3,
)
}
_ => return None,
};
let body = if lit_type == 1 {
1usize
} else {
regen as usize
};
payload.get(hdr + body..)
}
2 | 3 => {
let (csize, hdr) = match size_fmt {
0 | 1 => {
let b1 = *payload.get(1)?;
let b2 = *payload.get(2)?;
let csize = ((u32::from(b1) >> 6) + (u32::from(b2) << 2)) & 0x3FF;
(csize as usize, 3usize)
}
2 => {
let b2 = *payload.get(2)?;
let b3 = *payload.get(3)?;
let csize = (u32::from(b2) >> 2) + (u32::from(b3) << 6);
((csize as usize) & 0x3FFF, 4)
}
3 => {
let b2 = *payload.get(2)?;
let b3 = *payload.get(3)?;
let b4 = *payload.get(4)?;
let csize =
(u32::from(b2) >> 6) + (u32::from(b3) << 2) + (u32::from(b4) << 10);
((csize as usize) & 0x3FFFF, 5)
}
_ => return None,
};
payload.get(hdr + csize..)
}
_ => None,
}
}
fn read_nseq(src: &[u8]) -> (u32, &[u8]) {
let Some(&b0) = src.first() else {
return (0, src);
};
if b0 == 0 {
(0, &src[1..])
} else if b0 < 128 {
(u32::from(b0), &src[1..])
} else if b0 < 255 {
let b1 = src.get(1).copied().unwrap_or(0);
(
((u32::from(b0) - 128) << 8) + u32::from(b1),
src.get(2..).unwrap_or(&[]),
)
} else {
let b1 = src.get(1).copied().unwrap_or(0);
let b2 = src.get(2).copied().unwrap_or(0);
(
0x7F00 + u32::from(b1) + (u32::from(b2) << 8),
src.get(3..).unwrap_or(&[]),
)
}
}
fn xorshift(seed: u64, n: usize) -> Vec<u8> {
let mut s = seed;
let mut v = vec![0u8; n];
for b in &mut v {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
*b = (s & 0xFF) as u8;
if *b == 0 {
*b = 1;
}
}
v
}
fn count_zstd_blocks(zst: &[u8]) -> usize {
let mut r = crate::reader::Reader::new(zst);
crate::frame::parse_kind(&mut r).expect("frame header");
let mut n = 0usize;
loop {
let h = crate::block::parse_block_header(&mut r).expect("block header");
n += 1;
r.take(h.payload_len() as usize).expect("payload");
if h.last {
break;
}
}
n
}
#[test]
fn long_forces_window_descriptor() {
let src = xorshift(0x5E1A_B1E5, 300 * 1024);
let mut params = compression_params(1, Some(src.len() as u64)).unwrap();
params.window_log = 18;
let zst = compress_with_advanced(
&src,
params,
true,
None,
&[],
true,
AdvancedOptions {
ldm: crate::ldm::LdmParams::enabled(),
..AdvancedOptions::default()
},
)
.expect("long compress");
match crate::get_frame_header(&zst).expect("hdr") {
crate::FrameKind::Zstd(h) => {
assert!(
!h.single_segment,
"300 KiB > 256 KiB window must emit Window_Descriptor"
);
assert_eq!(h.window_size, 1u64 << 18);
}
other => panic!("expected zstd frame, got {other:?}"),
}
assert_eq!(decompress(&zst).expect("decode"), src);
}
#[test]
fn enable_ldm_zstd_keys_roundtrip() {
let src = xorshift(0x1D1D, 32 * 1024);
let mut params = compression_params(1, Some(src.len() as u64)).unwrap();
params
.apply_zstd_option_string("enableLdm=1,ldmHashLog=12,ldmMinMatch=64,ldmHashRateLog=7")
.unwrap();
let ldm = params.ldm_params();
assert!(ldm.enable);
assert_eq!(ldm.hash_log, 12);
assert_eq!(ldm.min_match, 64);
let zst = compress_with_advanced(
&src,
params,
true,
None,
&[],
true,
AdvancedOptions {
ldm,
..AdvancedOptions::default()
},
)
.expect("enableLdm compress");
assert_eq!(decompress(&zst).expect("decode"), src);
}
#[test]
fn rsyncable_splits_blocks() {
let src = xorshift(0xA11, 64 * 1024);
let mut params = compression_params(1, Some(src.len() as u64)).unwrap();
params.window_log = 18;
let plain = compress_with_advanced(
&src,
params,
true,
None,
&[],
true,
AdvancedOptions::default(),
)
.unwrap();
let rsync = compress_with_advanced(
&src,
params,
true,
None,
&[],
true,
AdvancedOptions {
ldm: crate::ldm::LdmParams::enabled(),
rsyncable: true,
target_cblock_size: 0,
..AdvancedOptions::default()
},
)
.unwrap();
let n_plain = count_zstd_blocks(&plain);
let n_rsync = count_zstd_blocks(&rsync);
assert_eq!(decompress(&rsync).unwrap(), src);
assert!(
n_rsync > n_plain,
"rsyncable should cut extra blocks (plain={n_plain} rsync={n_rsync})"
);
}
#[test]
fn target_cblock_caps_uncompressed_blocks() {
let src = xorshift(0xC0B1, 32 * 1024);
let params = compression_params(1, Some(src.len() as u64)).unwrap();
let plain = compress_with_params(&src, params, true).unwrap();
let capped = compress_with_advanced(
&src,
params,
true,
None,
&[],
true,
AdvancedOptions {
target_cblock_size: 256,
..AdvancedOptions::default()
},
)
.unwrap();
let n_plain = count_zstd_blocks(&plain);
let n_capped = count_zstd_blocks(&capped);
assert_eq!(decompress(&capped).unwrap(), src);
assert!(
n_capped > n_plain,
"target cblock 256 => ~1 KiB raw blocks (plain={n_plain} capped={n_capped})"
);
}
#[test]
fn decompress_long_raises_window_cap() {
let src = xorshift(0x5716, 128 * 1024 + 64);
let mut params = compression_params(1, Some(src.len() as u64)).unwrap();
params.window_log = 16;
let zst = compress_with_params(&src, params, true).unwrap();
match crate::get_frame_header(&zst).unwrap() {
crate::FrameKind::Zstd(h) => {
assert!(!h.single_segment);
assert_eq!(h.window_size, 1u64 << 16);
}
other => panic!("{other:?}"),
}
assert_eq!(
crate::decompress_with(
&zst,
crate::DecompressOptions {
window_max: 32 * 1024,
..Default::default()
}
)
.unwrap_err(),
crate::Error::WindowTooLarge
);
assert_eq!(
crate::decompress_with(
&zst,
crate::DecompressOptions {
window_max: 1u64 << 16,
..Default::default()
}
)
.unwrap(),
src
);
}
#[test]
fn fast_sparse_match_fill_roundtrips_repeating_text() {
let src = b"The quick brown fox jumps over the lazy dog. 0123456789.\n".repeat(8000);
for level in [1, -1, -4] {
rt(&src, level);
let zst = compress(&src, level).unwrap();
assert!(
zst.len() < src.len() / 20,
"L{level}: repeating text should stay compact ({} vs {})",
zst.len(),
src.len()
);
}
}
#[test]
fn count_match_words_match_byte_loop() {
fn bytes(src: &[u8], m: usize, ip: usize, limit: usize) -> usize {
let max = (limit - ip).min(src.len() - m).min(src.len() - ip);
let mut n = 0usize;
while n < max && src[m + n] == src[ip + n] {
n += 1;
}
n
}
let mut src = vec![0u8; 4096];
for (i, b) in src.iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let head: Vec<u8> = src[0..200].to_vec();
src[200..400].copy_from_slice(&head);
let mid: Vec<u8> = src[3..20].to_vec();
src[800..800 + 17].copy_from_slice(&mid);
for m in [0usize, 1, 3, 7, 8, 15, 200] {
for ip in [200usize, 201, 400, 800, 801, 2000] {
if m >= src.len() || ip >= src.len() {
continue;
}
for limit in [ip, ip + 1, ip + 7, ip + 8, ip + 9, ip + 64, src.len()] {
let limit = limit.min(src.len());
if ip > limit {
continue;
}
assert_eq!(
count_match(&src, m, ip, limit),
bytes(&src, m, ip, limit),
"m={m} ip={ip} limit={limit}"
);
}
}
}
}
#[test]
fn min_gain_matches_c_fast() {
assert_eq!(
min_gain(128 * 1024, Strategy::Fast),
((128 * 1024) >> 6) + 2
);
assert_eq!(min_gain(100, Strategy::Greedy), (100 >> 6) + 2);
#[allow(clippy::identity_op)]
let bt_ultra_100 = (100 >> 7) + 2;
assert_eq!(min_gain(100, Strategy::BtUltra), bt_ultra_100);
}
#[test]
fn early_raw_skip_fast_rung_low_matches() {
SKIP_OVERRIDE.with(|c| c.set(None));
let fast = compression_params(-1, Some(128 * 1024)).unwrap();
assert!(fast.target_length >= 1);
assert!(fast.target_length <= 7);
let mg = min_gain(128 * 1024, fast.strategy);
assert!(early_raw_skip(mg.saturating_sub(1), 128 * 1024, fast));
assert!(!early_raw_skip(mg + 10, 128 * 1024, fast));
let l1 = compression_params(1, Some(128 * 1024)).unwrap();
assert_eq!(l1.target_length, 0);
assert!(!early_raw_skip(0, 128 * 1024, l1));
let l3 = compression_params(3, Some(128 * 1024)).unwrap();
assert!(l3.strategy != Strategy::Fast);
assert!(!early_raw_skip(0, 128 * 1024, l3));
}
#[test]
fn skip_off_l1_bytes_match_unset() {
let src = xorshift(0xBEEF, 32 * 1024);
SKIP_OVERRIDE.with(|c| c.set(Some(false)));
let off = compress(&src, 1).expect("off");
SKIP_OVERRIDE.with(|c| c.set(None));
let unset = compress(&src, 1).expect("unset");
assert_eq!(off, unset, "knob-off at -1 must match default (tlen=0)");
assert_eq!(decompress(&off).unwrap(), src);
}
#[test]
fn skip_off_fast_roundtrip_and_on_skips_noise() {
let src = xorshift(0xA11E, 64 * 1024);
SKIP_OVERRIDE.with(|c| c.set(Some(false)));
let off = compress(&src, -1).expect("off");
SKIP_OVERRIDE.with(|c| c.set(Some(true)));
let on = compress(&src, -1).expect("on");
SKIP_OVERRIDE.with(|c| c.set(None));
assert_eq!(decompress(&off).unwrap(), src);
assert_eq!(decompress(&on).unwrap(), src);
let l1 = compress(&src, 1).expect("l1");
assert_eq!(decompress(&l1).unwrap(), src);
let off_c = frame_block_census(&off).unwrap();
let on_c = frame_block_census(&on).unwrap();
assert!(
on_c.raw >= off_c.raw,
"skip-on should dump at least as many raw blocks"
);
}
}