fix(compress): select dispatch strategy from input size

The Rust dispatch gate for ZSTD_compress2 and complete-input
ZSTD_compressStream2 calls was selecting compression parameters with an
unknown content size. For the 30-byte level-4 CCtx reuse case that chooses the
dfast strategy, while the actual source size selects greedy; the Rust frame
then emitted different output from the C-owned ZSTD_compressCCtx path.

Pass the actual source size through both helper ABIs. The one-shot and complete
stream entry points now select the same strategy as C, while the existing
sentinel keeps every non-fast/non-dfast configuration on the original C
fallback. The existing fuzzer test 56 exercises the regression without
modifying the shared C test harness.

Test Plan:
- `cargo test --manifest-path rust/Cargo.toml --no-default-features
  --features compression` -- 348 passed.
- Compression clippy for the library, benches, and tests, followed by
  nightly fmt and the repeated three clippy checks -- passed.
- `make -C tests -j2 fuzzer` and `./tests/fuzzer -s4560 -t56 -i57 -v` --
  passed; the 30-byte test-56 regression now completes.
- `./tests/fuzzer -s4142 -t63 -i64 -v` -- passed all 64 focused cases.
- `make -C lib -j2 lib-mt` and `make -C lib -j2 lib-nomt` -- passed.
- `make -C tests -j2 test-zstream` -- passed 84 deterministic, 4,385 first
  randomized, and 8,137 new-API randomized cases.
- `make -C tests -j2 test-fuzzer` reaches test 113's flat-dictionary
  efficiency assertion with concurrent unstaged LDM changes; test 56 passes.
This commit is contained in:
2026-07-18 15:38:13 +02:00
parent 293cf96892
commit 63ca375557
2 changed files with 13 additions and 12 deletions
+8 -7
View File
@@ -65,8 +65,8 @@ size_t ZSTD_compressStream2_c(ZSTD_CCtx* cctx,
ZSTD_outBuffer* output,
ZSTD_inBuffer* input,
ZSTD_EndDirective endOp);
int ZSTD_rust_simpleCompress2Level(const void* cctx);
int ZSTD_rust_simpleCompressStream2Level(const void* cctx);
int ZSTD_rust_simpleCompress2Level(const void* cctx, size_t srcSize);
int ZSTD_rust_simpleCompressStream2Level(const void* cctx, size_t srcSize);
void ZSTD_rust_reduceIndex(U32* hashTable, U32 hashSize,
U32* chainTable, U32 chainSize,
U32* hashTable3, U32 hashSize3,
@@ -3886,13 +3886,13 @@ void ZSTD_rust_markSimpleCompression2Complete(void* cctx)
* 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)
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,
ZSTD_CONTENTSIZE_UNKNOWN, 0,
srcSize, 0,
ZSTD_cpm_noAttachDict)
: (ZSTD_compressionParameters){ 0 };
if (params == NULL
@@ -3959,7 +3959,7 @@ int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx)
* Rust handles this complete-input case; all other stream states stay on the
* original implementation so partial output and advanced buffering remain
* governed by the C state machine. */
int ZSTD_rust_simpleCompressStream2Level(const void* opaqueCctx)
int ZSTD_rust_simpleCompressStream2Level(const void* opaqueCctx, size_t srcSize)
{
ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)opaqueCctx;
if (cctx == NULL
@@ -3968,7 +3968,7 @@ int ZSTD_rust_simpleCompressStream2Level(const void* opaqueCctx)
|| cctx->rustSimpleCompress2Completed != 0) {
return (-2147483647 - 1);
}
return ZSTD_rust_simpleCompress2Level(opaqueCctx);
return ZSTD_rust_simpleCompress2Level(opaqueCctx, srcSize);
}
/* ZSTD_compress() is implemented by rust/src/zstd_compress.rs. */
@@ -4932,7 +4932,8 @@ size_t ZSTD_compressStream2_c( ZSTD_CCtx* cctx,
if (endOp == ZSTD_e_end
&& cctx->streamStage == zcss_init
&& cctx->pledgedSrcSizePlusOne == 0
&& ZSTD_rust_simpleCompress2Level(cctx) != (-2147483647 - 1)) {
&& ZSTD_rust_simpleCompress2Level(
cctx, input->size - input->pos) != (-2147483647 - 1)) {
void* const dst = output->dst ?
(char*)output->dst + output->pos : output->dst;
const void* const src = input->src ?
+5 -5
View File
@@ -43,7 +43,7 @@ unsafe extern "C" {
fn ZSTD_rust_compressCCtxStrategy(src_size: usize, compression_level: c_int) -> c_int;
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_rust_simpleCompress2Level(cctx: *const c_void, src_size: usize) -> c_int;
fn ZSTD_compress_usingDict(
cctx: *mut c_void,
dst: *mut c_void,
@@ -54,7 +54,7 @@ unsafe extern "C" {
dict_size: usize,
compression_level: c_int,
) -> usize;
fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void) -> c_int;
fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void, src_size: usize) -> c_int;
fn ZSTD_compress2_c(
cctx: *mut c_void,
dst: *mut c_void,
@@ -1195,7 +1195,7 @@ pub unsafe extern "C" fn ZSTD_compress2(
#[cfg(not(test))]
{
let level = unsafe { ZSTD_rust_simpleCompress2Level(cctx.cast_const()) };
let level = unsafe { ZSTD_rust_simpleCompress2Level(cctx.cast_const(), src_size) };
if level != c_int::MIN {
let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
if ERR_isError(reset) {
@@ -1244,10 +1244,10 @@ pub unsafe extern "C" fn ZSTD_compressStream2(
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let src_size = input_ref.size - input_ref.pos;
if end_op == ZSTD_E_END {
let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const()) };
let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const(), src_size) };
if level != c_int::MIN {
let src_size = input_ref.size - input_ref.pos;
let dst_capacity = output_ref.size - output_ref.pos;
let bound = ZSTD_compressBound(src_size);
if !ERR_isError(bound) && dst_capacity >= bound {