feat(compress): move no-delimiter sequence transfer to Rust
Port the no-delimiter external-sequence block scanner, match splitting, repcode updates, validation, and sequence/literal storage behind a narrow C shim. Preserve trailing bytes as literals when the sequence buffer ends before the requested block, and remove the obsolete C validation wrapper.
Test Plan:
- cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression (242 passed)
- cargo clippy --manifest-path rust/Cargo.toml --no-default-features --features compression [--benches|--tests] (clean)
- cargo +nightly fmt --manifest-path rust/Cargo.toml (clean)
- make -B -C lib -j2 lib (passed)
- make -C tests -j2 test-zstream (84 deterministic; 5960 and 8307 randomized passed)
- make -C tests -j2 test-fuzzer (existing test 56 CCtx-reuse mismatch; also fails at 37eb75758)
- make -C tests -j2 test-zstd (existing Rust CLI --patch-from gap)
This commit is contained in:
@@ -784,6 +784,183 @@ pub unsafe extern "C" fn ZSTD_rust_transferSequencesWBlockDelim(
|
||||
block_size
|
||||
}
|
||||
|
||||
/// Transfers externally produced sequences through a block-size boundary when
|
||||
/// the producer did not provide explicit delimiters. A block may end in the
|
||||
/// middle of a match; in that case the sequence position is retained for the
|
||||
/// next call. This is the Rust leaf for
|
||||
/// `ZSTD_transferSequences_noDelim()`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_transferSequencesNoDelim(
|
||||
seq_store: *mut SeqStore_t,
|
||||
seq_pos: *mut ZSTD_SequencePosition,
|
||||
in_seqs: *const ZSTD_Sequence,
|
||||
in_seqs_size: usize,
|
||||
src: *const u8,
|
||||
block_size: usize,
|
||||
prev_repcodes: *const u32,
|
||||
next_repcodes: *mut u32,
|
||||
dict_size: u32,
|
||||
validate_sequences: c_int,
|
||||
min_match: u32,
|
||||
window_log: u32,
|
||||
use_sequence_producer: c_int,
|
||||
) -> usize {
|
||||
if seq_store.is_null()
|
||||
|| seq_pos.is_null()
|
||||
|| in_seqs.is_null()
|
||||
|| src.is_null()
|
||||
|| prev_repcodes.is_null()
|
||||
|| next_repcodes.is_null()
|
||||
|| block_size > u32::MAX as usize
|
||||
{
|
||||
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
||||
}
|
||||
|
||||
let seq_store = unsafe { &mut *seq_store };
|
||||
let seq_pos = unsafe { &mut *seq_pos };
|
||||
let start_idx = seq_pos.idx as usize;
|
||||
if start_idx > in_seqs_size {
|
||||
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
||||
}
|
||||
|
||||
let block_size_u32 = block_size as u32;
|
||||
let mut start_pos_in_sequence = seq_pos.posInSequence;
|
||||
let mut end_pos_in_sequence = start_pos_in_sequence.wrapping_add(block_size_u32);
|
||||
let mut source_offset = 0usize;
|
||||
let mut bytes_adjustment = 0u32;
|
||||
let mut final_match_split = false;
|
||||
let mut updated_repcodes = [0u32; ZSTD_REP_NUM];
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(prev_repcodes, updated_repcodes.as_mut_ptr(), ZSTD_REP_NUM);
|
||||
}
|
||||
|
||||
let mut idx = start_idx;
|
||||
while end_pos_in_sequence != 0 && idx < in_seqs_size && !final_match_split {
|
||||
let current_sequence = unsafe { *in_seqs.add(idx) };
|
||||
let mut lit_length = current_sequence.litLength;
|
||||
let mut match_length = current_sequence.matchLength;
|
||||
let raw_offset = current_sequence.offset;
|
||||
let current_size = current_sequence
|
||||
.litLength
|
||||
.wrapping_add(current_sequence.matchLength);
|
||||
|
||||
if end_pos_in_sequence >= current_size {
|
||||
if start_pos_in_sequence >= lit_length {
|
||||
start_pos_in_sequence = start_pos_in_sequence.wrapping_sub(lit_length);
|
||||
lit_length = 0;
|
||||
match_length = match_length.wrapping_sub(start_pos_in_sequence);
|
||||
} else {
|
||||
lit_length = lit_length.wrapping_sub(start_pos_in_sequence);
|
||||
}
|
||||
end_pos_in_sequence = end_pos_in_sequence.wrapping_sub(current_size);
|
||||
start_pos_in_sequence = 0;
|
||||
} else {
|
||||
/* The block ends inside this sequence. */
|
||||
if end_pos_in_sequence > lit_length {
|
||||
lit_length = if start_pos_in_sequence >= lit_length {
|
||||
0
|
||||
} else {
|
||||
lit_length.wrapping_sub(start_pos_in_sequence)
|
||||
};
|
||||
let first_half_match_length = end_pos_in_sequence
|
||||
.wrapping_sub(start_pos_in_sequence)
|
||||
.wrapping_sub(lit_length);
|
||||
if match_length > block_size_u32 && first_half_match_length >= min_match {
|
||||
let second_half_match_length = current_size.wrapping_sub(end_pos_in_sequence);
|
||||
let mut first_half_match_length = first_half_match_length;
|
||||
if second_half_match_length < min_match {
|
||||
let adjustment = min_match.wrapping_sub(second_half_match_length);
|
||||
end_pos_in_sequence = end_pos_in_sequence.wrapping_sub(adjustment);
|
||||
bytes_adjustment = adjustment;
|
||||
first_half_match_length = first_half_match_length.wrapping_sub(adjustment);
|
||||
}
|
||||
match_length = first_half_match_length;
|
||||
final_match_split = true;
|
||||
} else {
|
||||
bytes_adjustment = end_pos_in_sequence.wrapping_sub(current_sequence.litLength);
|
||||
end_pos_in_sequence = current_sequence.litLength;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* The block ends inside the literals; leave them as literals. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let ll0 = lit_length == 0;
|
||||
let off_base = unsafe {
|
||||
ZSTD_rust_finalizeOffBase(raw_offset, updated_repcodes.as_ptr(), u32::from(ll0))
|
||||
};
|
||||
update_rep(&mut updated_repcodes, off_base, ll0);
|
||||
|
||||
if validate_sequences != 0 {
|
||||
let sequence_size = lit_length.wrapping_add(match_length) as usize;
|
||||
seq_pos.posInSrc = seq_pos.posInSrc.wrapping_add(sequence_size);
|
||||
let validation = ZSTD_rust_validateSequence(
|
||||
off_base,
|
||||
match_length,
|
||||
min_match,
|
||||
seq_pos.posInSrc,
|
||||
window_log,
|
||||
dict_size as usize,
|
||||
use_sequence_producer,
|
||||
);
|
||||
if ERR_isError(validation) {
|
||||
return validation;
|
||||
}
|
||||
}
|
||||
|
||||
if idx - start_idx >= seq_store.maxNbSeq {
|
||||
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
||||
}
|
||||
let stored = unsafe {
|
||||
store_external_sequence(
|
||||
seq_store,
|
||||
src,
|
||||
source_offset,
|
||||
block_size,
|
||||
lit_length as usize,
|
||||
off_base,
|
||||
match_length as usize,
|
||||
)
|
||||
};
|
||||
if !stored {
|
||||
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
||||
}
|
||||
let sequence_size = lit_length.wrapping_add(match_length) as usize;
|
||||
source_offset = match source_offset.checked_add(sequence_size) {
|
||||
Some(offset) => offset,
|
||||
None => return ERROR(ZstdErrorCode::ExternalSequencesInvalid),
|
||||
};
|
||||
if !final_match_split {
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
seq_pos.idx = idx as u32;
|
||||
seq_pos.posInSequence = end_pos_in_sequence;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(updated_repcodes.as_ptr(), next_repcodes, ZSTD_REP_NUM);
|
||||
}
|
||||
|
||||
let consumed_size = match block_size.checked_sub(bytes_adjustment as usize) {
|
||||
Some(size) => size,
|
||||
None => return ERROR(ZstdErrorCode::ExternalSequencesInvalid),
|
||||
};
|
||||
if source_offset > consumed_size {
|
||||
return ERROR(ZstdErrorCode::ExternalSequencesInvalid);
|
||||
}
|
||||
if source_offset != consumed_size {
|
||||
let last_literal_size = consumed_size - source_offset;
|
||||
unsafe {
|
||||
ZSTD_rust_storeLastLiterals(seq_store, src.add(source_offset), last_literal_size);
|
||||
}
|
||||
seq_pos.posInSrc = seq_pos.posInSrc.wrapping_add(last_literal_size);
|
||||
}
|
||||
consumed_size
|
||||
}
|
||||
|
||||
/// Finds the next explicit block delimiter and returns the represented size.
|
||||
///
|
||||
/// The scan is half-open at `inSeqsSize`: a delimiter at the final element is
|
||||
@@ -2318,6 +2495,22 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_external_seq_store(sequences: &mut [SeqDef], literals: &mut [u8]) -> SeqStore_t {
|
||||
SeqStore_t {
|
||||
sequencesStart: sequences.as_mut_ptr(),
|
||||
sequences: sequences.as_mut_ptr(),
|
||||
litStart: literals.as_mut_ptr(),
|
||||
lit: literals.as_mut_ptr(),
|
||||
llCode: std::ptr::null_mut(),
|
||||
mlCode: std::ptr::null_mut(),
|
||||
ofCode: std::ptr::null_mut(),
|
||||
maxNbSeq: sequences.len(),
|
||||
maxNbLit: literals.len(),
|
||||
longLengthType: 0,
|
||||
longLengthPos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c_leaf_layouts_match_supported_abis() {
|
||||
assert_eq!(size_of::<SeqDef>(), 8);
|
||||
@@ -2605,6 +2798,180 @@ mod tests {
|
||||
assert_eq!(literals, [9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_sequences_no_delim_splits_a_match_at_the_block_boundary() {
|
||||
let input = [ZSTD_Sequence {
|
||||
offset: 50,
|
||||
litLength: 0,
|
||||
matchLength: 10,
|
||||
rep: 0,
|
||||
}];
|
||||
let source = [1u8, 2, 3, 4, 5, 6];
|
||||
let mut output = [SeqDef::default(); 1];
|
||||
let mut literals = [0u8; 1];
|
||||
let mut seq_store = empty_external_seq_store(&mut output, &mut literals);
|
||||
let mut position = ZSTD_SequencePosition::default();
|
||||
let previous_repcodes = [7u32, 20, 30];
|
||||
let mut next_repcodes = [0u32; ZSTD_REP_NUM];
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_transferSequencesNoDelim(
|
||||
&mut seq_store,
|
||||
&mut position,
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
source.as_ptr(),
|
||||
source.len(),
|
||||
previous_repcodes.as_ptr(),
|
||||
next_repcodes.as_mut_ptr(),
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, source.len());
|
||||
assert_eq!(position.idx, 0);
|
||||
assert_eq!(position.posInSequence, source.len() as u32);
|
||||
assert_eq!(position.posInSrc, 0);
|
||||
assert_eq!(output[0].offBase, 50 + ZSTD_REP_NUM as u32);
|
||||
assert_eq!(output[0].litLength, 0);
|
||||
assert_eq!(output[0].mlBase, source.len() as u16 - MINMATCH as u16);
|
||||
assert_eq!(next_repcodes, [50, 7, 20]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_sequences_no_delim_shortens_a_match_to_keep_the_tail_valid() {
|
||||
let input = [ZSTD_Sequence {
|
||||
offset: 50,
|
||||
litLength: 0,
|
||||
matchLength: 10,
|
||||
rep: 0,
|
||||
}];
|
||||
let source = [0u8; 8];
|
||||
let mut output = [SeqDef::default(); 1];
|
||||
let mut literals = [0u8; 1];
|
||||
let mut seq_store = empty_external_seq_store(&mut output, &mut literals);
|
||||
let mut position = ZSTD_SequencePosition::default();
|
||||
let previous_repcodes = [7u32, 20, 30];
|
||||
let mut next_repcodes = [0u32; ZSTD_REP_NUM];
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_transferSequencesNoDelim(
|
||||
&mut seq_store,
|
||||
&mut position,
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
source.as_ptr(),
|
||||
source.len(),
|
||||
previous_repcodes.as_ptr(),
|
||||
next_repcodes.as_mut_ptr(),
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, 7);
|
||||
assert_eq!(position.idx, 0);
|
||||
assert_eq!(position.posInSequence, 7);
|
||||
assert_eq!(output[0].offBase, 50 + ZSTD_REP_NUM as u32);
|
||||
assert_eq!(output[0].mlBase, 7 - MINMATCH as u16);
|
||||
assert_eq!(next_repcodes, [50, 7, 20]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_sequences_no_delim_leaves_a_literal_prefix_unsequenced() {
|
||||
let input = [ZSTD_Sequence {
|
||||
offset: 50,
|
||||
litLength: 4,
|
||||
matchLength: 6,
|
||||
rep: 0,
|
||||
}];
|
||||
let source = [1u8, 2, 3, 4, 5, 6];
|
||||
let mut output = [SeqDef::default(); 1];
|
||||
let mut literals = [0u8; 2];
|
||||
let mut seq_store = empty_external_seq_store(&mut output, &mut literals);
|
||||
let mut position = ZSTD_SequencePosition::default();
|
||||
let previous_repcodes = [7u32, 20, 30];
|
||||
let mut next_repcodes = [0u32; ZSTD_REP_NUM];
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_transferSequencesNoDelim(
|
||||
&mut seq_store,
|
||||
&mut position,
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
source.as_ptr(),
|
||||
2,
|
||||
previous_repcodes.as_ptr(),
|
||||
next_repcodes.as_mut_ptr(),
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, 2);
|
||||
assert_eq!(position.idx, 0);
|
||||
assert_eq!(position.posInSequence, 2);
|
||||
assert_eq!(position.posInSrc, 2);
|
||||
assert_eq!(seq_store.sequences, seq_store.sequencesStart);
|
||||
assert_eq!(literals, [1, 2]);
|
||||
assert_eq!(next_repcodes, previous_repcodes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_sequences_no_delim_treats_bytes_after_the_last_sequence_as_literals() {
|
||||
let input = [ZSTD_Sequence {
|
||||
offset: 50,
|
||||
litLength: 2,
|
||||
matchLength: 3,
|
||||
rep: 0,
|
||||
}];
|
||||
let source = [10u8, 11, 12, 13, 14, 15, 16];
|
||||
let mut output = [SeqDef::default(); 1];
|
||||
let mut literals = [0u8; 4];
|
||||
let mut seq_store = empty_external_seq_store(&mut output, &mut literals);
|
||||
let mut position = ZSTD_SequencePosition::default();
|
||||
let previous_repcodes = [7u32, 20, 30];
|
||||
let mut next_repcodes = [0u32; ZSTD_REP_NUM];
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_transferSequencesNoDelim(
|
||||
&mut seq_store,
|
||||
&mut position,
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
source.as_ptr(),
|
||||
source.len(),
|
||||
previous_repcodes.as_ptr(),
|
||||
next_repcodes.as_mut_ptr(),
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, source.len());
|
||||
assert_eq!(position.idx, 1);
|
||||
assert_eq!(position.posInSequence, 2);
|
||||
assert_eq!(position.posInSrc, 2);
|
||||
assert_eq!(output[0].offBase, 50 + ZSTD_REP_NUM as u32);
|
||||
assert_eq!(output[0].litLength, 2);
|
||||
assert_eq!(output[0].mlBase, 0);
|
||||
assert_eq!(literals, [10, 11, 15, 16]);
|
||||
assert_eq!(next_repcodes, [50, 7, 20]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_sequences_rejects_invalid_match_lengths_and_block_mismatches() {
|
||||
let invalid_match = [
|
||||
|
||||
Reference in New Issue
Block a user