feat(compress): route complete stream frames through Rust

Route ordinary complete-input ZSTD_compressStream2 calls through the Rust frame compressor while retaining the C state machine for partial, advanced, and dictionary-backed streams. Track explicit maxBlockSize requests so the migrated path preserves the original byte-identical default behavior, and extend the C archive smoke test across context reuse and fallback cases.

Test Plan:

- cargo clippy --manifest-path rust/Cargo.toml

- cargo clippy --manifest-path rust/Cargo.toml --benches

- cargo clippy --manifest-path rust/Cargo.toml --tests

- cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression (133 passed)

- make -B -C tests -j2 test-rust-lib-smoke and ./tests/rustLibSmoke

- make -B -C tests -j2 test-zstream (passed, including both APIs and maxBlockSize case 78)
This commit is contained in:
2026-07-18 01:52:28 +02:00
parent 96a0eabdb9
commit 23d45378bd
4 changed files with 232 additions and 11 deletions
+95 -5
View File
@@ -8,14 +8,15 @@
//! The layout-independent one-shot entry point in this module drives the
//! 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. `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.
//! remains C-owned because it shares the configuration-dependent private
//! `ZSTD_CCtx_s` layout. `ZSTD_compress2` and the complete-input simple
//! `ZSTD_compressStream2(..., ZSTD_e_end)` path dispatch through Rust while
//! retaining the C implementation for advanced and partial-stream cases.
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use crate::mem::MEM_writeLE24;
#[cfg(not(test))]
use crate::zstd_compress_api::ZSTD_compressBound;
use crate::zstd_compress_frame::{ZSTD_rust_writeFrameHeader, ZSTD_writeLastEmptyBlock};
use crate::zstd_compress_params::{
ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams,
@@ -40,6 +41,7 @@ unsafe extern "C" {
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_simpleCompressStream2Level(cctx: *const c_void) -> c_int;
fn ZSTD_compress2_c(
cctx: *mut c_void,
dst: *mut c_void,
@@ -47,12 +49,36 @@ unsafe extern "C" {
src: *const c_void,
src_size: usize,
) -> usize;
fn ZSTD_compressStream2_c(
cctx: *mut c_void,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
end_op: c_int,
) -> usize;
}
const ZSTD_FAST: c_int = 1;
const ZSTD_DFAST: c_int = 2;
const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
#[cfg(not(test))]
const ZSTD_E_END: c_int = 2;
#[cfg(not(test))]
#[repr(C)]
pub struct ZSTD_inBuffer {
src: *const c_void,
size: usize,
pos: usize,
}
#[cfg(not(test))]
#[repr(C)]
pub struct ZSTD_outBuffer {
dst: *mut c_void,
size: usize,
pos: usize,
}
/* HUF_WORKSPACE_SIZE + (MaxSeq + 2) * sizeof(unsigned), rounded up. The
* superblock leaf also accepts the larger pre-split workspace, so a fixed
@@ -433,6 +459,70 @@ pub unsafe extern "C" fn ZSTD_compress2(
}
}
/// Compress a streaming call when the complete input and a full output bound
/// are already available for the ordinary context configuration.
///
/// The C implementation remains the fallback for partial-output streaming,
/// `ZSTD_e_continue`/`ZSTD_e_flush`, dictionaries, and every advanced context
/// configuration. The Rust path resets the C-owned session after emitting a
/// complete frame so the same context can immediately start another frame.
#[cfg(not(test))]
#[no_mangle]
pub unsafe extern "C" fn ZSTD_compressStream2(
cctx: *mut c_void,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
end_op: c_int,
) -> usize {
if cctx.is_null() || output.is_null() || input.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let output_ref = unsafe { &mut *output };
let input_ref = unsafe { &mut *input };
if output_ref.pos > output_ref.size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if input_ref.pos > input_ref.size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if end_op == ZSTD_E_END {
let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const()) };
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 {
if dst_capacity != 0 && output_ref.dst.is_null() {
return ERROR(ZstdErrorCode::DstBufferNull);
}
if src_size != 0 && input_ref.src.is_null() {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let dst = if output_ref.dst.is_null() {
ptr::null_mut()
} else {
unsafe { output_ref.dst.cast::<u8>().add(output_ref.pos).cast() }
};
let src = if input_ref.src.is_null() {
ptr::null()
} else {
unsafe { input_ref.src.cast::<u8>().add(input_ref.pos).cast() }
};
let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) };
if !ERR_isError(result) {
output_ref.pos += result;
input_ref.pos = input_ref.size;
return unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
}
}
}
}
unsafe { ZSTD_compressStream2_c(cctx, output, input, end_op) }
}
#[cfg(test)]
mod tests {
use super::*;