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);
|
unsigned bucketLog, unsigned previousBucketLog);
|
||||||
typedef void (*ZSTDMT_serialResetZeroTablesFn)(
|
typedef void (*ZSTDMT_serialResetZeroTablesFn)(
|
||||||
void* opaque, size_t hashSize, size_t numBuckets);
|
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 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(
|
int ZSTDMT_rust_serialStateReset(
|
||||||
const ZSTDMT_RustSerialResetProjection* projection, void* opaque,
|
const ZSTDMT_RustSerialResetProjection* projection, void* opaque,
|
||||||
ZSTDMT_serialResetSetLdmParamsFn setLdmParams,
|
ZSTDMT_serialResetSetLdmParamsFn setLdmParams,
|
||||||
@@ -1487,24 +1512,42 @@ static void ZSTDMT_serialResetZeroTables(
|
|||||||
ZSTD_memset(serialState->ldmState.bucketOffsets, 0, numBuckets);
|
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;
|
ZSTDMT_serialResetContext* const context = (ZSTDMT_serialResetContext*)opaque;
|
||||||
SerialState* const serialState = context->serialState;
|
SerialState* const serialState = context->serialState;
|
||||||
|
BYTE const* const dictEnd = (const BYTE*)dict + dictSize;
|
||||||
|
|
||||||
serialState->ldmState.loadedDictEnd = 0;
|
ZSTD_window_update(&serialState->ldmState.window,
|
||||||
if (context->dictSize > 0 &&
|
dict, dictSize,
|
||||||
context->dictContentType == ZSTD_dct_rawContent) {
|
/* forceNonContiguous */ 0);
|
||||||
BYTE const* const dictEnd = (const BYTE*)context->dict + context->dictSize;
|
ZSTD_ldm_fillHashTable(&serialState->ldmState,
|
||||||
ZSTD_window_update(&serialState->ldmState.window,
|
(const BYTE*)dict, dictEnd,
|
||||||
context->dict, context->dictSize,
|
&context->params->ldmParams);
|
||||||
/* forceNonContiguous */ 0);
|
return (size_t)(dictEnd - serialState->ldmState.window.base);
|
||||||
ZSTD_ldm_fillHashTable(&serialState->ldmState,
|
}
|
||||||
(const BYTE*)context->dict, dictEnd,
|
|
||||||
&context->params->ldmParams);
|
static void ZSTDMT_serialResetSetLoadedDictEnd(
|
||||||
serialState->ldmState.loadedDictEnd = context->params->forceWindow
|
void* opaque, U32 loadedDictEnd)
|
||||||
? 0 : (U32)(dictEnd - serialState->ldmState.window.base);
|
{
|
||||||
}
|
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)
|
static void ZSTDMT_serialResetCopyWindow(void* opaque)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const ZSTD_BTULTRA: c_int = 8;
|
|||||||
const ZSTD_BTULTRA2: c_int = 9;
|
const ZSTD_BTULTRA2: c_int = 9;
|
||||||
const ZSTD_PS_ENABLE: c_int = 1;
|
const ZSTD_PS_ENABLE: c_int = 1;
|
||||||
const ZSTD_PS_DISABLE: c_int = 2;
|
const ZSTD_PS_DISABLE: c_int = 2;
|
||||||
|
const ZSTD_DCT_RAW_CONTENT: c_int = 1;
|
||||||
|
|
||||||
const RSYNC_LENGTH: usize = 32;
|
const RSYNC_LENGTH: usize = 32;
|
||||||
const RSYNC_MIN_BLOCK_LOG: usize = 17;
|
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 =
|
pub type ZSTDMT_serialResetResizeFn =
|
||||||
unsafe extern "C" fn(*mut c_void, usize, usize, c_uint, c_uint) -> c_int;
|
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_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_serialResetPublishFn = unsafe extern "C" fn(*mut c_void, usize);
|
||||||
pub type ZSTDMT_serialStateVoidFn = unsafe extern "C" fn(*mut c_void);
|
pub type ZSTDMT_serialStateVoidFn = unsafe extern "C" fn(*mut c_void);
|
||||||
pub type ZSTDMT_serialStateInitFn = unsafe extern "C" fn(*mut c_void) -> c_int;
|
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
|
/// 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
|
/// 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.
|
/// 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>());
|
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" {
|
unsafe extern "C" {
|
||||||
fn ZSTD_ldm_adjustParameters(
|
fn ZSTD_ldm_adjustParameters(
|
||||||
params: *mut ZSTDMT_serialResetLdmParameters,
|
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)]
|
#[derive(Default)]
|
||||||
struct SerialLifecycleTestContext {
|
struct SerialLifecycleTestContext {
|
||||||
events: Vec<&'static str>,
|
events: Vec<&'static str>,
|
||||||
|
|||||||
Reference in New Issue
Block a user