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:
@@ -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() :
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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) };
|
||||
|
||||
Reference in New Issue
Block a user