diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 1c9e166e3..65c538206 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -241,6 +241,13 @@ 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_countSeqStoreLiteralsBytes(const SeqStore_t* seqStore); +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); +U32 ZSTD_rust_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], + U32 offBase, U32 ll0); typedef char ZSTD_rust_stats_seqdef_layout[(sizeof(SeqDef) == 8) ? 1 : -1]; typedef char ZSTD_rust_stats_seqstore_long_length_pos[ @@ -2817,31 +2824,13 @@ ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(SeqStore_t* seqStore, ZSTD_CC /* Returns literals bytes represented in a seqStore */ static size_t ZSTD_countSeqStoreLiteralsBytes(const SeqStore_t* const seqStore) { - size_t literalsBytes = 0; - size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); - size_t i; - for (i = 0; i < nbSeqs; ++i) { - SeqDef const seq = seqStore->sequencesStart[i]; - literalsBytes += seq.litLength; - if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_literalLength) { - literalsBytes += 0x10000; - } } - return literalsBytes; + return ZSTD_rust_countSeqStoreLiteralsBytes(seqStore); } /* Returns match bytes represented in a seqStore */ static size_t ZSTD_countSeqStoreMatchBytes(const SeqStore_t* const seqStore) { - size_t matchBytes = 0; - size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); - size_t i; - for (i = 0; i < nbSeqs; ++i) { - SeqDef seq = seqStore->sequencesStart[i]; - matchBytes += seq.mlBase + MINMATCH; - if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_matchLength) { - matchBytes += 0x10000; - } } - return matchBytes; + return ZSTD_rust_countSeqStoreMatchBytes(seqStore); } /* Derives the seqStore that is a chunk of the originalSeqStore from [startIdx, endIdx). @@ -2851,32 +2840,8 @@ static void ZSTD_deriveSeqStoreChunk(SeqStore_t* resultSeqStore, const SeqStore_t* originalSeqStore, size_t startIdx, size_t endIdx) { - *resultSeqStore = *originalSeqStore; - if (startIdx > 0) { - resultSeqStore->sequences = originalSeqStore->sequencesStart + startIdx; - resultSeqStore->litStart += ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); - } - - /* Move longLengthPos into the correct position if necessary */ - if (originalSeqStore->longLengthType != ZSTD_llt_none) { - if (originalSeqStore->longLengthPos < startIdx || originalSeqStore->longLengthPos > endIdx) { - resultSeqStore->longLengthType = ZSTD_llt_none; - } else { - resultSeqStore->longLengthPos -= (U32)startIdx; - } - } - resultSeqStore->sequencesStart = originalSeqStore->sequencesStart + startIdx; - resultSeqStore->sequences = originalSeqStore->sequencesStart + endIdx; - if (endIdx == (size_t)(originalSeqStore->sequences - originalSeqStore->sequencesStart)) { - /* This accounts for possible last literals if the derived chunk reaches the end of the block */ - assert(resultSeqStore->lit == originalSeqStore->lit); - } else { - size_t const literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); - resultSeqStore->lit = resultSeqStore->litStart + literalsBytes; - } - resultSeqStore->llCode += startIdx; - resultSeqStore->mlCode += startIdx; - resultSeqStore->ofCode += startIdx; + ZSTD_rust_deriveSeqStoreChunk(resultSeqStore, originalSeqStore, + startIdx, endIdx); } /** @@ -2886,20 +2851,8 @@ static void ZSTD_deriveSeqStoreChunk(SeqStore_t* resultSeqStore, static U32 ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offBase, const U32 ll0) { - U32 const adjustedRepCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0; /* [ 0 - 3 ] */ assert(OFFBASE_IS_REPCODE(offBase)); - if (adjustedRepCode == ZSTD_REP_NUM) { - assert(ll0); - /* litlength == 0 and offCode == 2 implies selection of first repcode - 1 - * This is only valid if it results in a valid offset value, aka > 0. - * Note : it may happen that `rep[0]==1` in exceptional circumstances. - * In which case this function will return 0, which is an invalid offset. - * It's not an issue though, since this value will be - * compared and discarded within ZSTD_seqStore_resolveOffCodes(). - */ - return rep[0] - 1; - } - return rep[adjustedRepCode]; + return ZSTD_rust_resolveRepcodeToRawOffset(rep, offBase, ll0); } /** diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index 6bbd87799..5d947d3a3 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -115,6 +115,25 @@ void ZSTDMT_rust_buffer_pool_release(ZSTDMT_RustBufferPool* pool, ZSTDMT_RustBuffer ZSTDMT_rust_buffer_pool_resize(ZSTDMT_RustBufferPool* pool, ZSTDMT_RustBuffer buffer); +typedef struct { + rawSeq* seq; + size_t pos; + size_t posInSequence; + size_t size; + size_t capacity; +} ZSTDMT_RustRawSeqStore; + +typedef char ZSTDMT_rust_raw_seq_layout[ + (sizeof(rawSeq) == 3 * sizeof(U32)) ? 1 : -1]; +typedef char ZSTDMT_rust_raw_seq_store_layout[ + (sizeof(ZSTDMT_RustRawSeqStore) == sizeof(RawSeqStore_t) + && offsetof(ZSTDMT_RustRawSeqStore, pos) == offsetof(RawSeqStore_t, pos) + && offsetof(ZSTDMT_RustRawSeqStore, capacity) == offsetof(RawSeqStore_t, capacity)) + ? 1 : -1]; + +ZSTDMT_RustRawSeqStore ZSTDMT_rust_bufferToSeq(ZSTDMT_RustBuffer buffer); +ZSTDMT_RustBuffer ZSTDMT_rust_seqToBuffer(ZSTDMT_RustRawSeqStore seq); + unsigned ZSTDMT_rust_computeTargetJobLog(unsigned windowLog, unsigned chainLog, int strategy, int enableLdm); int ZSTDMT_rust_overlapLog(int overlapLog, int strategy); @@ -234,18 +253,29 @@ static size_t ZSTDMT_sizeof_seqPool(ZSTDMT_seqPool* seqPool) static RawSeqStore_t bufferToSeq(Buffer buffer) { - RawSeqStore_t seq = kNullRawSeqStore; - seq.seq = (rawSeq*)buffer.start; - seq.capacity = buffer.capacity / sizeof(rawSeq); + ZSTDMT_RustRawSeqStore const rustSeq = + ZSTDMT_rust_bufferToSeq((ZSTDMT_RustBuffer){ buffer.start, buffer.capacity }); + RawSeqStore_t seq = { + (rawSeq*)rustSeq.seq, + rustSeq.pos, + rustSeq.posInSequence, + rustSeq.size, + rustSeq.capacity + }; return seq; } static Buffer seqToBuffer(RawSeqStore_t seq) { - Buffer buffer; - buffer.start = seq.seq; - buffer.capacity = seq.capacity * sizeof(rawSeq); - return buffer; + ZSTDMT_RustRawSeqStore const rustSeq = { + seq.seq, + seq.pos, + seq.posInSequence, + seq.size, + seq.capacity + }; + ZSTDMT_RustBuffer const rustBuffer = ZSTDMT_rust_seqToBuffer(rustSeq); + return (Buffer){ rustBuffer.start, rustBuffer.capacity }; } static RawSeqStore_t ZSTDMT_getSeq(ZSTDMT_seqPool* seqPool) diff --git a/rust/src/zstd_compress_stats.rs b/rust/src/zstd_compress_stats.rs index 10d9a5afd..3eb98a021 100644 --- a/rust/src/zstd_compress_stats.rs +++ b/rust/src/zstd_compress_stats.rs @@ -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]; diff --git a/rust/src/zstdmt_compress.rs b/rust/src/zstdmt_compress.rs index 63c3e3457..b8575913f 100644 --- a/rust/src/zstdmt_compress.rs +++ b/rust/src/zstdmt_compress.rs @@ -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::(), + pos: 0, + posInSequence: 0, + size: 0, + capacity: buffer.capacity / mem::size_of::(), + } +} + +#[inline] +fn seq_to_buffer(seq: ZstdMtRawSeqStore) -> ZstdMtBuffer { + ZstdMtBuffer { + start: seq.seq.cast::(), + capacity: seq.capacity.wrapping_mul(mem::size_of::()), + } +} + +/// 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::(); + 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::()) + ); + + let empty = ZSTDMT_rust_bufferToSeq(ZstdMtBuffer { + start: ptr::null_mut(), + capacity: mem::size_of::() - 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) };