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:
2026-07-18 06:03:39 +02:00
parent 3599d223f9
commit 48eb895af3
4 changed files with 139 additions and 62 deletions
+3 -28
View File
@@ -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.
+122
View File
@@ -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];