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:
@@ -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)
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user