feat(compress): move CDict initialization policy into Rust
Move shared CDict initialization ordering and scalar publication into a Rust-owned ABI leaf used by both dynamic and static CDict creation. Keep content allocation/copy, entropy workspace reservation, match-state reset, and dictionary insertion behind narrow C callbacks so private CDict and workspace layouts remain C-owned. 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 - make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s - focused cdict_init unit test
This commit is contained in:
@@ -232,6 +232,178 @@ pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingCDict(
|
||||
}
|
||||
}
|
||||
|
||||
type InitCDictAssignContentFn =
|
||||
unsafe extern "C" fn(*mut c_void, *const c_void, usize, c_int) -> usize;
|
||||
type InitCDictReserveEntropyFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||
type InitCDictResetBlockStateFn = unsafe extern "C" fn(*mut c_void);
|
||||
type InitCDictResetMatchStateFn = unsafe extern "C" fn(*mut c_void, *const c_void, c_int) -> usize;
|
||||
type InitCDictInsertDictionaryFn =
|
||||
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, usize, c_int) -> usize;
|
||||
|
||||
/// Projection for CDict content/state initialization.
|
||||
///
|
||||
/// Rust owns the initialization order and scalar field policy. C callbacks
|
||||
/// retain workspace allocation, match-state reset, and dictionary insertion
|
||||
/// because those operations use private CDict layouts.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_initCDictState {
|
||||
callback_context: *mut c_void,
|
||||
params: *const c_void,
|
||||
c_params: *const ZSTD_compressionParameters,
|
||||
match_state_c_params: *mut ZSTD_compressionParameters,
|
||||
dedicated_dict_search: *mut c_int,
|
||||
enable_dedicated_dict_search: *const c_int,
|
||||
use_row_match_finder: *const c_int,
|
||||
dict_content: *mut *const c_void,
|
||||
dict_content_size: *mut usize,
|
||||
dict_content_type: *mut c_int,
|
||||
entropy_workspace: *mut *mut c_void,
|
||||
dict_id: *mut c_uint,
|
||||
compression_level: *mut c_int,
|
||||
content_size_flag: *mut c_int,
|
||||
assign_content: Option<InitCDictAssignContentFn>,
|
||||
reserve_entropy: Option<InitCDictReserveEntropyFn>,
|
||||
reset_block_state: Option<InitCDictResetBlockStateFn>,
|
||||
reset_match_state: Option<InitCDictResetMatchStateFn>,
|
||||
insert_dictionary: Option<InitCDictInsertDictionaryFn>,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(size_of::<InitCDictAssignContentFn>() == size_of::<usize>());
|
||||
assert!(size_of::<InitCDictReserveEntropyFn>() == size_of::<usize>());
|
||||
assert!(size_of::<InitCDictResetBlockStateFn>() == size_of::<usize>());
|
||||
assert!(size_of::<InitCDictResetMatchStateFn>() == size_of::<usize>());
|
||||
assert!(size_of::<InitCDictInsertDictionaryFn>() == size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, callback_context) == 0);
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, params) == size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, c_params) == 2 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, match_state_c_params) == 3 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, dedicated_dict_search) == 4 * size_of::<usize>());
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_initCDictState, enable_dedicated_dict_search)
|
||||
== 5 * size_of::<usize>()
|
||||
);
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, use_row_match_finder) == 6 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content) == 7 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content_size) == size_of::<[usize; 8]>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content_type) == 9 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, entropy_workspace) == 10 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, dict_id) == 11 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, compression_level) == 12 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, content_size_flag) == 13 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, assign_content) == 14 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, reserve_entropy) == 15 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, reset_block_state) == 16 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, reset_match_state) == 17 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_initCDictState, insert_dictionary) == 18 * size_of::<usize>());
|
||||
assert!(size_of::<ZSTD_rust_initCDictState>() == size_of::<[usize; 19]>());
|
||||
};
|
||||
|
||||
const CDICT_DEFAULT_CLEVEL: c_int = 3;
|
||||
|
||||
/// Initialize a private CDict through narrow C callbacks.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_initCDict(
|
||||
state: *const ZSTD_rust_initCDictState,
|
||||
dict: *const c_void,
|
||||
dict_size: usize,
|
||||
dict_load_method: c_int,
|
||||
dict_content_type: c_int,
|
||||
) -> usize {
|
||||
if state.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
let state = unsafe { &*state };
|
||||
let Some(assign_content) = state.assign_content else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
let Some(reserve_entropy) = state.reserve_entropy else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
let Some(reset_block_state) = state.reset_block_state else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
let Some(reset_match_state) = state.reset_match_state else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
let Some(insert_dictionary) = state.insert_dictionary else {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
};
|
||||
if state.callback_context.is_null()
|
||||
|| state.params.is_null()
|
||||
|| state.c_params.is_null()
|
||||
|| state.match_state_c_params.is_null()
|
||||
|| state.dedicated_dict_search.is_null()
|
||||
|| state.enable_dedicated_dict_search.is_null()
|
||||
|| state.use_row_match_finder.is_null()
|
||||
|| state.dict_content.is_null()
|
||||
|| state.dict_content_size.is_null()
|
||||
|| state.dict_content_type.is_null()
|
||||
|| state.entropy_workspace.is_null()
|
||||
|| state.dict_id.is_null()
|
||||
|| state.compression_level.is_null()
|
||||
|| state.content_size_flag.is_null()
|
||||
{
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
let assign_result =
|
||||
unsafe { assign_content(state.callback_context, dict, dict_size, dict_load_method) };
|
||||
if ERR_isError(assign_result) {
|
||||
return assign_result;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*state.match_state_c_params = *state.c_params;
|
||||
*state.dedicated_dict_search = *state.enable_dedicated_dict_search;
|
||||
*state.dict_content_size = dict_size;
|
||||
*state.dict_content_type = dict_content_type;
|
||||
}
|
||||
|
||||
let entropy_workspace = unsafe { reserve_entropy(state.callback_context) };
|
||||
if entropy_workspace.is_null() {
|
||||
return ERROR(ZstdErrorCode::MemoryAllocation);
|
||||
}
|
||||
unsafe {
|
||||
*state.entropy_workspace = entropy_workspace;
|
||||
}
|
||||
|
||||
unsafe { reset_block_state(state.callback_context) };
|
||||
let reset_match_result = unsafe {
|
||||
reset_match_state(
|
||||
state.callback_context,
|
||||
state.c_params.cast(),
|
||||
*state.use_row_match_finder,
|
||||
)
|
||||
};
|
||||
if ERR_isError(reset_match_result) {
|
||||
return reset_match_result;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*state.compression_level = CDICT_DEFAULT_CLEVEL;
|
||||
*state.content_size_flag = 1;
|
||||
}
|
||||
let dict_content = unsafe { *state.dict_content };
|
||||
let dict_id = unsafe {
|
||||
insert_dictionary(
|
||||
state.callback_context,
|
||||
state.params,
|
||||
dict_content,
|
||||
dict_size,
|
||||
dict_content_type,
|
||||
)
|
||||
};
|
||||
if ERR_isError(dict_id) {
|
||||
return dict_id;
|
||||
}
|
||||
if dict_id > c_uint::MAX as usize {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
unsafe { *state.dict_id = dict_id as c_uint };
|
||||
0
|
||||
}
|
||||
|
||||
/// Scalar projections for the public CDict query helpers.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_cdictQueryState {
|
||||
@@ -746,6 +918,188 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct InitCDictProbe {
|
||||
events: Vec<&'static str>,
|
||||
assigned_dict: *const c_void,
|
||||
assigned_size: usize,
|
||||
assigned_load_method: c_int,
|
||||
reset_c_params: ZSTD_compressionParameters,
|
||||
reset_use_row_match_finder: c_int,
|
||||
inserted_params: *const c_void,
|
||||
inserted_dict: *const c_void,
|
||||
inserted_size: usize,
|
||||
inserted_content_type: c_int,
|
||||
assign_result: usize,
|
||||
entropy_workspace: *mut c_void,
|
||||
reset_match_result: usize,
|
||||
insert_result: usize,
|
||||
}
|
||||
|
||||
impl Default for InitCDictProbe {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events: Vec::new(),
|
||||
assigned_dict: ptr::null(),
|
||||
assigned_size: 0,
|
||||
assigned_load_method: 0,
|
||||
reset_c_params: ZSTD_compressionParameters::default(),
|
||||
reset_use_row_match_finder: 0,
|
||||
inserted_params: ptr::null(),
|
||||
inserted_dict: ptr::null(),
|
||||
inserted_size: 0,
|
||||
inserted_content_type: 0,
|
||||
assign_result: 0,
|
||||
entropy_workspace: ptr::null_mut(),
|
||||
reset_match_result: 0,
|
||||
insert_result: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_cdict_probe(context: *mut c_void) -> &'static mut InitCDictProbe {
|
||||
unsafe { &mut *context.cast::<InitCDictProbe>() }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn init_cdict_assign_content(
|
||||
context: *mut c_void,
|
||||
dict: *const c_void,
|
||||
dict_size: usize,
|
||||
dict_load_method: c_int,
|
||||
) -> usize {
|
||||
let probe = unsafe { init_cdict_probe(context) };
|
||||
probe.events.push("assign");
|
||||
probe.assigned_dict = dict;
|
||||
probe.assigned_size = dict_size;
|
||||
probe.assigned_load_method = dict_load_method;
|
||||
probe.assign_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn init_cdict_reserve_entropy(context: *mut c_void) -> *mut c_void {
|
||||
let probe = unsafe { init_cdict_probe(context) };
|
||||
probe.events.push("reserve");
|
||||
probe.entropy_workspace
|
||||
}
|
||||
|
||||
unsafe extern "C" fn init_cdict_reset_block_state(context: *mut c_void) {
|
||||
unsafe { init_cdict_probe(context) }
|
||||
.events
|
||||
.push("reset-block");
|
||||
}
|
||||
|
||||
unsafe extern "C" fn init_cdict_reset_match_state(
|
||||
context: *mut c_void,
|
||||
c_params: *const c_void,
|
||||
use_row_match_finder: c_int,
|
||||
) -> usize {
|
||||
let probe = unsafe { init_cdict_probe(context) };
|
||||
probe.events.push("reset-match");
|
||||
probe.reset_c_params = unsafe { *c_params.cast::<ZSTD_compressionParameters>() };
|
||||
probe.reset_use_row_match_finder = use_row_match_finder;
|
||||
probe.reset_match_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn init_cdict_insert_dictionary(
|
||||
context: *mut c_void,
|
||||
params: *const c_void,
|
||||
dict: *const c_void,
|
||||
dict_size: usize,
|
||||
dict_content_type: c_int,
|
||||
) -> usize {
|
||||
let probe = unsafe { init_cdict_probe(context) };
|
||||
probe.events.push("insert");
|
||||
probe.inserted_params = params;
|
||||
probe.inserted_dict = dict;
|
||||
probe.inserted_size = dict_size;
|
||||
probe.inserted_content_type = dict_content_type;
|
||||
probe.insert_result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cdict_init_orders_callbacks_and_publishes_state() {
|
||||
let dictionary = [1u8, 2, 3, 4];
|
||||
let mut probe = InitCDictProbe {
|
||||
entropy_workspace: 0x1000usize as *mut c_void,
|
||||
insert_result: 0x1234,
|
||||
..Default::default()
|
||||
};
|
||||
let c_params = ZSTD_compressionParameters {
|
||||
windowLog: 21,
|
||||
chainLog: 18,
|
||||
hashLog: 19,
|
||||
searchLog: 4,
|
||||
minMatch: 5,
|
||||
targetLength: 16,
|
||||
strategy: 3,
|
||||
};
|
||||
let mut match_state_c_params = ZSTD_compressionParameters::default();
|
||||
let enable_dedicated_dict_search = 1;
|
||||
let use_row_match_finder = 2;
|
||||
let mut dedicated_dict_search = 0;
|
||||
let mut dict_content = dictionary.as_ptr().cast::<c_void>();
|
||||
let mut dict_content_size = 0;
|
||||
let mut dict_content_type = 0;
|
||||
let mut entropy_workspace = ptr::null_mut();
|
||||
let mut dict_id = 0;
|
||||
let mut compression_level = 99;
|
||||
let mut content_size_flag = 0;
|
||||
let params = 0x2000usize as *const c_void;
|
||||
let callback_context = (&mut probe as *mut InitCDictProbe).cast();
|
||||
let state = ZSTD_rust_initCDictState {
|
||||
callback_context,
|
||||
params,
|
||||
c_params: &c_params,
|
||||
match_state_c_params: &mut match_state_c_params,
|
||||
dedicated_dict_search: &mut dedicated_dict_search,
|
||||
enable_dedicated_dict_search: &enable_dedicated_dict_search,
|
||||
use_row_match_finder: &use_row_match_finder,
|
||||
dict_content: &mut dict_content,
|
||||
dict_content_size: &mut dict_content_size,
|
||||
dict_content_type: &mut dict_content_type,
|
||||
entropy_workspace: &mut entropy_workspace,
|
||||
dict_id: &mut dict_id,
|
||||
compression_level: &mut compression_level,
|
||||
content_size_flag: &mut content_size_flag,
|
||||
assign_content: Some(init_cdict_assign_content),
|
||||
reserve_entropy: Some(init_cdict_reserve_entropy),
|
||||
reset_block_state: Some(init_cdict_reset_block_state),
|
||||
reset_match_state: Some(init_cdict_reset_match_state),
|
||||
insert_dictionary: Some(init_cdict_insert_dictionary),
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_initCDict(
|
||||
&state,
|
||||
dictionary.as_ptr().cast(),
|
||||
dictionary.len(),
|
||||
ZSTD_DLM_BY_REF,
|
||||
ZSTD_DCT_RAW_CONTENT,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, 0);
|
||||
assert_eq!(
|
||||
probe.events,
|
||||
["assign", "reserve", "reset-block", "reset-match", "insert"]
|
||||
);
|
||||
assert_eq!(probe.assigned_dict, dictionary.as_ptr().cast());
|
||||
assert_eq!(probe.assigned_size, dictionary.len());
|
||||
assert_eq!(probe.assigned_load_method, ZSTD_DLM_BY_REF);
|
||||
assert_eq!(probe.reset_c_params, c_params);
|
||||
assert_eq!(probe.reset_use_row_match_finder, use_row_match_finder);
|
||||
assert_eq!(probe.inserted_params, params);
|
||||
assert_eq!(probe.inserted_dict, dictionary.as_ptr().cast());
|
||||
assert_eq!(probe.inserted_size, dictionary.len());
|
||||
assert_eq!(probe.inserted_content_type, ZSTD_DCT_RAW_CONTENT);
|
||||
assert_eq!(match_state_c_params, c_params);
|
||||
assert_eq!(dedicated_dict_search, enable_dedicated_dict_search);
|
||||
assert_eq!(dict_content_size, dictionary.len());
|
||||
assert_eq!(dict_content_type, ZSTD_DCT_RAW_CONTENT);
|
||||
assert_eq!(entropy_workspace, probe.entropy_workspace);
|
||||
assert_eq!(dict_id, probe.insert_result as c_uint);
|
||||
assert_eq!(compression_level, 3);
|
||||
assert_eq!(content_size_flag, 1);
|
||||
}
|
||||
|
||||
unsafe fn cctx_policy_probe(context: *mut c_void) -> &'static mut CctxPolicyProbe {
|
||||
unsafe { &mut *context.cast::<CctxPolicyProbe>() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user