feat(compress): move target block emission into Rust

Move the target-compressed-block body out of zstd_compress.c while keeping
sequence-store construction, matchfinding, context lifetime, and the outer
offcode repeat-mode cleanup in C. Rust now owns the target policy, repeated
block decision, Rust superblock call, compressed-state confirmation, and raw
fallback. The narrow C projection asserts the cross-language layout instead
of exposing ZSTD_CCtx.

Add focused tests for first-block RLE suppression, RLE output, superblock
errors, raw fallback, and state-slot swapping. Remove the C-only maybeRLE
wrapper that became unreachable after the extraction.

Test Plan:
- cargo test --manifest-path rust/Cargo.toml --lib -- --test-threads=1
- cargo clippy --manifest-path rust/Cargo.toml --lib -- -D warnings
- cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check
- make -B -C lib -j2 lib
- make -B -C tests -j2 test-cli-tests
- ZSTREAM_TESTTIME=-T2s make -B -C tests -j2 test-zstream
- FUZZERTEST=-T5s make -B -C tests -j2 test-fuzzer
This commit is contained in:
2026-07-18 19:47:42 +02:00
parent b0488f285a
commit 23f3c6dea7
2 changed files with 532 additions and 85 deletions
+476
View File
@@ -33,6 +33,7 @@ use crate::zstd_compress_stats::{
ZSTD_rust_resetSeqStore, ZSTD_rust_transferSequencesNoDelim,
ZSTD_rust_transferSequencesWBlockDelim,
};
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
use std::ffi::c_void;
use std::mem::{offset_of, size_of, MaybeUninit};
use std::os::raw::{c_int, c_uint};
@@ -163,6 +164,59 @@ const _: () = {
);
};
/// Explicit projection of the state used by the target-sized block body.
///
/// Sequence-store construction and the matchfinder remain in C. This state
/// contains only the already-Rust-owned block-compression inputs and the two
/// pointer slots that the compressed-block confirmation leaf swaps.
#[repr(C)]
pub struct ZSTD_rust_targetCBlockSizeState {
seq_store: *mut SeqStore_t,
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
tmp_workspace: *mut c_void,
tmp_wksp_size: usize,
strategy: c_int,
disable_literal_compression: c_int,
bmi2: c_int,
window_log: c_uint,
target_c_block_size: usize,
is_first_block: c_int,
}
const _: () = {
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, seq_store) == 0);
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, prev_c_block) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, next_c_block) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_workspace) == 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_wksp_size) == 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, strategy) == 5 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_targetCBlockSizeState, disable_literal_compression)
== 5 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_targetCBlockSizeState, bmi2)
== 5 * size_of::<usize>() + 2 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_targetCBlockSizeState, window_log)
== 5 * size_of::<usize>() + 3 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_targetCBlockSizeState, target_c_block_size)
== 5 * size_of::<usize>() + 4 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_targetCBlockSizeState, is_first_block)
== 5 * size_of::<usize>() + 4 * size_of::<c_int>() + size_of::<usize>()
);
assert!(
size_of::<ZSTD_rust_targetCBlockSizeState>()
== if size_of::<usize>() == 8 { 72 } else { 44 }
);
};
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum TargetCBlockAction {
@@ -227,6 +281,149 @@ fn target_c_block_size_action(
}
}
type TargetCBlockSuperBlockFn = unsafe extern "C" fn(
*const c_void,
*const c_void,
*mut c_void,
c_int,
c_int,
*mut c_void,
usize,
c_int,
c_uint,
usize,
*mut c_void,
usize,
*const c_void,
usize,
c_uint,
) -> usize;
/// Rust implementation of `ZSTD_compressBlock_targetCBlockSize_body()`.
///
/// The C caller has already built the sequence store. This function owns
/// only the RLE/superblock/raw decision and leaves context construction,
/// matchfinder state, frame progress, and outer repeat-mode cleanup in C.
#[allow(clippy::too_many_arguments)]
unsafe fn compress_block_target_c_block_size_body_with(
state: &ZSTD_rust_targetCBlockSizeState,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
bss: c_int,
last_block: c_uint,
compress_super_block: TargetCBlockSuperBlockFn,
) -> usize {
if state.seq_store.is_null() || state.prev_c_block.is_null() || state.next_c_block.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let prev_c_block = unsafe { *state.prev_c_block };
let next_c_block = unsafe { *state.next_c_block };
if prev_c_block.is_null() || next_c_block.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let is_compress = bss == ZSTD_TARGET_CBLOCK_BSS_COMPRESS;
let (maybe_rle, is_rle) = if is_compress {
let maybe_rle = unsafe { ZSTD_rust_maybeRLE(state.seq_store) };
let is_rle = unsafe { ZSTD_rust_isRLE(src.cast(), src_size) };
(maybe_rle, is_rle)
} else {
(0, 0)
};
let action = target_c_block_size_action(
bss,
state.is_first_block,
maybe_rle,
is_rle,
0,
src_size,
state.strategy,
);
if action == TargetCBlockAction::Rle {
return unsafe {
ZSTD_rust_rleCompressBlock(dst, dst_capacity, *src.cast::<u8>(), src_size, last_block)
};
}
if is_compress {
/* The superblock result is not bounded by ZSTD_compressBound(). The
* policy helper therefore falls back to a raw block for zero,
* dstSize_tooSmall, or an expansion beyond blockBound(srcSize). */
let c_size = unsafe {
compress_super_block(
state.seq_store.cast(),
prev_c_block.cast(),
next_c_block.cast(),
state.strategy,
state.disable_literal_compression,
state.tmp_workspace,
state.tmp_wksp_size,
state.bmi2,
state.window_log,
state.target_c_block_size,
dst,
dst_capacity,
src,
src_size,
last_block,
)
};
let action = target_c_block_size_action(
bss,
state.is_first_block,
0,
0,
c_size,
src_size,
state.strategy,
);
if action == TargetCBlockAction::Error {
return c_size;
}
if action == TargetCBlockAction::Compressed {
unsafe {
ZSTD_rust_confirmRepcodesAndEntropyTables(state.prev_c_block, state.next_c_block);
}
return c_size;
}
}
unsafe { ZSTD_rust_noCompressBlock(dst, dst_capacity, src, src_size, last_block) }
}
/// C ABI entry point for the target-sized block body. The public and outer
/// block APIs remain C-owned; this is only the body after `ZSTD_buildSeqStore`.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressBlockTargetCBlockSize(
state: *const ZSTD_rust_targetCBlockSizeState,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
bss: c_int,
last_block: c_uint,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe {
compress_block_target_c_block_size_body_with(
&*state,
dst,
dst_capacity,
src,
src_size,
bss,
last_block,
ZSTD_rust_compressSuperBlock,
)
}
}
/// Select the strategy used by the simple compression entry points.
///
/// This is the Rust equivalent of the strategy portion of
@@ -2167,6 +2364,285 @@ mod tests {
);
}
#[allow(clippy::too_many_arguments)]
unsafe extern "C" fn target_block_test_superblock_error(
_seq_store: *const c_void,
_prev_cblock: *const c_void,
_next_cblock: *mut c_void,
_strategy: c_int,
_disable_literal_compression: c_int,
_workspace: *mut c_void,
_wksp_size: usize,
_bmi2: c_int,
_window_log: c_uint,
_target_cblock_size: usize,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
_last_block: c_uint,
) -> usize {
ERROR(ZstdErrorCode::Generic)
}
#[allow(clippy::too_many_arguments)]
unsafe extern "C" fn target_block_test_superblock_empty(
_seq_store: *const c_void,
_prev_cblock: *const c_void,
_next_cblock: *mut c_void,
_strategy: c_int,
_disable_literal_compression: c_int,
_workspace: *mut c_void,
_wksp_size: usize,
_bmi2: c_int,
_window_log: c_uint,
_target_cblock_size: usize,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
_last_block: c_uint,
) -> usize {
0
}
#[allow(clippy::too_many_arguments)]
unsafe extern "C" fn target_block_test_superblock_too_small(
_seq_store: *const c_void,
_prev_cblock: *const c_void,
_next_cblock: *mut c_void,
_strategy: c_int,
_disable_literal_compression: c_int,
_workspace: *mut c_void,
_wksp_size: usize,
_bmi2: c_int,
_window_log: c_uint,
_target_cblock_size: usize,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
_last_block: c_uint,
) -> usize {
ERROR(ZstdErrorCode::DstSizeTooSmall)
}
#[allow(clippy::too_many_arguments)]
unsafe extern "C" fn target_block_test_superblock_compressed(
_seq_store: *const c_void,
_prev_cblock: *const c_void,
_next_cblock: *mut c_void,
_strategy: c_int,
_disable_literal_compression: c_int,
_workspace: *mut c_void,
_wksp_size: usize,
_bmi2: c_int,
_window_log: c_uint,
_target_cblock_size: usize,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
_last_block: c_uint,
) -> usize {
1
}
fn target_block_test_state(
seq_store: &mut SeqStore_t,
prev_block: &mut ZSTD_compressedBlockState_t,
next_block: &mut ZSTD_compressedBlockState_t,
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
is_first_block: c_int,
) -> ZSTD_rust_targetCBlockSizeState {
*prev_c_block = prev_block as *mut ZSTD_compressedBlockState_t;
*next_c_block = next_block as *mut ZSTD_compressedBlockState_t;
ZSTD_rust_targetCBlockSizeState {
seq_store,
prev_c_block,
next_c_block,
tmp_workspace: ptr::null_mut(),
tmp_wksp_size: 0,
strategy: ZSTD_FAST,
disable_literal_compression: 0,
bmi2: 0,
window_log: 20,
target_c_block_size: 0,
is_first_block,
}
}
fn target_block_test_seq_store() -> (SeqStore_t, [SeqDef; 1], [u8; 16]) {
let mut sequences = [SeqDef::default(); 1];
let mut literals = [0u8; 16];
let sequences_start = sequences.as_mut_ptr();
let literals_start = literals.as_mut_ptr();
let seq_store = SeqStore_t {
sequencesStart: sequences_start,
sequences: sequences_start,
litStart: literals_start,
lit: literals_start,
llCode: ptr::null_mut(),
mlCode: ptr::null_mut(),
ofCode: ptr::null_mut(),
maxNbSeq: sequences.len(),
maxNbLit: literals.len(),
longLengthType: 0,
longLengthPos: 0,
};
(seq_store, sequences, literals)
}
#[test]
fn target_block_body_emits_rle_only_for_nonfirst_repeated_blocks() {
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
let mut prev_block = zeroed_state();
let mut next_block = zeroed_state();
let mut prev_c_block = ptr::null_mut();
let mut next_c_block = ptr::null_mut();
let state = target_block_test_state(
&mut seq_store,
&mut prev_block,
&mut next_block,
&mut prev_c_block,
&mut next_c_block,
0,
);
let source = [0x5au8; 16];
let mut output = [0xa5u8; 4];
let result = unsafe {
compress_block_target_c_block_size_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
1,
target_block_test_superblock_error,
)
};
assert_eq!(result, 4);
assert_eq!(output[3], source[0]);
}
#[test]
fn target_block_body_suppresses_first_block_rle_and_propagates_superblock_error() {
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
let mut prev_block = zeroed_state();
let mut next_block = zeroed_state();
let mut prev_c_block = ptr::null_mut();
let mut next_c_block = ptr::null_mut();
let state = target_block_test_state(
&mut seq_store,
&mut prev_block,
&mut next_block,
&mut prev_c_block,
&mut next_c_block,
1,
);
let source = [0x5au8; 16];
let mut output = [0xa5u8; 16];
let result = unsafe {
compress_block_target_c_block_size_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
1,
target_block_test_superblock_error,
)
};
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
assert_eq!(output, [0xa5u8; 16]);
}
#[test]
fn target_block_body_falls_back_to_raw_for_empty_or_small_superblocks() {
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
let mut prev_block = zeroed_state();
let mut next_block = zeroed_state();
let mut prev_c_block = ptr::null_mut();
let mut next_c_block = ptr::null_mut();
let state = target_block_test_state(
&mut seq_store,
&mut prev_block,
&mut next_block,
&mut prev_c_block,
&mut next_c_block,
1,
);
let source = *b"raw fallback";
for superblock in [
target_block_test_superblock_empty as TargetCBlockSuperBlockFn,
target_block_test_superblock_too_small,
] {
let mut output = [0xa5u8; 32];
let result = unsafe {
compress_block_target_c_block_size_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
0,
superblock,
)
};
assert_eq!(result, source.len() + ZSTD_BLOCK_HEADER_SIZE);
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &source);
}
}
#[test]
fn target_block_body_swaps_state_only_for_compressed_output() {
let (mut seq_store, _sequences, _literals) = target_block_test_seq_store();
let mut prev_block = zeroed_state();
let mut next_block = zeroed_state();
let mut prev_c_block = &mut prev_block as *mut ZSTD_compressedBlockState_t;
let mut next_c_block = &mut next_block as *mut ZSTD_compressedBlockState_t;
let state = target_block_test_state(
&mut seq_store,
&mut prev_block,
&mut next_block,
&mut prev_c_block,
&mut next_c_block,
1,
);
let prev_ptr = prev_c_block;
let next_ptr = next_c_block;
let source = [0x3cu8; 128];
let mut output = [0xa5u8; 128];
let result = unsafe {
compress_block_target_c_block_size_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
0,
target_block_test_superblock_compressed,
)
};
assert_eq!(result, 1);
assert_eq!(unsafe { *state.prev_c_block }, next_ptr);
assert_eq!(unsafe { *state.next_c_block }, prev_ptr);
assert_eq!(output, [0xa5u8; 128]);
}
#[test]
fn frame_progress_unknown_pledge_updates_counters() {
let mut consumed = 7;