refactor(mt): move serial reset policy to Rust

Project the MT serial-reset scalar inputs into a narrow Rust ABI and let Rust own LDM normalization, table sizing, callback order, error short-circuiting, and parameter publication. Keep SerialState, ldmState_t, allocators, dictionary/window/checksum operations, and table mutation in C callbacks while preserving the previous bucket-log policy and the size_t-to-U32 job-size cast.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --all-targets
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040; cc -std=c99 -fsyntax-only -Ilib -Ilib/common -Ilib/compress -Ilib/dict -Ilib/deprecated -Iprograms lib/compress/zstdmt_compress.c
- ulimit -v 41943040; clang -std=c99 -fsyntax-only -Ilib -Ilib/common -Ilib/compress -Ilib/dict -Ilib/deprecated -Iprograms lib/compress/zstdmt_compress.c
- Focused Rust tests added but not executed per the no-heavy-test instruction.
This commit is contained in:
2026-07-21 10:35:23 +02:00
parent 6cc080a35a
commit f43ecc9c61
2 changed files with 507 additions and 66 deletions
+401 -24
View File
@@ -1345,12 +1345,127 @@ pub unsafe extern "C" fn ZSTDMT_rust_updateCStreamDictionary(
}
type ZSTDMT_resetStreamCallbackFn = unsafe extern "C" fn(*mut c_void);
/// Scalar inputs for the MT serial-state reset. The private C parameter,
/// serial-state, LDM-state, and sequence-pool layouts stay behind callbacks;
/// Rust owns only their scalar policy and ordering.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTDMT_RustSerialResetProjection {
pub enableLdm: c_int,
pub checksumEnabled: c_int,
pub jobSize: usize,
pub windowLog: c_uint,
pub strategy: c_int,
pub hashLog: c_uint,
pub bucketSizeLog: c_uint,
pub minMatchLength: c_uint,
pub hashRateLog: c_uint,
pub previousHashLog: c_uint,
pub previousBucketSizeLog: c_uint,
pub ldmEntrySize: usize,
}
const _: () = {
assert!(offset_of!(ZSTDMT_RustSerialResetProjection, enableLdm) == 0);
assert!(offset_of!(ZSTDMT_RustSerialResetProjection, checksumEnabled) == size_of::<c_int>());
assert!(offset_of!(ZSTDMT_RustSerialResetProjection, jobSize) == 8);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, windowLog)
== if size_of::<usize>() == 8 { 16 } else { 12 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, strategy)
== if size_of::<usize>() == 8 { 20 } else { 16 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, hashLog)
== if size_of::<usize>() == 8 { 24 } else { 20 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, bucketSizeLog)
== if size_of::<usize>() == 8 { 28 } else { 24 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, minMatchLength)
== if size_of::<usize>() == 8 { 32 } else { 28 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, hashRateLog)
== if size_of::<usize>() == 8 { 36 } else { 32 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, previousHashLog)
== if size_of::<usize>() == 8 { 40 } else { 36 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, previousBucketSizeLog)
== if size_of::<usize>() == 8 { 44 } else { 40 }
);
assert!(
offset_of!(ZSTDMT_RustSerialResetProjection, ldmEntrySize)
== if size_of::<usize>() == 8 { 48 } else { 44 }
);
assert!(
size_of::<ZSTDMT_RustSerialResetProjection>()
== if size_of::<usize>() == 8 { 56 } else { 48 }
);
};
pub type ZSTDMT_serialResetSetLdmParamsFn =
unsafe extern "C" fn(*mut c_void, c_int, c_uint, c_uint, c_uint, c_uint, c_uint);
pub type ZSTDMT_serialResetVoidFn = unsafe extern "C" fn(*mut c_void);
pub type ZSTDMT_serialResetSetNbSeqFn = unsafe extern "C" fn(*mut c_void, usize);
pub type ZSTDMT_serialResetResizeFn = unsafe extern "C" fn(*mut c_void) -> c_int;
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_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;
/// 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.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct ZSTDMT_serialResetLdmParameters {
enableLdm: c_int,
hashLog: c_uint,
bucketSizeLog: c_uint,
minMatchLength: c_uint,
hashRateLog: c_uint,
windowLog: c_uint,
}
const _: () = {
assert!(offset_of!(ZSTDMT_serialResetLdmParameters, enableLdm) == 0);
assert!(offset_of!(ZSTDMT_serialResetLdmParameters, hashLog) == size_of::<c_int>());
assert!(
offset_of!(ZSTDMT_serialResetLdmParameters, bucketSizeLog)
== size_of::<c_int>() + size_of::<c_uint>()
);
assert!(
offset_of!(ZSTDMT_serialResetLdmParameters, minMatchLength)
== size_of::<c_int>() + 2 * size_of::<c_uint>()
);
assert!(
offset_of!(ZSTDMT_serialResetLdmParameters, hashRateLog)
== size_of::<c_int>() + 3 * size_of::<c_uint>()
);
assert!(
offset_of!(ZSTDMT_serialResetLdmParameters, windowLog)
== size_of::<c_int>() + 4 * size_of::<c_uint>()
);
assert!(size_of::<ZSTDMT_serialResetLdmParameters>() == 6 * size_of::<c_uint>());
};
unsafe extern "C" {
fn ZSTD_ldm_adjustParameters(
params: *mut ZSTDMT_serialResetLdmParameters,
cParams: *const ZSTD_compressionParameters,
);
fn ZSTD_ldm_getMaxNbSeq(params: ZSTDMT_serialResetLdmParameters, maxChunkSize: usize) -> usize;
}
/// Callback projection for the MT stream reset. C retains the private buffer,
/// job, flag, and progress fields; Rust owns the order in which each group is
/// reset.
@@ -2777,24 +2892,97 @@ where
/// Run the MT serial-state reset policy while C retains its private LDM
/// tables, dictionary/window state, checksum state, and allocator callbacks.
/// The callback order mirrors the original reset path and stops immediately
/// when table allocation reports failure.
/// Rust prepares the scalar LDM policy and table sizes, then the callback order
/// mirrors the original reset path and stops immediately when table allocation
/// reports failure.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct ZSTDMT_serialResetPlan {
ldmParams: ZSTDMT_serialResetLdmParameters,
maxNbSeq: usize,
hashSize: usize,
numBuckets: usize,
bucketLog: c_uint,
previousBucketLog: c_uint,
}
#[inline]
unsafe fn serial_state_reset_plan(
projection: ZSTDMT_RustSerialResetProjection,
) -> ZSTDMT_serialResetPlan {
let mut ldm_params = ZSTDMT_serialResetLdmParameters {
enableLdm: projection.enableLdm,
hashLog: projection.hashLog,
bucketSizeLog: projection.bucketSizeLog,
minMatchLength: projection.minMatchLength,
hashRateLog: projection.hashRateLog,
windowLog: projection.windowLog,
};
if projection.enableLdm == ZSTD_PS_ENABLE {
let c_params = ZSTD_compressionParameters {
windowLog: projection.windowLog,
strategy: projection.strategy,
..Default::default()
};
unsafe { ZSTD_ldm_adjustParameters(&mut ldm_params, &c_params) };
} else {
ldm_params = ZSTDMT_serialResetLdmParameters::default();
}
let ldm_enabled = ldm_params.enableLdm == ZSTD_PS_ENABLE;
let bucket_log = if ldm_enabled {
ldm_params.hashLog.wrapping_sub(ldm_params.bucketSizeLog)
} else {
0
};
let previous_bucket_log = if ldm_enabled {
projection
.previousHashLog
.wrapping_sub(projection.previousBucketSizeLog)
} else {
0
};
let hash_size = if ldm_enabled {
(1usize.wrapping_shl(ldm_params.hashLog)).wrapping_mul(projection.ldmEntrySize)
} else {
0
};
let num_buckets = if ldm_enabled {
1usize.wrapping_shl(bucket_log)
} else {
0
};
let max_nb_seq = unsafe { ZSTD_ldm_getMaxNbSeq(ldm_params, projection.jobSize) };
ZSTDMT_serialResetPlan {
ldmParams: ldm_params,
maxNbSeq: max_nb_seq,
hashSize: hash_size,
numBuckets: num_buckets,
bucketLog: bucket_log,
previousBucketLog: previous_bucket_log,
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTDMT_rust_serialStateReset(
enable_ldm: c_int,
checksum_enabled: c_int,
max_nb_seq: usize,
projection: *const ZSTDMT_RustSerialResetProjection,
opaque: *mut c_void,
set_ldm_params: Option<ZSTDMT_serialResetSetLdmParamsFn>,
reset_next_job: Option<ZSTDMT_serialResetVoidFn>,
reset_checksum: Option<ZSTDMT_serialResetVoidFn>,
set_nb_seq: Option<ZSTDMT_serialResetSetNbSeqFn>,
reset_window: Option<ZSTDMT_serialResetVoidFn>,
resize_tables: Option<ZSTDMT_serialResetResizeFn>,
zero_tables: Option<ZSTDMT_serialResetVoidFn>,
zero_tables: Option<ZSTDMT_serialResetZeroTablesFn>,
load_dictionary: Option<ZSTDMT_serialResetVoidFn>,
copy_window: Option<ZSTDMT_serialResetVoidFn>,
publish_params: Option<ZSTDMT_serialResetPublishFn>,
) -> c_int {
let Some(projection) = (unsafe { projection.as_ref() }).copied() else {
return 1;
};
let (
Some(set_ldm_params),
Some(reset_next_job),
Some(reset_checksum),
Some(set_nb_seq),
@@ -2803,7 +2991,9 @@ pub unsafe extern "C" fn ZSTDMT_rust_serialStateReset(
Some(zero_tables),
Some(load_dictionary),
Some(copy_window),
Some(publish_params),
) = (
set_ldm_params,
reset_next_job,
reset_checksum,
set_nb_seq,
@@ -2812,26 +3002,45 @@ pub unsafe extern "C" fn ZSTDMT_rust_serialStateReset(
zero_tables,
load_dictionary,
copy_window,
publish_params,
)
else {
return 1;
};
let plan = unsafe { serial_state_reset_plan(projection) };
unsafe {
set_ldm_params(
opaque,
plan.ldmParams.enableLdm,
plan.ldmParams.hashLog,
plan.ldmParams.bucketSizeLog,
plan.ldmParams.minMatchLength,
plan.ldmParams.hashRateLog,
plan.ldmParams.windowLog,
);
reset_next_job(opaque);
if checksum_enabled != 0 {
if projection.checksumEnabled != 0 {
reset_checksum(opaque);
}
if enable_ldm == ZSTD_PS_ENABLE {
set_nb_seq(opaque, max_nb_seq);
if plan.ldmParams.enableLdm == ZSTD_PS_ENABLE {
set_nb_seq(opaque, plan.maxNbSeq);
reset_window(opaque);
if resize_tables(opaque) != 0 {
if resize_tables(
opaque,
plan.hashSize,
plan.numBuckets,
plan.bucketLog,
plan.previousBucketLog,
) != 0
{
return 1;
}
zero_tables(opaque);
zero_tables(opaque, plan.hashSize, plan.numBuckets);
load_dictionary(opaque);
copy_window(opaque);
}
publish_params(opaque, projection.jobSize);
}
0
}
@@ -6867,8 +7076,38 @@ mod tests {
#[derive(Default)]
struct SerialResetTestContext {
events: Vec<&'static str>,
ldm_params: ZSTDMT_serialResetLdmParameters,
max_nb_seq: usize,
resize_result: c_int,
hash_size: usize,
num_buckets: usize,
bucket_log: c_uint,
previous_bucket_log: c_uint,
published_job_size: usize,
published_job_size_u32: u32,
}
unsafe extern "C" fn serial_reset_test_set_ldm_params(
context: *mut c_void,
enable_ldm: c_int,
hash_log: c_uint,
bucket_size_log: c_uint,
min_match_length: c_uint,
hash_rate_log: c_uint,
window_log: c_uint,
) {
unsafe {
let context = &mut *context.cast::<SerialResetTestContext>();
context.events.push("ldm-params");
context.ldm_params = ZSTDMT_serialResetLdmParameters {
enableLdm: enable_ldm,
hashLog: hash_log,
bucketSizeLog: bucket_size_log,
minMatchLength: min_match_length,
hashRateLog: hash_rate_log,
windowLog: window_log,
};
}
}
unsafe extern "C" fn serial_reset_test_next_job(context: *mut c_void) {
@@ -6903,15 +7142,29 @@ mod tests {
}
}
unsafe extern "C" fn serial_reset_test_resize(context: *mut c_void) -> c_int {
unsafe extern "C" fn serial_reset_test_resize(
context: *mut c_void,
hash_size: usize,
num_buckets: usize,
bucket_log: c_uint,
previous_bucket_log: c_uint,
) -> c_int {
unsafe {
let context = &mut *context.cast::<SerialResetTestContext>();
context.events.push("resize");
context.hash_size = hash_size;
context.num_buckets = num_buckets;
context.bucket_log = bucket_log;
context.previous_bucket_log = previous_bucket_log;
context.resize_result
}
}
unsafe extern "C" fn serial_reset_test_zero(context: *mut c_void) {
unsafe extern "C" fn serial_reset_test_zero(
context: *mut c_void,
_hash_size: usize,
_num_buckets: usize,
) {
unsafe {
(*context.cast::<SerialResetTestContext>())
.events
@@ -6935,19 +7188,31 @@ mod tests {
}
}
unsafe extern "C" fn serial_reset_test_publish(context: *mut c_void, job_size: usize) {
unsafe {
let context = &mut *context.cast::<SerialResetTestContext>();
context.events.push("publish");
context.published_job_size = job_size;
context.published_job_size_u32 = job_size as u32;
}
}
type SerialResetTestCallbacks = (
Option<ZSTDMT_serialResetSetLdmParamsFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetSetNbSeqFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetResizeFn>,
Option<ZSTDMT_serialResetZeroTablesFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetVoidFn>,
Option<ZSTDMT_serialResetPublishFn>,
);
fn serial_reset_test_callbacks() -> SerialResetTestCallbacks {
(
Some(serial_reset_test_set_ldm_params),
Some(serial_reset_test_next_job),
Some(serial_reset_test_checksum),
Some(serial_reset_test_set_nb_seq),
@@ -6956,19 +7221,40 @@ mod tests {
Some(serial_reset_test_zero),
Some(serial_reset_test_dictionary),
Some(serial_reset_test_copy_window),
Some(serial_reset_test_publish),
)
}
fn serial_reset_test_projection(
enable_ldm: c_int,
checksum_enabled: c_int,
job_size: usize,
) -> ZSTDMT_RustSerialResetProjection {
ZSTDMT_RustSerialResetProjection {
enableLdm: enable_ldm,
checksumEnabled: checksum_enabled,
jobSize: job_size,
windowLog: 20,
strategy: ZSTD_GREEDY,
hashLog: 0,
bucketSizeLog: 0,
minMatchLength: 0,
hashRateLog: 0,
previousHashLog: 10,
previousBucketSizeLog: 4,
ldmEntrySize: 8,
}
}
#[test]
fn serial_state_reset_runs_ldm_operations_in_c_order() {
fn serial_state_reset_enabled_prepares_scalars_and_preserves_order() {
let mut context = SerialResetTestContext::default();
let callbacks = serial_reset_test_callbacks();
let projection = serial_reset_test_projection(ZSTD_PS_ENABLE, 1, 640);
let result = unsafe {
ZSTDMT_rust_serialStateReset(
ZSTD_PS_ENABLE,
1,
123,
&projection,
(&mut context as *mut SerialResetTestContext).cast(),
callbacks.0,
callbacks.1,
@@ -6978,14 +7264,33 @@ mod tests {
callbacks.5,
callbacks.6,
callbacks.7,
callbacks.8,
callbacks.9,
)
};
assert_eq!(result, 0);
assert_eq!(context.max_nb_seq, 123);
assert_eq!(
context.ldm_params,
ZSTDMT_serialResetLdmParameters {
enableLdm: ZSTD_PS_ENABLE,
hashLog: 14,
bucketSizeLog: 4,
minMatchLength: 64,
hashRateLog: 6,
windowLog: 20,
}
);
assert_eq!(context.max_nb_seq, 10);
assert_eq!(context.hash_size, 1 << 14 << 3);
assert_eq!(context.num_buckets, 1 << 10);
assert_eq!(context.bucket_log, 10);
assert_eq!(context.previous_bucket_log, 6);
assert_eq!(context.published_job_size, 640);
assert_eq!(
context.events,
vec![
"ldm-params",
"next-job",
"checksum",
"seq-size",
@@ -6994,10 +7299,77 @@ mod tests {
"zero",
"dictionary",
"copy-window",
"publish",
]
);
}
#[test]
fn serial_state_reset_disabled_zeroes_ldm_scalars_and_skips_ldm_work() {
let mut context = SerialResetTestContext::default();
let callbacks = serial_reset_test_callbacks();
let mut projection = serial_reset_test_projection(ZSTD_PS_DISABLE, 0, 640);
projection.hashLog = 20;
projection.bucketSizeLog = 4;
projection.minMatchLength = 32;
projection.hashRateLog = 5;
let result = unsafe {
ZSTDMT_rust_serialStateReset(
&projection,
(&mut context as *mut SerialResetTestContext).cast(),
callbacks.0,
callbacks.1,
callbacks.2,
callbacks.3,
callbacks.4,
callbacks.5,
callbacks.6,
callbacks.7,
callbacks.8,
callbacks.9,
)
};
assert_eq!(result, 0);
assert_eq!(
context.ldm_params,
ZSTDMT_serialResetLdmParameters::default()
);
assert_eq!(context.max_nb_seq, 0);
assert_eq!(context.published_job_size, 640);
assert_eq!(context.events, vec!["ldm-params", "next-job", "publish"]);
}
#[test]
fn serial_state_reset_preserves_size_t_job_size_overflow_and_cast() {
let mut context = SerialResetTestContext::default();
let callbacks = serial_reset_test_callbacks();
let projection = serial_reset_test_projection(ZSTD_PS_ENABLE, 0, usize::MAX);
let result = unsafe {
ZSTDMT_rust_serialStateReset(
&projection,
(&mut context as *mut SerialResetTestContext).cast(),
callbacks.0,
callbacks.1,
callbacks.2,
callbacks.3,
callbacks.4,
callbacks.5,
callbacks.6,
callbacks.7,
callbacks.8,
callbacks.9,
)
};
assert_eq!(result, 0);
assert_eq!(context.max_nb_seq, usize::MAX / 64);
assert_eq!(context.published_job_size, usize::MAX);
assert_eq!(context.published_job_size_u32, usize::MAX as u32);
}
#[test]
fn serial_state_reset_stops_before_zeroing_after_resize_failure() {
let mut context = SerialResetTestContext {
@@ -7005,12 +7377,11 @@ mod tests {
..Default::default()
};
let callbacks = serial_reset_test_callbacks();
let projection = serial_reset_test_projection(ZSTD_PS_ENABLE, 0, 640);
let result = unsafe {
ZSTDMT_rust_serialStateReset(
ZSTD_PS_ENABLE,
0,
123,
&projection,
(&mut context as *mut SerialResetTestContext).cast(),
callbacks.0,
callbacks.1,
@@ -7020,13 +7391,19 @@ mod tests {
callbacks.5,
callbacks.6,
callbacks.7,
callbacks.8,
callbacks.9,
)
};
assert_eq!(result, 1);
assert_eq!(
context.events,
vec!["next-job", "seq-size", "window", "resize"]
vec!["ldm-params", "next-job", "seq-size", "window", "resize"]
);
assert_eq!(
ZSTDMT_rust_initSerialResetResult(result as c_int),
ERROR(ZstdErrorCode::MemoryAllocation)
);
}