From dae40246aa2260cd05e8e66235f9e4154cf353c3 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 21:02:44 +0200 Subject: [PATCH] feat(compress): move match-state invalidation leaf into Rust Route match-state invalidation through a Rust leaf. C still computes the private window pointer difference and passes the opaque dictionary-state pointer, while Rust preserves the window-limit, next-index, dictionary, optimal-parser, and dictionary-match-state reset order. Add a focused test for all invalidated fields and use the concrete C pointer type at the ABI boundary to avoid strict-aliasing warnings. Test Plan: - cargo fmt --manifest-path rust/Cargo.toml -- --check - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml zstd_compress::tests::invalidate_match_state -- --nocapture - ulimit -v 41943040 && make -j1 - ulimit -v 41943040 && make -j1 -C tests test-fuzzer FUZZERTEST=-T3s FUZZER_FLAGS=--no-big-tests --- lib/compress/zstd_compress.c | 27 +++++++---------- rust/src/zstd_compress.rs | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 9c6b2c6fe..b9b3ce701 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1722,6 +1722,10 @@ size_t ZSTD_rust_buildBlockEntropyStats( void* workspace, size_t wkspSize); void ZSTD_rust_resetCompressedBlockState(ZSTD_compressedBlockState_t* bs); void ZSTD_rust_invalidateRepCodes(U32 rep[ZSTD_REP_NUM]); +void ZSTD_rust_invalidateMatchState( + size_t endT, U32* lowLimit, U32* dictLimit, + U32* nextToUpdate, U32* loadedDictEnd, U32* litLengthSum, + const ZSTD_MatchState_t** dictMatchState); typedef char ZSTD_rust_invalidate_rep_count[(ZSTD_REP_NUM == 3) ? 1 : -1]; void ZSTD_rust_confirmRepcodesAndEntropyTables( ZSTD_compressedBlockState_t** prevCBlock, @@ -3718,21 +3722,6 @@ void ZSTD_reset_compressedBlockState(ZSTD_compressedBlockState_t* bs) ZSTD_rust_resetCompressedBlockState(bs); } -/*! ZSTD_invalidateMatchState() - * Invalidate all the matches in the match finder tables. - * Requires nextSrc and base to be set (can be NULL). - */ -static void ZSTD_invalidateMatchState(ZSTD_MatchState_t* ms) -{ - ZSTD_rust_windowClear((size_t)(ms->window.nextSrc - ms->window.base), - &ms->window.lowLimit, &ms->window.dictLimit); - - ms->nextToUpdate = ms->window.dictLimit; - ms->loadedDictEnd = 0; - ms->opt.litLengthSum = 0; /* force reset of btopt stats */ - ms->dictMatchState = NULL; -} - /** * Controls, for this matchState reset, whether the tables need to be cleared / * prepared for the coming compression (ZSTDcrp_makeClean), or whether the @@ -3807,7 +3796,13 @@ static void ZSTD_rust_resetMatchState_invalidate(void* opaque) { ZSTD_rust_resetMatchStateContext* const context = (ZSTD_rust_resetMatchStateContext*)opaque; - ZSTD_invalidateMatchState(context->ms); + ZSTD_MatchState_t* const ms = context->ms; + ZSTD_rust_invalidateMatchState( + (size_t)(ms->window.nextSrc - ms->window.base), + &ms->window.lowLimit, &ms->window.dictLimit, + &ms->nextToUpdate, &ms->loadedDictEnd, + &ms->opt.litLengthSum, + &ms->dictMatchState); } static void ZSTD_rust_resetMatchState_clearTables(void* opaque) diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 221e93c60..d1c060703 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -6916,6 +6916,34 @@ pub unsafe extern "C" fn ZSTD_rust_windowClear( } } +/// Invalidate a C-owned match-state projection after the caller computes the +/// private window pointer difference. Rust owns the paired window-limit and +/// match-state field updates while C retains the match-state layout. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_invalidateMatchState( + end_t: usize, + low_limit: *mut u32, + dict_limit: *mut u32, + next_to_update: *mut u32, + loaded_dict_end: *mut u32, + lit_length_sum: *mut u32, + dict_match_state: *mut *const c_void, +) { + debug_assert!(!low_limit.is_null()); + debug_assert!(!dict_limit.is_null()); + debug_assert!(!next_to_update.is_null()); + debug_assert!(!loaded_dict_end.is_null()); + debug_assert!(!lit_length_sum.is_null()); + debug_assert!(!dict_match_state.is_null()); + unsafe { + ZSTD_rust_windowClear(end_t, low_limit, dict_limit); + *next_to_update = *dict_limit; + *loaded_dict_end = 0; + *lit_length_sum = 0; + *dict_match_state = ptr::null(); + } +} + const HASH_READ_SIZE: u32 = 8; /// Private C window fields projected for the shared window-update policy. @@ -12808,6 +12836,36 @@ mod tests { assert_eq!(dict_limit, 0x89ab_cdef); } + #[test] + fn invalidate_match_state_clears_window_and_private_fields() { + let mut low_limit = 11u32; + let mut dict_limit = 22u32; + let mut next_to_update = 33u32; + let mut loaded_dict_end = 44u32; + let mut lit_length_sum = 55u32; + let marker = 0x5au8; + let mut dict_match_state = (&marker as *const u8).cast::(); + + unsafe { + ZSTD_rust_invalidateMatchState( + 0x1234_5678, + &mut low_limit, + &mut dict_limit, + &mut next_to_update, + &mut loaded_dict_end, + &mut lit_length_sum, + &mut dict_match_state, + ) + }; + + assert_eq!(low_limit, 0x1234_5678); + assert_eq!(dict_limit, 0x1234_5678); + assert_eq!(next_to_update, 0x1234_5678); + assert_eq!(loaded_dict_end, 0); + assert_eq!(lit_length_sum, 0); + assert!(dict_match_state.is_null()); + } + #[test] fn window_update_leaves_empty_input_untouched() { let mut state = ZSTD_rust_windowUpdateState {