feat(compress): aggregate MT frame progression in Rust

Move the arithmetic aggregation in ZSTDMT_getFrameProgression across the
existing C/Rust boundary. C still owns MT state traversal, mutex locking,
jobIDMask ordering, nextJobID plus jobReady handling, error normalization,
and the flushed <= produced assertion. Rust constructs the base result and
folds each compact, C-normalized job snapshot into the complete repr(C)
ZSTD_frameProgression, preserving size_t-to-U64 wrapping and active-worker
counting without exposing private job or mutex layouts.

Focused Rust tests cover zero jobs, a ready job, completed jobs, normalized
error output, and mixed active/completed aggregation.

Test Plan:
- All three compression clippy commands passed before formatting.
- cargo +nightly fmt --manifest-path rust/Cargo.toml -- passed.
- All three compression clippy commands passed after formatting.
- cargo test --manifest-path rust/Cargo.toml --no-default-features
  --features compression -- 332 passed.
- make -B -C lib -j2 lib-mt -- passed.
- make -B -C lib -j2 lib-nomt -- passed.
- make -B -C tests -j2 test-zstream -- passed: 84 named, 6,004, and 8,392
  randomized cases.
- git diff --check and git diff --cached --check -- passed.

The zstream build retained the pre-existing warning at
tests/zstreamtest.c:1899 about an unterminated initializer string.
This commit is contained in:
2026-07-18 14:05:46 +02:00
parent 4c114b8e7b
commit 3bd4dba2ee
2 changed files with 143 additions and 11 deletions
+132
View File
@@ -18,6 +18,8 @@ use std::os::raw::{c_int, c_uint, c_void};
use std::ptr;
use std::sync::Mutex;
use crate::zstd_compress::ZSTD_frameProgression;
const ZSTDMT_JOBLOG_MAX: c_uint = if mem::size_of::<usize>() == 4 { 29 } else { 30 };
const ZSTD_WINDOWLOG_MAX: c_uint = if mem::size_of::<usize>() == 4 { 30 } else { 31 };
@@ -146,6 +148,65 @@ pub extern "C" fn ZSTDMT_rust_computeOverlapSize(
compute_overlap_size(windowLog, chainLog, strategy, overlapLog, enableLdm)
}
#[inline]
fn frame_progression(
consumed: u64,
in_buff_filled: usize,
produced: u64,
current_job_id: c_uint,
) -> ZSTD_frameProgression {
ZSTD_frameProgression {
// C's usual arithmetic conversions promote size_t to U64 before wrapping.
ingested: consumed.wrapping_add(in_buff_filled as u64),
consumed,
produced,
flushed: produced,
currentJobID: current_job_id,
nbActiveWorkers: 0,
}
}
#[inline]
fn frame_progression_add_job(
mut progression: ZSTD_frameProgression,
src_size: usize,
consumed: usize,
produced: usize,
flushed: usize,
) -> ZSTD_frameProgression {
progression.ingested = progression.ingested.wrapping_add(src_size as u64);
progression.consumed = progression.consumed.wrapping_add(consumed as u64);
progression.produced = progression.produced.wrapping_add(produced as u64);
progression.flushed = progression.flushed.wrapping_add(flushed as u64);
progression.nbActiveWorkers = progression
.nbActiveWorkers
.wrapping_add((consumed < src_size) as c_uint);
progression
}
/// Construct the base MT frame progression from C-owned scalar state.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_frameProgression(
consumed: u64,
in_buff_filled: usize,
produced: u64,
current_job_id: c_uint,
) -> ZSTD_frameProgression {
frame_progression(consumed, in_buff_filled, produced, current_job_id)
}
/// Add one C-normalized, mutex-protected job snapshot to MT frame progression.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_frameProgressionAddJob(
progression: ZSTD_frameProgression,
src_size: usize,
consumed: usize,
produced: usize,
flushed: usize,
) -> ZSTD_frameProgression {
frame_progression_add_job(progression, src_size, consumed, produced, flushed)
}
#[inline]
unsafe fn rolling_hash_append(mut hash: u64, input: *const u8, size: usize) -> u64 {
for pos in 0..size {
@@ -1093,6 +1154,77 @@ mod tests {
assert_eq!(ZSTDMT_rust_rollingHashPrimePower(0), 0x12A9_3A33_31E0_3D4B);
}
#[test]
fn frame_progression_zero_jobs_keeps_mt_base_values() {
assert_eq!(
ZSTDMT_rust_frameProgression(11, 7, 13, 4),
ZSTD_frameProgression {
ingested: 18,
consumed: 11,
produced: 13,
flushed: 13,
currentJobID: 4,
nbActiveWorkers: 0,
}
);
}
#[test]
fn frame_progression_one_ready_job_counts_as_active() {
let base = ZSTDMT_rust_frameProgression(100, 20, 300, 9);
assert_eq!(
ZSTDMT_rust_frameProgressionAddJob(base, 80, 17, 42, 10),
ZSTD_frameProgression {
ingested: 200,
consumed: 117,
produced: 342,
flushed: 310,
currentJobID: 9,
nbActiveWorkers: 1,
}
);
}
#[test]
fn frame_progression_completed_job_is_not_active() {
let base = ZSTDMT_rust_frameProgression(100, 20, 300, 9);
let progression = ZSTDMT_rust_frameProgressionAddJob(base, 80, 80, 42, 42);
assert_eq!(progression.nbActiveWorkers, 0);
assert_eq!(progression.ingested, 200);
assert_eq!(progression.consumed, 180);
}
#[test]
fn frame_progression_error_job_uses_normalized_zero_output() {
let base = ZSTDMT_rust_frameProgression(100, 20, 300, 9);
let progression = ZSTDMT_rust_frameProgressionAddJob(base, 80, 0, 0, 0);
assert_eq!(progression.produced, 300);
assert_eq!(progression.flushed, 300);
assert_eq!(progression.nbActiveWorkers, 1);
}
#[test]
fn frame_progression_aggregates_active_and_completed_jobs() {
let base = ZSTDMT_rust_frameProgression(100, 20, 300, 9);
let progression = ZSTDMT_rust_frameProgressionAddJob(base, 80, 17, 42, 10);
let progression = ZSTDMT_rust_frameProgressionAddJob(progression, 30, 30, 8, 8);
let progression = ZSTDMT_rust_frameProgressionAddJob(progression, 40, 5, 7, 6);
assert_eq!(
progression,
ZSTD_frameProgression {
ingested: 270,
consumed: 152,
produced: 357,
flushed: 324,
currentJobID: 9,
nbActiveWorkers: 2,
}
);
}
fn call_synchronization_point(
input: &[u8],
input_pos: usize,