diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 65f32fd98..21f393efe 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1043,6 +1043,64 @@ typedef char ZSTD_rust_compress_stream2_mt_coordinator_state_layout[ == 6 * sizeof(void*)) ? 1 : -1]; int ZSTD_rust_simpleCompress2Level(const void* cctx, size_t srcSize); +typedef struct { + int format; + int contentSizeFlag; + int checksumFlag; + int cParamsWindowLog; + int cParamsChainLog; + int cParamsHashLog; + int cParamsSearchLog; + int cParamsMinMatch; + int cParamsTargetLength; + int requestedStrategy; + int forceWindow; + int targetCBlockSizeSet; + int srcSizeHintSet; + int attachDictPref; + int literalCompressionMode; + int nbWorkersSet; + int jobSizeSet; + int overlapLogSet; + int rsyncable; + int ldmEnable; + int ldmHashLogSet; + int ldmBucketSizeLog; + int ldmMinMatchLengthSet; + int ldmHashRateLog; + int ldmWindowLogSet; + int dedicatedDictSearch; + int inBufferMode; + int outBufferMode; + int blockDelimiters; + int validateSequences; + int postBlockSplitter; + int preBlockSplitterLevelSet; + int maxBlockSizeSet; + int maxBlockSizeSetByApi; + int useRowMatchFinder; + int deterministicRefPrefix; + int prefetchCDictTables; + int matchFinderFallback; + int extSeqProdFuncSet; + int searchForExternalRepcodes; + int poolSet; + int collectSequences; + int cdictSet; + int prefixDictSet; + int localDictSet; + int compressionLevel; + int selectedStrategy; +} ZSTD_rust_simpleCompress2LevelState; +int ZSTD_rust_simpleCompress2LevelPolicy( + const ZSTD_rust_simpleCompress2LevelState* state); +typedef char ZSTD_rust_simple_compress2_level_state_layout[ + (offsetof(ZSTD_rust_simpleCompress2LevelState, format) == 0 + && offsetof(ZSTD_rust_simpleCompress2LevelState, selectedStrategy) + == 46 * sizeof(int) + && sizeof(ZSTD_rust_simpleCompress2LevelState) + == 47 * sizeof(int)) + ? 1 : -1]; typedef int (*ZSTD_rust_simpleCompress2Level_f)(const void* cctx, size_t srcSize); typedef struct { int streamStage; @@ -7236,78 +7294,70 @@ void ZSTD_rust_markSimpleCompression2Complete(void* cctx) ((ZSTD_CCtx*)cctx)->rustSimpleCompress2Completed = 1; } -/* Return the requested level when the context has only the parameters that - * the Rust frame path currently implements. All other contexts continue - * through ZSTD_compress2_c(), preserving the full C stateful implementation - * while this boundary is migrated incrementally. */ +/* Project private context state before Rust evaluates the simple-path policy. + * The parameter-selection leaf remains C-owned because it depends on the + * private compression-parameter tables and implementation details. */ int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx, size_t srcSize) { ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)opaqueCctx; - ZSTD_CCtx_params const* const params = cctx ? &cctx->requestedParams : NULL; - ZSTD_compressionParameters const cParams = params - ? ZSTD_getCParams_internal(params->compressionLevel, - srcSize, 0, - ZSTD_cpm_noAttachDict) - : (ZSTD_compressionParameters){ 0 }; - if (params == NULL - || params->format != ZSTD_f_zstd1 - || params->fParams.contentSizeFlag == 0 - || params->fParams.checksumFlag != 0 - || params->cParams.windowLog != 0 - || params->cParams.chainLog != 0 - || params->cParams.hashLog != 0 - || params->cParams.searchLog != 0 - || params->cParams.minMatch != 0 - || params->cParams.targetLength != 0 - || params->cParams.strategy != 0 - || params->forceWindow != 0 - || params->targetCBlockSize != 0 - || params->srcSizeHint != 0 - || params->attachDictPref != ZSTD_dictDefaultAttach - || params->literalCompressionMode != (ZSTD_ParamSwitch_e)ZSTD_lcm_auto - || params->nbWorkers != 0 - || params->jobSize != 0 - || params->overlapLog != 0 - || params->rsyncable != 0 - || params->ldmParams.enableLdm != ZSTD_ps_auto - || params->ldmParams.hashLog != 0 - || (params->ldmParams.bucketSizeLog != 0 - && params->ldmParams.bucketSizeLog != 9999) - || params->ldmParams.minMatchLength != 0 - || (params->ldmParams.hashRateLog != 0 - && params->ldmParams.hashRateLog != 9999) - || params->ldmParams.windowLog != 0 - || params->enableDedicatedDictSearch != 0 - || params->inBufferMode != ZSTD_bm_buffered - || params->outBufferMode != ZSTD_bm_buffered - || params->blockDelimiters != ZSTD_sf_noBlockDelimiters - || params->validateSequences != 0 - || params->postBlockSplitter != ZSTD_ps_auto - || params->preBlockSplitter_level != 0 - || params->maxBlockSize != 0 - || cctx->rustSimpleCompress2MaxBlockSizeSet != 0 - || params->useRowMatchFinder != ZSTD_ps_auto - || params->deterministicRefPrefix != 0 - || params->prefetchCDictTables != ZSTD_ps_auto - || params->enableMatchFinderFallback != 0 - || params->extSeqProdFunc != NULL - || params->searchForExternalRepcodes != ZSTD_ps_auto - || cctx->pool != NULL - || cctx->seqCollector.collectSequences != 0 - || cctx->cdict != NULL - || cctx->prefixDict.dict != NULL - || cctx->localDict.dict != NULL - || cctx->localDict.dictBuffer != NULL - || cctx->localDict.cdict != NULL) { - return (-2147483647 - 1); + if (cctx == NULL) return (-2147483647 - 1); + { + ZSTD_CCtx_params const* const params = &cctx->requestedParams; + ZSTD_compressionParameters const cParams = ZSTD_getCParams_internal( + params->compressionLevel, srcSize, 0, ZSTD_cpm_noAttachDict); + ZSTD_rust_simpleCompress2LevelState const state = { + (int)params->format, + (int)params->fParams.contentSizeFlag, + (int)params->fParams.checksumFlag, + (int)params->cParams.windowLog, + (int)params->cParams.chainLog, + (int)params->cParams.hashLog, + (int)params->cParams.searchLog, + (int)params->cParams.minMatch, + (int)params->cParams.targetLength, + (int)params->cParams.strategy, + params->forceWindow != 0, + (int)(params->targetCBlockSize != 0), + (int)(params->srcSizeHint != 0), + (int)params->attachDictPref, + (int)params->literalCompressionMode, + (int)(params->nbWorkers != 0), + (int)(params->jobSize != 0), + (int)(params->overlapLog != 0), + params->rsyncable != 0, + (int)params->ldmParams.enableLdm, + (int)(params->ldmParams.hashLog != 0), + (int)params->ldmParams.bucketSizeLog, + (int)(params->ldmParams.minMatchLength != 0), + (int)params->ldmParams.hashRateLog, + (int)(params->ldmParams.windowLog != 0), + params->enableDedicatedDictSearch != 0, + (int)params->inBufferMode, + (int)params->outBufferMode, + (int)params->blockDelimiters, + params->validateSequences != 0, + (int)params->postBlockSplitter, + (int)(params->preBlockSplitter_level != 0), + (int)(params->maxBlockSize != 0), + (int)(cctx->rustSimpleCompress2MaxBlockSizeSet != 0), + (int)params->useRowMatchFinder, + params->deterministicRefPrefix != 0, + (int)params->prefetchCDictTables, + params->enableMatchFinderFallback != 0, + (int)(params->extSeqProdFunc != NULL), + (int)params->searchForExternalRepcodes, + (int)(cctx->pool != NULL), + cctx->seqCollector.collectSequences != 0, + (int)(cctx->cdict != NULL), + (int)(cctx->prefixDict.dict != NULL), + (int)(cctx->localDict.dict != NULL + || cctx->localDict.dictBuffer != NULL + || cctx->localDict.cdict != NULL), + params->compressionLevel, + (int)cParams.strategy + }; + return ZSTD_rust_simpleCompress2LevelPolicy(&state); } - /* The Rust frame leaf currently implements only the fast and double-fast - * match finders. Keep lazy and optimal strategies on the stateful path, - * which owns their strategy-specific match-table lifecycle. */ - if (cParams.strategy != ZSTD_fast && cParams.strategy != ZSTD_dfast) { - return (-2147483647 - 1); - } - return params->compressionLevel; } /* Return the simple level only while a new stream frame can be initialized. diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 1e066e8ab..9111f2362 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -151,6 +151,143 @@ const _: () = { pub type ZSTD_rust_simpleCompress2LevelFn = unsafe extern "C" fn(*const c_void, usize) -> c_int; +const ZSTD_DICT_DEFAULT_ATTACH: c_int = 0; +const ZSTD_LCM_AUTO: c_int = 0; +const ZSTD_LDM_UNUSED_LOG: c_int = 9999; + +/// Scalar projection of the private context and requested parameters used to +/// select the complete-input simple-compression fast path. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rust_simpleCompress2LevelState { + format: c_int, + content_size_flag: c_int, + checksum_flag: c_int, + cparams_window_log: c_int, + cparams_chain_log: c_int, + cparams_hash_log: c_int, + cparams_search_log: c_int, + cparams_min_match: c_int, + cparams_target_length: c_int, + requested_strategy: c_int, + force_window: c_int, + target_c_block_size_set: c_int, + src_size_hint_set: c_int, + attach_dict_pref: c_int, + literal_compression_mode: c_int, + nb_workers_set: c_int, + job_size_set: c_int, + overlap_log_set: c_int, + rsyncable: c_int, + ldm_enable: c_int, + ldm_hash_log_set: c_int, + ldm_bucket_size_log: c_int, + ldm_min_match_length_set: c_int, + ldm_hash_rate_log: c_int, + ldm_window_log_set: c_int, + dedicated_dict_search: c_int, + in_buffer_mode: c_int, + out_buffer_mode: c_int, + block_delimiters: c_int, + validate_sequences: c_int, + post_block_splitter: c_int, + pre_block_splitter_level_set: c_int, + max_block_size_set: c_int, + max_block_size_set_by_api: c_int, + use_row_match_finder: c_int, + deterministic_ref_prefix: c_int, + prefetch_cdict_tables: c_int, + match_finder_fallback: c_int, + ext_seq_prod_func_set: c_int, + search_for_external_repcodes: c_int, + pool_set: c_int, + collect_sequences: c_int, + cdict_set: c_int, + prefix_dict_set: c_int, + local_dict_set: c_int, + compression_level: c_int, + selected_strategy: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_simpleCompress2LevelState, format) == 0); + assert!( + offset_of!(ZSTD_rust_simpleCompress2LevelState, selected_strategy) + == 46 * size_of::() + ); + assert!(size_of::() == 47 * size_of::()); +}; + +#[inline] +fn simple_compress2_level_policy(state: &ZSTD_rust_simpleCompress2LevelState) -> c_int { + if state.format != ZSTD_F_ZSTD1 + || state.content_size_flag == 0 + || state.checksum_flag != 0 + || state.cparams_window_log != 0 + || state.cparams_chain_log != 0 + || state.cparams_hash_log != 0 + || state.cparams_search_log != 0 + || state.cparams_min_match != 0 + || state.cparams_target_length != 0 + || state.requested_strategy != 0 + || state.force_window != 0 + || state.target_c_block_size_set != 0 + || state.src_size_hint_set != 0 + || state.attach_dict_pref != ZSTD_DICT_DEFAULT_ATTACH + || state.literal_compression_mode != ZSTD_LCM_AUTO + || state.nb_workers_set != 0 + || state.job_size_set != 0 + || state.overlap_log_set != 0 + || state.rsyncable != 0 + || state.ldm_enable != ZSTD_RUST_PS_AUTO + || state.ldm_hash_log_set != 0 + || (state.ldm_bucket_size_log != 0 && state.ldm_bucket_size_log != ZSTD_LDM_UNUSED_LOG) + || state.ldm_min_match_length_set != 0 + || (state.ldm_hash_rate_log != 0 && state.ldm_hash_rate_log != ZSTD_LDM_UNUSED_LOG) + || state.ldm_window_log_set != 0 + || state.dedicated_dict_search != 0 + || state.in_buffer_mode != ZSTD_BM_BUFFERED + || state.out_buffer_mode != ZSTD_BM_BUFFERED + || state.block_delimiters != ZSTD_SF_NO_BLOCK_DELIMITERS + || state.validate_sequences != 0 + || state.post_block_splitter != ZSTD_RUST_PS_AUTO + || state.pre_block_splitter_level_set != 0 + || state.max_block_size_set != 0 + || state.max_block_size_set_by_api != 0 + || state.use_row_match_finder != ZSTD_RUST_PS_AUTO + || state.deterministic_ref_prefix != 0 + || state.prefetch_cdict_tables != ZSTD_RUST_PS_AUTO + || state.match_finder_fallback != 0 + || state.ext_seq_prod_func_set != 0 + || state.search_for_external_repcodes != ZSTD_RUST_PS_AUTO + || state.pool_set != 0 + || state.collect_sequences != 0 + || state.cdict_set != 0 + || state.prefix_dict_set != 0 + || state.local_dict_set != 0 + { + return c_int::MIN; + } + + if state.selected_strategy != ZSTD_FAST && state.selected_strategy != ZSTD_DFAST { + return c_int::MIN; + } + state.compression_level +} + +/// Decide whether a projected context can use the complete-input Rust frame +/// compressor. C retains private-layout projection and parameter selection; +/// Rust owns the eligibility predicate and strategy gate. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_simpleCompress2LevelPolicy( + state: *const ZSTD_rust_simpleCompress2LevelState, +) -> c_int { + let Some(state) = (unsafe { state.as_ref() }) else { + return c_int::MIN; + }; + simple_compress2_level_policy(state) +} + const ZSTD_WINDOW_START_INDEX: u32 = 2; const ZSTD_DUBT_UNSORTED_MARK: u32 = 1; const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20; @@ -14834,6 +14971,55 @@ mod tests { ); } + fn simple_compress2_level_policy_state() -> ZSTD_rust_simpleCompress2LevelState { + ZSTD_rust_simpleCompress2LevelState { + format: ZSTD_F_ZSTD1, + content_size_flag: 1, + compression_level: 5, + selected_strategy: ZSTD_FAST, + ..Default::default() + } + } + + #[test] + fn simple_compress2_level_policy_accepts_fast_and_double_fast() { + for strategy in [ZSTD_FAST, ZSTD_DFAST] { + let mut state = simple_compress2_level_policy_state(); + state.selected_strategy = strategy; + assert_eq!(simple_compress2_level_policy(&state), 5); + } + } + + #[test] + fn simple_compress2_level_policy_rejects_advanced_state() { + let mut state = simple_compress2_level_policy_state(); + state.checksum_flag = 1; + assert_eq!(simple_compress2_level_policy(&state), c_int::MIN); + + let mut state = simple_compress2_level_policy_state(); + state.cdict_set = 1; + assert_eq!(simple_compress2_level_policy(&state), c_int::MIN); + + let mut state = simple_compress2_level_policy_state(); + state.ldm_bucket_size_log = 1; + assert_eq!(simple_compress2_level_policy(&state), c_int::MIN); + + let mut state = simple_compress2_level_policy_state(); + state.ldm_bucket_size_log = ZSTD_LDM_UNUSED_LOG; + assert_eq!(simple_compress2_level_policy(&state), 5); + } + + #[test] + fn simple_compress2_level_policy_rejects_non_fast_or_null_state() { + let mut state = simple_compress2_level_policy_state(); + state.selected_strategy = ZSTD_BTLAZY2; + assert_eq!(simple_compress2_level_policy(&state), c_int::MIN); + assert_eq!( + unsafe { ZSTD_rust_simpleCompress2LevelPolicy(ptr::null()) }, + c_int::MIN + ); + } + #[derive(Default)] struct SimpleStreamPolicyTestContext { calls: usize,