diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index e8b42224c..3c5fffb72 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -150,6 +150,7 @@ void ZSTDMT_rust_findSynchronizationPoint(const void* inputSrc, size_t inputSize const void* inBuffStart, size_t inBuffFilled, int rsyncable, U64 primePower, U64 hitMask, size_t* toLoad, int* flush); +U64 ZSTDMT_rust_rollingHashPrimePower(U32 length); size_t ZSTDMT_rust_nextInputSizeHint(size_t targetSectionSize, size_t inBuffFilled); size_t ZSTDMT_rust_sizeofCCtx(size_t mtctxSize, size_t factorySize, @@ -1253,7 +1254,7 @@ size_t ZSTDMT_initCStream_internal( DEBUGLOG(4, "rsyncLog = %u", rsyncBits); mtctx->rsync.hash = 0; mtctx->rsync.hitMask = (1ULL << rsyncBits) - 1; - mtctx->rsync.primePower = ZSTD_rollingHash_primePower(RSYNC_LENGTH); + mtctx->rsync.primePower = ZSTDMT_rust_rollingHashPrimePower(RSYNC_LENGTH); } if (mtctx->targetSectionSize < mtctx->targetPrefixSize) mtctx->targetSectionSize = mtctx->targetPrefixSize; /* job size must be >= overlap size */ DEBUGLOG(4, "Job Size : %u KB (note : set to %u)", (U32)(mtctx->targetSectionSize>>10), (U32)params.jobSize); diff --git a/rust/src/zstdmt_compress.rs b/rust/src/zstdmt_compress.rs index d52a8d2df..21397ade5 100644 --- a/rust/src/zstdmt_compress.rs +++ b/rust/src/zstdmt_compress.rs @@ -173,6 +173,27 @@ fn rolling_hash_rotate(hash: u64, to_remove: u8, to_add: u8, prime_power: u64) - .wrapping_add(u64::from(to_add).wrapping_add(ROLL_HASH_CHAR_OFFSET)) } +#[inline] +fn rolling_hash_prime_power(length: c_uint) -> u64 { + let mut base = PRIME8_BYTES; + let mut exponent = u64::from(length.wrapping_sub(1)); + 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 +} + +/// C ABI for the rolling-hash prime power used by rsyncable MT compression. +#[no_mangle] +pub extern "C" fn ZSTDMT_rust_rollingHashPrimePower(length: c_uint) -> u64 { + rolling_hash_prime_power(length) +} + /// Search for a synchronization point without exposing the C MT context or /// its private buffer types across the FFI boundary. #[inline] @@ -1056,6 +1077,22 @@ mod tests { }) } + #[test] + fn rolling_hash_prime_power_matches_c_ipow_for_common_lengths() { + assert_eq!(ZSTDMT_rust_rollingHashPrimePower(1), 0x0000_0000_0000_0001); + assert_eq!(ZSTDMT_rust_rollingHashPrimePower(2), 0xCF1B_BCDC_B7A5_6463); + assert_eq!( + ZSTDMT_rust_rollingHashPrimePower(RSYNC_LENGTH as c_uint), + 0xF550_7FE3_5F91_F8CB + ); + } + + #[test] + fn rolling_hash_prime_power_preserves_zero_length_underflow() { + assert_eq!(0u32.wrapping_sub(1), c_uint::MAX); + assert_eq!(ZSTDMT_rust_rollingHashPrimePower(0), 0x12A9_3A33_31E0_3D4B); + } + fn call_synchronization_point( input: &[u8], input_pos: usize,