From 87a7cbdaec17289d7d2fdcea25eccd4666063a1d Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 13:15:53 +0200 Subject: [PATCH] feat(compress): move MT rolling hash prime power to Rust The MT rsync initialization previously computed the rolling-hash prime power through the C-only ZSTD_rollingHash_primePower helper. Move only that scalar exponentiation behind the existing Rust MT ABI boundary. The C caller still owns the MT context assignment, RSYNC_LENGTH constant, and surrounding initialization. The Rust helper mirrors ZSTD_ipow with wrapping u64 multiplication and applies u32 wrapping subtraction before widening the exponent, preserving the C length == 0 behavior. Rolling-hash scanning, rotation, and all other MT state remain unchanged. Test Plan: - Focused rolling_hash_prime_power Rust tests -- passed (2 tests). - Required clippy/fmt sequence in normal, bench, and test modes -- passed. - make -B -C lib -j2 lib -- passed. - make -C tests test-rust-lib-smoke -- passed. - tests/fuzzer -s4560 -t56 -i57 -v -- passed (57 tests). - make -C tests -j2 test-zstream -- passed (84 named, 5,305 + 7,351 randomized). - git diff --check and git diff --cached --check -- passed. --- lib/compress/zstdmt_compress.c | 3 ++- rust/src/zstdmt_compress.rs | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) 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,