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:
+408
-7
@@ -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!(
|
||||
|
||||
Reference in New Issue
Block a user