From 5c448120cf5f9f688cb2b9c08e02e3e60934914d Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 12 Jul 2026 19:16:14 +0200 Subject: [PATCH] feat(compress): route simple context frames through Rust Dispatch ordinary dictionary-free ZSTD_compress2 calls through the Rust frame compressor while retaining the original C implementation for advanced contexts. Prepare the C workspace first so allocation downsizing and static-context limits remain observable. Mark successful Rust frames so a following one-shot ZSTD_compressStream2(..., ZSTD_e_end) call uses the same output path. Other streaming directives and advanced parameters remain on the C implementation. Test Plan: - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression zstd_compress::tests - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo clippy --manifest-path rust/Cargo.toml --no-default-features --features compression -- -D warnings - cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check - make -B -C tests fuzzer - ./tests/fuzzer -t114 -i1000 --no-big-tests -s1 The basic fuzzer run now reaches the existing Rust one-shot compression-ratio limit at test 113; the new stateful reuse tests pass before that point. --- lib/compress/zstd_compress.c | 128 +++++++++++++++++++++++++- lib/compress/zstd_compress_internal.h | 2 + rust/src/zstd_compress.rs | 71 +++++++++++++- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index c7f4988f7..b93870e58 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -38,6 +38,15 @@ size_t ZSTD_rust_writeFrameHeader(void* dst, size_t dstCapacity, U32 windowLog, U64 pledgedSrcSize, U32 dictID); size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx); +size_t ZSTD_rust_prepareCCtxForSimpleCompression(void* cctx, + size_t srcSize, + int compressionLevel); +size_t ZSTD_rust_resetCCtxForSimpleCompressionSession(void* cctx); +void ZSTD_rust_markSimpleCompression2Complete(void* cctx); +size_t ZSTD_compress2_c(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); +int ZSTD_rust_simpleCompress2Level(const void* cctx); /* Context-free compression-parameter selection and sizing leaves live in * Rust (rust/src/zstd_compress_params.rs). This file retains @@ -1146,6 +1155,7 @@ size_t ZSTD_CCtx_refPrefix_advanced( * Also dumps dictionary */ size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset) { + cctx->rustSimpleCompress2Completed = 0; if ( (reset == ZSTD_reset_session_only) || (reset == ZSTD_reset_session_and_parameters) ) { cctx->streamStage = zcss_init; @@ -4472,6 +4482,94 @@ size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx) return ZSTD_CCtx_reset((ZSTD_CCtx*)cctx, ZSTD_reset_session_and_parameters); } +size_t ZSTD_rust_prepareCCtxForSimpleCompression(void* opaqueCctx, + size_t srcSize, + int compressionLevel) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)opaqueCctx; + ZSTD_CCtx_params params; + ZSTD_parameters const zstdParams = ZSTD_getParams_internal( + compressionLevel, srcSize, 0, ZSTD_cpm_noAttachDict); + ZSTD_CCtxParams_init_internal( + ¶ms, &zstdParams, + compressionLevel == 0 ? ZSTD_CLEVEL_DEFAULT : compressionLevel); + FORWARD_IF_ERROR(ZSTD_compressBegin_internal( + cctx, NULL, 0, ZSTD_dct_auto, ZSTD_dtlm_fast, NULL, + ¶ms, srcSize, ZSTDb_not_buffered), ""); + return ZSTD_CCtx_reset(cctx, ZSTD_reset_session_only); +} + +size_t ZSTD_rust_resetCCtxForSimpleCompressionSession(void* cctx) +{ + return ZSTD_CCtx_reset((ZSTD_CCtx*)cctx, ZSTD_reset_session_only); +} + +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. */ +int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx) +{ + ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)opaqueCctx; + ZSTD_CCtx_params const* const params = cctx ? &cctx->requestedParams : NULL; + 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_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 + || 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.dictBuffer != NULL + || cctx->localDict.cdict != NULL) { + return (-2147483647 - 1); + } + return params->compressionLevel; +} + /* ZSTD_compress() is implemented by rust/src/zstd_compress.rs. */ @@ -5418,6 +5516,30 @@ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, assert(cctx != NULL); /* transparent initialization stage */ + if (cctx->rustSimpleCompress2Completed) { + if (endOp == ZSTD_e_end + && cctx->streamStage == zcss_init + && cctx->pledgedSrcSizePlusOne == 0 + && ZSTD_rust_simpleCompress2Level(cctx) != (-2147483647 - 1)) { + void* const dst = output->dst ? + (char*)output->dst + output->pos : output->dst; + const void* const src = input->src ? + (const char*)input->src + input->pos : input->src; + size_t const result = ZSTD_compress2( + cctx, dst, output->size - output->pos, + src, input->size - input->pos); + cctx->rustSimpleCompress2Completed = 0; + if (!ZSTD_isError(result)) { + output->pos += result; + input->pos = input->size; + return 0; + } + } + /* A different streaming directive or an advanced parameter means the + * frame can no longer reuse the one-shot Rust result. */ + cctx->rustSimpleCompress2Completed = 0; + } + if (cctx->streamStage == zcss_init) { size_t const inputSize = input->size - input->pos; /* no obligation to start from pos==0 */ size_t const totalInputSize = inputSize + cctx->stableIn_notConsumed; @@ -5526,9 +5648,9 @@ size_t ZSTD_compressStream2_simpleArgs ( } } -size_t ZSTD_compress2(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize) +size_t ZSTD_compress2_c(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) { ZSTD_bufferMode_e const originalInBufferMode = cctx->requestedParams.inBufferMode; ZSTD_bufferMode_e const originalOutBufferMode = cctx->requestedParams.outBufferMode; diff --git a/lib/compress/zstd_compress_internal.h b/lib/compress/zstd_compress_internal.h index ca5e2a4c5..a81ae184e 100644 --- a/lib/compress/zstd_compress_internal.h +++ b/lib/compress/zstd_compress_internal.h @@ -516,6 +516,8 @@ struct ZSTD_CCtx_s { size_t outBuffFlushedSize; ZSTD_cStreamStage streamStage; U32 frameEnded; + /* The next one-shot stream call may reuse a Rust-compressed frame. */ + unsigned rustSimpleCompress2Completed; /* Stable in/out buffer verification */ ZSTD_inBuffer expectedInBuffer; diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 23f7a12c2..a2ea3dce5 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -9,7 +9,10 @@ //! already migrated compression leaves. `ZSTD_compressCCtx` uses the same //! path after a narrow C-owned context reset; the public context lifecycle //! and streaming APIs remain C-owned because they share the -//! configuration-dependent private `ZSTD_CCtx_s` layout. +//! configuration-dependent private `ZSTD_CCtx_s` layout. `ZSTD_compress2` +//! dispatches simple, dictionary-free contexts through the same Rust frame +//! path and retains the C implementation for advanced contexts. A following +//! one-shot `ZSTD_compressStream2(..., ZSTD_e_end)` call can reuse that path. use crate::errors::{ERR_isError, ZstdErrorCode, ERROR}; use crate::mem::MEM_writeLE24; @@ -29,6 +32,21 @@ use std::ptr; #[cfg(not(test))] unsafe extern "C" { fn ZSTD_rust_resetCCtxForSimpleCompression(cctx: *mut c_void) -> usize; + fn ZSTD_rust_prepareCCtxForSimpleCompression( + cctx: *mut c_void, + src_size: usize, + compression_level: c_int, + ) -> usize; + fn ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx: *mut c_void) -> usize; + fn ZSTD_rust_markSimpleCompression2Complete(cctx: *mut c_void); + fn ZSTD_rust_simpleCompress2Level(cctx: *const c_void) -> c_int; + fn ZSTD_compress2_c( + cctx: *mut c_void, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + ) -> usize; } const ZSTD_FAST: c_int = 1; @@ -360,10 +378,61 @@ pub unsafe extern "C" fn ZSTD_compressCCtx( if ERR_isError(reset) { return reset; } + let prepare = + unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, compression_level) }; + if ERR_isError(prepare) { + return prepare; + } } unsafe { compress_frame(dst, dst_capacity, src, src_size, compression_level) } } +/// Stateful compression entry point during the context migration. +/// +/// A context with only the ordinary frame settings is reset by the C shim and +/// compressed through the Rust frame path. Contexts using dictionaries, +/// checksums, target-sized blocks, sequence collection, or other advanced +/// state still use the renamed C implementation until their state is moved. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_compress2( + cctx: *mut c_void, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, +) -> usize { + if cctx.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + + #[cfg(test)] + { + unsafe { compress_frame(dst, dst_capacity, src, src_size, 3) } + } + + #[cfg(not(test))] + { + let level = unsafe { ZSTD_rust_simpleCompress2Level(cctx.cast_const()) }; + if level != c_int::MIN { + let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) }; + if ERR_isError(reset) { + return reset; + } + let prepare = + unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, level) }; + if ERR_isError(prepare) { + return prepare; + } + let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) }; + if !ERR_isError(result) { + unsafe { ZSTD_rust_markSimpleCompression2Complete(cctx) }; + } + return result; + } + unsafe { ZSTD_compress2_c(cctx, dst, dst_capacity, src, src_size) } + } +} + #[cfg(test)] mod tests { use super::*;