feat(compress): move sequence policy leaves to Rust

Move external-sequence postprocessing and MT overlap detection into the Rust compression modules. Keep C stateful dispatch and locking code intact while preserving error encoding, delimiter handling, and half-open range semantics.

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 -C tests -j2 test-cli-tests
This commit is contained in:
2026-07-18 04:14:27 +02:00
parent af2991ea40
commit e80fbe4b06
4 changed files with 270 additions and 54 deletions
+5 -38
View File
@@ -241,6 +241,9 @@ size_t ZSTD_rust_fastSequenceLengthSum(const ZSTD_Sequence* seqBuf,
size_t seqBufSize);
int ZSTD_rust_isRLE(const BYTE* src, size_t length);
int ZSTD_rust_maybeRLE(const SeqStore_t* seqStore);
size_t ZSTD_rust_postProcessSequenceProducerResult(
ZSTD_Sequence* outSeqs, size_t nbExternalSeqs,
size_t outSeqsCapacity, size_t srcSize);
size_t ZSTD_rust_countSeqStoreLiteralsBytes(const SeqStore_t* seqStore);
size_t ZSTD_rust_countSeqStoreMatchBytes(const SeqStore_t* seqStore);
void ZSTD_rust_deriveSeqStoreChunk(SeqStore_t* resultSeqStore,
@@ -2333,44 +2336,8 @@ void ZSTD_resetSeqStore(SeqStore_t* ssPtr)
static size_t ZSTD_postProcessSequenceProducerResult(
ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, size_t outSeqsCapacity, size_t srcSize
) {
RETURN_ERROR_IF(
nbExternalSeqs > outSeqsCapacity,
sequenceProducer_failed,
"External sequence producer returned error code %lu",
(unsigned long)nbExternalSeqs
);
RETURN_ERROR_IF(
nbExternalSeqs == 0 && srcSize > 0,
sequenceProducer_failed,
"Got zero sequences from external sequence producer for a non-empty src buffer!"
);
if (srcSize == 0) {
ZSTD_memset(&outSeqs[0], 0, sizeof(ZSTD_Sequence));
return 1;
}
{
ZSTD_Sequence const lastSeq = outSeqs[nbExternalSeqs - 1];
/* We can return early if lastSeq is already a block delimiter. */
if (lastSeq.offset == 0 && lastSeq.matchLength == 0) {
return nbExternalSeqs;
}
/* This error condition is only possible if the external matchfinder
* produced an invalid parse, by definition of ZSTD_sequenceBound(). */
RETURN_ERROR_IF(
nbExternalSeqs == outSeqsCapacity,
sequenceProducer_failed,
"nbExternalSeqs == outSeqsCapacity but lastSeq is not a block delimiter!"
);
/* lastSeq is not a block delimiter, so we need to append one. */
ZSTD_memset(&outSeqs[nbExternalSeqs], 0, sizeof(ZSTD_Sequence));
return nbExternalSeqs + 1;
}
return ZSTD_rust_postProcessSequenceProducerResult(
outSeqs, nbExternalSeqs, outSeqsCapacity, srcSize);
}
/* ZSTD_fastSequenceLengthSum() :
+4 -16
View File
@@ -139,6 +139,8 @@ unsigned ZSTDMT_rust_computeTargetJobLog(unsigned windowLog, unsigned chainLog,
int ZSTDMT_rust_overlapLog(int overlapLog, int strategy);
size_t ZSTDMT_rust_computeOverlapSize(unsigned windowLog, unsigned chainLog,
int strategy, int overlapLog, int enableLdm);
int ZSTDMT_rust_isOverlapped(const void* bufferStart, size_t bufferCapacity,
const void* rangeStart, size_t rangeSize);
typedef struct ZSTDMT_bufferPool_s {
ZSTDMT_RustBufferPool* rustPool;
@@ -1531,22 +1533,8 @@ static Range ZSTDMT_getInputDataInUse(ZSTDMT_CCtx* mtctx)
*/
static int ZSTDMT_isOverlapped(Buffer buffer, Range range)
{
BYTE const* const bufferStart = (BYTE const*)buffer.start;
BYTE const* const rangeStart = (BYTE const*)range.start;
if (rangeStart == NULL || bufferStart == NULL)
return 0;
{
BYTE const* const bufferEnd = bufferStart + buffer.capacity;
BYTE const* const rangeEnd = rangeStart + range.size;
/* Empty ranges cannot overlap */
if (bufferStart == bufferEnd || rangeStart == rangeEnd)
return 0;
return bufferStart < rangeEnd && rangeStart < bufferEnd;
}
return ZSTDMT_rust_isOverlapped(buffer.start, buffer.capacity,
range.start, range.size);
}
static int ZSTDMT_doesOverlapWindow(Buffer buffer, ZSTD_window_t window)
+160
View File
@@ -158,6 +158,56 @@ pub struct ZSTD_Sequence {
pub rep: u32,
}
/// Validates and post-processes sequences returned by an external sequence
/// producer. This is the Rust leaf for C's
/// `ZSTD_postProcessSequenceProducerResult()`.
///
/// A sequence with zero offset and zero match length terminates a block. If
/// the producer did not append one, this function appends a zeroed delimiter
/// when capacity permits. Error-shaped producer counts and invalid empty
/// parses return the `sequenceProducer_failed` error unchanged.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_postProcessSequenceProducerResult(
out_seqs: *mut ZSTD_Sequence,
nb_external_seqs: usize,
out_seqs_capacity: usize,
src_size: usize,
) -> usize {
if nb_external_seqs > out_seqs_capacity {
return ERROR(ZstdErrorCode::SequenceProducerFailed);
}
if nb_external_seqs == 0 && src_size > 0 {
return ERROR(ZstdErrorCode::SequenceProducerFailed);
}
if src_size == 0 {
unsafe {
ptr::write_bytes(out_seqs.cast::<u8>(), 0, size_of::<ZSTD_Sequence>());
}
return 1;
}
let last_seq = unsafe { *out_seqs.add(nb_external_seqs - 1) };
if last_seq.offset == 0 && last_seq.matchLength == 0 {
return nb_external_seqs;
}
if nb_external_seqs == out_seqs_capacity {
return ERROR(ZstdErrorCode::SequenceProducerFailed);
}
unsafe {
ptr::write_bytes(
out_seqs.add(nb_external_seqs).cast::<u8>(),
0,
size_of::<ZSTD_Sequence>(),
);
}
nb_external_seqs + 1
}
/// Resets the state that is carried from one compressed block to the next.
///
/// This is the direct Rust leaf for C's
@@ -1676,6 +1726,116 @@ mod tests {
);
}
#[test]
fn post_process_rejects_external_count_overflow() {
let mut out = [ZSTD_Sequence {
offset: 0xA5A5_A5A5,
litLength: 0x5A5A_5A5A,
matchLength: 0xA5A5_A5A5,
rep: 0x5A5A_5A5A,
}];
let result = unsafe {
ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), usize::MAX, out.len(), 1)
};
assert_eq!(result, ERROR(ZstdErrorCode::SequenceProducerFailed));
assert_eq!(out[0].offset, 0xA5A5_A5A5);
assert_eq!(out[0].litLength, 0x5A5A_5A5A);
assert_eq!(out[0].matchLength, 0xA5A5_A5A5);
assert_eq!(out[0].rep, 0x5A5A_5A5A);
}
#[test]
fn post_process_empty_source_zeroes_the_output_sequence() {
let mut out = [ZSTD_Sequence {
offset: 1,
litLength: 2,
matchLength: 3,
rep: 4,
}];
let result =
unsafe { ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), 0, 1, 0) };
assert_eq!(result, 1);
assert_eq!(out[0].offset, 0);
assert_eq!(out[0].litLength, 0);
assert_eq!(out[0].matchLength, 0);
assert_eq!(out[0].rep, 0);
}
#[test]
fn post_process_preserves_an_existing_delimiter() {
let mut out = [ZSTD_Sequence {
offset: 0,
litLength: 0xA5A5_A5A5,
matchLength: 0,
rep: 0x5A5A_5A5A,
}];
let result =
unsafe { ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), 1, 1, 7) };
assert_eq!(result, 1);
assert_eq!(out[0].offset, 0);
assert_eq!(out[0].litLength, 0xA5A5_A5A5);
assert_eq!(out[0].matchLength, 0);
assert_eq!(out[0].rep, 0x5A5A_5A5A);
}
#[test]
fn post_process_appends_a_zeroed_delimiter() {
let mut out = [
ZSTD_Sequence {
offset: 3,
litLength: 4,
matchLength: 5,
rep: 6,
},
ZSTD_Sequence {
offset: 0xA5A5_A5A5,
litLength: 0x5A5A_5A5A,
matchLength: 0xA5A5_A5A5,
rep: 0x5A5A_5A5A,
},
];
let result = unsafe {
ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), 1, out.len(), 7)
};
assert_eq!(result, 2);
assert_eq!(out[0].offset, 3);
assert_eq!(out[0].litLength, 4);
assert_eq!(out[0].matchLength, 5);
assert_eq!(out[0].rep, 6);
assert_eq!(out[1].offset, 0);
assert_eq!(out[1].litLength, 0);
assert_eq!(out[1].matchLength, 0);
assert_eq!(out[1].rep, 0);
}
#[test]
fn post_process_reports_empty_parse_for_non_empty_source() {
let mut out = [ZSTD_Sequence {
offset: 0xA5A5_A5A5,
litLength: 0x5A5A_5A5A,
matchLength: 0xA5A5_A5A5,
rep: 0x5A5A_5A5A,
}];
let result = unsafe {
ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), 0, out.len(), 1)
};
assert_eq!(result, ERROR(ZstdErrorCode::SequenceProducerFailed));
}
#[test]
fn post_process_reports_full_capacity_without_a_delimiter() {
let mut out = [ZSTD_Sequence {
offset: 1,
litLength: 2,
matchLength: 3,
rep: 4,
}];
let result = unsafe {
ZSTD_rust_postProcessSequenceProducerResult(out.as_mut_ptr(), 1, out.len(), 7)
};
assert_eq!(result, ERROR(ZstdErrorCode::SequenceProducerFailed));
}
#[test]
fn is_rle_matches_empty_one_byte_and_unrolled_inputs() {
let one_byte = [0x5Au8];
+101
View File
@@ -199,6 +199,30 @@ fn seq_to_buffer(seq: ZstdMtRawSeqStore) -> ZstdMtBuffer {
}
}
#[inline]
fn is_overlapped(
buffer_start: *const c_void,
buffer_capacity: usize,
range_start: *const c_void,
range_size: usize,
) -> c_int {
if buffer_start.is_null() || range_start.is_null() {
return 0;
}
let buffer_start = buffer_start.cast::<u8>();
let range_start = range_start.cast::<u8>();
let buffer_end = buffer_start.wrapping_add(buffer_capacity);
let range_end = range_start.wrapping_add(range_size);
/* Empty ranges cannot overlap. */
if buffer_start == buffer_end || range_start == range_end {
return 0;
}
(buffer_start < range_end && range_start < buffer_end) as c_int
}
/// 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.
@@ -215,6 +239,17 @@ pub extern "C" fn ZSTDMT_rust_seqToBuffer(seq: ZstdMtRawSeqStore) -> ZstdMtBuffe
seq_to_buffer(seq)
}
/// Return non-zero when two non-empty byte ranges overlap.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_isOverlapped(
bufferStart: *const c_void,
bufferCapacity: usize,
rangeStart: *const c_void,
rangeSize: usize,
) -> c_int {
is_overlapped(bufferStart, bufferCapacity, rangeStart, rangeSize)
}
#[derive(Default)]
struct BufferPoolState {
buffer_size: usize,
@@ -825,6 +860,72 @@ mod tests {
assert_eq!(empty.size, 0);
}
#[test]
fn overlapped_rejects_null_ranges() {
let bytes = [0u8; 8];
let start = bytes.as_ptr().cast::<c_void>();
assert_eq!(ZSTDMT_rust_isOverlapped(ptr::null(), 4, start, 4), 0);
assert_eq!(ZSTDMT_rust_isOverlapped(start, 4, ptr::null(), 4), 0);
}
#[test]
fn overlapped_rejects_empty_ranges() {
let bytes = [0u8; 8];
let start = bytes.as_ptr().cast::<c_void>();
assert_eq!(ZSTDMT_rust_isOverlapped(start, 0, start, 4), 0);
assert_eq!(ZSTDMT_rust_isOverlapped(start, 4, start, 0), 0);
}
#[test]
fn overlapped_uses_half_open_range_boundaries() {
let bytes = [0u8; 16];
let start = bytes.as_ptr();
let middle = start.wrapping_add(4);
assert_eq!(
ZSTDMT_rust_isOverlapped(start.cast(), 4, middle.cast(), 4),
0
);
assert_eq!(
ZSTDMT_rust_isOverlapped(middle.cast(), 4, start.cast(), 4),
0
);
}
#[test]
fn overlapped_detects_contained_ranges() {
let bytes = [0u8; 16];
let start = bytes.as_ptr();
let inner = start.wrapping_add(4);
assert_eq!(
ZSTDMT_rust_isOverlapped(start.cast(), 12, inner.cast(), 4),
1
);
assert_eq!(
ZSTDMT_rust_isOverlapped(inner.cast(), 4, start.cast(), 12),
1
);
}
#[test]
fn overlapped_rejects_disjoint_ranges() {
let bytes = [0u8; 16];
let start = bytes.as_ptr();
let after = start.wrapping_add(8);
assert_eq!(
ZSTDMT_rust_isOverlapped(start.cast(), 4, after.cast(), 4),
0
);
assert_eq!(
ZSTDMT_rust_isOverlapped(after.cast(), 4, start.cast(), 4),
0
);
}
#[test]
fn buffer_pool_reuses_and_resizes_buffers() {
let pool = unsafe { ZSTDMT_rust_buffer_pool_create(2, DEFAULT_MEM) };