From 87b616c49b3c329b37cc8b5b7425b742bb6e0a07 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 22:44:37 +0200 Subject: [PATCH] feat(compress): move dictionary match-state publication to Rust Replace the dictionary loader's C match-state publication callback with a live field projection. Rust now publishes nextToUpdate, loadedDictEnd, and forceNonContiguous at the existing orchestration point while C retains the private match-state layout. Keep the window base indirect so a preceding window update cannot leave Rust with a stale offset origin. Test Plan: - git diff --cached --check - capped cargo check --tests - capped cargo clippy --tests -- -A clippy::manual-bits -D warnings - capped make -j1 - capped make -j1 -C tests test --- lib/compress/zstd_compress.c | 48 ++++++---- rust/src/zstd_compress_dictionary.rs | 135 ++++++++++++++++++++++++--- 2 files changed, 149 insertions(+), 34 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index b39e6302b..3f46ecf49 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -2769,13 +2769,31 @@ typedef void (*ZSTD_rust_loadDictionaryContent_windowUpdate_f)( void* context, int ldm, const void* src, size_t srcSize); typedef void (*ZSTD_rust_loadDictionaryContent_setLdmLoadedDictEnd_f)( void* context, const void* iend, int forceWindow); -typedef void (*ZSTD_rust_loadDictionaryContent_publishMatchState_f)( - void* context, const void* ip, const void* iend, - int forceWindow, int deterministicRefPrefix); typedef void (*ZSTD_rust_loadDictionaryContent_fillLdm_f)( void* context, const void* ip, const void* iend); typedef void (*ZSTD_rust_loadDictionaryContent_overflowCorrect_f)( void* context, const void* ip, const void* iend); +typedef struct { + const BYTE** base; + U32* nextToUpdate; + U32* loadedDictEnd; + int* forceNonContiguous; +} ZSTD_rust_loadDictionaryContentMatchStatePublicationState; +typedef char ZSTD_rust_load_dictionary_match_state_publication_layout[ + (offsetof(ZSTD_rust_loadDictionaryContentMatchStatePublicationState, base) + == 0 + && offsetof(ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + nextToUpdate) + == sizeof(void*) + && offsetof(ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + loadedDictEnd) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + forceNonContiguous) + == 3 * sizeof(void*) + && sizeof(ZSTD_rust_loadDictionaryContentMatchStatePublicationState) + == 4 * sizeof(void*)) + ? 1 : -1]; /* The Fast dictionary-table leaf is implemented in Rust. Keep only the * fields needed by that leaf in the ABI projection; the complete * ZSTD_MatchState_t layout remains private to C. */ @@ -2874,7 +2892,7 @@ typedef struct { ZSTD_rust_loadDictionaryContent_assertWindowEmpty_f assertWindowEmpty; ZSTD_rust_loadDictionaryContent_windowUpdate_f windowUpdate; ZSTD_rust_loadDictionaryContent_setLdmLoadedDictEnd_f setLdmLoadedDictEnd; - ZSTD_rust_loadDictionaryContent_publishMatchState_f publishMatchState; + const ZSTD_rust_loadDictionaryContentMatchStatePublicationState* publishMatchState; ZSTD_rust_loadDictionaryContent_fillLdm_f fillLdm; ZSTD_rust_loadDictionaryContent_overflowCorrect_f overflowCorrect; const ZSTD_rust_loadDictionaryContentFastTableState* fastTable; @@ -6897,20 +6915,6 @@ static void ZSTD_loadDictionaryContent_setLdmLoadedDictEnd( : (U32)((const BYTE*)iend - context->ldmState->window.base); } -static void ZSTD_loadDictionaryContent_publishMatchState( - void* opaque, const void* ip, const void* iend, - int forceWindow, int deterministicRefPrefix) -{ - ZSTD_loadDictionaryContent_context const* const context = - (const ZSTD_loadDictionaryContent_context*)opaque; - context->matchState->nextToUpdate = - (U32)((const BYTE*)ip - context->matchState->window.base); - context->matchState->loadedDictEnd = forceWindow - ? 0 - : (U32)((const BYTE*)iend - context->matchState->window.base); - context->matchState->forceNonContiguous = deterministicRefPrefix; -} - static void ZSTD_loadDictionaryContent_fillLdm( void* opaque, const void* ip, const void* iend) { @@ -7026,6 +7030,7 @@ static size_t ZSTD_loadDictionaryContent_callback( int dtlm, int tfp) { ZSTD_loadDictionaryContent_context context; + ZSTD_rust_loadDictionaryContentMatchStatePublicationState matchStatePublication; ZSTD_rust_loadDictionaryContentFastTableState fastTable; ZSTD_rust_loadDictionaryContentDoubleFastTableState doubleFastTable; ZSTD_rust_loadDictionaryContentState state; @@ -7039,6 +7044,11 @@ static size_t ZSTD_loadDictionaryContent_callback( context.workspace = ws; context.params = cctxParams; + matchStatePublication.base = &ms->window.base; + matchStatePublication.nextToUpdate = &ms->nextToUpdate; + matchStatePublication.loadedDictEnd = &ms->loadedDictEnd; + matchStatePublication.forceNonContiguous = &ms->forceNonContiguous; + assert((tfp == ZSTD_tfp_forCDict && dtlm == ZSTD_dtlm_full) || (tfp != ZSTD_tfp_forCDict && dtlm == ZSTD_dtlm_fast)); /* These fields are mutated by the window/publish callbacks before the @@ -7088,7 +7098,7 @@ static size_t ZSTD_loadDictionaryContent_callback( state.assertWindowEmpty = ZSTD_loadDictionaryContent_assertWindowEmpty; state.windowUpdate = ZSTD_loadDictionaryContent_windowUpdate; state.setLdmLoadedDictEnd = ZSTD_loadDictionaryContent_setLdmLoadedDictEnd; - state.publishMatchState = ZSTD_loadDictionaryContent_publishMatchState; + state.publishMatchState = &matchStatePublication; state.fillLdm = ZSTD_loadDictionaryContent_fillLdm; state.overflowCorrect = ZSTD_loadDictionaryContent_overflowCorrect; state.fastTable = &fastTable; diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index a523f296b..4b515376f 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -129,17 +129,50 @@ type LoadDictionaryContentWindowUpdateFn = unsafe extern "C" fn(context: *mut c_void, ldm: c_int, src: *const c_void, src_size: usize); type LoadDictionaryContentSetLdmLoadedDictEndFn = unsafe extern "C" fn(context: *mut c_void, iend: *const c_void, force_window: c_int); -type LoadDictionaryContentPublishMatchStateFn = unsafe extern "C" fn( - context: *mut c_void, - ip: *const c_void, - iend: *const c_void, - force_window: c_int, - deterministic_ref_prefix: c_int, -); type LoadDictionaryContentFillLdmFn = unsafe extern "C" fn(context: *mut c_void, ip: *const c_void, iend: *const c_void); type LoadDictionaryContentOverflowCorrectFn = unsafe extern "C" fn(context: *mut c_void, ip: *const c_void, iend: *const c_void); + +/// Live match-state fields used when the dictionary loader publishes the +/// current prefix. The base is indirect because window updates may replace it +/// before this publication point. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct ZSTD_rust_loadDictionaryContentMatchStatePublicationState { + base: *const *const u8, + next_to_update: *mut c_uint, + loaded_dict_end: *mut c_uint, + force_non_contiguous: *mut c_int, +} + +const _: () = { + assert!( + offset_of!(ZSTD_rust_loadDictionaryContentMatchStatePublicationState, base) == 0 + ); + assert!( + offset_of!( + ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + next_to_update + ) == size_of::() + ); + assert!( + offset_of!( + ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + loaded_dict_end + ) == 2 * size_of::() + ); + assert!( + offset_of!( + ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + force_non_contiguous + ) == 3 * size_of::() + ); + assert!( + size_of::() + == 4 * size_of::() + ); +}; /// Direct projection for the Fast dictionary-table leaf. /// /// C exposes pointers to the live scalar fields needed by the Fast leaf. @@ -210,7 +243,7 @@ pub struct ZSTD_rust_loadDictionaryContentState { assert_window_empty: LoadDictionaryContentAssertWindowEmptyFn, window_update: LoadDictionaryContentWindowUpdateFn, set_ldm_loaded_dict_end: LoadDictionaryContentSetLdmLoadedDictEndFn, - publish_match_state: LoadDictionaryContentPublishMatchStateFn, + publish_match_state: *const ZSTD_rust_loadDictionaryContentMatchStatePublicationState, fill_ldm: LoadDictionaryContentFillLdmFn, overflow_correct: LoadDictionaryContentOverflowCorrectFn, fast_table: *const ZSTD_rust_loadDictionaryContentFastTableState, @@ -229,7 +262,6 @@ const _: () = { assert!(size_of::() == size_of::()); assert!(size_of::() == size_of::()); assert!(size_of::() == size_of::()); - assert!(size_of::() == size_of::()); assert!(size_of::() == size_of::()); assert!(size_of::() == size_of::()); assert!(offset_of!(ZSTD_rust_loadDictionaryContentFastTableState, hash_table) == 0); @@ -314,6 +346,10 @@ const _: () = { offset_of!(ZSTD_rust_loadDictionaryContentState, assert_c_params) == 18 * size_of::() ); + assert!( + offset_of!(ZSTD_rust_loadDictionaryContentState, publish_match_state) + == 22 * size_of::() + ); assert!( offset_of!(ZSTD_rust_loadDictionaryContentState, fast_table) == 25 * size_of::() ); @@ -470,11 +506,35 @@ unsafe fn fill_double_fast_dictionary_table( true } +#[inline] +unsafe fn publish_dictionary_match_state( + publication: &ZSTD_rust_loadDictionaryContentMatchStatePublicationState, + ip: *const u8, + iend: *const u8, + force_window: usize, + deterministic_ref_prefix: usize, +) { + let base = unsafe { *publication.base }; + unsafe { + *publication.next_to_update = ip.offset_from(base) as c_uint; + *publication.loaded_dict_end = if force_window != 0 { + 0 + } else { + iend.offset_from(base) as c_uint + }; + *publication.force_non_contiguous = deterministic_ref_prefix as c_int; + } +} + unsafe fn load_dictionary_content( state: &ZSTD_rust_loadDictionaryContentState, src: *const c_void, src_size: usize, ) -> usize { + if state.publish_match_state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let match_state_publication = unsafe { &*state.publish_match_state }; unsafe { (state.assert_c_params)(state.callback_context) }; let load_ldm_dict = state.ldm_enabled != 0 && state.has_ldm_state != 0; @@ -524,12 +584,12 @@ unsafe fn load_dictionary_content( } unsafe { - (state.publish_match_state)( - state.callback_context, - ip.cast(), - iend.cast(), - (state.force_window != 0) as c_int, - (state.deterministic_ref_prefix != 0) as c_int, + publish_dictionary_match_state( + match_state_publication, + ip, + iend, + state.force_window, + state.deterministic_ref_prefix, ) }; @@ -6805,6 +6865,51 @@ mod tests { ); } + #[test] + fn dictionary_match_state_projection_publishes_live_offsets() { + let input = [0u8; 64]; + let mut base_ptr = input.as_ptr(); + let mut next_to_update = 0u32; + let mut loaded_dict_end = 0u32; + let mut force_non_contiguous = 0i32; + let projection = ZSTD_rust_loadDictionaryContentMatchStatePublicationState { + base: &base_ptr, + next_to_update: &mut next_to_update, + loaded_dict_end: &mut loaded_dict_end, + force_non_contiguous: &mut force_non_contiguous, + }; + + unsafe { + publish_dictionary_match_state( + &projection, + input.as_ptr().add(7), + input.as_ptr().add(42), + 0, + 1, + ) + }; + assert_eq!(next_to_update, 7); + assert_eq!(loaded_dict_end, 42); + assert_eq!(force_non_contiguous, 1); + + unsafe { + ptr::write(&mut base_ptr, input.as_ptr().add(3)); + assert_eq!(*projection.base, input.as_ptr().add(3)); + } + unsafe { + publish_dictionary_match_state( + &projection, + input.as_ptr().add(10), + input.as_ptr().add(30), + 1, + 0, + ) + }; + assert_eq!(next_to_update, 7); + assert_eq!(loaded_dict_end, 0); + assert_eq!(force_non_contiguous, 0); + } + #[test] fn fast_dictionary_table_projection_dispatches_to_rust_leaf() { let hash_log = 4u32;