From 62eea1dd839735e1639363e70aaa022fd29368ce Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 18:28:03 +0200 Subject: [PATCH] feat(compress): move MT serial dictionary policy to Rust Move the serial LDM dictionary preload policy into Rust. The C MT adapter now exposes only the private window and hash-table operations through checked callbacks, while Rust owns empty/raw-content eligibility, loaded-dictionary reset ordering, and the force-window publication rule. The explicit projection keeps the dictionary inputs and callback ABI auditable without exposing the private SerialState layout. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --tests - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1 - ulimit -v 41943040; make -j1 -C tests test --- lib/compress/zstdmt_compress.c | 71 ++++++++++++--- rust/src/zstdmt_compress.rs | 153 +++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 14 deletions(-) diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index 267bfe4c5..e13f63822 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -837,7 +837,32 @@ typedef int (*ZSTDMT_serialResetResizeFn)( unsigned bucketLog, unsigned previousBucketLog); typedef void (*ZSTDMT_serialResetZeroTablesFn)( void* opaque, size_t hashSize, size_t numBuckets); +typedef size_t (*ZSTDMT_serialResetLoadRawDictionaryFn)( + void* opaque, const void* dict, size_t dictSize); +typedef void (*ZSTDMT_serialResetSetLoadedDictEndFn)( + void* opaque, U32 loadedDictEnd); typedef void (*ZSTDMT_serialResetPublishFn)(void* opaque, size_t jobSize); +typedef struct { + const void* dict; + size_t dictSize; + int dictContentType; + int forceWindow; +} ZSTDMT_RustSerialDictionaryProjection; +typedef char ZSTDMT_rust_serial_dictionary_projection_layout[ + (offsetof(ZSTDMT_RustSerialDictionaryProjection, dict) == 0 + && offsetof(ZSTDMT_RustSerialDictionaryProjection, dictSize) + == sizeof(void*) + && offsetof(ZSTDMT_RustSerialDictionaryProjection, dictContentType) + == sizeof(void*) + sizeof(size_t) + && offsetof(ZSTDMT_RustSerialDictionaryProjection, forceWindow) + == sizeof(void*) + sizeof(size_t) + sizeof(int) + && sizeof(ZSTDMT_RustSerialDictionaryProjection) + == sizeof(void*) + sizeof(size_t) + 2 * sizeof(int)) + ? 1 : -1]; +void ZSTDMT_rust_serialStateLoadDictionary( + const ZSTDMT_RustSerialDictionaryProjection* projection, + void* opaque, ZSTDMT_serialResetLoadRawDictionaryFn loadRawDictionary, + ZSTDMT_serialResetSetLoadedDictEndFn setLoadedDictEnd); int ZSTDMT_rust_serialStateReset( const ZSTDMT_RustSerialResetProjection* projection, void* opaque, ZSTDMT_serialResetSetLdmParamsFn setLdmParams, @@ -1487,24 +1512,42 @@ static void ZSTDMT_serialResetZeroTables( ZSTD_memset(serialState->ldmState.bucketOffsets, 0, numBuckets); } -static void ZSTDMT_serialResetLoadDictionary(void* opaque) +static size_t ZSTDMT_serialResetLoadRawDictionary( + void* opaque, const void* dict, size_t dictSize) { ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque; SerialState* const serialState = context->serialState; + BYTE const* const dictEnd = (const BYTE*)dict + dictSize; - serialState->ldmState.loadedDictEnd = 0; - if (context->dictSize > 0 && - context->dictContentType == ZSTD_dct_rawContent) { - BYTE const* const dictEnd = (const BYTE*)context->dict + context->dictSize; - ZSTD_window_update(&serialState->ldmState.window, - context->dict, context->dictSize, - /* forceNonContiguous */ 0); - ZSTD_ldm_fillHashTable(&serialState->ldmState, - (const BYTE*)context->dict, dictEnd, - &context->params->ldmParams); - serialState->ldmState.loadedDictEnd = context->params->forceWindow - ? 0 : (U32)(dictEnd - serialState->ldmState.window.base); - } + ZSTD_window_update(&serialState->ldmState.window, + dict, dictSize, + /* forceNonContiguous */ 0); + ZSTD_ldm_fillHashTable(&serialState->ldmState, + (const BYTE*)dict, dictEnd, + &context->params->ldmParams); + return (size_t)(dictEnd - serialState->ldmState.window.base); +} + +static void ZSTDMT_serialResetSetLoadedDictEnd( + void* opaque, U32 loadedDictEnd) +{ + ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque; + context->serialState->ldmState.loadedDictEnd = loadedDictEnd; +} + +static void ZSTDMT_serialResetLoadDictionary(void* opaque) +{ + ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque; + ZSTDMT_RustSerialDictionaryProjection const projection = { + context->dict, + context->dictSize, + (int)context->dictContentType, + context->params->forceWindow + }; + ZSTDMT_rust_serialStateLoadDictionary( + &projection, context, + ZSTDMT_serialResetLoadRawDictionary, + ZSTDMT_serialResetSetLoadedDictEnd); } static void ZSTDMT_serialResetCopyWindow(void* opaque) diff --git a/rust/src/zstdmt_compress.rs b/rust/src/zstdmt_compress.rs index 5a01a53e8..29a295f78 100644 --- a/rust/src/zstdmt_compress.rs +++ b/rust/src/zstdmt_compress.rs @@ -52,6 +52,7 @@ const ZSTD_BTULTRA: c_int = 8; const ZSTD_BTULTRA2: c_int = 9; const ZSTD_PS_ENABLE: c_int = 1; const ZSTD_PS_DISABLE: c_int = 2; +const ZSTD_DCT_RAW_CONTENT: c_int = 1; const RSYNC_LENGTH: usize = 32; const RSYNC_MIN_BLOCK_LOG: usize = 17; @@ -1419,10 +1420,44 @@ pub type ZSTDMT_serialResetSetNbSeqFn = unsafe extern "C" fn(*mut c_void, usize) pub type ZSTDMT_serialResetResizeFn = unsafe extern "C" fn(*mut c_void, usize, usize, c_uint, c_uint) -> c_int; pub type ZSTDMT_serialResetZeroTablesFn = unsafe extern "C" fn(*mut c_void, usize, usize); +pub type ZSTDMT_serialResetLoadRawDictionaryFn = + unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize; +pub type ZSTDMT_serialResetSetLoadedDictEndFn = unsafe extern "C" fn(*mut c_void, c_uint); pub type ZSTDMT_serialResetPublishFn = unsafe extern "C" fn(*mut c_void, usize); pub type ZSTDMT_serialStateVoidFn = unsafe extern "C" fn(*mut c_void); pub type ZSTDMT_serialStateInitFn = unsafe extern "C" fn(*mut c_void) -> c_int; +/// Dictionary inputs for the MT serial LDM preload policy. The private C +/// window/hash-table state stays behind callbacks; Rust owns eligibility, +/// reset, and the `forceWindow` publication rule. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTDMT_RustSerialDictionaryProjection { + pub dict: *const c_void, + pub dictSize: usize, + pub dictContentType: c_int, + pub forceWindow: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTDMT_RustSerialDictionaryProjection, dict) == 0); + assert!( + offset_of!(ZSTDMT_RustSerialDictionaryProjection, dictSize) == size_of::<*const c_void>() + ); + assert!( + offset_of!(ZSTDMT_RustSerialDictionaryProjection, dictContentType) + == size_of::<*const c_void>() + size_of::() + ); + assert!( + offset_of!(ZSTDMT_RustSerialDictionaryProjection, forceWindow) + == size_of::<*const c_void>() + size_of::() + size_of::() + ); + assert!( + size_of::() + == size_of::<*const c_void>() + size_of::() + 2 * size_of::() + ); +}; + /// Rust's LDM module already owns the scalar adjustment and maximum-sequence /// leaves. This mirror is Rust-internal: it contains only the six scalar LDM /// fields needed to call those leaves and is never exposed in the C ABI. @@ -1459,6 +1494,59 @@ const _: () = { assert!(size_of::() == 6 * size_of::()); }; +#[inline] +fn serial_state_load_dictionary_with( + projection: ZSTDMT_RustSerialDictionaryProjection, + mut load_raw_dictionary: L, + mut set_loaded_dict_end: S, +) where + L: FnMut(*const c_void, usize) -> usize, + S: FnMut(c_uint), +{ + /* Match the C reset: loadedDictEnd is cleared even when no raw + * dictionary is eligible for loading. */ + set_loaded_dict_end(0); + if projection.dictSize == 0 || projection.dictContentType != ZSTD_DCT_RAW_CONTENT { + return; + } + + let loaded_dict_end = load_raw_dictionary(projection.dict, projection.dictSize); + if projection.forceWindow == 0 { + set_loaded_dict_end(loaded_dict_end as c_uint); + } +} + +/// Apply the MT serial LDM dictionary preload policy. C retains the private +/// window/hash-table operations and publishes their computed offset through +/// the explicit callbacks. +#[no_mangle] +pub unsafe extern "C" fn ZSTDMT_rust_serialStateLoadDictionary( + projection: *const ZSTDMT_RustSerialDictionaryProjection, + opaque: *mut c_void, + load_raw_dictionary: Option, + set_loaded_dict_end: Option, +) { + let Some(projection) = (unsafe { projection.as_ref() }).copied() else { + return; + }; + let (Some(load_raw_dictionary), Some(set_loaded_dict_end)) = + (load_raw_dictionary, set_loaded_dict_end) + else { + return; + }; + if opaque.is_null() { + return; + } + + unsafe { + serial_state_load_dictionary_with( + projection, + |dict, dict_size| load_raw_dictionary(opaque, dict, dict_size), + |loaded_dict_end| set_loaded_dict_end(opaque, loaded_dict_end), + ); + } +} + unsafe extern "C" { fn ZSTD_ldm_adjustParameters( params: *mut ZSTDMT_serialResetLdmParameters, @@ -7417,6 +7505,71 @@ mod tests { ); } + #[test] + fn serial_dictionary_policy_resets_then_loads_only_raw_content() { + let events = RefCell::new(Vec::<(&'static str, usize)>::new()); + serial_state_load_dictionary_with( + ZSTDMT_RustSerialDictionaryProjection { + dict: ptr::null(), + dictSize: 12, + dictContentType: ZSTD_DCT_RAW_CONTENT, + forceWindow: 0, + }, + |dict, dict_size| { + assert!(dict.is_null()); + events.borrow_mut().push(("load", dict_size)); + 23 + }, + |loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)), + ); + assert_eq!( + events.into_inner(), + vec![("end", 0), ("load", 12), ("end", 23)] + ); + + let events = RefCell::new(Vec::<(&'static str, usize)>::new()); + serial_state_load_dictionary_with( + ZSTDMT_RustSerialDictionaryProjection { + dict: ptr::null(), + dictSize: 12, + dictContentType: ZSTD_DCT_RAW_CONTENT, + forceWindow: 1, + }, + |_, dict_size| { + events.borrow_mut().push(("load", dict_size)); + 23 + }, + |loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)), + ); + assert_eq!(events.into_inner(), vec![("end", 0), ("load", 12)]); + + let events = RefCell::new(Vec::<(&'static str, usize)>::new()); + serial_state_load_dictionary_with( + ZSTDMT_RustSerialDictionaryProjection { + dict: ptr::null(), + dictSize: 12, + dictContentType: 2, + forceWindow: 0, + }, + |_, _| panic!("non-raw dictionary must not be loaded"), + |loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)), + ); + assert_eq!(events.into_inner(), vec![("end", 0)]); + + let events = RefCell::new(Vec::<(&'static str, usize)>::new()); + serial_state_load_dictionary_with( + ZSTDMT_RustSerialDictionaryProjection { + dict: ptr::null(), + dictSize: 0, + dictContentType: ZSTD_DCT_RAW_CONTENT, + forceWindow: 0, + }, + |_, _| panic!("empty dictionary must not be loaded"), + |loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)), + ); + assert_eq!(events.into_inner(), vec![("end", 0)]); + } + #[derive(Default)] struct SerialLifecycleTestContext { events: Vec<&'static str>,