refactor(compress): move sequence generation policy to Rust
ZSTD_generateSequences() previously mixed public parameter validation, temporary-output allocation, sequence-collector setup, compression, and cleanup inside the C translation unit. Move that ordering into Rust behind callbacks so private ZSTD_CCtx and SeqCollector layouts remain C-owned. The adapter retains C allocation, context access, and the existing ZSTD_compress2 operation, while Rust guarantees temporary-output cleanup after collector setup failures and compression errors and preserves the original target-block-size and worker parameter checks. Focused callback tests cover validation-before-allocation, cleanup on setup failure, and successful callback ordering/count publication. Test Plan: - `cargo fmt --manifest-path rust/Cargo.toml --all -- --check` -- passed - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml generate_sequences --lib` -- passed (3 tests) - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --all-targets` -- passed (762 tests) - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings` -- passed - `ulimit -v 41943040; make -j1` -- passed
This commit is contained in:
@@ -1275,6 +1275,105 @@ const ZSTD_COMPRESSION_STAGE_ONGOING: c_int = 2;
|
||||
#[cfg(test)]
|
||||
const ZSTD_COMPRESSION_STAGE_ENDING: c_int = 3;
|
||||
|
||||
type GenerateSequencesGetParameterFn =
|
||||
unsafe extern "C" fn(*mut c_void, c_int, *mut c_int) -> usize;
|
||||
type GenerateSequencesAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
|
||||
type GenerateSequencesFreeFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
||||
type GenerateSequencesSetCollectorFn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut ZSTD_Sequence, usize) -> usize;
|
||||
type GenerateSequencesCompress2Fn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize;
|
||||
type GenerateSequencesGetCountFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
||||
|
||||
/// C-owned context projection for `ZSTD_generateSequences()`.
|
||||
///
|
||||
/// Rust owns the public policy and temporary-buffer lifetime. C callbacks keep
|
||||
/// the private `ZSTD_CCtx` and `SeqCollector` layouts out of this module.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_generateSequencesState {
|
||||
callback_context: *mut c_void,
|
||||
get_parameter: GenerateSequencesGetParameterFn,
|
||||
allocate: GenerateSequencesAllocateFn,
|
||||
free: GenerateSequencesFreeFn,
|
||||
set_collector: GenerateSequencesSetCollectorFn,
|
||||
compress2: GenerateSequencesCompress2Fn,
|
||||
get_count: GenerateSequencesGetCountFn,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, callback_context) == 0);
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, get_parameter) == size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, allocate) == 2 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, free) == 3 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, set_collector) == 4 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, compress2) == 5 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_generateSequencesState, get_count) == 6 * size_of::<usize>());
|
||||
assert!(size_of::<ZSTD_rust_generateSequencesState>() == 7 * size_of::<usize>());
|
||||
};
|
||||
|
||||
/// Own the validation, allocation, collection setup, compression call, and
|
||||
/// cleanup ordering for the public sequence-generation API.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_generateSequences(
|
||||
state: *const ZSTD_rust_generateSequencesState,
|
||||
out_seqs: *mut ZSTD_Sequence,
|
||||
out_seqs_size: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
) -> usize {
|
||||
if state.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
let state = unsafe { &*state };
|
||||
let dst_capacity = crate::zstd_compress_api::ZSTD_compressBound(src_size);
|
||||
let mut parameter = 0;
|
||||
|
||||
let result = unsafe {
|
||||
(state.get_parameter)(
|
||||
state.callback_context,
|
||||
ZSTD_C_TARGET_C_BLOCK_SIZE,
|
||||
&mut parameter,
|
||||
)
|
||||
};
|
||||
if ERR_isError(result) {
|
||||
return result;
|
||||
}
|
||||
if parameter != 0 {
|
||||
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
||||
}
|
||||
|
||||
let result =
|
||||
unsafe { (state.get_parameter)(state.callback_context, ZSTD_C_NB_WORKERS, &mut parameter) };
|
||||
if ERR_isError(result) {
|
||||
return result;
|
||||
}
|
||||
if parameter != 0 {
|
||||
return ERROR(ZstdErrorCode::ParameterUnsupported);
|
||||
}
|
||||
|
||||
let dst = unsafe { (state.allocate)(state.callback_context, dst_capacity) };
|
||||
if dst.is_null() {
|
||||
return ERROR(ZstdErrorCode::MemoryAllocation);
|
||||
}
|
||||
|
||||
let result = unsafe { (state.set_collector)(state.callback_context, out_seqs, out_seqs_size) };
|
||||
if ERR_isError(result) {
|
||||
unsafe { (state.free)(state.callback_context, dst) };
|
||||
return result;
|
||||
}
|
||||
|
||||
let result =
|
||||
unsafe { (state.compress2)(state.callback_context, dst, dst_capacity, src, src_size) };
|
||||
unsafe { (state.free)(state.callback_context, dst) };
|
||||
if ERR_isError(result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let sequence_count = unsafe { (state.get_count)(state.callback_context) };
|
||||
debug_assert!(sequence_count <= crate::zstd_compress_api::ZSTD_sequenceBound(src_size));
|
||||
sequence_count
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn compress_continue_block_body_with(
|
||||
state: &ZSTD_rust_compressContinueBlockState,
|
||||
@@ -9663,6 +9762,172 @@ mod tests {
|
||||
const ZSTD_BTOPT: c_int = 7;
|
||||
const ZSTD_BTULTRA2: c_int = 9;
|
||||
|
||||
struct GenerateSequencesTestContext {
|
||||
events: Vec<&'static str>,
|
||||
target_c_block_size: c_int,
|
||||
nb_workers: c_int,
|
||||
allocation_fails: bool,
|
||||
collector_result: usize,
|
||||
compress_result: usize,
|
||||
sequence_count: usize,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_get_parameter_test(
|
||||
context: *mut c_void,
|
||||
parameter: c_int,
|
||||
value: *mut c_int,
|
||||
) -> usize {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
let (event, result) = match parameter {
|
||||
ZSTD_C_TARGET_C_BLOCK_SIZE => ("target", context.target_c_block_size),
|
||||
ZSTD_C_NB_WORKERS => ("workers", context.nb_workers),
|
||||
_ => ("other", 0),
|
||||
};
|
||||
context.events.push(event);
|
||||
unsafe { *value = result };
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_allocate_test(
|
||||
context: *mut c_void,
|
||||
_size: usize,
|
||||
) -> *mut c_void {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
context.events.push("allocate");
|
||||
if context.allocation_fails {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
Box::into_raw(Box::new(0_u8)).cast()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_free_test(context: *mut c_void, pointer: *mut c_void) {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
context.events.push("free");
|
||||
if !pointer.is_null() {
|
||||
unsafe { drop(Box::from_raw(pointer.cast::<u8>())) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_set_collector_test(
|
||||
context: *mut c_void,
|
||||
_out_seqs: *mut ZSTD_Sequence,
|
||||
_out_seqs_size: usize,
|
||||
) -> usize {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
context.events.push("collector");
|
||||
context.collector_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_compress2_test(
|
||||
context: *mut c_void,
|
||||
_dst: *mut c_void,
|
||||
_dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
) -> usize {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
context.events.push("compress2");
|
||||
context.compress_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn generate_sequences_get_count_test(context: *mut c_void) -> usize {
|
||||
let context = unsafe { &mut *context.cast::<GenerateSequencesTestContext>() };
|
||||
context.events.push("count");
|
||||
context.sequence_count
|
||||
}
|
||||
|
||||
fn generate_sequences_test_state(
|
||||
context: &mut GenerateSequencesTestContext,
|
||||
) -> ZSTD_rust_generateSequencesState {
|
||||
ZSTD_rust_generateSequencesState {
|
||||
callback_context: context as *mut GenerateSequencesTestContext as *mut c_void,
|
||||
get_parameter: generate_sequences_get_parameter_test,
|
||||
allocate: generate_sequences_allocate_test,
|
||||
free: generate_sequences_free_test,
|
||||
set_collector: generate_sequences_set_collector_test,
|
||||
compress2: generate_sequences_compress2_test,
|
||||
get_count: generate_sequences_get_count_test,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sequences_rejects_unsupported_parameters_before_allocation() {
|
||||
let mut context = GenerateSequencesTestContext {
|
||||
events: Vec::new(),
|
||||
target_c_block_size: 1,
|
||||
nb_workers: 0,
|
||||
allocation_fails: false,
|
||||
collector_result: 0,
|
||||
compress_result: 0,
|
||||
sequence_count: 0,
|
||||
};
|
||||
let state = generate_sequences_test_state(&mut context);
|
||||
|
||||
let result =
|
||||
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
||||
|
||||
assert_eq!(
|
||||
ERR_getErrorCode(result),
|
||||
ZstdErrorCode::ParameterUnsupported as i32
|
||||
);
|
||||
assert_eq!(context.events, ["target"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sequences_frees_temporary_output_on_setup_failure() {
|
||||
let mut context = GenerateSequencesTestContext {
|
||||
events: Vec::new(),
|
||||
target_c_block_size: 0,
|
||||
nb_workers: 0,
|
||||
allocation_fails: false,
|
||||
collector_result: ERROR(ZstdErrorCode::Generic),
|
||||
compress_result: 0,
|
||||
sequence_count: 0,
|
||||
};
|
||||
let state = generate_sequences_test_state(&mut context);
|
||||
|
||||
let result =
|
||||
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
||||
|
||||
assert_eq!(ERR_getErrorCode(result), ZstdErrorCode::Generic as i32);
|
||||
assert_eq!(
|
||||
context.events,
|
||||
["target", "workers", "allocate", "collector", "free"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sequences_runs_callbacks_in_order_and_returns_count() {
|
||||
let mut context = GenerateSequencesTestContext {
|
||||
events: Vec::new(),
|
||||
target_c_block_size: 0,
|
||||
nb_workers: 0,
|
||||
allocation_fails: false,
|
||||
collector_result: 0,
|
||||
compress_result: 0,
|
||||
sequence_count: 2,
|
||||
};
|
||||
let state = generate_sequences_test_state(&mut context);
|
||||
|
||||
let result =
|
||||
unsafe { ZSTD_rust_generateSequences(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
||||
|
||||
assert_eq!(result, 2);
|
||||
assert_eq!(
|
||||
context.events,
|
||||
[
|
||||
"target",
|
||||
"workers",
|
||||
"allocate",
|
||||
"collector",
|
||||
"compress2",
|
||||
"free",
|
||||
"count"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn select_block_compressor_test_default(
|
||||
_match_state: *mut c_void,
|
||||
_seq_store: *mut SeqStore_t,
|
||||
|
||||
Reference in New Issue
Block a user