From 3bbfacdfd0112023abb60d3c62cf38362cf605d3 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 06:03:55 +0200 Subject: [PATCH] feat(compress): move repcode reconciliation to Rust Port ZSTD_seqStore_resolveOffCodes and its repcode-resolution helper to Rust. The C shim keeps Repcodes_t and SeqStore_t ownership in the compressor context while Rust preserves long-literal handling, mismatch materialization, and independent decoder/compressor history updates. Test Plan: - cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression (208 tests) - cargo clippy --manifest-path rust/Cargo.toml - cargo clippy --manifest-path rust/Cargo.toml --benches - cargo clippy --manifest-path rust/Cargo.toml --tests - make -B -C lib -j2 lib --- lib/compress/zstd_compress.c | 41 +------ rust/src/zstd_compress_stats.rs | 194 ++++++++++++++++++++++++++++++-- 2 files changed, 193 insertions(+), 42 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 28625179e..ff6cab2e4 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -261,6 +261,10 @@ size_t ZSTD_rust_countSeqStoreMatchBytes(const SeqStore_t* seqStore); void ZSTD_rust_deriveSeqStoreChunk(SeqStore_t* resultSeqStore, const SeqStore_t* originalSeqStore, size_t startIdx, size_t endIdx); +void ZSTD_rust_seqStore_resolveOffCodes(U32 dRep[ZSTD_REP_NUM], + U32 cRep[ZSTD_REP_NUM], + const SeqStore_t* seqStore, + U32 nbSeq); U32 ZSTD_rust_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], U32 offBase, U32 ll0); U32 ZSTD_rust_finalizeOffBase(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0); @@ -2738,17 +2742,6 @@ static void ZSTD_deriveSeqStoreChunk(SeqStore_t* resultSeqStore, startIdx, endIdx); } -/** - * Returns the raw offset represented by the combination of offBase, ll0, and repcode history. - * offBase must represent a repcode in the numeric representation of ZSTD_storeSeq(). - */ -static U32 -ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offBase, const U32 ll0) -{ - assert(OFFBASE_IS_REPCODE(offBase)); - return ZSTD_rust_resolveRepcodeToRawOffset(rep, offBase, ll0); -} - /** * ZSTD_seqStore_resolveOffCodes() reconciles any possible divergences in offset history that may arise * due to emission of RLE/raw blocks that disturb the offset history, @@ -2766,30 +2759,8 @@ static void ZSTD_seqStore_resolveOffCodes(Repcodes_t* const dRepcodes, Repcodes_t* const cRepcodes, const SeqStore_t* const seqStore, U32 const nbSeq) { - U32 idx = 0; - U32 const longLitLenIdx = seqStore->longLengthType == ZSTD_llt_literalLength ? seqStore->longLengthPos : nbSeq; - for (; idx < nbSeq; ++idx) { - SeqDef* const seq = seqStore->sequencesStart + idx; - U32 const ll0 = (seq->litLength == 0) && (idx != longLitLenIdx); - U32 const offBase = seq->offBase; - assert(offBase > 0); - if (OFFBASE_IS_REPCODE(offBase)) { - U32 const dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offBase, ll0); - U32 const cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offBase, ll0); - /* Adjust simulated decompression repcode history if we come across a mismatch. Replace - * the repcode with the offset it actually references, determined by the compression - * repcode history. - */ - if (dRawOffset != cRawOffset) { - seq->offBase = OFFSET_TO_OFFBASE(cRawOffset); - } - } - /* Compression repcode history is always updated with values directly from the unmodified seqStore. - * Decompression repcode history may use modified seq->offset value taken from compression repcode history. - */ - ZSTD_updateRep(dRepcodes->rep, seq->offBase, ll0); - ZSTD_updateRep(cRepcodes->rep, offBase, ll0); - } + ZSTD_rust_seqStore_resolveOffCodes(dRepcodes->rep, cRepcodes->rep, + seqStore, nbSeq); } /* ZSTD_compressSeqStore_singleBlock(): diff --git a/rust/src/zstd_compress_stats.rs b/rust/src/zstd_compress_stats.rs index 96eaf6bff..e9cd1f32d 100644 --- a/rust/src/zstd_compress_stats.rs +++ b/rust/src/zstd_compress_stats.rs @@ -573,20 +573,70 @@ pub unsafe extern "C" fn ZSTD_rust_deriveSeqStoreChunk( /// This is the Rust leaf for C's `ZSTD_resolveRepcodeToRawOffset()`. The /// caller must pass an `off_base` in the repcode range `1..=ZSTD_REP_NUM` and /// an `ll0` value of zero or one. +#[inline] +fn resolve_repcode_to_raw_offset(rep: &[u32; ZSTD_REP_NUM], off_base: u32, ll0: u32) -> u32 { + debug_assert!((1..=ZSTD_REP_NUM as u32).contains(&off_base)); + let adjusted_rep_code = off_base.wrapping_sub(1).wrapping_add(ll0); + if adjusted_rep_code == ZSTD_REP_NUM as u32 { + debug_assert_ne!(ll0, 0); + rep[0].wrapping_sub(1) + } else { + rep[adjusted_rep_code as usize] + } +} + #[no_mangle] pub unsafe extern "C" fn ZSTD_rust_resolveRepcodeToRawOffset( rep: *const u32, off_base: u32, ll0: u32, ) -> u32 { - debug_assert!((1..=ZSTD_REP_NUM as u32).contains(&off_base)); - let adjusted_rep_code = off_base.wrapping_sub(1).wrapping_add(ll0); - let rep = unsafe { std::slice::from_raw_parts(rep, ZSTD_REP_NUM) }; - if adjusted_rep_code == ZSTD_REP_NUM as u32 { - debug_assert_ne!(ll0, 0); - rep[0].wrapping_sub(1) + let rep = unsafe { &*rep.cast::<[u32; ZSTD_REP_NUM]>() }; + resolve_repcode_to_raw_offset(rep, off_base, ll0) +} + +/// Reconciles decompression and compression repcode histories for a sequence +/// store. The two histories remain owned by the C caller; only their scalar +/// arrays and the ABI-compatible sequence store cross the language boundary. +/// +/// A repcode mismatch is materialized as a full offset in the sequence store, +/// while the compression history continues to follow the original offBase. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_seqStore_resolveOffCodes( + d_rep: *mut u32, + c_rep: *mut u32, + seq_store: *const SeqStore_t, + nb_seq: u32, +) { + let d_rep = unsafe { &mut *d_rep.cast::<[u32; ZSTD_REP_NUM]>() }; + let c_rep = unsafe { &mut *c_rep.cast::<[u32; ZSTD_REP_NUM]>() }; + let seq_store = unsafe { &*seq_store }; + let long_lit_len_idx = if seq_store.longLengthType == ZSTD_LLT_LITERAL_LENGTH { + seq_store.longLengthPos } else { - rep[adjusted_rep_code as usize] + nb_seq + }; + + for index in 0..nb_seq { + let seq = unsafe { &mut *seq_store.sequencesStart.add(index as usize) }; + let ll0 = u32::from(seq.litLength == 0 && index != long_lit_len_idx); + let off_base = seq.offBase; + debug_assert!(off_base > 0); + + if (1..=ZSTD_REP_NUM as u32).contains(&off_base) { + let d_raw_offset = resolve_repcode_to_raw_offset(d_rep, off_base, ll0); + let c_raw_offset = resolve_repcode_to_raw_offset(c_rep, off_base, ll0); + /* Keep the decoder's simulated history aligned with the raw + * offset represented by the compression-side history. */ + if d_raw_offset != c_raw_offset { + seq.offBase = c_raw_offset.wrapping_add(ZSTD_REP_NUM as u32); + } + } + + /* The decoder sees the corrected sequence; compression follows the + * original sequence exactly. */ + update_rep(d_rep, seq.offBase, ll0 != 0); + update_rep(c_rep, off_base, ll0 != 0); } } @@ -2211,6 +2261,136 @@ mod tests { assert_eq!(reps, [4, 7, 1]); } + #[test] + fn resolve_off_codes_keeps_matching_histories_aligned() { + let mut fixture = TestSeqStore::new( + vec![ + SeqDef { + offBase: 1, + litLength: 4, + mlBase: 0, + }, + SeqDef { + offBase: 2, + litLength: 0, + mlBase: 0, + }, + ], + vec![0; 4], + ); + let seq_store = fixture.seq_store(); + let mut d_rep = [11, 22, 33]; + let mut c_rep = d_rep; + + unsafe { + ZSTD_rust_seqStore_resolveOffCodes( + d_rep.as_mut_ptr(), + c_rep.as_mut_ptr(), + &seq_store, + 2, + ); + } + + assert_eq!(fixture.sequences[0].offBase, 1); + assert_eq!(fixture.sequences[1].offBase, 2); + assert_eq!(d_rep, [33, 11, 22]); + assert_eq!(c_rep, d_rep); + } + + #[test] + fn resolve_off_codes_materializes_mismatched_repcode() { + let mut fixture = TestSeqStore::new( + vec![SeqDef { + offBase: 2, + litLength: 1, + mlBase: 0, + }], + vec![0; 1], + ); + let seq_store = fixture.seq_store(); + let mut d_rep = [11, 22, 33]; + let mut c_rep = [11, 44, 33]; + + unsafe { + ZSTD_rust_seqStore_resolveOffCodes( + d_rep.as_mut_ptr(), + c_rep.as_mut_ptr(), + &seq_store, + 1, + ); + } + + assert_eq!(fixture.sequences[0].offBase, 44 + ZSTD_REP_NUM as u32); + assert_eq!(d_rep, [44, 11, 22]); + assert_eq!(c_rep, [44, 11, 33]); + } + + #[test] + fn resolve_off_codes_updates_both_histories_for_full_offsets() { + let mut fixture = TestSeqStore::new( + vec![SeqDef { + offBase: 103, + litLength: 1, + mlBase: 0, + }], + vec![0; 1], + ); + let seq_store = fixture.seq_store(); + let mut d_rep = [10, 20, 30]; + let mut c_rep = [90, 80, 70]; + + unsafe { + ZSTD_rust_seqStore_resolveOffCodes( + d_rep.as_mut_ptr(), + c_rep.as_mut_ptr(), + &seq_store, + 1, + ); + } + + assert_eq!(fixture.sequences[0].offBase, 103); + assert_eq!(d_rep, [100, 10, 20]); + assert_eq!(c_rep, [100, 90, 80]); + } + + #[test] + fn resolve_off_codes_excludes_long_literal_from_ll0() { + let mut fixture = TestSeqStore::new( + vec![ + SeqDef { + offBase: 1, + litLength: 0, + mlBase: 0, + }, + SeqDef { + offBase: 1, + litLength: 0, + mlBase: 0, + }, + ], + vec![0; 2], + ); + let mut seq_store = fixture.seq_store(); + seq_store.longLengthType = ZSTD_LLT_LITERAL_LENGTH; + seq_store.longLengthPos = 0; + let mut d_rep = [11, 22, 33]; + let mut c_rep = d_rep; + + unsafe { + ZSTD_rust_seqStore_resolveOffCodes( + d_rep.as_mut_ptr(), + c_rep.as_mut_ptr(), + &seq_store, + 2, + ); + } + + assert_eq!(fixture.sequences[0].offBase, 1); + assert_eq!(fixture.sequences[1].offBase, 1); + assert_eq!(d_rep, [22, 11, 33]); + assert_eq!(c_rep, d_rep); + } + #[test] fn seq_to_codes_matches_the_reference_tables() { let mut store = TestSeqStore::new(