From 724c7c60fa24427eb1849c99c73c3378e0314099 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 14:26:34 +0200 Subject: [PATCH] feat(compress): move CDict query leaves into Rust Move CDict compression-parameter and dictionary-ID snapshot reads behind a Rust-owned projection while retaining the private C field access and the existing non-null assertion for ZSTD_getCParamsFromCDict. Preserve the public NULL dictionary-ID result and ABI-compatible compression-parameter return. Test Plan: - cargo test --manifest-path rust/Cargo.toml --lib - cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - make -B -C programs -j1 zstd - make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s - focused cdict_query_projection unit tests --- lib/compress/zstd_compress.c | 24 +++++++- rust/src/zstd_compress_dictionary.rs | 83 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 728d7c7dc..1fac39aec 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1277,6 +1277,19 @@ typedef char ZSTD_rust_compress_begin_using_cdict_state_layout[ && sizeof(ZSTD_rust_compressBeginUsingCDictState) == 12 * sizeof(void*)) ? 1 : -1]; +typedef struct { + const ZSTD_compressionParameters* cParams; + const unsigned* dictID; +} ZSTD_rust_cdictQueryState; +ZSTD_compressionParameters ZSTD_rust_getCParamsFromCDict( + const ZSTD_rust_cdictQueryState* state); +unsigned ZSTD_rust_getDictIDFromCDict( + const ZSTD_rust_cdictQueryState* state); +typedef char ZSTD_rust_cdict_query_state_layout[ + (offsetof(ZSTD_rust_cdictQueryState, cParams) == 0 + && offsetof(ZSTD_rust_cdictQueryState, dictID) == sizeof(void*) + && sizeof(ZSTD_rust_cdictQueryState) == 2 * sizeof(void*)) + ? 1 : -1]; size_t ZSTD_rust_transferSequencesWBlockDelim( SeqStore_t* seqStore, ZSTD_SequencePosition* seqPos, const ZSTD_Sequence* inSeqs, size_t inSeqsSize, @@ -4939,8 +4952,11 @@ const ZSTD_CDict* ZSTD_initStaticCDict( ZSTD_compressionParameters ZSTD_getCParamsFromCDict(const ZSTD_CDict* cdict) { + ZSTD_rust_cdictQueryState state; assert(cdict != NULL); - return cdict->matchState.cParams; + state.cParams = &cdict->matchState.cParams; + state.dictID = NULL; + return ZSTD_rust_getCParamsFromCDict(&state); } /*! ZSTD_getDictID_fromCDict() : @@ -4949,8 +4965,10 @@ ZSTD_compressionParameters ZSTD_getCParamsFromCDict(const ZSTD_CDict* cdict) * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */ unsigned ZSTD_getDictID_fromCDict(const ZSTD_CDict* cdict) { - if (cdict==NULL) return 0; - return cdict->dictID; + ZSTD_rust_cdictQueryState state; + state.cParams = NULL; + state.dictID = cdict == NULL ? NULL : &cdict->dictID; + return ZSTD_rust_getDictIDFromCDict(&state); } static void ZSTD_rust_compressBeginUsingCDict_initParams( diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index 3acd0b665..d825e4f8a 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -232,6 +232,49 @@ pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingCDict( } } +/// Scalar projections for the public CDict query helpers. +#[repr(C)] +pub struct ZSTD_rust_cdictQueryState { + c_params: *const ZSTD_compressionParameters, + dict_id: *const c_uint, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_cdictQueryState, c_params) == 0); + assert!(offset_of!(ZSTD_rust_cdictQueryState, dict_id) == size_of::()); + assert!(size_of::() == 2 * size_of::()); +}; + +/// Return a copied compression-parameter snapshot from a CDict projection. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_getCParamsFromCDict( + state: *const ZSTD_rust_cdictQueryState, +) -> ZSTD_compressionParameters { + if state.is_null() { + return ZSTD_compressionParameters::default(); + } + let state = unsafe { &*state }; + if state.c_params.is_null() { + return ZSTD_compressionParameters::default(); + } + unsafe { *state.c_params } +} + +/// Return the dictionary ID from a CDict projection, preserving NULL -> 0. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_getDictIDFromCDict( + state: *const ZSTD_rust_cdictQueryState, +) -> c_uint { + if state.is_null() { + return 0; + } + let state = unsafe { &*state }; + if state.dict_id.is_null() { + return 0; + } + unsafe { *state.dict_id } +} + /// Rust-owned policy for `ZSTD_CCtx_loadDictionary_advanced()`. #[no_mangle] pub unsafe extern "C" fn ZSTD_rust_CCtx_loadDictionaryAdvanced( @@ -891,6 +934,46 @@ mod tests { assert_eq!(probe.events, [3]); } + #[test] + fn cdict_query_projection_returns_private_snapshots() { + let cparams = ZSTD_compressionParameters { + windowLog: 17, + chainLog: 12, + hashLog: 13, + searchLog: 1, + minMatch: 4, + targetLength: 16, + strategy: 3, + }; + let dict_id = 0x1234_5678; + let state = ZSTD_rust_cdictQueryState { + c_params: &cparams, + dict_id: &dict_id, + }; + + assert_eq!(unsafe { ZSTD_rust_getCParamsFromCDict(&state) }, cparams); + assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(&state) }, dict_id); + } + + #[test] + fn cdict_query_projection_handles_missing_snapshots() { + let state = ZSTD_rust_cdictQueryState { + c_params: ptr::null(), + dict_id: ptr::null(), + }; + + assert_eq!( + unsafe { ZSTD_rust_getCParamsFromCDict(&state) }, + ZSTD_compressionParameters::default() + ); + assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(&state) }, 0); + assert_eq!( + unsafe { ZSTD_rust_getCParamsFromCDict(ptr::null()) }, + ZSTD_compressionParameters::default() + ); + assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(ptr::null()) }, 0); + } + #[derive(Default)] struct CdictBeginProbe { events: Vec<&'static str>,