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
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user