feat(compress): move advanced CDict policy into Rust

ZSTD_createCDict_advanced2 previously selected compression parameters,
resolved dedicated-dictionary-search fallback, and ordered allocation and
initialization entirely in C. That left a large public dictionary boundary
outside the Rust rewrite and made the failure ordering implicit in the C
wrapper.

The Rust params API now owns advanced-CDict parameter preparation, including
the dedicated-search override/fallback and row-matchfinder resolution. A Rust
dictionary bridge owns the create -> init sequence and frees a created CDict
when initialization fails. The C shim retains only the private workspace and
custom-memory allocation, dictionary initialization, and teardown callbacks;
opaque parameter-field pointers and compile-time layout assertions preserve
the existing ABI. Focused probes cover policy publication and all callback
failure-ordering paths, and the migration README records the remaining C
boundary.

Test Plan:
- `rustfmt --check` and `cargo fmt --manifest-path rust/Cargo.toml -- --check` -- passed
- `cargo test --manifest-path rust/Cargo.toml --release` -- 663 passed
- `cargo clippy --manifest-path rust/Cargo.toml --release --all-targets -- -D warnings` -- passed
- `make -B -C programs -j1 zstd` -- passed
- `make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s` -- 84 tests and both fuzz rounds passed
This commit is contained in:
2026-07-19 16:53:31 +02:00
parent 35ad752b00
commit 5449a4b3c0
4 changed files with 684 additions and 57 deletions
+5 -3
View File
@@ -153,8 +153,9 @@ static-CCtx workspace validation and initialization dispatch, and public CDict
constructor parameter selection, default-level normalization, and
workspace/object teardown ordering, public static-CDict workspace validation
and initialization dispatch, public advanced-CDict one-shot validation and
begin/end ordering, public
advanced-compression parameter validation/init policy, public
begin/end ordering, advanced-CDict parameter selection, dedicated-search
fallback, row-matchfinder resolution, and create/init/failure ordering,
public advanced-compression parameter validation/init policy, public
usingDict parameter selection, dictionary-presence handling, and default-level
normalization, and public usingCDict frame-policy construction and begin/end
sequencing, legacy public CDict-begin frame-policy construction and
@@ -163,7 +164,8 @@ parameter selection and default-level normalization, and public advanced-begin
parameter validation and init-then-begin ordering now run in Rust.
CDict advanced allocation/lifecycle machinery, private static-CCtx and
static-CDict workspace construction and dictionary-content allocation/loading,
reset policy,
and advanced-CDict private workspace construction and dictionary-content
loading remain in C. Reset policy,
private CCtx/matchfinder/workspace operations, and codec/adaptive-policy
callbacks remain in C. CDict initialization ordering and scalar publication,
shared compression-begin dictionary selection, CDict reset attach-versus-copy
+348
View File
@@ -500,6 +500,151 @@ pub unsafe extern "C" fn ZSTD_rust_createCDict(
cdict
}
type CreateCDictAdvancedCreateFn = unsafe extern "C" fn(
*mut c_void,
usize,
c_int,
*const ZSTD_compressionParameters,
c_int,
c_int,
) -> *mut c_void;
type CreateCDictAdvancedInitFn = unsafe extern "C" fn(
*mut c_void,
*mut c_void,
*const c_void,
usize,
c_int,
c_int,
*const ZSTD_CCtx_params,
) -> usize;
type CreateCDictAdvancedFreeFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
/// Explicit projection for the public `ZSTD_createCDict_advanced2` wrapper.
///
/// Rust owns the context-free parameter preparation and callback ordering. C
/// retains custom-memory allocation, private workspace construction, CDict
/// initialization, and teardown behind narrow callbacks. The three field
/// pointers keep the private `ZSTD_CCtx_params` layout opaque here while
/// allowing C to publish the fields selected by the Rust parameter leaf.
#[repr(C)]
pub struct ZSTD_rust_createCDictAdvancedState {
callback_context: *mut c_void,
cctx_params: *mut ZSTD_CCtx_params,
cparams: *const ZSTD_compressionParameters,
enable_dedicated_dict_search: *const c_int,
use_row_match_finder: *const c_int,
exclusion_mask: u32,
ldm_default_window_log: u32,
create: CreateCDictAdvancedCreateFn,
init: CreateCDictAdvancedInitFn,
free: CreateCDictAdvancedFreeFn,
}
const _: () = {
assert!(offset_of!(ZSTD_rust_createCDictAdvancedState, callback_context) == 0);
assert!(offset_of!(ZSTD_rust_createCDictAdvancedState, cctx_params) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_createCDictAdvancedState, cparams) == 2 * size_of::<usize>());
assert!(
offset_of!(
ZSTD_rust_createCDictAdvancedState,
enable_dedicated_dict_search
) == 3 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, use_row_match_finder)
== 4 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, exclusion_mask) == 5 * size_of::<usize>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, ldm_default_window_log)
== 5 * size_of::<usize>() + size_of::<u32>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, create)
== size_of::<[usize; 5]>() + size_of::<[u32; 2]>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, init)
== size_of::<[usize; 6]>() + size_of::<[u32; 2]>()
);
assert!(
offset_of!(ZSTD_rust_createCDictAdvancedState, free)
== size_of::<[usize; 7]>() + size_of::<[u32; 2]>()
);
assert!(
size_of::<ZSTD_rust_createCDictAdvancedState>()
== size_of::<[usize; 8]>() + size_of::<[u32; 2]>()
);
};
/// Prepare advanced-CDict parameters and run the C-owned construction path.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_createCDictAdvanced(
state: *const ZSTD_rust_createCDictAdvancedState,
dict: *const c_void,
dict_size: usize,
dict_load_method: c_int,
dict_content_type: c_int,
) -> *mut c_void {
if state.is_null() {
return ptr::null_mut();
}
let state = unsafe { &*state };
if state.callback_context.is_null()
|| state.cctx_params.is_null()
|| state.cparams.is_null()
|| state.enable_dedicated_dict_search.is_null()
|| state.use_row_match_finder.is_null()
{
return ptr::null_mut();
}
let prepare_result = unsafe {
crate::zstd_compress_params_api::ZSTD_rust_params_prepareAdvancedCDict(
state.cctx_params,
dict_size,
state.ldm_default_window_log,
state.exclusion_mask,
)
};
if ERR_isError(prepare_result) {
return ptr::null_mut();
}
let cdict = unsafe {
(state.create)(
state.callback_context,
dict_size,
dict_load_method,
state.cparams,
*state.use_row_match_finder,
*state.enable_dedicated_dict_search,
)
};
if cdict.is_null() {
return ptr::null_mut();
}
let init_result = unsafe {
(state.init)(
state.callback_context,
cdict,
dict,
dict_size,
dict_load_method,
dict_content_type,
state.cctx_params.cast_const(),
)
};
if ERR_isError(init_result) {
unsafe { (state.free)(state.callback_context, cdict) };
return ptr::null_mut();
}
cdict
}
type FreeCDictWorkspaceFn = unsafe extern "C" fn(*mut c_void);
type FreeCDictObjectFn = unsafe extern "C" fn(*mut c_void);
@@ -3055,6 +3200,209 @@ mod tests {
assert!(probe.published_cdict.is_null());
}
#[derive(Default)]
struct CreateCDictAdvancedProbe {
events: Vec<&'static str>,
cparams: *const ZSTD_compressionParameters,
use_row_match_finder: c_int,
enable_dedicated_dict_search: c_int,
cdict_result: *mut c_void,
init_cdict: *mut c_void,
init_dict: *const c_void,
init_dict_size: usize,
init_dict_load_method: c_int,
init_dict_content_type: c_int,
init_cctx_params: *const ZSTD_CCtx_params,
free_cdict: *mut c_void,
init_result: usize,
}
unsafe extern "C" fn create_cdict_advanced_test_create(
context: *mut c_void,
_dict_size: usize,
_dict_load_method: c_int,
cparams: *const ZSTD_compressionParameters,
use_row_match_finder: c_int,
enable_dedicated_dict_search: c_int,
) -> *mut c_void {
let probe = unsafe { &mut *context.cast::<CreateCDictAdvancedProbe>() };
probe.events.push("create");
probe.cparams = cparams;
probe.use_row_match_finder = use_row_match_finder;
probe.enable_dedicated_dict_search = enable_dedicated_dict_search;
probe.cdict_result
}
unsafe extern "C" fn create_cdict_advanced_test_init(
context: *mut c_void,
cdict: *mut c_void,
dict: *const c_void,
dict_size: usize,
dict_load_method: c_int,
dict_content_type: c_int,
cctx_params: *const ZSTD_CCtx_params,
) -> usize {
let probe = unsafe { &mut *context.cast::<CreateCDictAdvancedProbe>() };
probe.events.push("init");
probe.init_cdict = cdict;
probe.init_dict = dict;
probe.init_dict_size = dict_size;
probe.init_dict_load_method = dict_load_method;
probe.init_dict_content_type = dict_content_type;
probe.init_cctx_params = cctx_params;
probe.init_result
}
unsafe extern "C" fn create_cdict_advanced_test_free(context: *mut c_void, cdict: *mut c_void) {
let probe = unsafe { &mut *context.cast::<CreateCDictAdvancedProbe>() };
probe.events.push("free");
probe.free_cdict = cdict;
}
fn create_cdict_advanced_test_params() -> MaybeUninit<ZSTD_CCtx_params> {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::zeroed();
unsafe {
assert_eq!(
crate::zstd_compress_params_api::ZSTD_CCtxParams_init(
storage.as_mut_ptr(),
ZSTD_rust_params_defaultCLevel(),
),
0
);
}
storage
}
fn create_cdict_advanced_test_state(
probe: &mut CreateCDictAdvancedProbe,
cctx_params: *mut ZSTD_CCtx_params,
cparams: &ZSTD_compressionParameters,
enable_dedicated_dict_search: &c_int,
use_row_match_finder: &c_int,
) -> ZSTD_rust_createCDictAdvancedState {
ZSTD_rust_createCDictAdvancedState {
callback_context: (probe as *mut CreateCDictAdvancedProbe).cast(),
cctx_params,
cparams,
enable_dedicated_dict_search,
use_row_match_finder,
exclusion_mask: 0,
ldm_default_window_log: 27,
create: create_cdict_advanced_test_create,
init: create_cdict_advanced_test_init,
free: create_cdict_advanced_test_free,
}
}
#[test]
fn create_cdict_advanced_publishes_params_and_preserves_create_init_order() {
let mut probe = CreateCDictAdvancedProbe {
cdict_result: ptr::dangling_mut(),
..Default::default()
};
let mut params_storage = create_cdict_advanced_test_params();
let cctx_params = params_storage.as_mut_ptr();
let cparams = ZSTD_compressionParameters {
windowLog: 20,
chainLog: 19,
hashLog: 18,
searchLog: 5,
minMatch: 4,
targetLength: 16,
strategy: 3,
};
let enable_dedicated_dict_search = 1;
let use_row_match_finder = 2;
let state = create_cdict_advanced_test_state(
&mut probe,
cctx_params,
&cparams,
&enable_dedicated_dict_search,
&use_row_match_finder,
);
let dict = [1u8, 2, 3, 4];
let result = unsafe {
ZSTD_rust_createCDictAdvanced(
&state,
dict.as_ptr().cast(),
dict.len(),
ZSTD_DLM_BY_REF,
ZSTD_DCT_RAW_CONTENT,
)
};
assert_eq!(result, probe.cdict_result);
assert_eq!(probe.events, ["create", "init"]);
assert_eq!(probe.cparams, &cparams);
assert_eq!(probe.use_row_match_finder, use_row_match_finder);
assert_eq!(
probe.enable_dedicated_dict_search,
enable_dedicated_dict_search
);
assert_eq!(probe.init_cdict, probe.cdict_result);
assert_eq!(probe.init_dict, dict.as_ptr().cast());
assert_eq!(probe.init_dict_size, dict.len());
assert_eq!(probe.init_dict_load_method, ZSTD_DLM_BY_REF);
assert_eq!(probe.init_dict_content_type, ZSTD_DCT_RAW_CONTENT);
assert_eq!(probe.init_cctx_params, cctx_params.cast_const());
}
#[test]
fn create_cdict_advanced_does_not_init_or_free_after_creation_failure() {
let mut probe = CreateCDictAdvancedProbe::default();
let mut params_storage = create_cdict_advanced_test_params();
let cctx_params = params_storage.as_mut_ptr();
let cparams = ZSTD_compressionParameters::default();
let enable_dedicated_dict_search = 0;
let use_row_match_finder = 0;
let state = create_cdict_advanced_test_state(
&mut probe,
cctx_params,
&cparams,
&enable_dedicated_dict_search,
&use_row_match_finder,
);
let result = unsafe {
ZSTD_rust_createCDictAdvanced(&state, ptr::null(), 0, ZSTD_DLM_BY_REF, ZSTD_DCT_AUTO)
};
assert!(result.is_null());
assert_eq!(probe.events, ["create"]);
assert!(probe.free_cdict.is_null());
}
#[test]
fn create_cdict_advanced_frees_after_initialization_failure() {
let cdict = ptr::dangling_mut::<c_void>();
let mut probe = CreateCDictAdvancedProbe {
cdict_result: cdict,
init_result: ERROR(ZstdErrorCode::MemoryAllocation),
..Default::default()
};
let mut params_storage = create_cdict_advanced_test_params();
let cctx_params = params_storage.as_mut_ptr();
let cparams = ZSTD_compressionParameters::default();
let enable_dedicated_dict_search = 0;
let use_row_match_finder = 0;
let state = create_cdict_advanced_test_state(
&mut probe,
cctx_params,
&cparams,
&enable_dedicated_dict_search,
&use_row_match_finder,
);
let result = unsafe {
ZSTD_rust_createCDictAdvanced(&state, ptr::null(), 0, ZSTD_DLM_BY_REF, ZSTD_DCT_AUTO)
};
assert!(result.is_null());
assert_eq!(probe.events, ["create", "init", "free"]);
assert_eq!(probe.free_cdict, cdict);
}
#[derive(Default)]
struct FreeCDictProbe {
events: Vec<&'static str>,
+234 -2
View File
@@ -14,8 +14,12 @@
use crate::errors::{ZstdErrorCode, ERROR};
use crate::zstd_compress_params::{
ZSTD_bounds, ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
ZSTD_rust_params_checkCParams, ZSTD_rust_params_getBounds,
ZSTD_rust_params_resolveMaxBlockSize, ZSTD_RUST_PS_DISABLE, ZSTD_RUST_PS_ENABLE,
ZSTD_rust_params_checkCParams, ZSTD_rust_params_dedicatedDictSearchIsSupported,
ZSTD_rust_params_dedicatedDictSearch_getCParams, ZSTD_rust_params_getBounds,
ZSTD_rust_params_getCParamsFromCCtxParams, ZSTD_rust_params_getCParamsInternal,
ZSTD_rust_params_overrideCParams, ZSTD_rust_params_resolveMaxBlockSize,
ZSTD_rust_params_resolveRowMatchFinderMode, ZSTD_CONTENTSIZE_UNKNOWN,
ZSTD_RUST_CPM_CREATE_CDICT, ZSTD_RUST_PS_DISABLE, ZSTD_RUST_PS_ENABLE,
};
use std::mem::size_of;
use std::os::raw::{c_int, c_void};
@@ -508,6 +512,72 @@ pub unsafe extern "C" fn ZSTD_rust_CCtxParams_setZstdParams(
unsafe { set_zstd_params_impl(cctx_params, zstd_params) }
}
/// Prepares the parameter subset used by `ZSTD_createCDict_advanced2()`.
///
/// The C dictionary allocator remains responsible for allocation and
/// initialization. This leaf owns the context-free parameter selection,
/// dedicated-dictionary-search fallback, and final row-match-finder
/// resolution while keeping the `ZSTD_CCtx_params` fields private here.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_params_prepareAdvancedCDict(
params: *mut ZSTD_CCtx_params,
dictSize: usize,
ldmDefaultWindowLog: u32,
exclusionMask: u32,
) -> usize {
if params.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let params = unsafe { &mut *params };
let mut cparams = if params.enableDedicatedDictSearch != 0 {
let mut cparams = ZSTD_rust_params_getCParamsInternal(
params.compressionLevel,
0,
dictSize,
ZSTD_RUST_CPM_CREATE_CDICT,
exclusionMask,
);
cparams = ZSTD_rust_params_dedicatedDictSearch_getCParams(cparams);
unsafe { ZSTD_rust_params_overrideCParams(&mut cparams, &params.cParams) };
cparams
} else {
ZSTD_rust_params_getCParamsFromCCtxParams(
params.compressionLevel,
params.srcSizeHint,
ZSTD_CONTENTSIZE_UNKNOWN,
dictSize,
ZSTD_RUST_CPM_CREATE_CDICT,
params.ldmParams.enableLdm,
ldmDefaultWindowLog,
params.cParams,
params.useRowMatchFinder,
exclusionMask,
)
};
if ZSTD_rust_params_dedicatedDictSearchIsSupported(cparams) == 0 {
params.enableDedicatedDictSearch = 0;
cparams = ZSTD_rust_params_getCParamsFromCCtxParams(
params.compressionLevel,
params.srcSizeHint,
ZSTD_CONTENTSIZE_UNKNOWN,
dictSize,
ZSTD_RUST_CPM_CREATE_CDICT,
params.ldmParams.enableLdm,
ldmDefaultWindowLog,
params.cParams,
params.useRowMatchFinder,
exclusionMask,
);
}
params.cParams = cparams;
params.useRowMatchFinder =
ZSTD_rust_params_resolveRowMatchFinderMode(params.useRowMatchFinder, cparams);
0
}
unsafe fn custom_calloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void {
if let Some(custom_alloc) = custom_mem.customAlloc {
let allocation = unsafe { custom_alloc(custom_mem.opaque, size) };
@@ -1520,6 +1590,168 @@ mod tests {
}
}
#[test]
fn prepare_advanced_cdict_rejects_null_params() {
assert_eq!(
unsafe { ZSTD_rust_params_prepareAdvancedCDict(ptr::null_mut(), 32 * 1024, 27, 0) },
ERROR(ZstdErrorCode::Generic)
);
}
#[test]
fn prepare_advanced_cdict_publishes_normal_policy() {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::zeroed();
let params = storage.as_mut_ptr();
let dict_size = 32 * 1024;
let ldm_default_window_log = 27;
let exclusion_mask = 0;
unsafe {
assert_eq!(ZSTD_CCtxParams_init(params, DEFAULT_CLEVEL), 0);
(*params).srcSizeHint = 16 * 1024;
(*params).ldmParams.enableLdm = ZSTD_RUST_PS_ENABLE;
(*params).cParams = ZSTD_compressionParameters {
windowLog: 27,
chainLog: 20,
hashLog: 25,
searchLog: 5,
minMatch: 4,
targetLength: 16,
strategy: STRATEGY_GREEDY,
};
(*params).useRowMatchFinder = PS_AUTO;
let expected = ZSTD_rust_params_getCParamsFromCCtxParams(
(*params).compressionLevel,
(*params).srcSizeHint,
ZSTD_CONTENTSIZE_UNKNOWN,
dict_size,
ZSTD_RUST_CPM_CREATE_CDICT,
(*params).ldmParams.enableLdm,
ldm_default_window_log,
(*params).cParams,
(*params).useRowMatchFinder,
exclusion_mask,
);
let expected_row =
ZSTD_rust_params_resolveRowMatchFinderMode((*params).useRowMatchFinder, expected);
assert_eq!(
ZSTD_rust_params_prepareAdvancedCDict(
params,
dict_size,
ldm_default_window_log,
exclusion_mask,
),
0
);
assert_eq!((*params).enableDedicatedDictSearch, 0);
assert_eq!((*params).cParams, expected);
assert_eq!((*params).useRowMatchFinder, expected_row);
}
}
#[test]
fn prepare_advanced_cdict_applies_dedicated_search_overrides() {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::zeroed();
let params = storage.as_mut_ptr();
let dict_size = 32 * 1024;
let ldm_default_window_log = 27;
let exclusion_mask = 0;
unsafe {
assert_eq!(ZSTD_CCtxParams_init(params, DEFAULT_CLEVEL), 0);
(*params).enableDedicatedDictSearch = 1;
(*params).cParams = ZSTD_compressionParameters {
windowLog: 20,
chainLog: 20,
hashLog: 25,
searchLog: 5,
minMatch: 4,
targetLength: 32,
strategy: STRATEGY_GREEDY,
};
(*params).useRowMatchFinder = PS_DISABLE;
let mut expected = ZSTD_rust_params_getCParamsInternal(
(*params).compressionLevel,
0,
dict_size,
ZSTD_RUST_CPM_CREATE_CDICT,
exclusion_mask,
);
expected = ZSTD_rust_params_dedicatedDictSearch_getCParams(expected);
ZSTD_rust_params_overrideCParams(&mut expected, &(*params).cParams);
assert_eq!(ZSTD_rust_params_dedicatedDictSearchIsSupported(expected), 1);
assert_eq!(
ZSTD_rust_params_prepareAdvancedCDict(
params,
dict_size,
ldm_default_window_log,
exclusion_mask,
),
0
);
assert_eq!((*params).enableDedicatedDictSearch, 1);
assert_eq!((*params).cParams, expected);
assert_eq!((*params).useRowMatchFinder, PS_DISABLE);
}
}
#[test]
fn prepare_advanced_cdict_falls_back_from_unsupported_dedicated_search() {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::zeroed();
let params = storage.as_mut_ptr();
let dict_size = 32 * 1024;
let ldm_default_window_log = 27;
let exclusion_mask = 0;
unsafe {
assert_eq!(ZSTD_CCtxParams_init(params, DEFAULT_CLEVEL), 0);
(*params).srcSizeHint = 16 * 1024;
(*params).enableDedicatedDictSearch = 1;
(*params).cParams = ZSTD_compressionParameters {
windowLog: 20,
chainLog: 20,
hashLog: 25,
searchLog: 5,
minMatch: 4,
targetLength: 32,
strategy: STRATEGY_BTOPT,
};
(*params).useRowMatchFinder = PS_AUTO;
let expected = ZSTD_rust_params_getCParamsFromCCtxParams(
(*params).compressionLevel,
(*params).srcSizeHint,
ZSTD_CONTENTSIZE_UNKNOWN,
dict_size,
ZSTD_RUST_CPM_CREATE_CDICT,
(*params).ldmParams.enableLdm,
ldm_default_window_log,
(*params).cParams,
(*params).useRowMatchFinder,
exclusion_mask,
);
let expected_row =
ZSTD_rust_params_resolveRowMatchFinderMode((*params).useRowMatchFinder, expected);
assert_eq!(
ZSTD_rust_params_prepareAdvancedCDict(
params,
dict_size,
ldm_default_window_log,
exclusion_mask,
),
0
);
assert_eq!((*params).enableDedicatedDictSearch, 0);
assert_eq!((*params).cParams, expected);
assert_eq!((*params).useRowMatchFinder, expected_row);
}
}
#[test]
fn allocation_lifecycle_preserves_default_and_custom_memory_contracts() {
unsafe extern "C" fn counting_alloc(opaque: *mut c_void, size: usize) -> *mut c_void {