feat(compress): move CCtx reset policy into Rust

Move ZSTD_CCtx_reset directive handling into a Rust-owned state machine while keeping dictionary cleanup and parameter reset behind C callbacks. Preserve the original session ordering, stage error boundary, completion/max-block flag updates, and reset error propagation across the ABI bridge.

Test Plan:
- cargo test --manifest-path rust/Cargo.toml --lib
- cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- make -B -C programs -j1 zstd
- focused reset_cctx unit tests
This commit is contained in:
2026-07-19 13:22:25 +02:00
parent 878e18a966
commit 1cf5a92e02
2 changed files with 291 additions and 15 deletions
+242
View File
@@ -123,6 +123,9 @@ const ZSTD_C_STRATEGY: c_int = 107;
const ZSTD_C_CONTENT_SIZE_FLAG: c_int = 200;
const ZSTD_C_CHECKSUM_FLAG: c_int = 201;
const ZSTD_C_DICT_ID_FLAG: c_int = 202;
const ZSTD_RESET_SESSION_ONLY: c_int = 1;
const ZSTD_RESET_PARAMETERS: c_int = 2;
const ZSTD_RESET_SESSION_AND_PARAMETERS: c_int = 3;
const ZSTD_CSTREAM_STAGE_LOAD: c_int = 1;
const ZSTD_CSTREAM_STAGE_FLUSH: c_int = 2;
const ZSTD_BSS_COMPRESS: c_int = 0;
@@ -814,6 +817,80 @@ pub unsafe extern "C" fn ZSTD_rust_compress2(
unsafe { compress2_body_with(&*state, dst, dst_capacity, src, src_size) }
}
type ResetCCtxClearAllDictsFn = unsafe extern "C" fn(*mut c_void);
type ResetCCtxResetParamsFn = unsafe extern "C" fn(*mut c_void) -> usize;
/// Explicit projection for the public `ZSTD_CCtx_reset` wrapper.
///
/// Rust owns the reset-directive policy and ordering. The callback context
/// remains in C, where the dictionary and parameter reset operations retain
/// access to the private `ZSTD_CCtx` layout.
#[repr(C)]
pub struct ZSTD_rust_resetCCtxState {
callback_context: *mut c_void,
rust_simple_compress2_completed: *mut c_uint,
stream_stage: *mut c_int,
pledged_src_size_plus_one: *mut u64,
rust_simple_compress2_max_block_size_set: *mut c_uint,
clear_all_dicts: ResetCCtxClearAllDictsFn,
reset_params: ResetCCtxResetParamsFn,
}
const _: () = {
assert!(size_of::<ResetCCtxClearAllDictsFn>() == size_of::<usize>());
assert!(size_of::<ResetCCtxResetParamsFn>() == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_resetCCtxState, callback_context) == 0);
assert!(
offset_of!(ZSTD_rust_resetCCtxState, rust_simple_compress2_completed) == size_of::<usize>()
);
assert!(offset_of!(ZSTD_rust_resetCCtxState, stream_stage) == 2 * size_of::<usize>());
assert!(
offset_of!(ZSTD_rust_resetCCtxState, pledged_src_size_plus_one) == 3 * size_of::<usize>()
);
assert!(
offset_of!(
ZSTD_rust_resetCCtxState,
rust_simple_compress2_max_block_size_set
) == 4 * size_of::<usize>()
);
assert!(offset_of!(ZSTD_rust_resetCCtxState, clear_all_dicts) == 5 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_resetCCtxState, reset_params) == 6 * size_of::<usize>());
assert!(size_of::<ZSTD_rust_resetCCtxState>() == 7 * size_of::<usize>());
};
/// Drive the public `ZSTD_CCtx_reset` policy through C-owned callbacks.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_resetCCtx(
state: *const ZSTD_rust_resetCCtxState,
reset: c_int,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let state = unsafe { &*state };
unsafe {
*state.rust_simple_compress2_completed = 0;
}
if reset == ZSTD_RESET_SESSION_ONLY || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
unsafe {
*state.stream_stage = ZSTD_CSTREAM_STAGE_INIT;
*state.pledged_src_size_plus_one = 0;
}
}
if reset == ZSTD_RESET_PARAMETERS || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
if unsafe { *state.stream_stage } != ZSTD_CSTREAM_STAGE_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
unsafe { (state.clear_all_dicts)(state.callback_context) };
unsafe {
*state.rust_simple_compress2_max_block_size_set = 0;
}
return unsafe { (state.reset_params)(state.callback_context) };
}
0
}
type ResetCStreamResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
type ResetCStreamSetPledgedSrcSizeFn = unsafe extern "C" fn(*mut c_void, u64) -> usize;
@@ -9116,6 +9193,171 @@ mod tests {
assert_eq!(dict_limit, 0x89ab_cdef);
}
#[derive(Default)]
struct ResetCCtxTestContext {
events: Vec<&'static str>,
rust_simple_compress2_completed: c_uint,
stream_stage: c_int,
pledged_src_size_plus_one: u64,
rust_simple_compress2_max_block_size_set: c_uint,
reset_params_result: usize,
}
unsafe fn reset_cctx_test_context(context: *mut c_void) -> &'static mut ResetCCtxTestContext {
unsafe { &mut *context.cast::<ResetCCtxTestContext>() }
}
unsafe extern "C" fn reset_cctx_test_clear_all_dicts(context: *mut c_void) {
unsafe { reset_cctx_test_context(context) }
.events
.push("clear-dicts");
}
unsafe extern "C" fn reset_cctx_test_reset_params(context: *mut c_void) -> usize {
let context = unsafe { reset_cctx_test_context(context) };
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
context.events.push("reset-params");
context.reset_params_result
}
fn reset_cctx_test_state(context: &mut ResetCCtxTestContext) -> ZSTD_rust_resetCCtxState {
ZSTD_rust_resetCCtxState {
callback_context: (context as *mut ResetCCtxTestContext).cast(),
rust_simple_compress2_completed: &mut context.rust_simple_compress2_completed,
stream_stage: &mut context.stream_stage,
pledged_src_size_plus_one: &mut context.pledged_src_size_plus_one,
rust_simple_compress2_max_block_size_set: &mut context
.rust_simple_compress2_max_block_size_set,
clear_all_dicts: reset_cctx_test_clear_all_dicts,
reset_params: reset_cctx_test_reset_params,
}
}
#[test]
fn reset_cctx_session_only_clears_session_state() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_SESSION_ONLY) };
assert_eq!(result, 0);
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
assert_eq!(context.pledged_src_size_plus_one, 0);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
assert!(context.events.is_empty());
}
#[test]
fn reset_cctx_parameters_runs_callbacks_in_order_and_clears_max_block_flag() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_INIT,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
assert_eq!(result, 0);
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
assert_eq!(context.pledged_src_size_plus_one, 123);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
}
#[test]
fn reset_cctx_session_and_parameters_resets_session_before_callbacks() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_SESSION_AND_PARAMETERS) };
assert_eq!(result, 0);
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_INIT);
assert_eq!(context.pledged_src_size_plus_one, 0);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
}
#[test]
fn reset_cctx_parameters_rejects_non_init_stage_before_mutating_parameters() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_LOAD);
assert_eq!(context.pledged_src_size_plus_one, 123);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
assert!(context.events.is_empty());
}
#[test]
fn reset_cctx_propagates_parameter_reset_error_after_clearing_state() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_INIT,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
reset_params_result: ERROR(ZstdErrorCode::MemoryAllocation),
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, ZSTD_RESET_PARAMETERS) };
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 0);
assert_eq!(context.events, ["clear-dicts", "reset-params"]);
}
#[test]
fn reset_cctx_ignores_unknown_directives_after_clearing_completion() {
let mut context = ResetCCtxTestContext {
rust_simple_compress2_completed: 1,
stream_stage: ZSTD_CSTREAM_STAGE_LOAD,
pledged_src_size_plus_one: 123,
rust_simple_compress2_max_block_size_set: 1,
..ResetCCtxTestContext::default()
};
let state = reset_cctx_test_state(&mut context);
let result = unsafe { ZSTD_rust_resetCCtx(&state, 99) };
assert_eq!(result, 0);
assert_eq!(context.rust_simple_compress2_completed, 0);
assert_eq!(context.stream_stage, ZSTD_CSTREAM_STAGE_LOAD);
assert_eq!(context.pledged_src_size_plus_one, 123);
assert_eq!(context.rust_simple_compress2_max_block_size_set, 1);
assert!(context.events.is_empty());
}
#[derive(Default)]
struct ResetCStreamTestContext {
events: Vec<&'static str>,