feat(compress): move frame-chunk loop into Rust

Port ZSTD_compress_frameChunk's per-block orchestration into Rust behind an
explicit C-layout projection and callback table. Rust now owns block sizing,
target/split/internal dispatch, block framing and accounting, checksum
sequencing, and terminal frame-state updates. C retains CCtx and match-state
preparation plus the codec-specific callbacks.

Test Plan:
- cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1
- cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- make -B -C lib -j2 lib
- make -B -C programs -j2 zstd
- make -B -C tests -j2 test-zstd
This commit is contained in:
2026-07-18 22:08:37 +02:00
parent 896397f729
commit f3bc5e98f1
2 changed files with 630 additions and 99 deletions
+488 -2
View File
@@ -38,7 +38,7 @@ use crate::zstd_compress_stats::{
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};
use std::os::raw::{c_int, c_longlong, c_uint};
use std::ptr;
#[cfg(not(test))]
@@ -92,7 +92,8 @@ const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_TARGET_CBLOCK_BSS_COMPRESS: c_int = 0;
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = 2 + ZSTD_BLOCK_HEADER_SIZE + 1 + 1;
const MIN_CBLOCK_SIZE: usize = 2;
const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = MIN_CBLOCK_SIZE + ZSTD_BLOCK_HEADER_SIZE + 1 + 1;
const ZSTD_ROWSIZE: usize = 16;
const ZSTD_CSTREAM_STAGE_INIT: c_int = 0;
const ZSTD_WINDOW_START_INDEX: u32 = 2;
@@ -108,6 +109,237 @@ const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX;
#[cfg(not(test))]
const ZSTD_E_END: c_int = 2;
type FrameChunkPrepareFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
type FrameChunkCompressFn =
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
type FrameChunkChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
/// Explicit projection of the state used by `ZSTD_compress_frameChunk`.
///
/// The Rust side owns the per-frame block loop and its savings/dispatch
/// policy. The callback context is opaque to Rust and is only returned to
/// C-owned callbacks, which retain private window, workspace, and CCtx
/// layout-sensitive operations.
#[repr(C)]
pub struct ZSTD_rust_frameChunkState {
callback_context: *mut c_void,
tmp_workspace: *mut c_void,
checksum_state: *mut c_void,
is_first_block: *mut c_int,
stage: *mut c_int,
tmp_wksp_size: usize,
block_size_max: usize,
savings: c_longlong,
pre_block_splitter_level: c_int,
strategy: c_int,
use_target_c_block_size: c_int,
block_splitter_enabled: c_int,
checksum_flag: c_int,
ending_stage: c_int,
prepare_block: FrameChunkPrepareFn,
compress_target: FrameChunkCompressFn,
compress_split: FrameChunkCompressFn,
compress_internal: FrameChunkCompressFn,
update_checksum: FrameChunkChecksumFn,
}
const _: () = {
assert!(offset_of!(ZSTD_rust_frameChunkState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_workspace) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, checksum_state) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, is_first_block) == 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, stage) == 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_wksp_size) == 5 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, block_size_max) == 6 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_frameChunkState, savings) == 7 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_frameChunkState, pre_block_splitter_level)
== 7 * size_of::<usize>() + size_of::<c_longlong>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, strategy)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, use_target_c_block_size)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 2 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, block_splitter_enabled)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 3 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, checksum_flag)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 4 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, ending_stage)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 5 * size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_frameChunkState, prepare_block)
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
);
assert!(
size_of::<ZSTD_rust_frameChunkState>()
== 12 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
);
};
/// Rust implementation of `ZSTD_compress_frameChunk`.
///
/// C still prepares match-state windows and invokes the selected block body
/// through callbacks. Rust owns the block-size heuristic call, output
/// framing, savings accounting, checksum sequencing, and frame-state update.
#[allow(clippy::too_many_arguments)]
unsafe fn compress_frame_chunk_body_with(
state: &ZSTD_rust_frameChunkState,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
last_frame_chunk: c_uint,
) -> usize {
if state.is_first_block.is_null() || state.stage.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
if state.checksum_flag != 0 && src_size != 0 {
if state.checksum_state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe { (state.update_checksum)(state.checksum_state, src, src_size) };
}
let mut remaining = src_size;
let mut ip = src.cast::<u8>();
let mut op = dst.cast::<u8>();
let mut remaining_capacity = dst_capacity;
let mut savings = state.savings;
let mut compressed_size_total = 0usize;
while remaining != 0 {
let block_size = unsafe {
crate::zstd_compress_frame::ZSTD_rust_optimalBlockSize(
ip.cast(),
remaining,
state.block_size_max,
state.pre_block_splitter_level,
state.strategy,
savings,
state.tmp_workspace,
state.tmp_wksp_size,
)
};
if block_size == 0 || block_size > remaining {
return ERROR(ZstdErrorCode::Generic);
}
let last_block = last_frame_chunk & u32::from(block_size == remaining);
/* Keep the original early capacity guard: even a raw block needs the
* minimum block header plus the minimum compressible payload budget. */
if remaining_capacity < ZSTD_BLOCK_HEADER_SIZE + MIN_CBLOCK_SIZE + 1 {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe { (state.prepare_block)(state.callback_context, ip.cast(), block_size) };
let c_size = if state.use_target_c_block_size != 0 {
unsafe {
(state.compress_target)(
state.callback_context,
op.cast(),
remaining_capacity,
ip.cast(),
block_size,
last_block,
)
}
} else if state.block_splitter_enabled != 0 {
unsafe {
(state.compress_split)(
state.callback_context,
op.cast(),
remaining_capacity,
ip.cast(),
block_size,
last_block,
)
}
} else {
let compressed_size = unsafe {
(state.compress_internal)(
state.callback_context,
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
remaining_capacity - ZSTD_BLOCK_HEADER_SIZE,
ip.cast(),
block_size,
last_block,
)
};
if ERR_isError(compressed_size) {
return compressed_size;
}
if compressed_size == 0 {
unsafe {
ZSTD_rust_noCompressBlock(
op.cast(),
remaining_capacity,
ip.cast(),
block_size,
last_block,
)
}
} else {
unsafe {
ZSTD_rust_writeBlockHeader(op.cast(), compressed_size, block_size, last_block)
};
compressed_size + ZSTD_BLOCK_HEADER_SIZE
}
};
if ERR_isError(c_size) {
return c_size;
}
savings =
savings.wrapping_add((block_size as c_longlong).wrapping_sub(c_size as c_longlong));
unsafe {
ip = ip.add(block_size);
op = op.add(c_size);
}
remaining -= block_size;
debug_assert!(c_size <= remaining_capacity);
remaining_capacity = remaining_capacity.wrapping_sub(c_size);
compressed_size_total = compressed_size_total.wrapping_add(c_size);
unsafe { *state.is_first_block = 0 };
}
if last_frame_chunk != 0 && compressed_size_total != 0 {
unsafe { *state.stage = state.ending_stage };
}
compressed_size_total
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressFrameChunk(
state: *const ZSTD_rust_frameChunkState,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
last_frame_chunk: c_uint,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe {
compress_frame_chunk_body_with(&*state, dst, dst_capacity, src, src_size, last_frame_chunk)
}
}
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
///
/// The C context and its function-pointer-bearing parameter structure remain
@@ -2675,6 +2907,260 @@ mod tests {
output
}
#[derive(Default)]
struct FrameChunkTestContext {
prepare_calls: usize,
prepared_sizes: [usize; 4],
target_calls: usize,
split_calls: usize,
internal_calls: usize,
last_blocks: [c_uint; 4],
checksum_calls: usize,
checksum_size: usize,
target_result: usize,
split_result: usize,
internal_result: usize,
}
unsafe fn frame_chunk_test_context(context: *mut c_void) -> &'static mut FrameChunkTestContext {
unsafe { &mut *context.cast::<FrameChunkTestContext>() }
}
unsafe extern "C" fn frame_chunk_test_prepare(
context: *mut c_void,
_src: *const c_void,
block_size: usize,
) {
let context = unsafe { frame_chunk_test_context(context) };
if context.prepare_calls < context.prepared_sizes.len() {
context.prepared_sizes[context.prepare_calls] = block_size;
}
context.prepare_calls += 1;
}
unsafe extern "C" fn frame_chunk_test_target(
context: *mut c_void,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
last_block: c_uint,
) -> usize {
let context = unsafe { frame_chunk_test_context(context) };
if context.target_calls < context.last_blocks.len() {
context.last_blocks[context.target_calls] = last_block;
}
context.target_calls += 1;
context.target_result
}
unsafe extern "C" fn frame_chunk_test_split(
context: *mut c_void,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
last_block: c_uint,
) -> usize {
let context = unsafe { frame_chunk_test_context(context) };
if context.split_calls < context.last_blocks.len() {
context.last_blocks[context.split_calls] = last_block;
}
context.split_calls += 1;
context.split_result
}
unsafe extern "C" fn frame_chunk_test_internal(
context: *mut c_void,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
last_block: c_uint,
) -> usize {
let context = unsafe { frame_chunk_test_context(context) };
if context.internal_calls < context.last_blocks.len() {
context.last_blocks[context.internal_calls] = last_block;
}
context.internal_calls += 1;
context.internal_result
}
unsafe extern "C" fn frame_chunk_test_checksum(
context: *mut c_void,
_src: *const c_void,
src_size: usize,
) {
let context = unsafe { frame_chunk_test_context(context) };
context.checksum_calls += 1;
context.checksum_size = context.checksum_size.wrapping_add(src_size);
}
fn frame_chunk_test_state(
context: &mut FrameChunkTestContext,
is_first_block: &mut c_int,
stage: &mut c_int,
use_target_c_block_size: c_int,
block_splitter_enabled: c_int,
checksum_flag: c_int,
) -> ZSTD_rust_frameChunkState {
let context = context as *mut FrameChunkTestContext as *mut c_void;
ZSTD_rust_frameChunkState {
callback_context: context,
tmp_workspace: ptr::null_mut(),
checksum_state: context,
is_first_block,
stage,
tmp_wksp_size: 0,
block_size_max: 4,
savings: 0,
pre_block_splitter_level: 1,
strategy: ZSTD_FAST,
use_target_c_block_size,
block_splitter_enabled,
checksum_flag,
ending_stage: 77,
prepare_block: frame_chunk_test_prepare,
compress_target: frame_chunk_test_target,
compress_split: frame_chunk_test_split,
compress_internal: frame_chunk_test_internal,
update_checksum: frame_chunk_test_checksum,
}
}
#[test]
fn frame_chunk_internal_path_emits_blocks_and_updates_state_once() {
let mut context = FrameChunkTestContext {
internal_result: 2,
..FrameChunkTestContext::default()
};
let mut is_first_block = 1;
let mut stage = 0;
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
let source = [0x11u8; 8];
let mut output = [0xa5u8; 16];
let result = unsafe {
compress_frame_chunk_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
1,
)
};
assert_eq!(result, 10);
assert_eq!(context.prepare_calls, 2);
assert_eq!(context.prepared_sizes[..2], [4, 4]);
assert_eq!(context.internal_calls, 2);
assert_eq!(context.last_blocks[..2], [0, 1]);
assert_eq!(context.checksum_calls, 1);
assert_eq!(context.checksum_size, source.len());
assert_eq!(is_first_block, 0);
assert_eq!(stage, 77);
assert_ne!(output[..3], [0xa5; 3]);
assert_ne!(output[5..8], [0xa5; 3]);
}
#[test]
fn frame_chunk_dispatches_target_and_split_paths_before_internal() {
let source = [0x22u8; 4];
let mut target_context = FrameChunkTestContext {
target_result: 4,
split_result: 5,
internal_result: 6,
..FrameChunkTestContext::default()
};
let mut target_first = 1;
let mut target_stage = 0;
let target_state = frame_chunk_test_state(
&mut target_context,
&mut target_first,
&mut target_stage,
1,
1,
0,
);
let mut target_output = [0xa5u8; 8];
let target_result = unsafe {
compress_frame_chunk_body_with(
&target_state,
target_output.as_mut_ptr().cast(),
target_output.len(),
source.as_ptr().cast(),
source.len(),
0,
)
};
assert_eq!(target_result, 4);
assert_eq!(target_context.target_calls, 1);
assert_eq!(target_context.split_calls, 0);
assert_eq!(target_context.internal_calls, 0);
let mut split_context = FrameChunkTestContext {
split_result: 5,
internal_result: 6,
..FrameChunkTestContext::default()
};
let mut split_first = 1;
let mut split_stage = 0;
let split_state = frame_chunk_test_state(
&mut split_context,
&mut split_first,
&mut split_stage,
0,
1,
0,
);
let mut split_output = [0xa5u8; 8];
let split_result = unsafe {
compress_frame_chunk_body_with(
&split_state,
split_output.as_mut_ptr().cast(),
split_output.len(),
source.as_ptr().cast(),
source.len(),
0,
)
};
assert_eq!(split_result, 5);
assert_eq!(split_context.target_calls, 0);
assert_eq!(split_context.split_calls, 1);
assert_eq!(split_context.internal_calls, 0);
}
#[test]
fn frame_chunk_keeps_state_unchanged_on_early_capacity_error() {
let mut context = FrameChunkTestContext::default();
let mut is_first_block = 1;
let mut stage = 23;
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
let source = [0x33u8; 4];
let mut output = [0xa5u8; 5];
let result = unsafe {
compress_frame_chunk_body_with(
&state,
output.as_mut_ptr().cast(),
output.len(),
source.as_ptr().cast(),
source.len(),
1,
)
};
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
assert_eq!(context.checksum_calls, 1);
assert_eq!(context.prepare_calls, 0);
assert_eq!(context.internal_calls, 0);
assert_eq!(is_first_block, 1);
assert_eq!(stage, 23);
assert_eq!(output, [0xa5; 5]);
}
#[test]
fn simple_strategy_follows_source_size_tiers() {
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);