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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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() :
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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::<u8>();
|
||||
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];
|
||||
|
||||
Reference in New Issue
Block a user