diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 0e86adc40..26b78b174 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -731,6 +731,25 @@ size_t ZSTD_compressStream2_c(ZSTD_CCtx* cctx, ZSTD_inBuffer* input, ZSTD_EndDirective endOp); int ZSTD_rust_simpleCompress2Level(const void* cctx, size_t srcSize); +typedef int (*ZSTD_rust_simpleCompress2Level_f)(const void* cctx, size_t srcSize); +typedef struct { + int streamStage; + unsigned long long pledgedSrcSizePlusOne; + unsigned rustSimpleCompress2Completed; +} ZSTD_rust_simpleCompressStream2Projection; +typedef char ZSTD_rust_simple_compress_stream2_projection_layout[ + (offsetof(ZSTD_rust_simpleCompressStream2Projection, streamStage) == 0 + && offsetof(ZSTD_rust_simpleCompressStream2Projection, pledgedSrcSizePlusOne) + == (sizeof(void*) == 8 ? 8 : 4) + && offsetof(ZSTD_rust_simpleCompressStream2Projection, + rustSimpleCompress2Completed) + == (sizeof(void*) == 8 ? 16 : 12) + && sizeof(ZSTD_rust_simpleCompressStream2Projection) + == (sizeof(void*) == 8 ? 24 : 16)) ? 1 : -1]; +int ZSTD_rust_simpleCompressStream2Policy( + const ZSTD_rust_simpleCompressStream2Projection* projection, + const void* cctx, size_t srcSize, + ZSTD_rust_simpleCompress2Level_f simpleCompress2Level); int ZSTD_rust_simpleCompressStream2Level(const void* cctx, size_t srcSize); U32 ZSTD_rust_limitNextToUpdate(U32 curr, U32 nextToUpdate); void ZSTD_rust_advanceHashSaltInPlace(U64* hashSalt, U64 hashSaltEntropy); @@ -6888,13 +6907,13 @@ int ZSTD_rust_simpleCompress2Level(const void* opaqueCctx, size_t srcSize) int ZSTD_rust_simpleCompressStream2Level(const void* opaqueCctx, size_t srcSize) { ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)opaqueCctx; - if (cctx == NULL - || cctx->streamStage != zcss_init - || cctx->pledgedSrcSizePlusOne != 0 - || cctx->rustSimpleCompress2Completed != 0) { - return (-2147483647 - 1); - } - return ZSTD_rust_simpleCompress2Level(opaqueCctx, srcSize); + ZSTD_rust_simpleCompressStream2Projection const projection = { + cctx == NULL ? 0 : (int)cctx->streamStage, + cctx == NULL ? 0 : cctx->pledgedSrcSizePlusOne, + cctx == NULL ? 0 : cctx->rustSimpleCompress2Completed + }; + return ZSTD_rust_simpleCompressStream2Policy( + &projection, opaqueCctx, srcSize, ZSTD_rust_simpleCompress2Level); } /* ZSTD_compress() is implemented by rust/src/zstd_compress.rs. */ diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 098304439..a488e0ab6 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -117,6 +117,40 @@ const MIN_CBLOCK_SIZE: usize = 2; const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = MIN_CBLOCK_SIZE + ZSTD_BLOCK_HEADER_SIZE + 1 + 1; const ZSTD_ROWSIZE: usize = 16; const ZSTD_CSTREAM_STAGE_INIT: c_int = 0; + +/// Scalar stream state used to select the complete-input fast path. The C +/// context remains opaque; its private fields are projected before Rust +/// evaluates the eligibility predicate. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rust_simpleCompressStream2Projection { + stream_stage: c_int, + pledged_src_size_plus_one: u64, + rust_simple_compress2_completed: c_uint, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_simpleCompressStream2Projection, stream_stage) == 0); + assert!( + offset_of!( + ZSTD_rust_simpleCompressStream2Projection, + pledged_src_size_plus_one + ) == if size_of::() == 8 { 8 } else { 4 } + ); + assert!( + offset_of!( + ZSTD_rust_simpleCompressStream2Projection, + rust_simple_compress2_completed + ) == if size_of::() == 8 { 16 } else { 12 } + ); + assert!( + size_of::() + == if size_of::() == 8 { 24 } else { 16 } + ); +}; + +pub type ZSTD_rust_simpleCompress2LevelFn = unsafe extern "C" fn(*const c_void, usize) -> c_int; + const ZSTD_WINDOW_START_INDEX: u32 = 2; const ZSTD_DUBT_UNSORTED_MARK: u32 = 1; const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20; @@ -9825,6 +9859,32 @@ unsafe fn compress_frame( output_offset } +/// Select the complete-input streaming fast path before invoking the C-owned +/// simple-level predicate. Rust owns the stream-state branch; C keeps the +/// private context and level-selection leaf behind a callback. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_simpleCompressStream2Policy( + projection: *const ZSTD_rust_simpleCompressStream2Projection, + cctx: *const c_void, + src_size: usize, + simple_compress2_level: Option, +) -> c_int { + let Some(projection) = (unsafe { projection.as_ref() }) else { + return c_int::MIN; + }; + if cctx.is_null() + || projection.stream_stage != ZSTD_CSTREAM_STAGE_INIT + || projection.pledged_src_size_plus_one != 0 + || projection.rust_simple_compress2_completed != 0 + { + return c_int::MIN; + } + let Some(simple_compress2_level) = simple_compress2_level else { + return c_int::MIN; + }; + unsafe { simple_compress2_level(cctx, src_size) } +} + /// Simple one-shot compression entry point. #[no_mangle] pub unsafe extern "C" fn ZSTD_compress( @@ -13674,6 +13734,77 @@ mod tests { ); } + #[derive(Default)] + struct SimpleStreamPolicyTestContext { + calls: usize, + src_size: usize, + } + + unsafe extern "C" fn simple_stream_policy_test_level( + context: *const c_void, + src_size: usize, + ) -> c_int { + let context = unsafe { &mut *context.cast_mut().cast::() }; + context.calls += 1; + context.src_size = src_size; + 7 + } + + #[test] + fn simple_stream_policy_calls_the_level_leaf_only_for_a_new_frame() { + let mut context = SimpleStreamPolicyTestContext::default(); + let cctx = (&mut context as *mut SimpleStreamPolicyTestContext).cast(); + let projection = ZSTD_rust_simpleCompressStream2Projection { + stream_stage: ZSTD_CSTREAM_STAGE_INIT, + pledged_src_size_plus_one: 0, + rust_simple_compress2_completed: 0, + }; + + let result = unsafe { + ZSTD_rust_simpleCompressStream2Policy( + &projection, + cctx, + 123, + Some(simple_stream_policy_test_level), + ) + }; + + assert_eq!(result, 7); + assert_eq!(context.calls, 1); + assert_eq!(context.src_size, 123); + } + + #[test] + fn simple_stream_policy_rejects_non_initial_or_completed_frames() { + let mut context = SimpleStreamPolicyTestContext::default(); + let cctx = (&mut context as *mut SimpleStreamPolicyTestContext).cast(); + for projection in [ + ZSTD_rust_simpleCompressStream2Projection { + stream_stage: ZSTD_CSTREAM_STAGE_LOAD, + ..Default::default() + }, + ZSTD_rust_simpleCompressStream2Projection { + pledged_src_size_plus_one: 1, + ..Default::default() + }, + ZSTD_rust_simpleCompressStream2Projection { + rust_simple_compress2_completed: 1, + ..Default::default() + }, + ] { + let result = unsafe { + ZSTD_rust_simpleCompressStream2Policy( + &projection, + cctx, + 123, + Some(simple_stream_policy_test_level), + ) + }; + assert_eq!(result, c_int::MIN); + } + assert_eq!(context.calls, 0); + } + #[test] fn reduce_table_applies_threshold_and_wrapping_subtraction() { let mut table = [0, 1, 2, 3, 4, 5, 6, u32::MAX, 0, 0, 0, 0, 0, 0, 0, 0];