diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 02d73dc1d..7ad728ab0 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -3945,7 +3945,7 @@ U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat) return ZSTD_rust_params_cycleLog(hashLog, (int)strat); } -static U32 ZSTD_getCParamsExclusionMask(void); +U32 ZSTD_getCParamsExclusionMask(void); /** ZSTD_adjustCParams_internal() : * optimize `cPar` for a specified input (`srcSize` and `dictSize`). @@ -4001,7 +4001,7 @@ enum { ZSTD_RUST_PARAMS_EXCLUDE_DFAST = 1u << 6 }; -static U32 ZSTD_getCParamsExclusionMask(void) +U32 ZSTD_getCParamsExclusionMask(void) { U32 mask = 0; #ifdef ZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index e1c26ade9..6018a1c5e 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -25,6 +25,53 @@ #include "zstd_ldm.h" #include "zstdmt_compress.h" +/* The Rust parameter leaf receives only this scalar snapshot. Keep the + * private ZSTDMT_CCtx and ZSTD_CCtx_params layouts in this translation unit. */ +typedef struct { + int compressionLevel; + int cctxSrcSizeHint; + U64 srcSizeHint; + size_t dictSize; + int mode; + int enableLdm; + U32 ldmDefaultWindowLog; + ZSTD_compressionParameters overrides; + int useRowMatchFinder; + U32 exclusionMask; + U32 savedWindowLog; +} ZSTDMT_RustCParamsUpdateProjection; +typedef char ZSTDMT_rust_cparams_update_projection_layout[ + (offsetof(ZSTDMT_RustCParamsUpdateProjection, compressionLevel) == 0 + && offsetof(ZSTDMT_RustCParamsUpdateProjection, cctxSrcSizeHint) + == sizeof(int) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, srcSizeHint) + == 2 * sizeof(int) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, dictSize) == 16 + && offsetof(ZSTDMT_RustCParamsUpdateProjection, mode) + == (sizeof(void*) == 8 ? 24 : 20) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, enableLdm) + == (sizeof(void*) == 8 ? 28 : 24) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, ldmDefaultWindowLog) + == (sizeof(void*) == 8 ? 32 : 28) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, overrides) + == (sizeof(void*) == 8 ? 36 : 32) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, useRowMatchFinder) + == (sizeof(void*) == 8 ? 64 : 60) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, exclusionMask) + == (sizeof(void*) == 8 ? 68 : 64) + && offsetof(ZSTDMT_RustCParamsUpdateProjection, savedWindowLog) + == (sizeof(void*) == 8 ? 72 : 68) + && sizeof(ZSTDMT_RustCParamsUpdateProjection) + == (sizeof(void*) == 8 ? 80 : 72)) + ? 1 : -1]; + +ZSTD_compressionParameters ZSTD_rust_params_updateCParamsWhileCompressing( + ZSTDMT_RustCParamsUpdateProjection projection); + +/* Defined in zstd_compress.c so all C translation units use the same + * preprocessor-derived block-compressor exclusion mask. */ +U32 ZSTD_getCParamsExclusionMask(void); + /* Guards code to support resizing the SeqPool. * We will want to resize the SeqPool to save memory in the future. * Until then, comment the code out since it is unused. @@ -2260,9 +2307,21 @@ void ZSTDMT_updateCParams_whileCompressing(ZSTDMT_CCtx* mtctx, const ZSTD_CCtx_p DEBUGLOG(5, "ZSTDMT_updateCParams_whileCompressing (level:%i)", compressionLevel); mtctx->params.compressionLevel = compressionLevel; - { ZSTD_compressionParameters cParams = ZSTD_getCParamsFromCCtxParams(cctxParams, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_cpm_noAttachDict); - cParams.windowLog = saved_wlog; - mtctx->params.cParams = cParams; + { ZSTDMT_RustCParamsUpdateProjection const projection = { + cctxParams->compressionLevel, + cctxParams->srcSizeHint, + ZSTD_CONTENTSIZE_UNKNOWN, + 0, + ZSTD_cpm_noAttachDict, + cctxParams->ldmParams.enableLdm, + ZSTD_LDM_DEFAULT_WINDOW_LOG, + cctxParams->cParams, + cctxParams->useRowMatchFinder, + ZSTD_getCParamsExclusionMask(), + saved_wlog + }; + mtctx->params.cParams = + ZSTD_rust_params_updateCParamsWhileCompressing(projection); } } diff --git a/rust/src/zstd_compress_params.rs b/rust/src/zstd_compress_params.rs index a79b46643..f83d8841e 100644 --- a/rust/src/zstd_compress_params.rs +++ b/rust/src/zstd_compress_params.rs @@ -15,7 +15,7 @@ //! state across the ABI. use crate::errors::{ZstdErrorCode, ERROR}; -use std::mem::size_of; +use std::mem::{offset_of, size_of}; use std::os::raw::c_int; pub const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX; @@ -130,6 +130,98 @@ pub struct ZSTD_bounds { pub upperBound: c_int, } +/// Scalar snapshot used when MT compression updates parameters between jobs. +/// +/// The private `ZSTD_CCtx_params` and `ZSTDMT_CCtx` layouts stay in C. This +/// projection carries the same scalar inputs as +/// `ZSTD_getCParamsFromCCtxParams()` and the active frame's window log. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rust_params_updateCParamsWhileCompressingState { + pub compressionLevel: c_int, + pub cctxSrcSizeHint: c_int, + pub srcSizeHint: u64, + pub dictSize: usize, + pub mode: c_int, + pub enableLdm: c_int, + pub ldmDefaultWindowLog: u32, + pub overrides: ZSTD_compressionParameters, + pub useRowMatchFinder: c_int, + pub exclusionMask: u32, + pub savedWindowLog: u32, +} + +const _: () = { + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + compressionLevel + ) == 0 + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + cctxSrcSizeHint + ) == size_of::() + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + srcSizeHint + ) == 2 * size_of::() + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + dictSize + ) == 16 + ); + assert!( + offset_of!(ZSTD_rust_params_updateCParamsWhileCompressingState, mode) + == if size_of::() == 8 { 24 } else { 20 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + enableLdm + ) == if size_of::() == 8 { 28 } else { 24 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + ldmDefaultWindowLog + ) == if size_of::() == 8 { 32 } else { 28 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + overrides + ) == if size_of::() == 8 { 36 } else { 32 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + useRowMatchFinder + ) == if size_of::() == 8 { 64 } else { 60 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + exclusionMask + ) == if size_of::() == 8 { 68 } else { 64 } + ); + assert!( + offset_of!( + ZSTD_rust_params_updateCParamsWhileCompressingState, + savedWindowLog + ) == if size_of::() == 8 { 72 } else { 68 } + ); + assert!( + size_of::() + == if size_of::() == 8 { 80 } else { 72 } + ); +}; + /* `clevels.h`, represented as raw fields so the table stays compact and easy * to compare mechanically against its C source. Field order is W, C, H, S, * L, TL, strategy. */ @@ -969,6 +1061,25 @@ fn get_cparams_from_cctx_params( ) } +fn update_cparams_while_compressing( + projection: ZSTD_rust_params_updateCParamsWhileCompressingState, +) -> ZSTD_compressionParameters { + let mut cparams = get_cparams_from_cctx_params( + projection.compressionLevel, + projection.cctxSrcSizeHint, + projection.srcSizeHint, + projection.dictSize, + projection.mode, + projection.enableLdm, + projection.ldmDefaultWindowLog, + projection.overrides, + projection.useRowMatchFinder, + projection.exclusionMask, + ); + cparams.windowLog = projection.savedWindowLog; + cparams +} + #[inline] fn make_params(cparams: ZSTD_compressionParameters) -> ZSTD_parameters { ZSTD_parameters { @@ -1195,6 +1306,16 @@ pub extern "C" fn ZSTD_rust_params_getCParamsFromCCtxParams( ) } +/// Derives the next MT job's parameters while preserving the active frame's +/// window log. The C shim owns the private-context projection and final +/// writes; Rust owns the parameter derivation and update policy. +#[no_mangle] +pub extern "C" fn ZSTD_rust_params_updateCParamsWhileCompressing( + projection: ZSTD_rust_params_updateCParamsWhileCompressingState, +) -> ZSTD_compressionParameters { + update_cparams_while_compressing(projection) +} + /// Builds `ZSTD_parameters` with the public default frame parameters. #[no_mangle] pub extern "C" fn ZSTD_rust_params_makeParams( @@ -2481,6 +2602,78 @@ mod tests { assert_ne!(no_attach, no_row); } + #[test] + fn mt_update_routes_scalar_policy_and_preserves_saved_window_log() { + let projection = ZSTD_rust_params_updateCParamsWhileCompressingState { + compressionLevel: 3, + cctxSrcSizeHint: 0, + srcSizeHint: ZSTD_CONTENTSIZE_UNKNOWN, + dictSize: 0, + mode: ZSTD_RUST_CPM_NO_ATTACH_DICT, + enableLdm: ZSTD_RUST_PS_DISABLE, + ldmDefaultWindowLog: 27, + overrides: ZSTD_compressionParameters::default(), + useRowMatchFinder: ZSTD_RUST_PS_AUTO, + exclusionMask: 0, + savedWindowLog: 18, + }; + let expected = ZSTD_rust_params_getCParamsFromCCtxParams( + projection.compressionLevel, + projection.cctxSrcSizeHint, + projection.srcSizeHint, + projection.dictSize, + projection.mode, + projection.enableLdm, + projection.ldmDefaultWindowLog, + projection.overrides, + projection.useRowMatchFinder, + projection.exclusionMask, + ); + let updated = ZSTD_rust_params_updateCParamsWhileCompressing(projection); + assert_eq!(updated.windowLog, projection.savedWindowLog); + assert_eq!(updated.windowLog, 18); + assert_eq!(updated.chainLog, expected.chainLog); + assert_eq!(updated.hashLog, expected.hashLog); + assert_eq!(updated.searchLog, expected.searchLog); + assert_eq!(updated.minMatch, expected.minMatch); + assert_eq!(updated.targetLength, expected.targetLength); + assert_eq!(updated.strategy, expected.strategy); + assert_eq!(ZSTD_rust_params_checkCParams(updated), 0); + } + + #[test] + fn mt_update_keeps_exclusion_mask_in_the_rust_routing() { + let exclusion_mask = ZSTD_RUST_EXCLUDE_BTULTRA + | ZSTD_RUST_EXCLUDE_BTOPT + | ZSTD_RUST_EXCLUDE_BTLAZY2 + | ZSTD_RUST_EXCLUDE_LAZY2 + | ZSTD_RUST_EXCLUDE_LAZY + | ZSTD_RUST_EXCLUDE_GREEDY + | ZSTD_RUST_EXCLUDE_DFAST; + let updated = ZSTD_rust_params_updateCParamsWhileCompressing( + ZSTD_rust_params_updateCParamsWhileCompressingState { + compressionLevel: 3, + cctxSrcSizeHint: 0, + srcSizeHint: ZSTD_CONTENTSIZE_UNKNOWN, + dictSize: 0, + mode: ZSTD_RUST_CPM_NO_ATTACH_DICT, + enableLdm: ZSTD_RUST_PS_DISABLE, + ldmDefaultWindowLog: 27, + overrides: ZSTD_compressionParameters { + strategy: ZSTD_BTULTRA2, + ..ZSTD_compressionParameters::default() + }, + useRowMatchFinder: ZSTD_RUST_PS_AUTO, + exclusionMask: exclusion_mask, + savedWindowLog: 21, + }, + ); + assert_eq!(updated.strategy, ZSTD_FAST); + assert_eq!(updated.targetLength, 0); + assert_eq!(updated.windowLog, 21); + assert_eq!(ZSTD_rust_params_checkCParams(updated), 0); + } + #[test] fn strategy_exclusion_mask_preserves_the_c_fallback_cascade() { let cparams = policy_cparams(ZSTD_BTULTRA2, 27);