From 934a589500b82439c6ca1f7c31cfefa85d1dea62 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 08:52:42 +0200 Subject: [PATCH] feat(compress): move block header serialization to Rust Keep the private writeBlockHeader signature and its debug logging in C while moving the pure block-type, size, and 24-bit little-endian encoding behind a narrow Rust ABI. The Rust leaf preserves the RLE blockSize path for cSize == 1 and the compressed cSize path for every other value. Test Plan: - `cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression block_header -- --nocapture` -- passed - `make -B -C lib -j2 lib` -- passed - `make -C tests -j2 fuzzer` -- passed - `./tests/fuzzer -s4560 -t47 -i48 -v` -- passed - Required clippy, nightly fmt, and diff checks -- passed --- lib/compress/zstd_compress.c | 7 +++--- rust/src/zstd_compress_frame.rs | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 53502d9c0..feba89aed 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -37,6 +37,8 @@ size_t ZSTD_rust_writeFrameHeader(void* dst, size_t dstCapacity, int contentSizeFlag, int format, U32 windowLog, U64 pledgedSrcSize, U32 dictID); +void ZSTD_rust_writeBlockHeader(void* op, size_t cSize, size_t blockSize, + U32 lastBlock); size_t ZSTD_rust_writeEpilogue(void* dst, size_t dstCapacity, int* stage, int noDictIDFlag, int checksumFlag, int contentSizeFlag, int format, U32 windowLog, @@ -2592,10 +2594,7 @@ ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) static void writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock) { - U32 const cBlockHeader = cSize == 1 ? - lastBlock + (((U32)bt_rle)<<1) + (U32)(blockSize << 3) : - lastBlock + (((U32)bt_compressed)<<1) + (U32)(cSize << 3); - MEM_writeLE24(op, cBlockHeader); + ZSTD_rust_writeBlockHeader(op, cSize, blockSize, lastBlock); DEBUGLOG(5, "writeBlockHeader: cSize: %zu blockSize: %zu lastBlock: %u", cSize, blockSize, lastBlock); } diff --git a/rust/src/zstd_compress_frame.rs b/rust/src/zstd_compress_frame.rs index 2d651ab35..2b6d3d4b9 100644 --- a/rust/src/zstd_compress_frame.rs +++ b/rust/src/zstd_compress_frame.rs @@ -25,6 +25,7 @@ const ZSTD_F_ZSTD1: c_int = 0; const ZSTD_BLOCK_SIZE: usize = 128 << 10; const ZSTD_FAST: c_int = 1; const ZSTD_BTULTRA2: c_int = 9; +const ZSTD_BT_COMPRESSED: u32 = 2; const SPLIT_LEVELS: [c_int; 10] = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4]; const ZSTD_BT_RLE: u32 = 1; const ZSTDCS_CREATED: c_int = 0; @@ -83,6 +84,31 @@ pub(crate) unsafe fn write_raw_block( needed } +/// Rust implementation of the private compressed-block header serializer. +/// +/// The C wrapper retains the original static helper signature and keeps its +/// debug logging at the call site. The helper itself only needs scalar +/// values and writes the low 24 bits in little-endian order, as `MEM_writeLE24` +/// does. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_writeBlockHeader( + op: *mut c_void, + c_size: usize, + block_size: usize, + last_block: c_uint, +) { + let block_type = if c_size == 1 { + ZSTD_BT_RLE + } else { + ZSTD_BT_COMPRESSED + }; + let size = if c_size == 1 { block_size } else { c_size }; + let header = last_block + .wrapping_add(block_type << 1) + .wrapping_add((size as u32).wrapping_shl(3)); + unsafe { write_le24(op.cast(), header) }; +} + /// Rust implementation of the private `ZSTD_writeFrameHeader()` leaf. /// /// `no_dict_id_flag`, `checksum_flag`, `content_size_flag`, `format`, and @@ -604,6 +630,24 @@ mod tests { ); } + #[test] + fn writes_rle_block_header() { + let mut output = [0u8; ZSTD_BLOCKHEADERSIZE]; + unsafe { + ZSTD_rust_writeBlockHeader(output.as_mut_ptr().cast(), 1, 0x12345, 1); + } + assert_eq!(output, [0x2b, 0x1a, 0x09]); + } + + #[test] + fn writes_compressed_block_header() { + let mut output = [0u8; ZSTD_BLOCKHEADERSIZE]; + unsafe { + ZSTD_rust_writeBlockHeader(output.as_mut_ptr().cast(), 0x23456, 0x54321, 1); + } + assert_eq!(output, [0xb5, 0xa2, 0x11]); + } + #[test] fn optimal_block_size_keeps_small_blocks_intact() { let result = unsafe {