use crate::common::bits::ZSTD_NbCommonBytes;
use crate::common::mem::{MEM_64bits, MEM_readLE32, MEM_readLE64};
pub const PRIME_3BYTES: u32 = 506_832_829;
pub const PRIME_4BYTES: u32 = 2_654_435_761;
pub const PRIME_5BYTES: u64 = 889_523_592_379;
pub const PRIME_6BYTES: u64 = 227_718_039_650_203;
pub const PRIME_7BYTES: u64 = 58_295_818_150_454_627;
pub const PRIME_8BYTES: u64 = 0xCF1B_BCDC_B7A5_6463;
pub const ZSTD_ROLL_HASH_CHAR_OFFSET: u64 = 10;
#[inline]
pub fn ZSTD_ipow(mut base: u64, mut exponent: u64) -> u64 {
let mut power: u64 = 1;
while exponent != 0 {
if exponent & 1 != 0 {
power = power.wrapping_mul(base);
}
exponent >>= 1;
base = base.wrapping_mul(base);
}
power
}
#[inline]
pub fn ZSTD_rollingHash_append(mut hash: u64, buf: &[u8]) -> u64 {
for &b in buf {
hash = hash.wrapping_mul(PRIME_8BYTES);
hash = hash.wrapping_add(b as u64 + ZSTD_ROLL_HASH_CHAR_OFFSET);
}
hash
}
#[inline]
pub fn ZSTD_rollingHash_compute(buf: &[u8]) -> u64 {
ZSTD_rollingHash_append(0, buf)
}
#[inline]
pub fn ZSTD_rollingHash_primePower(length: u32) -> u64 {
debug_assert!(length > 0);
ZSTD_ipow(PRIME_8BYTES, length.wrapping_sub(1) as u64)
}
#[inline]
pub fn ZSTD_rollingHash_rotate(hash: u64, toRemove: u8, toAdd: u8, primePower: u64) -> u64 {
let removed = (toRemove as u64 + ZSTD_ROLL_HASH_CHAR_OFFSET).wrapping_mul(primePower);
let after_remove = hash.wrapping_sub(removed);
let after_mult = after_remove.wrapping_mul(PRIME_8BYTES);
after_mult.wrapping_add(toAdd as u64 + ZSTD_ROLL_HASH_CHAR_OFFSET)
}
#[inline]
pub fn ZSTD_hash3(u: u32, h: u32, s: u32) -> u32 {
debug_assert!(h <= 32);
(((u << 8).wrapping_mul(PRIME_3BYTES)) ^ s) >> (32 - h)
}
#[inline]
pub fn ZSTD_hash3Ptr(ptr: &[u8], h: u32) -> usize {
ZSTD_hash3(MEM_readLE32(ptr), h, 0) as usize
}
#[inline]
pub fn ZSTD_hash3PtrS(ptr: &[u8], h: u32, s: u32) -> usize {
ZSTD_hash3(MEM_readLE32(ptr), h, s) as usize
}
#[inline]
pub fn ZSTD_hash4(u: u32, h: u32, s: u32) -> u32 {
debug_assert!(h <= 32);
(u.wrapping_mul(PRIME_4BYTES) ^ s) >> (32 - h)
}
#[inline]
pub fn ZSTD_hash4Ptr(ptr: &[u8], h: u32) -> usize {
ZSTD_hash4(MEM_readLE32(ptr), h, 0) as usize
}
#[inline]
pub fn ZSTD_hash4PtrS(ptr: &[u8], h: u32, s: u32) -> usize {
ZSTD_hash4(MEM_readLE32(ptr), h, s) as usize
}
#[inline]
pub fn ZSTD_hash5(u: u64, h: u32, s: u64) -> usize {
debug_assert!(h <= 64);
((((u << 24).wrapping_mul(PRIME_5BYTES)) ^ s) >> (64 - h)) as usize
}
#[inline]
pub fn ZSTD_hash5Ptr(p: &[u8], h: u32) -> usize {
ZSTD_hash5(MEM_readLE64(p), h, 0)
}
#[inline]
pub fn ZSTD_hash5PtrS(p: &[u8], h: u32, s: u64) -> usize {
ZSTD_hash5(MEM_readLE64(p), h, s)
}
#[inline]
pub fn ZSTD_hash6(u: u64, h: u32, s: u64) -> usize {
debug_assert!(h <= 64);
((((u << 16).wrapping_mul(PRIME_6BYTES)) ^ s) >> (64 - h)) as usize
}
#[inline]
pub fn ZSTD_hash6Ptr(p: &[u8], h: u32) -> usize {
ZSTD_hash6(MEM_readLE64(p), h, 0)
}
#[inline]
pub fn ZSTD_hash6PtrS(p: &[u8], h: u32, s: u64) -> usize {
ZSTD_hash6(MEM_readLE64(p), h, s)
}
#[inline]
pub fn ZSTD_hash7(u: u64, h: u32, s: u64) -> usize {
debug_assert!(h <= 64);
((((u << 8).wrapping_mul(PRIME_7BYTES)) ^ s) >> (64 - h)) as usize
}
#[inline]
pub fn ZSTD_hash7Ptr(p: &[u8], h: u32) -> usize {
ZSTD_hash7(MEM_readLE64(p), h, 0)
}
#[inline]
pub fn ZSTD_hash7PtrS(p: &[u8], h: u32, s: u64) -> usize {
ZSTD_hash7(MEM_readLE64(p), h, s)
}
#[inline]
pub fn ZSTD_hash8(u: u64, h: u32, s: u64) -> usize {
debug_assert!(h <= 64);
((u.wrapping_mul(PRIME_8BYTES) ^ s) >> (64 - h)) as usize
}
#[inline]
pub fn ZSTD_hash8Ptr(p: &[u8], h: u32) -> usize {
ZSTD_hash8(MEM_readLE64(p), h, 0)
}
#[inline]
pub fn ZSTD_hash8PtrS(p: &[u8], h: u32, s: u64) -> usize {
ZSTD_hash8(MEM_readLE64(p), h, s)
}
#[inline(always)]
pub fn ZSTD_hashPtr(p: &[u8], hBits: u32, mls: u32) -> usize {
debug_assert!(hBits <= 32);
match mls {
5 => ZSTD_hash5Ptr(p, hBits),
6 => ZSTD_hash6Ptr(p, hBits),
7 => ZSTD_hash7Ptr(p, hBits),
8 => ZSTD_hash8Ptr(p, hBits),
_ => ZSTD_hash4Ptr(p, hBits), }
}
#[inline]
pub fn ZSTD_hashPtrSalted(p: &[u8], hBits: u32, mls: u32, hashSalt: u64) -> usize {
debug_assert!(hBits <= 32);
match mls {
5 => ZSTD_hash5PtrS(p, hBits, hashSalt),
6 => ZSTD_hash6PtrS(p, hBits, hashSalt),
7 => ZSTD_hash7PtrS(p, hBits, hashSalt),
8 => ZSTD_hash8PtrS(p, hBits, hashSalt),
_ => ZSTD_hash4PtrS(p, hBits, hashSalt as u32),
}
}
pub fn ZSTD_count_2segments(
input_buf: &[u8],
ip_pos: usize,
iend_pos: usize,
istart_pos: usize,
dict_buf: &[u8],
match_pos: usize,
mend_pos: usize,
) -> usize {
if core::ptr::eq(input_buf.as_ptr(), dict_buf.as_ptr()) && mend_pos == iend_pos {
return ZSTD_count(input_buf, ip_pos, match_pos, iend_pos);
}
let dict_remaining = mend_pos - match_pos;
let vend_pos = (ip_pos + dict_remaining).min(iend_pos);
let matchLength = ZSTD_count_2segments_first_segment(
input_buf, ip_pos, vend_pos, dict_buf, match_pos, mend_pos,
);
if match_pos + matchLength != mend_pos {
return matchLength;
}
if istart_pos >= input_buf.len() || ip_pos + matchLength >= iend_pos {
return matchLength;
}
matchLength + ZSTD_count(input_buf, ip_pos + matchLength, istart_pos, iend_pos)
}
#[inline(always)]
pub unsafe fn ZSTD_count_2segments_unchecked(
input_buf: &[u8],
ip_pos: usize,
iend_pos: usize,
istart_pos: usize,
dict_buf: &[u8],
match_pos: usize,
mend_pos: usize,
) -> usize {
debug_assert!(ip_pos <= iend_pos);
debug_assert!(iend_pos <= input_buf.len());
debug_assert!(istart_pos < input_buf.len() || ip_pos >= iend_pos);
debug_assert!(match_pos <= mend_pos);
debug_assert!(mend_pos <= dict_buf.len());
let dict_remaining = mend_pos - match_pos;
let vend_pos = (ip_pos + dict_remaining).min(iend_pos);
let matchLength = unsafe {
ZSTD_count_2segments_first_segment_unchecked(
input_buf, ip_pos, vend_pos, dict_buf, match_pos,
)
};
if match_pos + matchLength != mend_pos {
return matchLength;
}
if istart_pos >= input_buf.len() || ip_pos + matchLength >= iend_pos {
return matchLength;
}
matchLength
+ unsafe { ZSTD_count_unchecked(input_buf, ip_pos + matchLength, istart_pos, iend_pos) }
}
#[inline(always)]
fn ZSTD_count_2segments_first_segment(
input_buf: &[u8],
ip_pos: usize,
vend_pos: usize,
dict_buf: &[u8],
match_pos: usize,
mend_pos: usize,
) -> usize {
let word = core::mem::size_of::<usize>();
let start = ip_pos;
let mut pIn = ip_pos;
let mut pMatch = match_pos;
let in_limit = vend_pos.min(input_buf.len());
let match_limit = mend_pos.min(dict_buf.len());
let inLoopLimit = in_limit.saturating_sub(word - 1);
let input_base = input_buf.as_ptr();
let dict_base = dict_buf.as_ptr();
if pIn < inLoopLimit && pIn + word <= input_buf.len() && pMatch + word <= match_limit {
let diff = unsafe {
(dict_base.add(pMatch) as *const usize).read_unaligned()
^ (input_base.add(pIn) as *const usize).read_unaligned()
};
if diff != 0 {
return ZSTD_NbCommonBytes(diff) as usize;
}
pIn += word;
pMatch += word;
while pIn < inLoopLimit && pIn + word <= input_buf.len() && pMatch + word <= match_limit {
let diff = unsafe {
(dict_base.add(pMatch) as *const usize).read_unaligned()
^ (input_base.add(pIn) as *const usize).read_unaligned()
};
if diff == 0 {
pIn += word;
pMatch += word;
continue;
}
pIn += ZSTD_NbCommonBytes(diff) as usize;
return pIn - start;
}
}
unsafe {
if MEM_64bits() != 0
&& pIn + 3 < in_limit
&& pIn + 4 <= input_buf.len()
&& pMatch + 4 <= match_limit
&& (dict_base.add(pMatch) as *const u32).read_unaligned()
== (input_base.add(pIn) as *const u32).read_unaligned()
{
pIn += 4;
pMatch += 4;
}
if pIn + 1 < in_limit
&& pIn + 2 <= input_buf.len()
&& pMatch + 2 <= match_limit
&& (dict_base.add(pMatch) as *const u16).read_unaligned()
== (input_base.add(pIn) as *const u16).read_unaligned()
{
pIn += 2;
pMatch += 2;
}
if pIn < in_limit
&& pIn < input_buf.len()
&& pMatch < match_limit
&& *dict_base.add(pMatch) == *input_base.add(pIn)
{
pIn += 1;
}
}
pIn - start
}
#[inline(always)]
unsafe fn ZSTD_count_2segments_first_segment_unchecked(
input_buf: &[u8],
ip_pos: usize,
vend_pos: usize,
dict_buf: &[u8],
match_pos: usize,
) -> usize {
let word = core::mem::size_of::<usize>();
let start = ip_pos;
let mut pIn = ip_pos;
let mut pMatch = match_pos;
let inLoopLimit = vend_pos.saturating_sub(word - 1);
let input_base = input_buf.as_ptr();
let dict_base = dict_buf.as_ptr();
if pIn < inLoopLimit {
loop {
let diff = unsafe {
(dict_base.add(pMatch) as *const usize).read_unaligned()
^ (input_base.add(pIn) as *const usize).read_unaligned()
};
if diff != 0 {
pIn += ZSTD_NbCommonBytes(diff) as usize;
return pIn - start;
}
pIn += word;
pMatch += word;
if pIn >= inLoopLimit {
break;
}
}
}
unsafe {
if MEM_64bits() != 0
&& pIn + 3 < vend_pos
&& (dict_base.add(pMatch) as *const u32).read_unaligned()
== (input_base.add(pIn) as *const u32).read_unaligned()
{
pIn += 4;
pMatch += 4;
}
if pIn + 1 < vend_pos
&& (dict_base.add(pMatch) as *const u16).read_unaligned()
== (input_base.add(pIn) as *const u16).read_unaligned()
{
pIn += 2;
pMatch += 2;
}
if pIn < vend_pos && *dict_base.add(pMatch) == *input_base.add(pIn) {
pIn += 1;
}
}
pIn - start
}
#[inline(always)]
pub fn ZSTD_count(buf: &[u8], in_pos: usize, match_pos: usize, in_limit: usize) -> usize {
let word = core::mem::size_of::<usize>();
let start = in_pos;
let mut pIn = in_pos;
let mut pMatch = match_pos;
let in_limit = in_limit.min(buf.len());
debug_assert!(in_limit >= word - 1);
let inLoopLimit = in_limit.saturating_sub(word - 1);
let base = buf.as_ptr();
if pIn < inLoopLimit && pIn + word <= buf.len() && pMatch + word <= buf.len() {
let diff = unsafe {
(base.add(pMatch) as *const usize).read_unaligned()
^ (base.add(pIn) as *const usize).read_unaligned()
};
if diff != 0 {
return ZSTD_NbCommonBytes(diff) as usize;
}
pIn += word;
pMatch += word;
while pIn < inLoopLimit && pIn + word <= buf.len() && pMatch + word <= buf.len() {
let diff = unsafe {
(base.add(pMatch) as *const usize).read_unaligned()
^ (base.add(pIn) as *const usize).read_unaligned()
};
if diff == 0 {
pIn += word;
pMatch += word;
continue;
}
pIn += ZSTD_NbCommonBytes(diff) as usize;
return pIn - start;
}
}
unsafe {
if MEM_64bits() != 0
&& pIn + 3 < in_limit
&& pIn + 4 <= buf.len()
&& pMatch + 4 <= buf.len()
&& (base.add(pMatch) as *const u32).read_unaligned()
== (base.add(pIn) as *const u32).read_unaligned()
{
pIn += 4;
pMatch += 4;
}
if pIn + 1 < in_limit
&& pIn + 2 <= buf.len()
&& pMatch + 2 <= buf.len()
&& (base.add(pMatch) as *const u16).read_unaligned()
== (base.add(pIn) as *const u16).read_unaligned()
{
pIn += 2;
pMatch += 2;
}
if pIn < in_limit
&& pIn < buf.len()
&& pMatch < buf.len()
&& *base.add(pMatch) == *base.add(pIn)
{
pIn += 1;
}
}
pIn - start
}
#[inline(always)]
pub unsafe fn ZSTD_count_unchecked(
buf: &[u8],
in_pos: usize,
match_pos: usize,
in_limit: usize,
) -> usize {
let word = core::mem::size_of::<usize>();
let start = in_pos;
let mut pIn = in_pos;
let mut pMatch = match_pos;
let in_limit = in_limit.min(buf.len());
let inLoopLimit = in_limit.saturating_sub(word - 1);
let base = buf.as_ptr();
if pIn < inLoopLimit {
loop {
let diff = unsafe {
(base.add(pMatch) as *const usize).read_unaligned()
^ (base.add(pIn) as *const usize).read_unaligned()
};
if diff != 0 {
pIn += ZSTD_NbCommonBytes(diff) as usize;
return pIn - start;
}
pIn += word;
pMatch += word;
if pIn >= inLoopLimit {
break;
}
}
}
unsafe {
if MEM_64bits() != 0
&& pIn + 3 < in_limit
&& (base.add(pMatch) as *const u32).read_unaligned()
== (base.add(pIn) as *const u32).read_unaligned()
{
pIn += 4;
pMatch += 4;
}
if pIn + 1 < in_limit
&& (base.add(pMatch) as *const u16).read_unaligned()
== (base.add(pIn) as *const u16).read_unaligned()
{
pIn += 2;
pMatch += 2;
}
if pIn < in_limit && *base.add(pMatch) == *base.add(pIn) {
pIn += 1;
}
}
pIn - start
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rollingHash_rotate_equals_recompute_on_slide() {
let buf = b"rolling123";
let window: u32 = 4;
let primePower = ZSTD_rollingHash_primePower(window);
let mut hash = ZSTD_rollingHash_compute(&buf[..window as usize]);
for i in 0..(buf.len() - window as usize) {
hash = ZSTD_rollingHash_rotate(
hash,
buf[i], buf[i + window as usize], primePower,
);
let fresh = ZSTD_rollingHash_compute(&buf[i + 1..i + 1 + window as usize]);
assert_eq!(hash, fresh, "slide {i}");
}
}
#[test]
fn count_2segments_stops_before_dict_end_on_mismatch() {
let input = b"HELLOworld";
let dict = b"HELxO";
let n = ZSTD_count_2segments(input, 0, input.len(), 0, dict, 0, dict.len());
assert_eq!(n, 3);
}
#[test]
fn count_2segments_wraps_into_prefix_exactly() {
let input = b"defabcdefQ";
let dict = b"abc";
let n = ZSTD_count_2segments(input, 3, input.len(), 0, dict, 0, dict.len());
assert_eq!(n, 6);
}
#[test]
fn count_2segments_unchecked_matches_safe_crossing_case() {
let input = b"defabcdefQ";
let dict = b"abc";
let safe = ZSTD_count_2segments(input, 3, input.len(), 0, dict, 0, dict.len());
let fast = unsafe {
ZSTD_count_2segments_unchecked(input, 3, input.len(), 0, dict, 0, dict.len())
};
assert_eq!(fast, safe);
}
#[test]
fn count_2segments_treats_mend_as_hard_dict_boundary() {
let input = b"defabcdefQ";
let dict = b"abcXXX";
let n = ZSTD_count_2segments(input, 3, input.len(), 0, dict, 0, 3);
assert_eq!(n, 6);
}
#[test]
fn hashptr_bit_range_respected() {
let buf = [0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04];
for mls in [4u32, 5, 6, 7, 8] {
for hBits in [6u32, 12, 20, 32] {
let h = ZSTD_hashPtr(&buf, hBits, mls);
assert!(
h < (1usize << hBits.min(62)),
"mls={mls} hBits={hBits} h={h:#x}"
);
}
}
}
#[test]
fn hash4_anchored_to_upstream_formula() {
assert_eq!(ZSTD_hash4(1, 8, 0), 158);
assert_eq!(ZSTD_hash4(0, 32, 0), 0);
}
#[test]
fn hash_family_matches_upstream_formula_vectors() {
let buf = [1u8, 2, 3, 4, 5, 6, 7, 8];
let u32v = MEM_readLE32(&buf);
let u64v = MEM_readLE64(&buf);
assert_eq!(ZSTD_hash3(u32v, 16, 0), 48161);
assert_eq!(ZSTD_hash4(u32v, 16, 0), 15933);
assert_eq!(ZSTD_hash5(u64v, 16, 0), 22295);
assert_eq!(ZSTD_hash6(u64v, 16, 0), 4145);
assert_eq!(ZSTD_hash7(u64v, 16, 0), 951);
assert_eq!(ZSTD_hash8(u64v, 16, 0), 64852);
assert_eq!(ZSTD_hash4(u32v, 16, 0xDEAD_BEEF), 57488);
assert_eq!(ZSTD_hash8(u64v, 16, 0xDEAD_BEEF_CAFE_BABE), 9209);
}
#[test]
fn hashPtrSalted_dispatches_to_matching_saltedPtr_helper() {
let buf = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
let hBits = 12u32;
let salt = 0xCAFE_BABE_DEAD_BEEFu64;
assert_eq!(
ZSTD_hashPtrSalted(&buf, hBits, 4, salt),
ZSTD_hash4PtrS(&buf, hBits, salt as u32)
);
assert_eq!(
ZSTD_hashPtrSalted(&buf, hBits, 5, salt),
ZSTD_hash5PtrS(&buf, hBits, salt)
);
assert_eq!(
ZSTD_hashPtrSalted(&buf, hBits, 6, salt),
ZSTD_hash6PtrS(&buf, hBits, salt)
);
assert_eq!(
ZSTD_hashPtrSalted(&buf, hBits, 7, salt),
ZSTD_hash7PtrS(&buf, hBits, salt)
);
assert_eq!(
ZSTD_hashPtrSalted(&buf, hBits, 8, salt),
ZSTD_hash8PtrS(&buf, hBits, salt)
);
}
#[test]
fn hashPtr_dispatches_to_matching_mls_specific_helper() {
let buf = [0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let hBits = 16u32;
assert_eq!(ZSTD_hashPtr(&buf, hBits, 4), ZSTD_hash4Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 5), ZSTD_hash5Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 6), ZSTD_hash6Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 7), ZSTD_hash7Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 8), ZSTD_hash8Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 3), ZSTD_hash4Ptr(&buf, hBits));
assert_eq!(ZSTD_hashPtr(&buf, hBits, 99), ZSTD_hash4Ptr(&buf, hBits));
}
#[test]
fn hash_family_3_to_8_stay_within_hbits_range() {
let buf = [0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89];
let bits: u32 = 12;
let max = 1usize << bits;
assert!((ZSTD_hash3(MEM_readLE32(&buf), bits, 0) as usize) < max);
assert!((ZSTD_hash4(MEM_readLE32(&buf), bits, 0) as usize) < max);
assert!(ZSTD_hash5(MEM_readLE64(&buf), bits, 0) < max);
assert!(ZSTD_hash6(MEM_readLE64(&buf), bits, 0) < max);
assert!(ZSTD_hash7(MEM_readLE64(&buf), bits, 0) < max);
assert!(ZSTD_hash8(MEM_readLE64(&buf), bits, 0) < max);
let buf2 = [0x00u8; 8];
assert_ne!(ZSTD_hash4Ptr(&buf, bits), ZSTD_hash4Ptr(&buf2, bits));
assert_ne!(ZSTD_hash8Ptr(&buf, bits), ZSTD_hash8Ptr(&buf2, bits));
}
#[test]
fn hashptr_different_salts_diverge() {
let buf = [1u8; 8];
let a = ZSTD_hashPtrSalted(&buf, 16, 5, 0);
let b = ZSTD_hashPtrSalted(&buf, 16, 5, 0xDEAD_BEEF_0000_0000);
assert_ne!(a, b);
}
#[test]
fn count_identical_region() {
let mut buf = vec![0u8; 64];
for (i, b) in buf.iter_mut().enumerate() {
*b = b'a' + (i % 8) as u8;
}
let n = ZSTD_count(&buf, 8, 0, buf.len());
assert_eq!(n, buf.len() - 8);
}
#[test]
fn count_first_mismatch() {
let mut buf = vec![b'a'; 32];
buf[3] = b'c';
buf[11] = b'b';
let n = ZSTD_count(&buf, 8, 0, buf.len());
assert_eq!(n, 3);
}
#[test]
fn count_at_in_limit_stops() {
let buf = [b'X'; 16];
let n = ZSTD_count(&buf, 4, 0, 10);
assert_eq!(n, 6);
}
#[test]
fn hash_different_mls_differ() {
let buf = [1u8, 2, 3, 4, 5, 6, 7, 8];
let h4 = ZSTD_hashPtr(&buf, 16, 4);
let h8 = ZSTD_hashPtr(&buf, 16, 8);
assert_ne!(h4, h8);
}
}