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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user