feat(compress): split static CCtx construction callbacks

Move static CCtx construction sequencing out of the monolithic C leaf. Rust
now validates the existing public workspace contract, sequences workspace
creation, CCtx and auxiliary reservations, publication, and BMI2 setup, and
short-circuits callback-reported failures. C callbacks retain the private
cwksp and CCtx layouts plus the native workspace-size and CPU-feature rules.

Add matching ABI layout assertions and focused callback-order, reservation-
failure, workspace-capacity, and NULL-path unit coverage. Static-CDict and
heap-CCtx paths remain unchanged.

Test Plan:
- `rustfmt --edition 2021 --check rust/src/zstd_compress.rs` -- passed
- GCC and Clang `-fsyntax-only` checks on `zstd_compress.c` -- passed
- GCC syntax checks with `ZSTD_DISABLE_ASM=1` and
  `ZSTD_ADDRESS_SANITIZER=1` -- passed
- `git diff --check` -- passed
- Cargo, make, native suites, and fuzzers not run per request
This commit is contained in:
2026-07-20 16:00:30 +02:00
parent 28a74c5edb
commit 5d91f86a34
2 changed files with 453 additions and 76 deletions
+330 -54
View File
@@ -2351,33 +2351,68 @@ pub unsafe extern "C" fn ZSTD_rust_createCDictAdvancedWrapper(
}
}
type InitStaticCCtxFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
type InitStaticCCtxCreateWorkspaceFn = unsafe extern "C" fn(*mut c_void);
type InitStaticCCtxReserveObjectFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
type InitStaticCCtxZeroFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
type InitStaticCCtxMoveWorkspaceFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
type InitStaticCCtxCheckAvailableFn = unsafe extern "C" fn(*mut c_void) -> c_int;
type InitStaticCCtxReserveBlockStateFn = unsafe extern "C" fn(*mut c_void, c_int) -> *mut c_void;
type InitStaticCCtxReserveTmpWorkspaceFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
type InitStaticCCtxSetBmi2Fn = unsafe extern "C" fn(*mut c_void);
const ZSTD_STATIC_WORKSPACE_ALIGNMENT: usize = 8;
const INIT_STATIC_CCTX_PREV_CBLOCK: c_int = 0;
const INIT_STATIC_CCTX_NEXT_CBLOCK: c_int = 1;
/// Explicit projection for the public `ZSTD_initStaticCCtx` wrapper.
///
/// Rust owns workspace pointer/alignment validation, minimum-size validation,
/// and callback ordering. C retains the private static-workspace layout and
/// context initialization behind one callback.
/// construction ordering, and callback failure policy. C retains the private
/// static-workspace and CCtx layout operations behind narrow callbacks.
#[repr(C)]
pub struct ZSTD_rust_initStaticCCtxState {
callback_context: *mut c_void,
workspace: *mut c_void,
workspace_size: usize,
cctx_size: usize,
init: InitStaticCCtxFn,
create_workspace: InitStaticCCtxCreateWorkspaceFn,
reserve_object: InitStaticCCtxReserveObjectFn,
zero: InitStaticCCtxZeroFn,
move_workspace: InitStaticCCtxMoveWorkspaceFn,
check_available: InitStaticCCtxCheckAvailableFn,
reserve_block_state: InitStaticCCtxReserveBlockStateFn,
reserve_tmp_workspace: InitStaticCCtxReserveTmpWorkspaceFn,
set_bmi2: InitStaticCCtxSetBmi2Fn,
}
const _: () = {
assert!(size_of::<InitStaticCCtxCreateWorkspaceFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxReserveObjectFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxZeroFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxMoveWorkspaceFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxCheckAvailableFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxReserveBlockStateFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxReserveTmpWorkspaceFn>() == size_of::<usize>());
assert!(size_of::<InitStaticCCtxSetBmi2Fn>() == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, workspace_size) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, cctx_size) == 3 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, init) == 4 * size_of::<usize>());
assert!(size_of::<ZSTD_rust_initStaticCCtxState>() == size_of::<[usize; 5]>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, create_workspace) == 4 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, reserve_object) == 5 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, zero) == 6 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, move_workspace) == 7 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, check_available) == 8 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_initStaticCCtxState, reserve_block_state) == 9 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_initStaticCCtxState, reserve_tmp_workspace) == 10 * size_of::<usize>()
);
assert!(offset_of!(ZSTD_rust_initStaticCCtxState, set_bmi2) == 11 * size_of::<usize>());
assert!(size_of::<ZSTD_rust_initStaticCCtxState>() == size_of::<[usize; 12]>());
};
/// Validate static CCtx workspace ownership before entering the C leaf.
/// Validate and sequence static CCtx construction before entering C leaves.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_initStaticCCtx(
state: *const ZSTD_rust_initStaticCCtxState,
@@ -2393,7 +2428,33 @@ pub unsafe extern "C" fn ZSTD_rust_initStaticCCtx(
{
return ptr::null_mut();
}
unsafe { (state.init)(state.callback_context) }
unsafe {
(state.create_workspace)(state.callback_context);
let cctx = (state.reserve_object)(state.callback_context);
if cctx.is_null() {
return ptr::null_mut();
}
(state.zero)(state.callback_context, cctx);
(state.move_workspace)(state.callback_context, cctx);
if (state.check_available)(state.callback_context) == 0 {
return ptr::null_mut();
}
if (state.reserve_block_state)(state.callback_context, INIT_STATIC_CCTX_PREV_CBLOCK)
.is_null()
{
return ptr::null_mut();
}
if (state.reserve_block_state)(state.callback_context, INIT_STATIC_CCTX_NEXT_CBLOCK)
.is_null()
{
return ptr::null_mut();
}
if (state.reserve_tmp_workspace)(state.callback_context).is_null() {
return ptr::null_mut();
}
(state.set_bmi2)(state.callback_context);
cctx
}
}
type FreeCCtxContentFn = unsafe extern "C" fn(*mut c_void);
@@ -18444,13 +18505,84 @@ mod tests {
#[derive(Default)]
struct InitStaticCCtxTestContext {
events: Vec<&'static str>,
result: *mut c_void,
reserved_cctx: *mut c_void,
check_available_result: c_int,
reserve_block_state_failure: Option<usize>,
reserve_block_state_count: usize,
reserve_tmp_workspace_result: *mut c_void,
}
unsafe extern "C" fn init_static_cctx_test_init(context: *mut c_void) -> *mut c_void {
unsafe fn init_static_cctx_test_context(
context: *mut c_void,
) -> &'static mut InitStaticCCtxTestContext {
unsafe { &mut *context.cast::<InitStaticCCtxTestContext>() }
}
unsafe extern "C" fn init_static_cctx_test_create_workspace(context: *mut c_void) {
unsafe { init_static_cctx_test_context(context) }
.events
.push("create-workspace");
}
unsafe extern "C" fn init_static_cctx_test_reserve_object(context: *mut c_void) -> *mut c_void {
let context = unsafe { init_static_cctx_test_context(context) };
context.events.push("reserve-object");
context.reserved_cctx
}
unsafe extern "C" fn init_static_cctx_test_zero(context: *mut c_void, _cctx: *mut c_void) {
unsafe { init_static_cctx_test_context(context) }
.events
.push("zero");
}
unsafe extern "C" fn init_static_cctx_test_move_workspace(
context: *mut c_void,
_cctx: *mut c_void,
) {
unsafe { init_static_cctx_test_context(context) }
.events
.push("move-workspace");
}
unsafe extern "C" fn init_static_cctx_test_check_available(context: *mut c_void) -> c_int {
let context = unsafe { init_static_cctx_test_context(context) };
context.events.push("check-available");
context.check_available_result
}
unsafe extern "C" fn init_static_cctx_test_reserve_block_state(
context: *mut c_void,
block_state: c_int,
) -> *mut c_void {
let context = unsafe { &mut *context.cast::<InitStaticCCtxTestContext>() };
context.events.push("init");
context.result
context.events.push(match block_state {
INIT_STATIC_CCTX_PREV_CBLOCK => "reserve-prev-block-state",
INIT_STATIC_CCTX_NEXT_CBLOCK => "reserve-next-block-state",
_ => "reserve-unknown-block-state",
});
let result =
if context.reserve_block_state_failure == Some(context.reserve_block_state_count) {
ptr::null_mut()
} else {
ptr::dangling_mut()
};
context.reserve_block_state_count += 1;
result
}
unsafe extern "C" fn init_static_cctx_test_reserve_tmp_workspace(
context: *mut c_void,
) -> *mut c_void {
let context = unsafe { init_static_cctx_test_context(context) };
context.events.push("reserve-tmp-workspace");
context.reserve_tmp_workspace_result
}
unsafe extern "C" fn init_static_cctx_test_set_bmi2(context: *mut c_void) {
unsafe { init_static_cctx_test_context(context) }
.events
.push("set-bmi2");
}
fn init_static_cctx_test_state(
@@ -18464,50 +18596,23 @@ mod tests {
workspace,
workspace_size,
cctx_size,
init: init_static_cctx_test_init,
create_workspace: init_static_cctx_test_create_workspace,
reserve_object: init_static_cctx_test_reserve_object,
zero: init_static_cctx_test_zero,
move_workspace: init_static_cctx_test_move_workspace,
check_available: init_static_cctx_test_check_available,
reserve_block_state: init_static_cctx_test_reserve_block_state,
reserve_tmp_workspace: init_static_cctx_test_reserve_tmp_workspace,
set_bmi2: init_static_cctx_test_set_bmi2,
}
}
#[test]
fn init_static_cctx_rejects_an_unaligned_workspace_before_callback() {
fn init_static_cctx_runs_private_construction_steps_in_order() {
let mut context = InitStaticCCtxTestContext {
result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 2];
let unaligned_workspace = workspace.as_mut_ptr().cast::<u8>().wrapping_add(1).cast();
let state = init_static_cctx_test_state(&mut context, unaligned_workspace, usize::MAX, 0);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert!(context.events.is_empty());
}
#[test]
fn init_static_cctx_rejects_a_small_workspace_before_callback() {
let mut context = InitStaticCCtxTestContext {
result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace),
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert!(context.events.is_empty());
}
#[test]
fn init_static_cctx_calls_the_initializer_after_workspace_validation() {
let mut context = InitStaticCCtxTestContext {
result: ptr::dangling_mut(),
reserved_cctx: ptr::dangling_mut(),
check_available_result: 1,
reserve_tmp_workspace_result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
@@ -18520,14 +18625,155 @@ mod tests {
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert_eq!(result, context.result);
assert_eq!(context.events, ["init"]);
assert_eq!(result, context.reserved_cctx);
assert_eq!(
context.events,
[
"create-workspace",
"reserve-object",
"zero",
"move-workspace",
"check-available",
"reserve-prev-block-state",
"reserve-next-block-state",
"reserve-tmp-workspace",
"set-bmi2",
]
);
}
#[test]
fn init_static_cctx_stops_after_cctx_reservation_failure() {
let mut context = InitStaticCCtxTestContext {
check_available_result: 1,
reserve_tmp_workspace_result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace),
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert_eq!(context.events, ["create-workspace", "reserve-object"]);
}
#[test]
fn init_static_cctx_stops_after_workspace_capacity_failure() {
let mut context = InitStaticCCtxTestContext {
reserved_cctx: ptr::dangling_mut(),
reserve_tmp_workspace_result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace) - 1,
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert_eq!(
context.events,
[
"create-workspace",
"reserve-object",
"zero",
"move-workspace",
"check-available",
]
);
}
#[test]
fn init_static_cctx_stops_after_block_state_reservation_failure() {
let mut context = InitStaticCCtxTestContext {
reserved_cctx: ptr::dangling_mut(),
check_available_result: 1,
reserve_block_state_failure: Some(1),
reserve_tmp_workspace_result: ptr::dangling_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace) - 1,
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert_eq!(
context.events,
[
"create-workspace",
"reserve-object",
"zero",
"move-workspace",
"check-available",
"reserve-prev-block-state",
"reserve-next-block-state",
]
);
}
#[test]
fn init_static_cctx_stops_after_tmp_workspace_reservation_failure() {
let mut context = InitStaticCCtxTestContext {
reserved_cctx: ptr::dangling_mut(),
check_available_result: 1,
reserve_tmp_workspace_result: ptr::null_mut(),
..Default::default()
};
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace) - 1,
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert_eq!(
context.events,
[
"create-workspace",
"reserve-object",
"zero",
"move-workspace",
"check-available",
"reserve-prev-block-state",
"reserve-next-block-state",
"reserve-tmp-workspace",
]
);
}
#[test]
fn init_static_cctx_rejects_a_null_state() {
let result = unsafe { ZSTD_rust_initStaticCCtx(ptr::null()) };
assert!(result.is_null());
}
#[test]
fn init_static_cctx_rejects_a_null_workspace() {
let mut context = InitStaticCCtxTestContext {
result: ptr::dangling_mut(),
reserved_cctx: ptr::dangling_mut(),
check_available_result: 1,
reserve_tmp_workspace_result: ptr::dangling_mut(),
..Default::default()
};
let state = init_static_cctx_test_state(&mut context, ptr::null_mut(), usize::MAX, 0);
@@ -18538,6 +18784,36 @@ mod tests {
assert!(context.events.is_empty());
}
#[test]
fn init_static_cctx_rejects_an_unaligned_workspace_before_callback() {
let mut context = InitStaticCCtxTestContext::default();
let mut workspace = [0usize; 2];
let unaligned_workspace = workspace.as_mut_ptr().cast::<u8>().wrapping_add(1).cast();
let state = init_static_cctx_test_state(&mut context, unaligned_workspace, usize::MAX, 0);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert!(context.events.is_empty());
}
#[test]
fn init_static_cctx_rejects_a_small_workspace_before_callback() {
let mut context = InitStaticCCtxTestContext::default();
let mut workspace = [0usize; 1];
let state = init_static_cctx_test_state(
&mut context,
workspace.as_mut_ptr().cast(),
size_of_val(&workspace),
size_of_val(&workspace),
);
let result = unsafe { ZSTD_rust_initStaticCCtx(&state) };
assert!(result.is_null());
assert!(context.events.is_empty());
}
#[derive(Default)]
struct FreeCCtxTestContext {
events: Vec<&'static str>,