feat(compress): move sequence API orchestration into Rust

Move the high-level orchestration of ZSTD_compressSequences() and
ZSTD_compressSequencesAndLiterals() into Rust. Rust now owns the validation
precedence, CCtx initialization handoff, frame-header and checksum ordering,
block-loop dispatch, and output accounting for both public sequence APIs.

Keep the private CCtx, sequence-store, block-state, checksum, and conversion
layouts in C. C supplies scalar block projections and callbacks for private
initialization, frame-header emission, checksum operations, and sequence-state
preparation, preserving the ABI boundary and the existing codec leaves.

Test Plan:
- focused sequence API policy tests: 4 passed
- cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1:
  533 passed
- cargo test --manifest-path rust/cli/Cargo.toml --all-targets --
  --test-threads=1: 169 passed
- legacy compression/decompression/dictionary-builder feature matrix:
  588 passed
- six library/CLI clippy gates with -D warnings
- capped serial native library/program rebuilds, 41 CLI tests, Rust library
  smoke, and full test-zstd round trips
- capped serial stress gates: 278 fuzzer cases, 84+129+143 zstream cases,
  and 1,601 decode-corpus cases
- every heavyweight command used CARGO_BUILD_JOBS=1 or make -j1 and
  ulimit -v 41943040; no worker/native process remained afterward

Commit is intentionally unsigned because GPG pinentry hangs in this
non-interactive environment.
This commit is contained in:
2026-07-19 10:12:21 +02:00
parent fa37b1d533
commit 3b989ab3ee
2 changed files with 626 additions and 169 deletions
+408 -7
View File
@@ -1443,6 +1443,107 @@ const _: () = {
);
};
type SequenceApiInitFn =
unsafe extern "C" fn(*mut c_void, usize, *mut ZSTD_rust_sequenceApiState) -> usize;
type SequenceApiWriteFrameHeaderFn =
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, usize) -> usize;
type SequenceApiUpdateChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
type SequenceApiDigestChecksumFn = unsafe extern "C" fn(*mut c_void) -> c_uint;
type SequenceApiWriteChecksumFn = unsafe extern "C" fn(*mut c_void, *mut c_void, c_uint);
/// Explicit projection for the public sequence-compression API orchestration.
///
/// Rust owns validation ordering, frame-header/checksum sequencing, and
/// output accounting. C retains the private CCtx, sequence-store, block,
/// and checksum layouts through the two block-state projections and callbacks.
#[repr(C)]
pub struct ZSTD_rust_sequenceApiState {
callback_context: *mut c_void,
sequence_state: *mut ZSTD_rust_sequenceCompressionState,
sequence_literals_state: *mut ZSTD_rust_sequenceLiteralsState,
init: SequenceApiInitFn,
write_frame_header: SequenceApiWriteFrameHeaderFn,
update_checksum: SequenceApiUpdateChecksumFn,
digest_checksum: SequenceApiDigestChecksumFn,
write_checksum: SequenceApiWriteChecksumFn,
checksum_flag: c_int,
block_delimiters: c_int,
validate_sequences: c_int,
}
const _: () = {
assert!(offset_of!(ZSTD_rust_sequenceApiState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_sequenceApiState, sequence_state) == size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_sequenceApiState, sequence_literals_state) == 2 * size_of::<usize>()
);
assert!(offset_of!(ZSTD_rust_sequenceApiState, init) == 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_sequenceApiState, write_frame_header) == 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_sequenceApiState, update_checksum) == 5 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_sequenceApiState, digest_checksum) == 6 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_sequenceApiState, write_checksum) == 7 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_sequenceApiState, checksum_flag) == size_of::<[usize; 8]>());
assert!(
offset_of!(ZSTD_rust_sequenceApiState, block_delimiters)
== size_of::<[usize; 8]>() + size_of::<c_int>()
);
assert!(
offset_of!(ZSTD_rust_sequenceApiState, validate_sequences)
== size_of::<[usize; 8]>() + 2 * size_of::<c_int>()
);
assert!(
size_of::<ZSTD_rust_sequenceApiState>() == if size_of::<usize>() == 8 { 80 } else { 44 }
);
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct SequenceApiPlan {
update_input_checksum: bool,
append_frame_checksum: bool,
}
/// Apply the public sequence API's post-initialization validation policy.
///
/// The order is intentional and matches the C entry point: the literals
/// variant rejects no-delimiter mode first, then sequence validation, then a
/// frame checksum. The ordinary variant permits all three independently.
fn sequence_api_plan(
with_literals: bool,
block_delimiters: c_int,
validate_sequences: c_int,
checksum_flag: c_int,
) -> Result<SequenceApiPlan, usize> {
if with_literals {
if block_delimiters == ZSTD_SF_NO_BLOCK_DELIMITERS {
return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported));
}
if validate_sequences != 0 {
return Err(ERROR(ZstdErrorCode::ParameterUnsupported));
}
if checksum_flag != 0 {
return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported));
}
return Ok(SequenceApiPlan {
update_input_checksum: false,
append_frame_checksum: false,
});
}
Ok(SequenceApiPlan {
update_input_checksum: checksum_flag != 0,
append_frame_checksum: checksum_flag != 0,
})
}
#[inline]
fn sequence_api_validate_literal_capacity(lit_size: usize, lit_capacity: usize) -> usize {
if lit_capacity < lit_size {
ERROR(ZstdErrorCode::WorkSpaceTooSmall)
} else {
0
}
}
/// Explicit projection of the state used by `ZSTD_compressSeqStore_singleBlock`.
///
/// Sequence-store construction and split discovery remain in C. Only the
@@ -3685,10 +3786,10 @@ unsafe fn write_empty_sequence_block(dst: *mut u8, dst_capacity: usize) -> usize
/// Rust implementation of the per-block loop from
/// `ZSTD_compressSequences_internal()`.
///
/// Context initialization, frame-header/checksum handling, and public API
/// validation remain in C. The projected state keeps the ABI explicit while
/// allowing the loop to reuse the existing Rust sequence-transfer, entropy,
/// and block-serialization leaves.
/// Public initialization, frame-header/checksum ordering, and validation are
/// driven by the Rust API orchestrator below. The projected state keeps the
/// ABI explicit while allowing this loop to reuse the existing Rust
/// sequence-transfer, entropy, and block-serialization leaves.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressSequencesInternal(
state: *const ZSTD_rust_sequenceCompressionState,
@@ -3926,9 +4027,10 @@ unsafe fn write_empty_sequence_literals_block(dst: *mut u8, dst_capacity: usize)
/// Rust implementation of the block loop from
/// ZSTD_compressSequencesAndLiterals_internal.
///
/// The C wrapper retains public-API initialization and the CCtx-dependent
/// sequence conversion callback. Rust owns the external literal cursor,
/// per-block entropy pass, compressed-block framing, and completion checks.
/// The C wrapper retains the private CCtx-dependent state and sequence
/// conversion callback. Rust owns the public validation/ordering layer, the
/// external literal cursor, per-block entropy pass, compressed-block framing,
/// and completion checks.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiteralsInternal(
state: *const ZSTD_rust_sequenceLiteralsState,
@@ -4086,6 +4188,251 @@ pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiteralsInternal(
c_size
}
unsafe fn sequence_api_prepare(
state: &mut ZSTD_rust_sequenceApiState,
pledged_src_size: usize,
with_literals: bool,
lit_size: usize,
lit_capacity: usize,
) -> Result<SequenceApiPlan, usize> {
if with_literals {
let capacity_result = sequence_api_validate_literal_capacity(lit_size, lit_capacity);
if ERR_isError(capacity_result) {
return Err(capacity_result);
}
}
if state.callback_context.is_null()
|| state.sequence_state.is_null()
|| state.sequence_literals_state.is_null()
|| state.init as usize == 0
|| state.write_frame_header as usize == 0
|| state.update_checksum as usize == 0
|| state.digest_checksum as usize == 0
|| state.write_checksum as usize == 0
{
return Err(ERROR(ZstdErrorCode::Generic));
}
let init_result = unsafe {
(state.init)(
state.callback_context,
pledged_src_size,
state as *mut ZSTD_rust_sequenceApiState,
)
};
if ERR_isError(init_result) {
return Err(init_result);
}
sequence_api_plan(
with_literals,
state.block_delimiters,
state.validate_sequences,
state.checksum_flag,
)
}
unsafe fn sequence_api_write_frame_header(
state: &ZSTD_rust_sequenceApiState,
op: &mut *mut u8,
dst_capacity: &mut usize,
c_size: &mut usize,
pledged_src_size: usize,
) -> usize {
let frame_header_size = unsafe {
(state.write_frame_header)(
state.callback_context,
(*op).cast(),
*dst_capacity,
pledged_src_size,
)
};
if ERR_isError(frame_header_size) {
return frame_header_size;
}
if frame_header_size > *dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
*op = (*op).add(frame_header_size);
}
*dst_capacity -= frame_header_size;
*c_size = c_size.wrapping_add(frame_header_size);
0
}
unsafe fn sequence_api_append_frame_checksum(
state: &ZSTD_rust_sequenceApiState,
plan: SequenceApiPlan,
op: *mut u8,
dst_capacity: usize,
c_size: &mut usize,
) -> usize {
if !plan.append_frame_checksum {
return 0;
}
/* Keep the original ordering: digest the checksum before checking the
* remaining destination capacity. */
let checksum = unsafe { (state.digest_checksum)(state.callback_context) };
if dst_capacity < 4 {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
(state.write_checksum)(state.callback_context, op.cast(), checksum);
}
*c_size = c_size.wrapping_add(4);
0
}
/// Rust-owned orchestration for `ZSTD_compressSequences`.
///
/// The C wrapper provides the post-initialization block-state projection and
/// callbacks for the private CCtx/header/checksum operations. Rust preserves
/// the public ordering: initialize, write the frame header, update the input
/// checksum, emit blocks, then append the frame checksum.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressSequences(
state: *mut ZSTD_rust_sequenceApiState,
dst: *mut c_void,
dst_capacity: usize,
in_seqs: *const ZSTD_Sequence,
in_seqs_size: usize,
src: *const c_void,
src_size: usize,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let state = unsafe { &mut *state };
let plan = match unsafe { sequence_api_prepare(state, src_size, false, 0, 0) } {
Ok(plan) => plan,
Err(result) => return result,
};
let mut op = dst.cast::<u8>();
let mut dst_capacity = dst_capacity;
let mut c_size = 0usize;
let header_result = unsafe {
sequence_api_write_frame_header(state, &mut op, &mut dst_capacity, &mut c_size, src_size)
};
if ERR_isError(header_result) {
return header_result;
}
if plan.update_input_checksum && src_size != 0 {
unsafe { (state.update_checksum)(state.callback_context, src, src_size) };
}
let block_size = unsafe {
ZSTD_rust_compressSequencesInternal(
&*state.sequence_state,
op.cast(),
dst_capacity,
in_seqs,
in_seqs_size,
src,
src_size,
)
};
if ERR_isError(block_size) {
return block_size;
}
if block_size > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
op = op.add(block_size);
}
dst_capacity -= block_size;
c_size = c_size.wrapping_add(block_size);
let checksum_result =
unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) };
if ERR_isError(checksum_result) {
return checksum_result;
}
c_size
}
/// Rust-owned orchestration for `ZSTD_compressSequencesAndLiterals`.
///
/// The literal-capacity check intentionally precedes CCtx initialization, and
/// the post-initialization incompatibility checks retain their original
/// precedence. The existing Rust block loop remains responsible for literal
/// accounting, sequence conversion, and block codec operations.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiterals(
state: *mut ZSTD_rust_sequenceApiState,
dst: *mut c_void,
dst_capacity: usize,
in_seqs: *const ZSTD_Sequence,
in_seqs_size: usize,
literals: *const c_void,
lit_size: usize,
lit_capacity: usize,
decompressed_size: usize,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let state = unsafe { &mut *state };
let plan = match unsafe {
sequence_api_prepare(state, decompressed_size, true, lit_size, lit_capacity)
} {
Ok(plan) => plan,
Err(result) => return result,
};
let mut op = dst.cast::<u8>();
let mut dst_capacity = dst_capacity;
let mut c_size = 0usize;
let header_result = unsafe {
sequence_api_write_frame_header(
state,
&mut op,
&mut dst_capacity,
&mut c_size,
decompressed_size,
)
};
if ERR_isError(header_result) {
return header_result;
}
let block_size = unsafe {
ZSTD_rust_compressSequencesAndLiteralsInternal(
&*state.sequence_literals_state,
op.cast(),
dst_capacity,
in_seqs,
in_seqs_size,
literals,
lit_size,
decompressed_size,
)
};
if ERR_isError(block_size) {
return block_size;
}
if block_size > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
dst_capacity -= block_size;
c_size = c_size.wrapping_add(block_size);
let checksum_result =
unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) };
if ERR_isError(checksum_result) {
return checksum_result;
}
c_size
}
unsafe fn compress_frame(
dst: *mut c_void,
dst_capacity: usize,
@@ -4546,6 +4893,7 @@ pub unsafe extern "C" fn ZSTD_compressStream2(
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::ERR_getErrorCode;
use std::io::Write;
use std::process::{Command, Stdio};
@@ -6992,6 +7340,59 @@ mod tests {
);
}
#[test]
fn sequence_api_plan_places_input_and_frame_checksums_around_blocks() {
assert_eq!(
sequence_api_plan(false, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1),
Ok(SequenceApiPlan {
update_input_checksum: true,
append_frame_checksum: true,
})
);
}
#[test]
fn sequence_api_plan_disables_checksums_for_literals_variant() {
assert_eq!(
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 0),
Ok(SequenceApiPlan {
update_input_checksum: false,
append_frame_checksum: false,
})
);
}
#[test]
fn sequence_api_plan_preserves_literals_validation_precedence() {
assert_eq!(
ERR_getErrorCode(
sequence_api_plan(true, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1).unwrap_err()
),
ZstdErrorCode::FrameParameterUnsupported as i32
);
assert_eq!(
ERR_getErrorCode(
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 1, 1).unwrap_err()
),
ZstdErrorCode::ParameterUnsupported as i32
);
assert_eq!(
ERR_getErrorCode(
sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 1).unwrap_err()
),
ZstdErrorCode::FrameParameterUnsupported as i32
);
}
#[test]
fn sequence_api_literal_capacity_check_precedes_initialization() {
assert_eq!(
sequence_api_validate_literal_capacity(9, 8),
ERROR(ZstdErrorCode::WorkSpaceTooSmall)
);
assert_eq!(sequence_api_validate_literal_capacity(8, 9), 0);
}
#[test]
fn sequence_copier_selector_returns_no_delimiters_mode() {
assert_eq!(