diff --git a/lib/compress/zstd_compress_internal.h b/lib/compress/zstd_compress_internal.h index d4610c6a7..749fe4105 100644 --- a/lib/compress/zstd_compress_internal.h +++ b/lib/compress/zstd_compress_internal.h @@ -654,6 +654,7 @@ size_t ZSTD_rust_noCompressBlock(void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock); size_t ZSTD_rust_rleCompressBlock(void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock); +U32 ZSTD_rust_windowCorrectOverflow(U32 curr, U32 cycleLog, U32 maxDist); /* ZSTD_minGain() : @@ -1161,24 +1162,17 @@ U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, * 3. (cctx->lowLimit + 1< 3<<29 + 1<base); - U32 const currentCycle = curr & cycleMask; - /* Ensure newCurrent - maxDist >= ZSTD_WINDOW_START_INDEX. */ - U32 const currentCycleCorrection = currentCycle < ZSTD_WINDOW_START_INDEX - ? MAX(cycleSize, ZSTD_WINDOW_START_INDEX) - : 0; - U32 const newCurrent = currentCycle - + currentCycleCorrection - + MAX(maxDist, cycleSize); - U32 const correction = curr - newCurrent; + U32 const correction = ZSTD_rust_windowCorrectOverflow(curr, cycleLog, maxDist); + U32 const newCurrent = curr - correction; /* maxDist must be a power of two so that: - * (newCurrent & cycleMask) == (curr & cycleMask) + * (newCurrent & cycleMask) == (curr & cycleMask), where cycleMask is + * (1 << cycleLog) - 1 * This is required to not corrupt the chains / binary tree. */ assert((maxDist & (maxDist - 1)) == 0); - assert((curr & cycleMask) == (newCurrent & cycleMask)); + assert((curr & ((1u << cycleLog) - 1)) == + (newCurrent & ((1u << cycleLog) - 1))); assert(curr > newCurrent); if (!ZSTD_WINDOW_OVERFLOW_CORRECT_FREQUENTLY) { /* Loose bound, should be around 1<<29 (see above) */ @@ -1204,6 +1198,7 @@ U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, /* Ensure that lowLimit and dictLimit didn't underflow. */ assert(window->lowLimit <= newCurrent); assert(window->dictLimit <= newCurrent); + (void)newCurrent; ++window->nbOverflowCorrections; diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index a2f1a6e1c..812f66ecf 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -384,6 +384,32 @@ pub extern "C" fn ZSTD_rust_frameProgression( frame_progression(consumed_src_size, buffered, produced_c_size) } +#[inline] +fn window_correct_overflow(curr: u32, cycle_log: u32, max_dist: u32) -> u32 { + let cycle_size = 1u32.wrapping_shl(cycle_log); + let cycle_mask = cycle_size.wrapping_sub(1); + let current_cycle = curr & cycle_mask; + let current_cycle_correction = if current_cycle < ZSTD_WINDOW_START_INDEX { + cycle_size.max(ZSTD_WINDOW_START_INDEX) + } else { + 0 + }; + let new_current = current_cycle + .wrapping_add(current_cycle_correction) + .wrapping_add(max_dist.max(cycle_size)); + curr.wrapping_sub(new_current) +} + +/// Return the scalar correction for C's window-overflow state transition. +/// +/// C retains pointer arithmetic, invariant checks, and all `ZSTD_window_t` +/// mutation. This leaf owns only the U32 cycle arithmetic so its wrapping +/// behavior is explicit and independent of the host pointer width. +#[no_mangle] +pub extern "C" fn ZSTD_rust_windowCorrectOverflow(curr: u32, cycle_log: u32, max_dist: u32) -> u32 { + window_correct_overflow(curr, cycle_log, max_dist) +} + #[inline] fn next_input_size_hint( in_buffer_mode: c_int, @@ -1296,6 +1322,37 @@ mod tests { assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold + 1), 1); } + #[test] + fn window_correction_handles_current_cycle_start_boundary() { + assert_eq!(window_correct_overflow(0x100, 3, 8), 0xf0); + assert_eq!(window_correct_overflow(0x101, 3, 8), 0xf0); + assert_eq!(window_correct_overflow(0x102, 3, 8), 0xf8); + + assert_eq!( + ZSTD_rust_windowCorrectOverflow(0x100, 3, 8), + window_correct_overflow(0x100, 3, 8) + ); + } + + #[test] + fn window_correction_selects_larger_of_cycle_and_max_distance() { + assert_eq!(window_correct_overflow(0x100, 5, 8), 0xc0); + assert_eq!(window_correct_overflow(0x200, 5, 64), 0x1a0); + } + + #[test] + fn window_correction_wraps_u32_intermediates() { + assert_eq!(window_correct_overflow(0, 0, 1), u32::MAX - 2); + assert_eq!( + window_correct_overflow(0x8000_0000, 31, 0x8000_0000), + 0x8000_0000 + ); + assert_eq!( + ZSTD_rust_windowCorrectOverflow(0x8000_0000, 31, 0x8000_0000), + 0x8000_0000 + ); + } + #[test] fn dict_too_big_uses_a_strict_chunk_size_boundary() { assert!(!dict_too_big(0));