From 48eb895af3353b46cb6df23d1cd5619bc7730997 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 06:03:39 +0200 Subject: [PATCH] feat(compress): move raw and RLE block emitters to Rust Replace the shared C raw and one-byte RLE fallback block serializers with Rust ABI leaves. Preserve the zstd block headers, payload copies, capacity errors, and all existing C compressor dispatch and context ownership; share the raw serializer with the Rust one-shot path. Test Plan: - cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression (208 tests) - cargo clippy --manifest-path rust/Cargo.toml - cargo clippy --manifest-path rust/Cargo.toml --benches - cargo clippy --manifest-path rust/Cargo.toml --tests - make -B -C lib -j2 lib --- lib/compress/zstd_compress.c | 18 ++-- lib/compress/zstd_compress_internal.h | 30 ++----- rust/src/zstd_compress.rs | 31 +------ rust/src/zstd_compress_frame.rs | 122 ++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 62 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 35f3093a8..28625179e 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -2845,12 +2845,12 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, } if (cSeqsSize == 0) { - cSize = ZSTD_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock); + cSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "Nocompress block failed"); DEBUGLOG(5, "Writing out nocompress block, size: %zu", cSize); *dRep = dRepOriginal; /* reset simulated decompression repcode history */ } else if (cSeqsSize == 1) { - cSize = ZSTD_rleCompressBlock(op, dstCapacity, *ip, srcSize, lastBlock); + cSize = ZSTD_rust_rleCompressBlock(op, dstCapacity, *ip, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "RLE compress block failed"); DEBUGLOG(5, "Writing out RLE block, size: %zu", cSize); *dRep = dRepOriginal; /* reset simulated decompression repcode history */ @@ -3062,7 +3062,7 @@ ZSTD_compressBlock_splitBlock(ZSTD_CCtx* zc, if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; RETURN_ERROR_IF(zc->seqCollector.collectSequences, sequenceProducer_failed, "Uncompressible block"); - cSize = ZSTD_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); + cSize = ZSTD_rust_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); DEBUGLOG(5, "ZSTD_compressBlock_splitBlock: Nocompress block"); return cSize; @@ -3158,7 +3158,7 @@ static size_t ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx* zc, ZSTD_maybeRLE(&zc->seqStore) && ZSTD_isRLE((BYTE const*)src, srcSize)) { - return ZSTD_rleCompressBlock(dst, dstCapacity, *(BYTE const*)src, srcSize, lastBlock); + return ZSTD_rust_rleCompressBlock(dst, dstCapacity, *(BYTE const*)src, srcSize, lastBlock); } /* Attempt superblock compression. * @@ -3196,7 +3196,7 @@ static size_t ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx* zc, /* Superblock compression failed, attempt to emit a single no compress block. * The decoder will be able to stream this block since it is uncompressed. */ - return ZSTD_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); + return ZSTD_rust_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); } static size_t ZSTD_compressBlock_targetCBlockSize(ZSTD_CCtx* zc, @@ -3320,7 +3320,7 @@ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, FORWARD_IF_ERROR(cSize, "ZSTD_compressBlock_internal failed"); if (cSize == 0) { /* block is not compressible */ - cSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); + cSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); } else { U32 const cBlockHeader = cSize == 1 ? @@ -5630,7 +5630,7 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding * additional 1. We need to revisit and change this logic to be more consistent */ if (blockSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1+1) { - cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); + cBlockSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); FORWARD_IF_ERROR(cBlockSize, "Nocompress block failed"); DEBUGLOG(5, "Block too small (%zu): data remains uncompressed: cSize=%zu", blockSize, cBlockSize); cSize += cBlockSize; @@ -5664,11 +5664,11 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, if (compressedSeqsSize == 0) { /* ZSTD_noCompressBlock writes the block header as well */ - cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); + cBlockSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); FORWARD_IF_ERROR(cBlockSize, "ZSTD_noCompressBlock failed"); DEBUGLOG(5, "Writing out nocompress block, size: %zu", cBlockSize); } else if (compressedSeqsSize == 1) { - cBlockSize = ZSTD_rleCompressBlock(op, dstCapacity, *ip, blockSize, lastBlock); + cBlockSize = ZSTD_rust_rleCompressBlock(op, dstCapacity, *ip, blockSize, lastBlock); FORWARD_IF_ERROR(cBlockSize, "ZSTD_rleCompressBlock failed"); DEBUGLOG(5, "Writing out RLE block, size: %zu", cBlockSize); } else { diff --git a/lib/compress/zstd_compress_internal.h b/lib/compress/zstd_compress_internal.h index b31037875..443b5c6b1 100644 --- a/lib/compress/zstd_compress_internal.h +++ b/lib/compress/zstd_compress_internal.h @@ -649,31 +649,11 @@ ZSTD_selectAddr(U32 index, U32 lowLimit, const BYTE* candidate, const BYTE* back #endif } -/* ZSTD_noCompressBlock() : - * Writes uncompressed block to dst buffer from given src. - * Returns the size of the block */ -MEM_STATIC size_t -ZSTD_noCompressBlock(void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock) -{ - U32 const cBlockHeader24 = lastBlock + (((U32)bt_raw)<<1) + (U32)(srcSize << 3); - DEBUGLOG(5, "ZSTD_noCompressBlock (srcSize=%zu, dstCapacity=%zu)", srcSize, dstCapacity); - RETURN_ERROR_IF(srcSize + ZSTD_blockHeaderSize > dstCapacity, - dstSize_tooSmall, "dst buf too small for uncompressed block"); - MEM_writeLE24(dst, cBlockHeader24); - ZSTD_memcpy((BYTE*)dst + ZSTD_blockHeaderSize, src, srcSize); - return ZSTD_blockHeaderSize + srcSize; -} - -MEM_STATIC size_t -ZSTD_rleCompressBlock(void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock) -{ - BYTE* const op = (BYTE*)dst; - U32 const cBlockHeader = lastBlock + (((U32)bt_rle)<<1) + (U32)(srcSize << 3); - RETURN_ERROR_IF(dstCapacity < 4, dstSize_tooSmall, ""); - MEM_writeLE24(op, cBlockHeader); - op[3] = src; - return 4; -} +/* The raw and RLE block serializers live in Rust. */ +size_t ZSTD_rust_noCompressBlock(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 lastBlock); +size_t ZSTD_rust_rleCompressBlock(void* dst, size_t dstCapacity, BYTE src, + size_t srcSize, U32 lastBlock); /* ZSTD_minGain() : diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index e58b3746c..59d7892fa 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -14,10 +14,11 @@ //! 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_frame::{ + write_raw_block, ZSTD_rust_writeFrameHeader, ZSTD_writeLastEmptyBlock, +}; use crate::zstd_compress_params::{ ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams, ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_DISABLE, @@ -204,32 +205,6 @@ fn ceil_log2(size: usize) -> u32 { } } -unsafe fn write_raw_block( - dst: *mut u8, - dst_capacity: usize, - src: *const u8, - src_size: usize, - last_block: u32, -) -> usize { - let needed = match src_size.checked_add(3) { - Some(value) => value, - None => return ERROR(ZstdErrorCode::DstSizeTooSmall), - }; - if needed > dst_capacity { - return ERROR(ZstdErrorCode::DstSizeTooSmall); - } - unsafe { - MEM_writeLE24( - dst.cast(), - last_block.wrapping_add((src_size as u32).wrapping_shl(3)), - ); - if src_size != 0 { - ptr::copy_nonoverlapping(src, dst.add(3), src_size); - } - } - needed -} - /// Compress one frame using the already migrated block leaves. /// /// This path deliberately starts a fresh match table for each 128 KiB block. diff --git a/rust/src/zstd_compress_frame.rs b/rust/src/zstd_compress_frame.rs index 6819705c9..2d651ab35 100644 --- a/rust/src/zstd_compress_frame.rs +++ b/rust/src/zstd_compress_frame.rs @@ -26,6 +26,7 @@ const ZSTD_BLOCK_SIZE: usize = 128 << 10; const ZSTD_FAST: c_int = 1; const ZSTD_BTULTRA2: c_int = 9; 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; const ZSTDCS_INIT: c_int = 1; const ZSTDCS_ONGOING: c_int = 2; @@ -55,6 +56,33 @@ unsafe fn write_le64(dst: *mut u8, value: u64) { unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()) }; } +/// Writes a raw block header and payload, returning the complete block size. +pub(crate) unsafe fn write_raw_block( + dst: *mut u8, + dst_capacity: usize, + src: *const u8, + src_size: usize, + last_block: u32, +) -> usize { + let needed = match src_size.checked_add(ZSTD_BLOCKHEADERSIZE) { + Some(value) => value, + None => return ERROR(ZstdErrorCode::DstSizeTooSmall), + }; + if needed > dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + unsafe { + write_le24( + dst, + last_block.wrapping_add((src_size as u32).wrapping_shl(3)), + ); + if src_size != 0 { + ptr::copy_nonoverlapping(src, dst.add(ZSTD_BLOCKHEADERSIZE), src_size); + } + } + needed +} + /// Rust implementation of the private `ZSTD_writeFrameHeader()` leaf. /// /// `no_dict_id_flag`, `checksum_flag`, `content_size_flag`, `format`, and @@ -218,6 +246,42 @@ pub unsafe extern "C" fn ZSTD_writeLastEmptyBlock(dst: *mut c_void, dst_capacity ZSTD_BLOCKHEADERSIZE } +/// Rust implementation of the raw fallback block serializer. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_noCompressBlock( + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + last_block: u32, +) -> usize { + unsafe { write_raw_block(dst.cast(), dst_capacity, src.cast(), src_size, last_block) } +} + +/// Rust implementation of the one-byte RLE fallback block serializer. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_rleCompressBlock( + dst: *mut c_void, + dst_capacity: usize, + src: u8, + src_size: usize, + last_block: u32, +) -> usize { + if dst_capacity < 4 { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + + let header = last_block + .wrapping_add(ZSTD_BT_RLE << 1) + .wrapping_add((src_size as u32).wrapping_shl(3)); + let dst = dst.cast::(); + unsafe { + write_le24(dst, header); + dst.add(3).write(src); + } + 4 +} + /// Rust implementation of the private `ZSTD_optimalBlockSize()` policy. /// /// The compressor context stays in C. This leaf receives only the source @@ -611,6 +675,64 @@ mod tests { assert!(result <= ZSTD_BLOCK_SIZE); } + #[test] + fn raw_block_serializer_writes_header_and_payload() { + let source = *b"abc"; + let mut output = [0u8; 6]; + let result = unsafe { + ZSTD_rust_noCompressBlock( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 1, + ) + }; + assert_eq!(result, output.len()); + assert_eq!(output, [0x19, 0, 0, b'a', b'b', b'c']); + } + + #[test] + fn raw_block_serializer_checks_capacity() { + let source = *b"a"; + let mut output = [0u8; 3]; + let result = unsafe { + ZSTD_rust_noCompressBlock( + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 0, + ) + }; + assert_eq!( + ERR_getErrorCode(result), + ZstdErrorCode::DstSizeTooSmall as i32 + ); + } + + #[test] + fn rle_block_serializer_writes_header_and_value() { + let mut output = [0u8; 4]; + let result = unsafe { + ZSTD_rust_rleCompressBlock(output.as_mut_ptr().cast(), output.len(), b'Z', 7, 0) + }; + assert_eq!(result, 4); + assert_eq!(output, [0x3a, 0, 0, b'Z']); + } + + #[test] + fn rle_block_serializer_checks_capacity() { + let mut output = [0u8; 3]; + let result = unsafe { + ZSTD_rust_rleCompressBlock(output.as_mut_ptr().cast(), output.len(), b'Z', 7, 0) + }; + assert_eq!( + ERR_getErrorCode(result), + ZstdErrorCode::DstSizeTooSmall as i32 + ); + } + #[test] fn epilogue_rejects_created_stage() { let mut output = [0u8; ZSTD_FRAMEHEADERSIZE_MAX];