feat(mt): move external sequence gate to Rust

The MT worker previously kept the external-sequence non-empty gate and LDM
invariant assertion in C, bundled with the private CCtx operation. Project the
CCtx pointer and raw-sequence store through a layout-checked seam so Rust owns
the gate and the apply point while C retains only the opaque
ZSTD_referenceExternalSequences() leaf. Preserve the callback ordering after
worker CCtx initialization and keep empty stores as a no-op.

Test Plan:
- git diff --cached --check -- passed before commit.
- Static rg inspection confirmed all projection initializers include the new
  sequence-state pointer and no old serialState_applySequences symbol remains.
- Cargo, make, compiler checks, and full tests were not run per worker scope.
This commit is contained in:
2026-07-21 20:42:30 +02:00
parent a3ffd36a63
commit 484ffa4d93
2 changed files with 182 additions and 32 deletions
+133 -3
View File
@@ -365,6 +365,18 @@ pub struct ZSTDMT_compressionJobProjection {
pub firstJob: c_uint,
pub lastJob: c_uint,
pub frameHeaderState: *const ZSTDMT_compressionJobFrameHeaderState,
pub sequenceState: *const ZSTDMT_compressionJobSequenceState,
}
/// Indirection into the worker-owned state used by the external-sequence
/// gate. Rust sees only pointers to the opaque CCtx and the already-projected
/// raw-sequence store; the private worker-state layout remains in C.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTDMT_compressionJobSequenceState {
pub cctx: *mut *mut c_void,
pub rawSeqStore: *const ZstdMtRawSeqStore,
pub ldmEnabled: c_int,
}
/// Scalar projection for the non-first MT job frame header. The destination,
@@ -393,9 +405,24 @@ const _: () = {
offset_of!(ZSTDMT_compressionJobProjection, frameHeaderState) == size_of::<[c_uint; 2]>()
);
assert!(
size_of::<ZSTDMT_compressionJobProjection>()
offset_of!(ZSTDMT_compressionJobProjection, sequenceState)
== size_of::<[c_uint; 2]>() + size_of::<usize>()
);
assert!(
size_of::<ZSTDMT_compressionJobProjection>()
== size_of::<[c_uint; 2]>() + 2 * size_of::<usize>()
);
assert!(offset_of!(ZSTDMT_compressionJobSequenceState, cctx) == 0);
assert!(
offset_of!(ZSTDMT_compressionJobSequenceState, rawSeqStore) == size_of::<usize>()
);
assert!(
offset_of!(ZSTDMT_compressionJobSequenceState, ldmEnabled) == 2 * size_of::<usize>()
);
assert!(
size_of::<ZSTDMT_compressionJobSequenceState>()
== if size_of::<usize>() == 8 { 24 } else { 12 }
);
assert!(offset_of!(ZSTDMT_compressionJobFrameHeaderState, dst) == 0);
assert!(offset_of!(ZSTDMT_compressionJobFrameHeaderState, dst_capacity) == size_of::<usize>());
assert!(offset_of!(ZSTDMT_compressionJobFrameHeaderState, stage) == 2 * size_of::<usize>());
@@ -504,6 +531,57 @@ pub type ZSTDMT_compressionJobCompressFn =
unsafe extern "C" fn(*mut c_void, c_uint) -> ZSTDMT_chunkProcessResult;
pub type ZSTDMT_compressionJobSizeFn = unsafe extern "C" fn(*mut c_void, usize);
pub type ZSTDMT_compressionJobSetParameterFn = unsafe extern "C" fn(*mut c_void, c_int) -> usize;
pub type ZSTDMT_compressionJobApplySequencesFn =
unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, usize);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct ZSTDMT_compressionJobSequenceProjection {
cctx: *mut c_void,
sequences: *mut c_void,
nbSequences: usize,
ldmEnabled: c_int,
}
#[inline]
unsafe fn compression_job_sequence_projection(
state: *const ZSTDMT_compressionJobSequenceState,
) -> Option<ZSTDMT_compressionJobSequenceProjection> {
let state = unsafe { state.as_ref() }?;
if state.cctx.is_null() || state.rawSeqStore.is_null() {
return None;
}
let cctx = unsafe { *state.cctx };
let raw_seq_store = unsafe { &*state.rawSeqStore };
Some(ZSTDMT_compressionJobSequenceProjection {
cctx,
sequences: raw_seq_store.seq.cast(),
nbSequences: raw_seq_store.size,
ldmEnabled: state.ldmEnabled,
})
}
/// Apply the worker's external-sequence policy after CCtx initialization.
/// Rust owns the non-empty gate and LDM invariant; C retains only the opaque
/// `ZSTD_referenceExternalSequences()` leaf behind the callback.
#[inline]
fn apply_compression_job_sequences_with<R>(
projection: Option<ZSTDMT_compressionJobSequenceProjection>,
mut reference_sequences: R,
) where
R: FnMut(*mut c_void, *mut c_void, usize),
{
let Some(projection) = projection else {
return;
};
if projection.nbSequences == 0 {
return;
}
debug_assert_eq!(projection.ldmEnabled, ZSTD_PS_ENABLE);
debug_assert!(!projection.cctx.is_null());
reference_sequences(projection.cctx, projection.sequences, projection.nbSequences);
}
/// Scalar view used to choose the private worker-context initialization path.
/// C retains the cdict, parameter, and CCtx objects; Rust owns the branch and
@@ -2779,7 +2857,7 @@ pub unsafe extern "C" fn ZSTDMT_rust_compressionJob(
prepareParameters: Option<ZSTDMT_compressionJobVoidFn>,
generateSequences: Option<ZSTDMT_compressionJobVoidFn>,
beginJob: Option<ZSTDMT_compressionJobStepFn>,
applySequences: Option<ZSTDMT_compressionJobVoidFn>,
applySequences: Option<ZSTDMT_compressionJobApplySequencesFn>,
compressJob: Option<ZSTDMT_compressionJobCompressFn>,
traceJob: Option<ZSTDMT_compressionJobVoidFn>,
) {
@@ -2810,6 +2888,8 @@ pub unsafe extern "C" fn ZSTDMT_rust_compressionJob(
return;
};
let sequence_state = projection.sequenceState;
compression_job_with(
projection,
finish,
@@ -2817,7 +2897,13 @@ pub unsafe extern "C" fn ZSTDMT_rust_compressionJob(
|| unsafe { prepare_parameters(opaque) },
|| unsafe { generate_sequences(opaque) },
|| unsafe { begin_job(opaque) },
|| unsafe { apply_sequences(opaque) },
|| {
let sequence_projection =
unsafe { compression_job_sequence_projection(sequence_state) };
apply_compression_job_sequences_with(sequence_projection, |cctx, sequences, count| {
unsafe { apply_sequences(opaque, cctx, sequences, count) }
});
},
|| unsafe { ZSTDMT_rust_writeFrameHeader(projection.frameHeaderState) },
|last_job| unsafe { compress_job(opaque, last_job) },
|| unsafe { trace_job(opaque) },
@@ -6591,6 +6677,47 @@ mod tests {
assert_eq!(&output[result..], &[0xa5; 12]);
}
#[test]
fn compression_job_sequence_gate_skips_empty_store_without_ldm_assertion() {
let mut calls = Vec::new();
let projection = ZSTDMT_compressionJobSequenceProjection {
cctx: 0x10usize as *mut c_void,
sequences: ptr::null_mut(),
nbSequences: 0,
ldmEnabled: 0,
};
apply_compression_job_sequences_with(Some(projection), |cctx, sequences, count| {
calls.push((cctx, sequences, count));
});
assert!(calls.is_empty());
}
#[test]
fn compression_job_sequence_gate_forwards_nonempty_store_after_ldm_check() {
let mut calls = Vec::new();
let projection = ZSTDMT_compressionJobSequenceProjection {
cctx: 0x10usize as *mut c_void,
sequences: 0x20usize as *mut c_void,
nbSequences: 3,
ldmEnabled: ZSTD_PS_ENABLE,
};
apply_compression_job_sequences_with(Some(projection), |cctx, sequences, count| {
calls.push((cctx, sequences, count));
});
assert_eq!(
calls,
vec![(
0x10usize as *mut c_void,
0x20usize as *mut c_void,
3
)]
);
}
#[test]
fn compression_job_resources_order_pool_gets_destination_and_ldm_check() {
let events = Rc::new(RefCell::new(Vec::<&'static str>::new()));
@@ -6753,6 +6880,7 @@ mod tests {
firstJob: 1,
lastJob: 1,
frameHeaderState: ptr::null(),
sequenceState: ptr::null(),
},
finish,
move || {
@@ -6814,6 +6942,7 @@ mod tests {
firstJob: 1,
lastJob: 0,
frameHeaderState: ptr::null(),
sequenceState: ptr::null(),
},
finish,
move || {
@@ -7077,6 +7206,7 @@ mod tests {
firstJob: 0,
lastJob: 1,
frameHeaderState: ptr::null(),
sequenceState: ptr::null(),
},
finish,
move || {