From 3a10d86c767f9dbbe5021afa3f02c060b0631594 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 03:14:33 +0200 Subject: [PATCH] feat(compress): move advanced CCtx parameter init to Rust Move ZSTD_CCtxParams_init_advanced and its default-resolution policy into the Rust parameter API while retaining the C public ABI wrapper. Preserve validation, zeroing, and the existing row-match, block-splitter, LDM, and repcode defaults. Test Plan:\n- cargo clippy --manifest-path rust/Cargo.toml\n- cargo clippy --manifest-path rust/Cargo.toml --benches\n- cargo clippy --manifest-path rust/Cargo.toml --tests\n- cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check\n- cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression zstd_compress_params_api\n- make -B -C lib -j2 lib --- lib/compress/zstd_compress.c | 7 +- rust/src/zstd_compress_params_api.rs | 194 ++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 9 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index cad72dc4f..ed062fc19 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -89,6 +89,8 @@ int ZSTD_rust_cctx_params_is_multithreaded(void); int ZSTD_rust_cctx_params_job_size_min(void); ZSTD_CCtx_params* ZSTD_rust_createCCtxParams(ZSTD_customMem customMem); size_t ZSTD_rust_freeCCtxParams(ZSTD_CCtx_params* params); +size_t ZSTD_rust_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, + ZSTD_parameters params); #define ZSTD_RUST_CCTX_PARAMS_ASSERT(name, condition) \ typedef char name[(condition) ? 1 : -1] @@ -577,10 +579,7 @@ ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params) { - RETURN_ERROR_IF(!cctxParams, GENERIC, "NULL pointer!"); - FORWARD_IF_ERROR( ZSTD_checkCParams(params.cParams) , ""); - ZSTD_CCtxParams_init_internal(cctxParams, ¶ms, ZSTD_NO_CLEVEL); - return 0; + return ZSTD_rust_CCtxParams_init_advanced(cctxParams, params); } /** diff --git a/rust/src/zstd_compress_params_api.rs b/rust/src/zstd_compress_params_api.rs index 44517edca..30d593359 100644 --- a/rust/src/zstd_compress_params_api.rs +++ b/rust/src/zstd_compress_params_api.rs @@ -5,15 +5,20 @@ //! Rust implementation of the narrow `ZSTD_CCtx_params` object API. //! -//! Advanced initialization, bounds calculation, and context policy remain in -//! `zstd_compress.c`. This module owns the parameter object's storage -//! lifecycle, reset/initialization, parameter set/get, and sequence-producer -//! registration entry points. +//! Advanced initialization, bounds calculation, and context policy are split +//! narrowly between this module and `zstd_compress.c`. This module owns the +//! parameter object's storage lifecycle, initialization, parameter set/get, +//! sequence-producer registration, and advanced initialization's small +//! default-resolution policy. use crate::errors::{ZstdErrorCode, ERROR}; #[cfg(test)] use crate::zstd_compress_params::ZSTD_bounds; -use crate::zstd_compress_params::{ZSTD_compressionParameters, ZSTD_frameParameters}; +use crate::zstd_compress_params::{ + ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters, + ZSTD_rust_params_checkCParams, ZSTD_rust_params_resolveMaxBlockSize, ZSTD_RUST_PS_DISABLE, + ZSTD_RUST_PS_ENABLE, +}; use std::mem::size_of; use std::os::raw::{c_int, c_void}; use std::ptr; @@ -119,11 +124,17 @@ pub struct ZSTD_CCtx_params { } const DEFAULT_CLEVEL: c_int = 3; +const NO_CLEVEL: c_int = 0; #[cfg(test)] const PS_AUTO: c_int = 0; #[cfg(test)] const PS_DISABLE: c_int = 2; +/* Private strategy values used by the existing C policy helpers. */ +const STRATEGY_GREEDY: c_int = 3; +const STRATEGY_LAZY2: c_int = 5; +const STRATEGY_BTOPT: c_int = 7; + #[cfg(test)] const BLOCKSIZE_MAX: usize = 1 << 17; #[cfg(test)] @@ -301,6 +312,72 @@ unsafe fn init_impl(params: *mut ZSTD_CCtx_params, compression_level: c_int) -> 0 } +#[inline] +fn resolve_row_match_finder(cparams: ZSTD_compressionParameters) -> c_int { + if (STRATEGY_GREEDY..=STRATEGY_LAZY2).contains(&cparams.strategy) && cparams.windowLog > 14 { + ZSTD_RUST_PS_ENABLE + } else { + ZSTD_RUST_PS_DISABLE + } +} + +#[inline] +fn resolve_block_splitter(cparams: ZSTD_compressionParameters) -> c_int { + if cparams.strategy >= STRATEGY_BTOPT && cparams.windowLog >= 17 { + ZSTD_RUST_PS_ENABLE + } else { + ZSTD_RUST_PS_DISABLE + } +} + +#[inline] +fn resolve_ldm(cparams: ZSTD_compressionParameters) -> c_int { + if cparams.strategy >= STRATEGY_BTOPT && cparams.windowLog >= 27 { + ZSTD_RUST_PS_ENABLE + } else { + ZSTD_RUST_PS_DISABLE + } +} + +#[inline] +fn resolve_external_repcode_search() -> c_int { + /* ZSTD_NO_CLEVEL is below the threshold used by the C policy helper. */ + 2 +} + +unsafe fn init_advanced_impl(params: *mut ZSTD_CCtx_params, zstd_params: ZSTD_parameters) -> usize { + if params.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + + let error = ZSTD_rust_params_checkCParams(zstd_params.cParams); + if error != 0 { + return error; + } + + unsafe { + ptr::write_bytes(params.cast::(), 0, size_of::()); + (*params).cParams = zstd_params.cParams; + (*params).fParams = zstd_params.fParams; + (*params).compressionLevel = NO_CLEVEL; + (*params).useRowMatchFinder = resolve_row_match_finder(zstd_params.cParams); + (*params).postBlockSplitter = resolve_block_splitter(zstd_params.cParams); + (*params).ldmParams.enableLdm = resolve_ldm(zstd_params.cParams); + (*params).validateSequences = 0; + (*params).maxBlockSize = ZSTD_rust_params_resolveMaxBlockSize((*params).maxBlockSize); + (*params).searchForExternalRepcodes = resolve_external_repcode_search(); + } + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_CCtxParams_init_advanced( + params: *mut ZSTD_CCtx_params, + zstd_params: ZSTD_parameters, +) -> usize { + unsafe { init_advanced_impl(params, zstd_params) } +} + unsafe fn custom_calloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void { if let Some(custom_alloc) = custom_mem.customAlloc { let allocation = unsafe { custom_alloc(custom_mem.opaque, size) }; @@ -929,6 +1006,113 @@ mod tests { ); } + #[test] + fn advanced_init_resolves_policy_and_resets_internal_fields() { + let cases = [ + ( + ZSTD_parameters { + cParams: ZSTD_compressionParameters { + windowLog: 15, + chainLog: 15, + hashLog: 15, + searchLog: 5, + minMatch: 4, + targetLength: 16, + strategy: STRATEGY_GREEDY, + }, + fParams: ZSTD_frameParameters { + contentSizeFlag: 0, + checksumFlag: 1, + noDictIDFlag: 1, + }, + }, + ZSTD_RUST_PS_ENABLE, + ZSTD_RUST_PS_DISABLE, + ZSTD_RUST_PS_DISABLE, + ), + ( + ZSTD_parameters { + cParams: ZSTD_compressionParameters { + windowLog: 27, + chainLog: 27, + hashLog: 25, + searchLog: 9, + minMatch: 3, + targetLength: 999, + strategy: STRATEGY_BTOPT + 2, + }, + fParams: ZSTD_frameParameters { + contentSizeFlag: 1, + checksumFlag: 0, + noDictIDFlag: 0, + }, + }, + ZSTD_RUST_PS_DISABLE, + ZSTD_RUST_PS_ENABLE, + ZSTD_RUST_PS_ENABLE, + ), + ]; + + for (zstd_params, expected_row, expected_splitter, expected_ldm) in cases { + let mut storage = MaybeUninit::::zeroed(); + let params = storage.as_mut_ptr(); + unsafe { + (*params).customMem.opaque = ptr::dangling_mut::(); + assert_eq!(ZSTD_rust_CCtxParams_init_advanced(params, zstd_params), 0); + assert_eq!((*params).cParams, zstd_params.cParams); + assert_eq!((*params).fParams, zstd_params.fParams); + assert_eq!((*params).compressionLevel, NO_CLEVEL); + assert_eq!((*params).useRowMatchFinder, expected_row); + assert_eq!((*params).postBlockSplitter, expected_splitter); + assert_eq!((*params).ldmParams.enableLdm, expected_ldm); + assert_eq!((*params).validateSequences, 0); + assert_eq!((*params).maxBlockSize, BLOCKSIZE_MAX); + assert_eq!((*params).searchForExternalRepcodes, PS_DISABLE); + assert!((*params).customMem.customAlloc.is_none()); + assert!((*params).customMem.customFree.is_none()); + assert!((*params).customMem.opaque.is_null()); + } + } + } + + #[test] + fn advanced_init_rejects_null_and_invalid_cparams_before_mutating() { + let valid_params = ZSTD_parameters { + cParams: ZSTD_compressionParameters { + windowLog: 20, + chainLog: 20, + hashLog: 20, + searchLog: 5, + minMatch: 4, + targetLength: 16, + strategy: STRATEGY_GREEDY, + }, + fParams: ZSTD_frameParameters::default(), + }; + assert_eq!( + unsafe { ZSTD_rust_CCtxParams_init_advanced(ptr::null_mut(), valid_params) }, + ERROR(ZstdErrorCode::Generic) + ); + + let mut storage = MaybeUninit::::zeroed(); + let params = storage.as_mut_ptr(); + unsafe { + (*params).format = 1; + (*params).customMem.opaque = ptr::dangling_mut::(); + } + + let mut invalid_params = valid_params; + invalid_params.cParams.minMatch = 2; + assert_eq!( + unsafe { ZSTD_rust_CCtxParams_init_advanced(params, invalid_params) }, + ERROR(ZstdErrorCode::ParameterOutOfBound) + ); + unsafe { + assert_eq!((*params).format, 1); + assert_eq!((*params).customMem.opaque, ptr::dangling_mut::()); + } + } + #[test] fn allocation_lifecycle_preserves_default_and_custom_memory_contracts() { unsafe extern "C" fn counting_alloc(opaque: *mut c_void, size: usize) -> *mut c_void {