feat(compress): move sequence leaves to Rust

Move sequence-store byte accounting, chunk derivation, and repcode resolution into the Rust compression module. Move MT raw-sequence buffer conversions into Rust while preserving the existing C adapters and ABI layouts.

Test Plan:\n- cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression\n- cargo clippy --manifest-path rust/Cargo.toml\n- cargo clippy --manifest-path rust/Cargo.toml --benches\n- cargo clippy --manifest-path rust/Cargo.toml --tests\n- make -B -C lib -j2 lib\n- make -B -C tests -j2 test-cli-tests
This commit is contained in:
2026-07-18 04:05:48 +02:00
parent 1edec2cc0e
commit bdb14b4fbf
4 changed files with 460 additions and 66 deletions
+307
View File
@@ -272,6 +272,141 @@ pub unsafe extern "C" fn ZSTD_rust_maybeRLE(seq_store: *const SeqStore_t) -> c_i
}
}
/// Returns the literal bytes represented by a sequence store.
///
/// The stored `u16` literal length may represent one long literal through the
/// sequence store's side-band marker. This is the Rust leaf for C's
/// `ZSTD_countSeqStoreLiteralsBytes()` helper.
#[inline]
unsafe fn count_seq_store_literals_bytes(seq_store: *const SeqStore_t) -> usize {
let seq_store = unsafe { &*seq_store };
let nb_seqs = unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as usize;
let mut literals_bytes = 0usize;
for index in 0..nb_seqs {
let sequence = unsafe { *seq_store.sequencesStart.add(index) };
literals_bytes = literals_bytes.wrapping_add(sequence.litLength as usize);
if index == seq_store.longLengthPos as usize
&& seq_store.longLengthType == ZSTD_LLT_LITERAL_LENGTH
{
literals_bytes = literals_bytes.wrapping_add(0x10000);
}
}
literals_bytes
}
/// Returns the match bytes represented by a sequence store.
///
/// `mlBase` stores `matchLength - MINMATCH`; the side-band long-length marker
/// adds the same `0x10000` extension used by the original C helper.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_countSeqStoreLiteralsBytes(
seq_store: *const SeqStore_t,
) -> usize {
unsafe { count_seq_store_literals_bytes(seq_store) }
}
#[inline]
unsafe fn count_seq_store_match_bytes(seq_store: *const SeqStore_t) -> usize {
let seq_store = unsafe { &*seq_store };
let nb_seqs = unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as usize;
let mut match_bytes = 0usize;
for index in 0..nb_seqs {
let sequence = unsafe { *seq_store.sequencesStart.add(index) };
match_bytes = match_bytes.wrapping_add(sequence.mlBase as usize + MINMATCH);
if index == seq_store.longLengthPos as usize
&& seq_store.longLengthType == ZSTD_LLT_MATCH_LENGTH
{
match_bytes = match_bytes.wrapping_add(0x10000);
}
}
match_bytes
}
/// Returns the match bytes represented by a sequence store.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_countSeqStoreMatchBytes(seq_store: *const SeqStore_t) -> usize {
unsafe { count_seq_store_match_bytes(seq_store) }
}
/// Derives the sequence-store view for the half-open sequence range
/// `[start_idx, end_idx)`.
///
/// This is the Rust leaf for C's `ZSTD_deriveSeqStoreChunk()`. The original
/// store remains untouched; the result is a shallow copy whose pointers refer
/// into the original sequence, literal, and code buffers.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_deriveSeqStoreChunk(
result_seq_store: *mut SeqStore_t,
original_seq_store: *const SeqStore_t,
start_idx: usize,
end_idx: usize,
) {
let original_seq_store = unsafe { &*original_seq_store };
let result_seq_store = unsafe { &mut *result_seq_store };
*result_seq_store = unsafe { ptr::read(original_seq_store) };
if start_idx > 0 {
result_seq_store.sequences = unsafe { original_seq_store.sequencesStart.add(start_idx) };
let literals_bytes =
unsafe { count_seq_store_literals_bytes(std::ptr::addr_of!(*result_seq_store)) };
result_seq_store.litStart = unsafe { result_seq_store.litStart.add(literals_bytes) };
}
/* Move longLengthPos into the correct position if necessary. */
if original_seq_store.longLengthType != 0 {
if original_seq_store.longLengthPos as usize > end_idx
|| (original_seq_store.longLengthPos as usize) < start_idx
{
result_seq_store.longLengthType = 0;
} else {
result_seq_store.longLengthPos = original_seq_store
.longLengthPos
.wrapping_sub(start_idx as u32);
}
}
result_seq_store.sequencesStart = unsafe { original_seq_store.sequencesStart.add(start_idx) };
result_seq_store.sequences = unsafe { original_seq_store.sequencesStart.add(end_idx) };
let original_nb_sequences = unsafe {
original_seq_store
.sequences
.offset_from(original_seq_store.sequencesStart)
} as usize;
if end_idx == original_nb_sequences {
/* This accounts for possible last literals at the end of the block. */
debug_assert_eq!(result_seq_store.lit, original_seq_store.lit);
} else {
let literals_bytes =
unsafe { count_seq_store_literals_bytes(std::ptr::addr_of!(*result_seq_store)) };
result_seq_store.lit = unsafe { result_seq_store.litStart.add(literals_bytes) };
}
result_seq_store.llCode = unsafe { result_seq_store.llCode.add(start_idx) };
result_seq_store.mlCode = unsafe { result_seq_store.mlCode.add(start_idx) };
result_seq_store.ofCode = unsafe { result_seq_store.ofCode.add(start_idx) };
}
/// Resolves a stored repcode against the current raw-offset history.
///
/// 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.
#[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)
} else {
rep[adjusted_rep_code as usize]
}
}
/// C's `SeqCollector` leaf from `zstd_compress_internal.h`.
#[repr(C)]
pub struct SeqCollector {
@@ -1598,6 +1733,178 @@ mod tests {
assert_eq!(unsafe { ZSTD_rust_maybeRLE(&seq_store) }, 0);
}
#[test]
fn sequence_store_byte_counts_include_the_side_band_long_length() {
let mut sequences = [
SeqDef {
offBase: 4,
litLength: 2,
mlBase: 4,
},
SeqDef {
offBase: 5,
litLength: 5,
mlBase: 5,
},
SeqDef {
offBase: 6,
litLength: 7,
mlBase: 6,
},
];
let mut literals = [0u8; 32];
let sequences_start = sequences.as_mut_ptr();
let literals_start = literals.as_mut_ptr();
let mut seq_store = SeqStore_t {
sequencesStart: sequences_start,
sequences: unsafe { sequences_start.add(sequences.len()) },
litStart: literals_start,
lit: unsafe { literals_start.add(14) },
llCode: std::ptr::null_mut(),
mlCode: std::ptr::null_mut(),
ofCode: std::ptr::null_mut(),
maxNbSeq: sequences.len(),
maxNbLit: literals.len(),
longLengthType: ZSTD_LLT_LITERAL_LENGTH,
longLengthPos: 1,
};
assert_eq!(
unsafe { ZSTD_rust_countSeqStoreLiteralsBytes(&seq_store) },
2 + 5 + 7 + 0x10000
);
assert_eq!(
unsafe { ZSTD_rust_countSeqStoreMatchBytes(&seq_store) },
(4 + MINMATCH) + (5 + MINMATCH) + (6 + MINMATCH)
);
seq_store.longLengthType = ZSTD_LLT_MATCH_LENGTH;
assert_eq!(
unsafe { ZSTD_rust_countSeqStoreLiteralsBytes(&seq_store) },
2 + 5 + 7
);
assert_eq!(
unsafe { ZSTD_rust_countSeqStoreMatchBytes(&seq_store) },
(4 + MINMATCH) + (5 + MINMATCH) + (6 + MINMATCH) + 0x10000
);
}
#[test]
fn derive_seq_store_chunk_shifts_views_and_long_length_position() {
let mut sequences = [
SeqDef {
offBase: 4,
litLength: 2,
mlBase: 0,
},
SeqDef {
offBase: 5,
litLength: 5,
mlBase: 1,
},
SeqDef {
offBase: 6,
litLength: 7,
mlBase: 2,
},
SeqDef {
offBase: 7,
litLength: 3,
mlBase: 3,
},
];
let mut literals = [0u8; 20];
let mut ll_codes = [10u8; 4];
let mut ml_codes = [20u8; 4];
let mut of_codes = [30u8; 4];
let sequences_start = sequences.as_mut_ptr();
let literals_start = literals.as_mut_ptr();
let original = SeqStore_t {
sequencesStart: sequences_start,
sequences: unsafe { sequences_start.add(sequences.len()) },
litStart: literals_start,
lit: unsafe { literals_start.add(17) },
llCode: ll_codes.as_mut_ptr(),
mlCode: ml_codes.as_mut_ptr(),
ofCode: of_codes.as_mut_ptr(),
maxNbSeq: sequences.len(),
maxNbLit: literals.len(),
longLengthType: ZSTD_LLT_MATCH_LENGTH,
longLengthPos: 2,
};
let mut result = SeqStore_t {
sequencesStart: std::ptr::null_mut(),
sequences: std::ptr::null_mut(),
litStart: std::ptr::null_mut(),
lit: std::ptr::null_mut(),
llCode: std::ptr::null_mut(),
mlCode: std::ptr::null_mut(),
ofCode: std::ptr::null_mut(),
maxNbSeq: 0,
maxNbLit: 0,
longLengthType: 0,
longLengthPos: 0,
};
unsafe {
ZSTD_rust_deriveSeqStoreChunk(&mut result, &original, 1, 3);
}
assert_eq!(result.sequencesStart, unsafe { sequences_start.add(1) });
assert_eq!(result.sequences, unsafe { sequences_start.add(3) });
assert_eq!(result.litStart, unsafe { literals_start.add(2) });
assert_eq!(result.lit, unsafe { literals_start.add(14) });
assert_eq!(result.llCode, unsafe { ll_codes.as_mut_ptr().add(1) });
assert_eq!(result.mlCode, unsafe { ml_codes.as_mut_ptr().add(1) });
assert_eq!(result.ofCode, unsafe { of_codes.as_mut_ptr().add(1) });
assert_eq!(result.longLengthType, ZSTD_LLT_MATCH_LENGTH);
assert_eq!(result.longLengthPos, 1);
assert_eq!(result.maxNbSeq, original.maxNbSeq);
assert_eq!(result.maxNbLit, original.maxNbLit);
assert_eq!(
unsafe { ZSTD_rust_countSeqStoreMatchBytes(&result) },
(1 + MINMATCH) + (2 + MINMATCH) + 0x10000
);
unsafe {
ZSTD_rust_deriveSeqStoreChunk(&mut result, &original, 3, 4);
}
assert_eq!(result.sequencesStart, unsafe { sequences_start.add(3) });
assert_eq!(result.sequences, unsafe { sequences_start.add(4) });
assert_eq!(result.litStart, unsafe { literals_start.add(14) });
assert_eq!(result.lit, original.lit);
assert_eq!(result.longLengthType, 0);
assert_eq!(result.longLengthPos, original.longLengthPos);
}
#[test]
fn resolve_repcode_to_raw_offset_matches_c_repcode_numbering() {
let reps = [11u32, 22, 33];
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 1, 0) },
11
);
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 1, 1) },
22
);
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 2, 0) },
22
);
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 2, 1) },
33
);
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 3, 0) },
33
);
assert_eq!(
unsafe { ZSTD_rust_resolveRepcodeToRawOffset(reps.as_ptr(), 3, 1) },
10
);
}
#[test]
fn repcode_updates_match_the_c_sum_type_rules() {
let mut reps = [1, 4, 8];
+104
View File
@@ -159,6 +159,62 @@ pub struct ZstdMtBuffer {
pub capacity: usize,
}
/// ABI-compatible representation of `rawSeq` from `zstd_compress_internal.h`.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ZstdMtRawSeq {
pub offset: u32,
pub litLength: u32,
pub matchLength: u32,
}
/// ABI-compatible representation of `RawSeqStore_t` from
/// `zstd_compress_internal.h`.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ZstdMtRawSeqStore {
pub seq: *mut ZstdMtRawSeq,
pub pos: usize,
pub posInSequence: usize,
pub size: usize,
pub capacity: usize,
}
#[inline]
fn buffer_to_seq(buffer: ZstdMtBuffer) -> ZstdMtRawSeqStore {
ZstdMtRawSeqStore {
seq: buffer.start.cast::<ZstdMtRawSeq>(),
pos: 0,
posInSequence: 0,
size: 0,
capacity: buffer.capacity / mem::size_of::<ZstdMtRawSeq>(),
}
}
#[inline]
fn seq_to_buffer(seq: ZstdMtRawSeqStore) -> ZstdMtBuffer {
ZstdMtBuffer {
start: seq.seq.cast::<c_void>(),
capacity: seq.capacity.wrapping_mul(mem::size_of::<ZstdMtRawSeq>()),
}
}
/// Convert a byte buffer into the raw-sequence store view used by the MT
/// sequence pool. The capacity is expressed in whole `rawSeq` elements, just
/// like the original C conversion leaf.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_bufferToSeq(buffer: ZstdMtBuffer) -> ZstdMtRawSeqStore {
buffer_to_seq(buffer)
}
/// Convert the raw-sequence store view back to a byte buffer for pool APIs.
/// The returned capacity is measured in bytes and follows C `size_t` wraparound
/// semantics for the multiplication.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_seqToBuffer(seq: ZstdMtRawSeqStore) -> ZstdMtBuffer {
seq_to_buffer(seq)
}
#[derive(Default)]
struct BufferPoolState {
buffer_size: usize,
@@ -721,6 +777,54 @@ mod tests {
opaque: ptr::null_mut(),
};
#[test]
fn raw_seq_buffer_conversion_uses_whole_element_capacity() {
let mut sequences = [ZstdMtRawSeq::default(); 3];
let element_size = mem::size_of::<ZstdMtRawSeq>();
let buffer = ZstdMtBuffer {
start: sequences.as_mut_ptr().cast(),
capacity: element_size * sequences.len() + element_size - 1,
};
let seq = ZSTDMT_rust_bufferToSeq(buffer);
assert_eq!(seq.seq, sequences.as_mut_ptr());
assert_eq!(seq.pos, 0);
assert_eq!(seq.posInSequence, 0);
assert_eq!(seq.size, 0);
assert_eq!(seq.capacity, sequences.len());
let roundtrip = ZSTDMT_rust_seqToBuffer(seq);
assert_eq!(roundtrip.start, buffer.start);
assert_eq!(roundtrip.capacity, element_size * sequences.len());
}
#[test]
fn raw_seq_to_buffer_preserves_pointer_and_size_t_multiplication() {
let seq = ZstdMtRawSeqStore {
seq: ptr::null_mut(),
pos: 4,
posInSequence: 5,
size: 6,
capacity: usize::MAX,
};
let buffer = ZSTDMT_rust_seqToBuffer(seq);
assert!(buffer.start.is_null());
assert_eq!(
buffer.capacity,
usize::MAX.wrapping_mul(mem::size_of::<ZstdMtRawSeq>())
);
let empty = ZSTDMT_rust_bufferToSeq(ZstdMtBuffer {
start: ptr::null_mut(),
capacity: mem::size_of::<ZstdMtRawSeq>() - 1,
});
assert!(empty.seq.is_null());
assert_eq!(empty.capacity, 0);
assert_eq!(empty.pos, 0);
assert_eq!(empty.posInSequence, 0);
assert_eq!(empty.size, 0);
}
#[test]
fn buffer_pool_reuses_and_resizes_buffers() {
let pool = unsafe { ZSTDMT_rust_buffer_pool_create(2, DEFAULT_MEM) };