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.
This commit is contained in:
2026-07-18 13:15:53 +02:00
parent bf3440aea6
commit 87a7cbdaec
2 changed files with 39 additions and 1 deletions
+2 -1
View File
@@ -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);
+37
View File
@@ -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,