feat(compress): move MT serial dictionary policy to Rust

Move the serial LDM dictionary preload policy into Rust. The C MT adapter now
exposes only the private window and hash-table operations through checked
callbacks, while Rust owns empty/raw-content eligibility, loaded-dictionary
reset ordering, and the force-window publication rule. The explicit projection
keeps the dictionary inputs and callback ABI auditable without exposing the
private SerialState layout.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --tests
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1
- ulimit -v 41943040; make -j1 -C tests test
This commit is contained in:
2026-07-21 18:28:03 +02:00
parent 67c6056203
commit 62eea1dd83
2 changed files with 210 additions and 14 deletions
+57 -14
View File
@@ -837,7 +837,32 @@ typedef int (*ZSTDMT_serialResetResizeFn)(
unsigned bucketLog, unsigned previousBucketLog);
typedef void (*ZSTDMT_serialResetZeroTablesFn)(
void* opaque, size_t hashSize, size_t numBuckets);
typedef size_t (*ZSTDMT_serialResetLoadRawDictionaryFn)(
void* opaque, const void* dict, size_t dictSize);
typedef void (*ZSTDMT_serialResetSetLoadedDictEndFn)(
void* opaque, U32 loadedDictEnd);
typedef void (*ZSTDMT_serialResetPublishFn)(void* opaque, size_t jobSize);
typedef struct {
const void* dict;
size_t dictSize;
int dictContentType;
int forceWindow;
} ZSTDMT_RustSerialDictionaryProjection;
typedef char ZSTDMT_rust_serial_dictionary_projection_layout[
(offsetof(ZSTDMT_RustSerialDictionaryProjection, dict) == 0
&& offsetof(ZSTDMT_RustSerialDictionaryProjection, dictSize)
== sizeof(void*)
&& offsetof(ZSTDMT_RustSerialDictionaryProjection, dictContentType)
== sizeof(void*) + sizeof(size_t)
&& offsetof(ZSTDMT_RustSerialDictionaryProjection, forceWindow)
== sizeof(void*) + sizeof(size_t) + sizeof(int)
&& sizeof(ZSTDMT_RustSerialDictionaryProjection)
== sizeof(void*) + sizeof(size_t) + 2 * sizeof(int))
? 1 : -1];
void ZSTDMT_rust_serialStateLoadDictionary(
const ZSTDMT_RustSerialDictionaryProjection* projection,
void* opaque, ZSTDMT_serialResetLoadRawDictionaryFn loadRawDictionary,
ZSTDMT_serialResetSetLoadedDictEndFn setLoadedDictEnd);
int ZSTDMT_rust_serialStateReset(
const ZSTDMT_RustSerialResetProjection* projection, void* opaque,
ZSTDMT_serialResetSetLdmParamsFn setLdmParams,
@@ -1487,24 +1512,42 @@ static void ZSTDMT_serialResetZeroTables(
ZSTD_memset(serialState->ldmState.bucketOffsets, 0, numBuckets);
}
static void ZSTDMT_serialResetLoadDictionary(void* opaque)
static size_t ZSTDMT_serialResetLoadRawDictionary(
void* opaque, const void* dict, size_t dictSize)
{
ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque;
SerialState* const serialState = context->serialState;
BYTE const* const dictEnd = (const BYTE*)dict + dictSize;
serialState->ldmState.loadedDictEnd = 0;
if (context->dictSize > 0 &&
context->dictContentType == ZSTD_dct_rawContent) {
BYTE const* const dictEnd = (const BYTE*)context->dict + context->dictSize;
ZSTD_window_update(&serialState->ldmState.window,
context->dict, context->dictSize,
/* forceNonContiguous */ 0);
ZSTD_ldm_fillHashTable(&serialState->ldmState,
(const BYTE*)context->dict, dictEnd,
&context->params->ldmParams);
serialState->ldmState.loadedDictEnd = context->params->forceWindow
? 0 : (U32)(dictEnd - serialState->ldmState.window.base);
}
ZSTD_window_update(&serialState->ldmState.window,
dict, dictSize,
/* forceNonContiguous */ 0);
ZSTD_ldm_fillHashTable(&serialState->ldmState,
(const BYTE*)dict, dictEnd,
&context->params->ldmParams);
return (size_t)(dictEnd - serialState->ldmState.window.base);
}
static void ZSTDMT_serialResetSetLoadedDictEnd(
void* opaque, U32 loadedDictEnd)
{
ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque;
context->serialState->ldmState.loadedDictEnd = loadedDictEnd;
}
static void ZSTDMT_serialResetLoadDictionary(void* opaque)
{
ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque;
ZSTDMT_RustSerialDictionaryProjection const projection = {
context->dict,
context->dictSize,
(int)context->dictContentType,
context->params->forceWindow
};
ZSTDMT_rust_serialStateLoadDictionary(
&projection, context,
ZSTDMT_serialResetLoadRawDictionary,
ZSTDMT_serialResetSetLoadedDictEnd);
}
static void ZSTDMT_serialResetCopyWindow(void* opaque)
+153
View File
@@ -52,6 +52,7 @@ const ZSTD_BTULTRA: c_int = 8;
const ZSTD_BTULTRA2: c_int = 9;
const ZSTD_PS_ENABLE: c_int = 1;
const ZSTD_PS_DISABLE: c_int = 2;
const ZSTD_DCT_RAW_CONTENT: c_int = 1;
const RSYNC_LENGTH: usize = 32;
const RSYNC_MIN_BLOCK_LOG: usize = 17;
@@ -1419,10 +1420,44 @@ pub type ZSTDMT_serialResetSetNbSeqFn = unsafe extern "C" fn(*mut c_void, usize)
pub type ZSTDMT_serialResetResizeFn =
unsafe extern "C" fn(*mut c_void, usize, usize, c_uint, c_uint) -> c_int;
pub type ZSTDMT_serialResetZeroTablesFn = unsafe extern "C" fn(*mut c_void, usize, usize);
pub type ZSTDMT_serialResetLoadRawDictionaryFn =
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
pub type ZSTDMT_serialResetSetLoadedDictEndFn = unsafe extern "C" fn(*mut c_void, c_uint);
pub type ZSTDMT_serialResetPublishFn = unsafe extern "C" fn(*mut c_void, usize);
pub type ZSTDMT_serialStateVoidFn = unsafe extern "C" fn(*mut c_void);
pub type ZSTDMT_serialStateInitFn = unsafe extern "C" fn(*mut c_void) -> c_int;
/// Dictionary inputs for the MT serial LDM preload policy. The private C
/// window/hash-table state stays behind callbacks; Rust owns eligibility,
/// reset, and the `forceWindow` publication rule.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTDMT_RustSerialDictionaryProjection {
pub dict: *const c_void,
pub dictSize: usize,
pub dictContentType: c_int,
pub forceWindow: c_int,
}
const _: () = {
assert!(offset_of!(ZSTDMT_RustSerialDictionaryProjection, dict) == 0);
assert!(
offset_of!(ZSTDMT_RustSerialDictionaryProjection, dictSize) == size_of::<*const c_void>()
);
assert!(
offset_of!(ZSTDMT_RustSerialDictionaryProjection, dictContentType)
== size_of::<*const c_void>() + size_of::<usize>()
);
assert!(
offset_of!(ZSTDMT_RustSerialDictionaryProjection, forceWindow)
== size_of::<*const c_void>() + size_of::<usize>() + size_of::<c_int>()
);
assert!(
size_of::<ZSTDMT_RustSerialDictionaryProjection>()
== size_of::<*const c_void>() + size_of::<usize>() + 2 * size_of::<c_int>()
);
};
/// Rust's LDM module already owns the scalar adjustment and maximum-sequence
/// leaves. This mirror is Rust-internal: it contains only the six scalar LDM
/// fields needed to call those leaves and is never exposed in the C ABI.
@@ -1459,6 +1494,59 @@ const _: () = {
assert!(size_of::<ZSTDMT_serialResetLdmParameters>() == 6 * size_of::<c_uint>());
};
#[inline]
fn serial_state_load_dictionary_with<L, S>(
projection: ZSTDMT_RustSerialDictionaryProjection,
mut load_raw_dictionary: L,
mut set_loaded_dict_end: S,
) where
L: FnMut(*const c_void, usize) -> usize,
S: FnMut(c_uint),
{
/* Match the C reset: loadedDictEnd is cleared even when no raw
* dictionary is eligible for loading. */
set_loaded_dict_end(0);
if projection.dictSize == 0 || projection.dictContentType != ZSTD_DCT_RAW_CONTENT {
return;
}
let loaded_dict_end = load_raw_dictionary(projection.dict, projection.dictSize);
if projection.forceWindow == 0 {
set_loaded_dict_end(loaded_dict_end as c_uint);
}
}
/// Apply the MT serial LDM dictionary preload policy. C retains the private
/// window/hash-table operations and publishes their computed offset through
/// the explicit callbacks.
#[no_mangle]
pub unsafe extern "C" fn ZSTDMT_rust_serialStateLoadDictionary(
projection: *const ZSTDMT_RustSerialDictionaryProjection,
opaque: *mut c_void,
load_raw_dictionary: Option<ZSTDMT_serialResetLoadRawDictionaryFn>,
set_loaded_dict_end: Option<ZSTDMT_serialResetSetLoadedDictEndFn>,
) {
let Some(projection) = (unsafe { projection.as_ref() }).copied() else {
return;
};
let (Some(load_raw_dictionary), Some(set_loaded_dict_end)) =
(load_raw_dictionary, set_loaded_dict_end)
else {
return;
};
if opaque.is_null() {
return;
}
unsafe {
serial_state_load_dictionary_with(
projection,
|dict, dict_size| load_raw_dictionary(opaque, dict, dict_size),
|loaded_dict_end| set_loaded_dict_end(opaque, loaded_dict_end),
);
}
}
unsafe extern "C" {
fn ZSTD_ldm_adjustParameters(
params: *mut ZSTDMT_serialResetLdmParameters,
@@ -7417,6 +7505,71 @@ mod tests {
);
}
#[test]
fn serial_dictionary_policy_resets_then_loads_only_raw_content() {
let events = RefCell::new(Vec::<(&'static str, usize)>::new());
serial_state_load_dictionary_with(
ZSTDMT_RustSerialDictionaryProjection {
dict: ptr::null(),
dictSize: 12,
dictContentType: ZSTD_DCT_RAW_CONTENT,
forceWindow: 0,
},
|dict, dict_size| {
assert!(dict.is_null());
events.borrow_mut().push(("load", dict_size));
23
},
|loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)),
);
assert_eq!(
events.into_inner(),
vec![("end", 0), ("load", 12), ("end", 23)]
);
let events = RefCell::new(Vec::<(&'static str, usize)>::new());
serial_state_load_dictionary_with(
ZSTDMT_RustSerialDictionaryProjection {
dict: ptr::null(),
dictSize: 12,
dictContentType: ZSTD_DCT_RAW_CONTENT,
forceWindow: 1,
},
|_, dict_size| {
events.borrow_mut().push(("load", dict_size));
23
},
|loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)),
);
assert_eq!(events.into_inner(), vec![("end", 0), ("load", 12)]);
let events = RefCell::new(Vec::<(&'static str, usize)>::new());
serial_state_load_dictionary_with(
ZSTDMT_RustSerialDictionaryProjection {
dict: ptr::null(),
dictSize: 12,
dictContentType: 2,
forceWindow: 0,
},
|_, _| panic!("non-raw dictionary must not be loaded"),
|loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)),
);
assert_eq!(events.into_inner(), vec![("end", 0)]);
let events = RefCell::new(Vec::<(&'static str, usize)>::new());
serial_state_load_dictionary_with(
ZSTDMT_RustSerialDictionaryProjection {
dict: ptr::null(),
dictSize: 0,
dictContentType: ZSTD_DCT_RAW_CONTENT,
forceWindow: 0,
},
|_, _| panic!("empty dictionary must not be loaded"),
|loaded_dict_end| events.borrow_mut().push(("end", loaded_dict_end as usize)),
);
assert_eq!(events.into_inner(), vec![("end", 0)]);
}
#[derive(Default)]
struct SerialLifecycleTestContext {
events: Vec<&'static str>,