feat(compress): move single-block serialization into Rust
`ZSTD_compressSeqStore_singleBlock` was still a C-owned decision body after sequence encoding and block-format leaves had moved into Rust. That left the C function responsible for mixing entropy errors, partition repcode repair, RLE/raw fallback, sequence collection, compressed-state confirmation, and block-header emission through the full private compression context. Keep sequence-store construction, matchfinding, and split-block orchestration in C, but pass a narrow repr(C) projection to Rust. The Rust body preserves the original order of partition off-code resolution, entropy compression, the non-first-block RLE rule, collector handling, dRep restoration for raw/RLE blocks, compressed-state confirmation, header serialization, and repeat-mode cleanup. Injectable leaf seams and focused tests cover entropy errors, raw fallback, RLE gating, pointer swaps, and repeat-mode transitions. Test Plan: - `cargo test --manifest-path rust/Cargo.toml --lib -- --test-threads=1` -- 447 passed - `cargo clippy --manifest-path rust/Cargo.toml -- -D warnings` -- passed - `cargo clippy --manifest-path rust/Cargo.toml --benches -- -D warnings` -- passed - `cargo clippy --manifest-path rust/Cargo.toml --tests -- -D warnings` -- blocked by the pre-existing manual_repeat_n lint in one_shot_promotes_nonfirst_rle_blocks - `cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check` -- passed - `make -B -C lib -j2 lib` -- passed - `make -B -C tests -j2 test-cli-tests` -- passed - `ZSTREAM_TESTTIME=-T2s make -B -C tests -j2 test-zstream` -- passed - `FUZZERTEST=-T5s make -B -C tests -j2 test-fuzzer` -- passed - `make -B -C tests/fuzz -j2 all` and `sequence_compression_api` -- passed - `make -C tests -j2 test-zstd` -- passed, including large-data cases
This commit is contained in:
@@ -120,6 +120,47 @@ typedef char ZSTD_rust_target_cblock_state_layout[
|
||||
&& sizeof(ZSTD_rust_targetCBlockSizeState)
|
||||
== (sizeof(void*) == 8 ? 72 : 44))
|
||||
? 1 : -1];
|
||||
/* The single-block sequence-store body only needs this narrow projection of
|
||||
* ZSTD_CCtx. Matchfinding, sequence-store construction, and split-block
|
||||
* control remain in C. */
|
||||
typedef struct {
|
||||
const SeqStore_t* seqStore;
|
||||
U32* dRep;
|
||||
U32* cRep;
|
||||
ZSTD_compressedBlockState_t** prevCBlock;
|
||||
ZSTD_compressedBlockState_t** nextCBlock;
|
||||
void* tmpWorkspace;
|
||||
size_t tmpWkspSize;
|
||||
SeqCollector* seqCollector;
|
||||
int strategy;
|
||||
int disableLiteralCompression;
|
||||
int bmi2;
|
||||
int isFirstBlock;
|
||||
} ZSTD_rust_seqStoreSingleBlockState;
|
||||
size_t ZSTD_rust_compressSeqStoreSingleBlock(
|
||||
const ZSTD_rust_seqStoreSingleBlockState* state,
|
||||
void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize,
|
||||
U32 lastBlock, U32 isPartition);
|
||||
typedef char ZSTD_rust_seqstore_single_block_state_layout[
|
||||
(offsetof(ZSTD_rust_seqStoreSingleBlockState, seqStore) == 0
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, dRep) == sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, cRep) == 2 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, prevCBlock) == 3 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, nextCBlock) == 4 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, tmpWorkspace) == 5 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, tmpWkspSize) == 6 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, seqCollector) == 7 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, strategy) == 8 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, disableLiteralCompression)
|
||||
== 8 * sizeof(void*) + sizeof(int)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, bmi2)
|
||||
== 8 * sizeof(void*) + 2 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_seqStoreSingleBlockState, isFirstBlock)
|
||||
== 8 * sizeof(void*) + 3 * sizeof(int)
|
||||
&& sizeof(ZSTD_rust_seqStoreSingleBlockState)
|
||||
== 8 * sizeof(void*) + 4 * sizeof(int))
|
||||
? 1 : -1];
|
||||
size_t ZSTD_rust_nextInputSizeHint(int inBufferMode,
|
||||
size_t blockSizeMax,
|
||||
size_t stableInNotConsumed,
|
||||
@@ -423,10 +464,6 @@ size_t ZSTD_rust_deriveBlockSplits(
|
||||
int strategy, int disableLiteralCompression,
|
||||
ZSTD_entropyCTablesMetadata_t* entropyMetadata,
|
||||
void* workspace, size_t workspaceSize);
|
||||
void ZSTD_rust_seqStore_resolveOffCodes(U32 dRep[ZSTD_REP_NUM],
|
||||
U32 cRep[ZSTD_REP_NUM],
|
||||
const SeqStore_t* seqStore,
|
||||
U32 nbSeq);
|
||||
U32 ZSTD_rust_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM],
|
||||
U32 offBase, U32 ll0);
|
||||
U32 ZSTD_rust_finalizeOffBase(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0);
|
||||
@@ -2671,14 +2708,6 @@ ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs)
|
||||
&bs->prevCBlock, &bs->nextCBlock);
|
||||
}
|
||||
|
||||
/* Writes the block header */
|
||||
static void
|
||||
writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock)
|
||||
{
|
||||
ZSTD_rust_writeBlockHeader(op, cSize, blockSize, lastBlock);
|
||||
DEBUGLOG(5, "writeBlockHeader: cSize: %zu blockSize: %zu lastBlock: %u", cSize, blockSize, lastBlock);
|
||||
}
|
||||
|
||||
/** ZSTD_buildBlockEntropyStats() :
|
||||
* Builds entropy for the block.
|
||||
* Requires workspace size ENTROPY_WORKSPACE_SIZE
|
||||
@@ -2773,27 +2802,6 @@ static void ZSTD_deriveSeqStoreChunk(SeqStore_t* resultSeqStore,
|
||||
startIdx, endIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* ZSTD_seqStore_resolveOffCodes() reconciles any possible divergences in offset history that may arise
|
||||
* due to emission of RLE/raw blocks that disturb the offset history,
|
||||
* and replaces any repcodes within the seqStore that may be invalid.
|
||||
*
|
||||
* dRepcodes are updated as would be on the decompression side.
|
||||
* cRepcodes are updated exactly in accordance with the seqStore.
|
||||
*
|
||||
* Note : this function assumes seq->offBase respects the following numbering scheme :
|
||||
* 0 : invalid
|
||||
* 1-3 : repcode 1-3
|
||||
* 4+ : real_offset+3
|
||||
*/
|
||||
static void
|
||||
ZSTD_seqStore_resolveOffCodes(Repcodes_t* const dRepcodes, Repcodes_t* const cRepcodes,
|
||||
const SeqStore_t* const seqStore, U32 const nbSeq)
|
||||
{
|
||||
ZSTD_rust_seqStore_resolveOffCodes(dRepcodes->rep, cRepcodes->rep,
|
||||
seqStore, nbSeq);
|
||||
}
|
||||
|
||||
/* ZSTD_compressSeqStore_singleBlock():
|
||||
* Compresses a seqStore into a block with a block header, into the buffer dst.
|
||||
*
|
||||
@@ -2807,66 +2815,21 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc,
|
||||
const void* src, size_t srcSize,
|
||||
U32 lastBlock, U32 isPartition)
|
||||
{
|
||||
const U32 rleMaxLength = 25;
|
||||
BYTE* op = (BYTE*)dst;
|
||||
const BYTE* ip = (const BYTE*)src;
|
||||
size_t cSize;
|
||||
size_t cSeqsSize;
|
||||
|
||||
/* In case of an RLE or raw block, the simulated decompression repcode history must be reset */
|
||||
Repcodes_t const dRepOriginal = *dRep;
|
||||
DEBUGLOG(5, "ZSTD_compressSeqStore_singleBlock");
|
||||
if (isPartition)
|
||||
ZSTD_seqStore_resolveOffCodes(dRep, cRep, seqStore, (U32)(seqStore->sequences - seqStore->sequencesStart));
|
||||
|
||||
RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize, dstSize_tooSmall, "Block header doesn't fit");
|
||||
cSeqsSize = ZSTD_entropyCompressSeqStore(seqStore,
|
||||
&zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy,
|
||||
&zc->appliedParams,
|
||||
op + ZSTD_blockHeaderSize, dstCapacity - ZSTD_blockHeaderSize,
|
||||
srcSize,
|
||||
zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */,
|
||||
zc->bmi2);
|
||||
FORWARD_IF_ERROR(cSeqsSize, "ZSTD_entropyCompressSeqStore failed!");
|
||||
|
||||
if (!zc->isFirstBlock &&
|
||||
cSeqsSize < rleMaxLength &&
|
||||
ZSTD_isRLE((BYTE const*)src, srcSize)) {
|
||||
/* We don't want to emit our first block as a RLE even if it qualifies because
|
||||
* doing so will cause the decoder (cli only) to throw a "should consume all input error."
|
||||
* This is only an issue for zstd <= v1.4.3
|
||||
*/
|
||||
cSeqsSize = 1;
|
||||
}
|
||||
|
||||
/* Sequence collection not supported when block splitting */
|
||||
if (zc->seqCollector.collectSequences) {
|
||||
FORWARD_IF_ERROR(ZSTD_copyBlockSequences(&zc->seqCollector, seqStore, dRepOriginal.rep), "copyBlockSequences failed");
|
||||
ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cSeqsSize == 0) {
|
||||
cSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock);
|
||||
FORWARD_IF_ERROR(cSize, "Nocompress block failed");
|
||||
DEBUGLOG(5, "Writing out nocompress block, size: %zu", cSize);
|
||||
*dRep = dRepOriginal; /* reset simulated decompression repcode history */
|
||||
} else if (cSeqsSize == 1) {
|
||||
cSize = ZSTD_rust_rleCompressBlock(op, dstCapacity, *ip, srcSize, lastBlock);
|
||||
FORWARD_IF_ERROR(cSize, "RLE compress block failed");
|
||||
DEBUGLOG(5, "Writing out RLE block, size: %zu", cSize);
|
||||
*dRep = dRepOriginal; /* reset simulated decompression repcode history */
|
||||
} else {
|
||||
ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState);
|
||||
writeBlockHeader(op, cSeqsSize, srcSize, lastBlock);
|
||||
cSize = ZSTD_blockHeaderSize + cSeqsSize;
|
||||
DEBUGLOG(5, "Writing out compressed block, size: %zu", cSize);
|
||||
}
|
||||
|
||||
if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid)
|
||||
zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check;
|
||||
|
||||
return cSize;
|
||||
ZSTD_rust_seqStoreSingleBlockState state;
|
||||
state.seqStore = seqStore;
|
||||
state.dRep = dRep->rep;
|
||||
state.cRep = cRep->rep;
|
||||
state.prevCBlock = &zc->blockState.prevCBlock;
|
||||
state.nextCBlock = &zc->blockState.nextCBlock;
|
||||
state.tmpWorkspace = zc->tmpWorkspace;
|
||||
state.tmpWkspSize = zc->tmpWkspSize;
|
||||
state.seqCollector = &zc->seqCollector;
|
||||
state.strategy = (int)zc->appliedParams.cParams.strategy;
|
||||
state.disableLiteralCompression = ZSTD_literalsCompressionIsDisabled(&zc->appliedParams);
|
||||
state.bmi2 = zc->bmi2;
|
||||
state.isFirstBlock = zc->isFirstBlock;
|
||||
return ZSTD_rust_compressSeqStoreSingleBlock(
|
||||
&state, dst, dstCapacity, src, srcSize, lastBlock, isPartition);
|
||||
}
|
||||
|
||||
/* Base recursive function.
|
||||
|
||||
+503
-5
@@ -27,11 +27,11 @@ use crate::zstd_compress_params::{
|
||||
};
|
||||
use crate::zstd_compress_sequences::SeqDef;
|
||||
use crate::zstd_compress_stats::{
|
||||
SeqStore_t, ZSTD_Sequence, ZSTD_SequencePosition, ZSTD_compressedBlockState_t,
|
||||
ZSTD_rust_confirmRepcodesAndEntropyTables, ZSTD_rust_determineBlockSize,
|
||||
ZSTD_rust_entropyCompressSeqStore, ZSTD_rust_isRLE, ZSTD_rust_maybeRLE,
|
||||
ZSTD_rust_resetSeqStore, ZSTD_rust_transferSequencesNoDelim,
|
||||
ZSTD_rust_transferSequencesWBlockDelim,
|
||||
SeqCollector, SeqStore_t, ZSTD_Sequence, ZSTD_SequencePosition, ZSTD_compressedBlockState_t,
|
||||
ZSTD_entropyCTables_t, ZSTD_rust_confirmRepcodesAndEntropyTables, ZSTD_rust_copyBlockSequences,
|
||||
ZSTD_rust_determineBlockSize, ZSTD_rust_entropyCompressSeqStore, ZSTD_rust_isRLE,
|
||||
ZSTD_rust_maybeRLE, ZSTD_rust_resetSeqStore, ZSTD_rust_seqStore_resolveOffCodes,
|
||||
ZSTD_rust_transferSequencesNoDelim, ZSTD_rust_transferSequencesWBlockDelim,
|
||||
};
|
||||
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
|
||||
use std::ffi::c_void;
|
||||
@@ -164,6 +164,64 @@ const _: () = {
|
||||
);
|
||||
};
|
||||
|
||||
/// Explicit projection of the state used by `ZSTD_compressSeqStore_singleBlock`.
|
||||
///
|
||||
/// Sequence-store construction and the surrounding split-block bookkeeping
|
||||
/// remain in C. Only the sequence store, simulated repcode histories,
|
||||
/// compressed-block state slots, sequence collector, and scalar compression
|
||||
/// settings cross the ABI.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_seqStoreSingleBlockState {
|
||||
seq_store: *const SeqStore_t,
|
||||
d_rep: *mut u32,
|
||||
c_rep: *mut u32,
|
||||
prev_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
||||
next_c_block: *mut *mut ZSTD_compressedBlockState_t,
|
||||
tmp_workspace: *mut c_void,
|
||||
tmp_wksp_size: usize,
|
||||
seq_collector: *mut SeqCollector,
|
||||
strategy: c_int,
|
||||
disable_literal_compression: c_int,
|
||||
bmi2: c_int,
|
||||
is_first_block: c_int,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, seq_store) == 0);
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, d_rep) == size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, c_rep) == 2 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, prev_c_block) == 3 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, next_c_block) == 4 * size_of::<usize>());
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_seqStoreSingleBlockState, tmp_workspace) == 5 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_seqStoreSingleBlockState, tmp_wksp_size) == 6 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_seqStoreSingleBlockState, seq_collector) == 7 * size_of::<usize>()
|
||||
);
|
||||
assert!(offset_of!(ZSTD_rust_seqStoreSingleBlockState, strategy) == usize::BITS as usize);
|
||||
assert!(
|
||||
offset_of!(
|
||||
ZSTD_rust_seqStoreSingleBlockState,
|
||||
disable_literal_compression
|
||||
) == usize::BITS as usize + size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_seqStoreSingleBlockState, bmi2)
|
||||
== usize::BITS as usize + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_seqStoreSingleBlockState, is_first_block)
|
||||
== usize::BITS as usize + 3 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
size_of::<ZSTD_rust_seqStoreSingleBlockState>()
|
||||
== usize::BITS as usize + 4 * size_of::<c_int>()
|
||||
);
|
||||
};
|
||||
|
||||
/// Explicit projection of the state used by the target-sized block body.
|
||||
///
|
||||
/// Sequence-store construction and the matchfinder remain in C. This state
|
||||
@@ -233,6 +291,60 @@ enum SequenceBlockAction {
|
||||
Compressed,
|
||||
}
|
||||
|
||||
type SingleBlockEntropyFn = unsafe extern "C" fn(
|
||||
*const SeqStore_t,
|
||||
*const ZSTD_entropyCTables_t,
|
||||
*mut ZSTD_entropyCTables_t,
|
||||
c_int,
|
||||
c_int,
|
||||
*mut c_void,
|
||||
usize,
|
||||
usize,
|
||||
*mut c_void,
|
||||
usize,
|
||||
c_int,
|
||||
) -> usize;
|
||||
type SingleBlockResolveOffCodesFn =
|
||||
unsafe extern "C" fn(*mut u32, *mut u32, *const SeqStore_t, c_uint);
|
||||
type SingleBlockCopySequencesFn =
|
||||
unsafe extern "C" fn(*mut SeqCollector, *const SeqStore_t, *const u32) -> usize;
|
||||
type SingleBlockConfirmFn = unsafe extern "C" fn(
|
||||
*mut *mut ZSTD_compressedBlockState_t,
|
||||
*mut *mut ZSTD_compressedBlockState_t,
|
||||
);
|
||||
type SingleBlockRawFn =
|
||||
unsafe extern "C" fn(*mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
||||
type SingleBlockRleFn = unsafe extern "C" fn(*mut c_void, usize, u8, usize, c_uint) -> usize;
|
||||
type SingleBlockWriteHeaderFn = unsafe extern "C" fn(*mut c_void, usize, usize, c_uint);
|
||||
type SingleBlockIsRleFn = unsafe extern "C" fn(*const u8, usize) -> c_int;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SingleBlockSeams {
|
||||
entropy_compress: SingleBlockEntropyFn,
|
||||
resolve_off_codes: SingleBlockResolveOffCodesFn,
|
||||
copy_sequences: SingleBlockCopySequencesFn,
|
||||
confirm: SingleBlockConfirmFn,
|
||||
raw: SingleBlockRawFn,
|
||||
rle: SingleBlockRleFn,
|
||||
write_header: SingleBlockWriteHeaderFn,
|
||||
is_rle: SingleBlockIsRleFn,
|
||||
}
|
||||
|
||||
impl SingleBlockSeams {
|
||||
fn production() -> Self {
|
||||
Self {
|
||||
entropy_compress: ZSTD_rust_entropyCompressSeqStore,
|
||||
resolve_off_codes: ZSTD_rust_seqStore_resolveOffCodes,
|
||||
copy_sequences: ZSTD_rust_copyBlockSequences,
|
||||
confirm: ZSTD_rust_confirmRepcodesAndEntropyTables,
|
||||
raw: ZSTD_rust_noCompressBlock,
|
||||
rle: ZSTD_rust_rleCompressBlock,
|
||||
write_header: ZSTD_rust_writeBlockHeader,
|
||||
is_rle: ZSTD_rust_isRLE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sequence_block_action(
|
||||
is_first_block: c_int,
|
||||
@@ -424,6 +536,168 @@ pub unsafe extern "C" fn ZSTD_rust_compressBlockTargetCBlockSize(
|
||||
}
|
||||
}
|
||||
|
||||
/// Rust implementation of `ZSTD_compressSeqStore_singleBlock`.
|
||||
///
|
||||
/// The C caller still owns sequence-store construction, split-block control,
|
||||
/// and the private compression context. This body owns the exact block
|
||||
/// decision and the associated state transitions after the sequence store is
|
||||
/// ready.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn compress_seq_store_single_block_body_with(
|
||||
state: &ZSTD_rust_seqStoreSingleBlockState,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
last_block: c_uint,
|
||||
is_partition: c_uint,
|
||||
seams: SingleBlockSeams,
|
||||
) -> usize {
|
||||
const RLE_MAX_LENGTH: usize = 25;
|
||||
|
||||
if state.seq_store.is_null()
|
||||
|| state.d_rep.is_null()
|
||||
|| state.c_rep.is_null()
|
||||
|| state.prev_c_block.is_null()
|
||||
|| state.next_c_block.is_null()
|
||||
|| state.seq_collector.is_null()
|
||||
{
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
let prev_c_block = unsafe { *state.prev_c_block };
|
||||
let next_c_block = unsafe { *state.next_c_block };
|
||||
if prev_c_block.is_null() || next_c_block.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
/* Preserve the decompression-side history for raw/RLE output and for the
|
||||
* sequence collector, exactly as the original C body does. */
|
||||
let mut d_rep_original = [0u32; ZSTD_REP_NUM];
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(state.d_rep, d_rep_original.as_mut_ptr(), ZSTD_REP_NUM);
|
||||
}
|
||||
|
||||
if is_partition != 0 {
|
||||
let seq_store = unsafe { &*state.seq_store };
|
||||
let nb_seq = unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as c_uint;
|
||||
unsafe {
|
||||
(seams.resolve_off_codes)(state.d_rep, state.c_rep, state.seq_store, nb_seq);
|
||||
}
|
||||
}
|
||||
|
||||
if dst_capacity < ZSTD_BLOCK_HEADER_SIZE {
|
||||
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
||||
}
|
||||
|
||||
let op = dst.cast::<u8>();
|
||||
let ip = src.cast::<u8>();
|
||||
let mut compressed_sequences_size = unsafe {
|
||||
(seams.entropy_compress)(
|
||||
state.seq_store,
|
||||
ptr::addr_of!((*prev_c_block).entropy),
|
||||
ptr::addr_of_mut!((*next_c_block).entropy),
|
||||
state.strategy,
|
||||
state.disable_literal_compression,
|
||||
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
||||
dst_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
||||
src_size,
|
||||
state.tmp_workspace,
|
||||
state.tmp_wksp_size,
|
||||
state.bmi2,
|
||||
)
|
||||
};
|
||||
if ERR_isError(compressed_sequences_size) {
|
||||
return compressed_sequences_size;
|
||||
}
|
||||
|
||||
if state.is_first_block == 0
|
||||
&& compressed_sequences_size < RLE_MAX_LENGTH
|
||||
&& unsafe { (seams.is_rle)(ip, src_size) } != 0
|
||||
{
|
||||
/* Do not emit the first frame block as RLE; this preserves the legacy
|
||||
* decoder compatibility rule in the original implementation. */
|
||||
compressed_sequences_size = 1;
|
||||
}
|
||||
|
||||
/* Sequence collection is deliberately before block serialization and
|
||||
* returns without the outer repeat-mode cleanup, matching C. */
|
||||
if unsafe { (*state.seq_collector).collectSequences } != 0 {
|
||||
let result = unsafe {
|
||||
(seams.copy_sequences)(
|
||||
state.seq_collector,
|
||||
state.seq_store,
|
||||
d_rep_original.as_ptr(),
|
||||
)
|
||||
};
|
||||
if ERR_isError(result) {
|
||||
return result;
|
||||
}
|
||||
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
||||
return 0;
|
||||
}
|
||||
|
||||
let c_size = if compressed_sequences_size == 0 {
|
||||
let c_size = unsafe { (seams.raw)(dst, dst_capacity, src, src_size, last_block) };
|
||||
if ERR_isError(c_size) {
|
||||
return c_size;
|
||||
}
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(d_rep_original.as_ptr(), state.d_rep, ZSTD_REP_NUM);
|
||||
}
|
||||
c_size
|
||||
} else if compressed_sequences_size == 1 {
|
||||
let c_size = unsafe { (seams.rle)(dst, dst_capacity, *ip, src_size, last_block) };
|
||||
if ERR_isError(c_size) {
|
||||
return c_size;
|
||||
}
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(d_rep_original.as_ptr(), state.d_rep, ZSTD_REP_NUM);
|
||||
}
|
||||
c_size
|
||||
} else {
|
||||
unsafe { (seams.confirm)(state.prev_c_block, state.next_c_block) };
|
||||
unsafe {
|
||||
(seams.write_header)(dst, compressed_sequences_size, src_size, last_block);
|
||||
}
|
||||
ZSTD_BLOCK_HEADER_SIZE.wrapping_add(compressed_sequences_size)
|
||||
};
|
||||
|
||||
let prev_c_block = unsafe { *state.prev_c_block };
|
||||
if unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode } == 2 {
|
||||
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode = 1 };
|
||||
}
|
||||
|
||||
c_size
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_compressSeqStoreSingleBlock(
|
||||
state: *const ZSTD_rust_seqStoreSingleBlockState,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
last_block: c_uint,
|
||||
is_partition: c_uint,
|
||||
) -> usize {
|
||||
if state.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
unsafe {
|
||||
compress_seq_store_single_block_body_with(
|
||||
&*state,
|
||||
dst,
|
||||
dst_capacity,
|
||||
src,
|
||||
src_size,
|
||||
last_block,
|
||||
is_partition,
|
||||
SingleBlockSeams::production(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the strategy used by the simple compression entry points.
|
||||
///
|
||||
/// This is the Rust equivalent of the strategy portion of
|
||||
@@ -3058,6 +3332,230 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe extern "C" fn single_block_test_entropy_compress(
|
||||
_seq_store: *const SeqStore_t,
|
||||
_prev_entropy: *const ZSTD_entropyCTables_t,
|
||||
_next_entropy: *mut ZSTD_entropyCTables_t,
|
||||
strategy: c_int,
|
||||
_disable_literal_compression: c_int,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
_src_size: usize,
|
||||
_workspace: *mut c_void,
|
||||
_workspace_size: usize,
|
||||
_bmi2: c_int,
|
||||
) -> usize {
|
||||
match strategy {
|
||||
0 => 0,
|
||||
1 => {
|
||||
assert!(dst_capacity >= 2);
|
||||
unsafe {
|
||||
*dst.cast::<u8>() = 0x11;
|
||||
*dst.cast::<u8>().add(1) = 0x22;
|
||||
}
|
||||
2
|
||||
}
|
||||
_ => ERROR(ZstdErrorCode::Generic),
|
||||
}
|
||||
}
|
||||
|
||||
fn single_block_test_state(
|
||||
seq_store: &SeqStore_t,
|
||||
d_rep: &mut [u32; ZSTD_REP_NUM],
|
||||
c_rep: &mut [u32; ZSTD_REP_NUM],
|
||||
prev_block: &mut ZSTD_compressedBlockState_t,
|
||||
next_block: &mut ZSTD_compressedBlockState_t,
|
||||
prev_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
||||
next_c_block: &mut *mut ZSTD_compressedBlockState_t,
|
||||
seq_collector: &mut SeqCollector,
|
||||
strategy: c_int,
|
||||
is_first_block: c_int,
|
||||
) -> ZSTD_rust_seqStoreSingleBlockState {
|
||||
*prev_c_block = prev_block;
|
||||
*next_c_block = next_block;
|
||||
ZSTD_rust_seqStoreSingleBlockState {
|
||||
seq_store,
|
||||
d_rep: d_rep.as_mut_ptr(),
|
||||
c_rep: c_rep.as_mut_ptr(),
|
||||
prev_c_block,
|
||||
next_c_block,
|
||||
tmp_workspace: ptr::null_mut(),
|
||||
tmp_wksp_size: 0,
|
||||
seq_collector,
|
||||
strategy,
|
||||
disable_literal_compression: 0,
|
||||
bmi2: 0,
|
||||
is_first_block,
|
||||
}
|
||||
}
|
||||
|
||||
fn single_block_test_seams() -> SingleBlockSeams {
|
||||
SingleBlockSeams {
|
||||
entropy_compress: single_block_test_entropy_compress,
|
||||
..SingleBlockSeams::production()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_block_body_propagates_entropy_errors_without_serializing() {
|
||||
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
||||
let mut d_rep = [1, 4, 8];
|
||||
let mut c_rep = d_rep;
|
||||
let mut prev_block = zeroed_state();
|
||||
let mut next_block = zeroed_state();
|
||||
let mut prev_c_block = ptr::null_mut();
|
||||
let mut next_c_block = ptr::null_mut();
|
||||
let mut seq_collector = SeqCollector {
|
||||
collectSequences: 0,
|
||||
seqStart: ptr::null_mut(),
|
||||
seqIndex: 0,
|
||||
maxSequences: 0,
|
||||
};
|
||||
let state = single_block_test_state(
|
||||
&seq_store,
|
||||
&mut d_rep,
|
||||
&mut c_rep,
|
||||
&mut prev_block,
|
||||
&mut next_block,
|
||||
&mut prev_c_block,
|
||||
&mut next_c_block,
|
||||
&mut seq_collector,
|
||||
-1,
|
||||
1,
|
||||
);
|
||||
let source = *b"entropy error";
|
||||
let mut output = [0xa5u8; 32];
|
||||
|
||||
let result = unsafe {
|
||||
compress_seq_store_single_block_body_with(
|
||||
&state,
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
1,
|
||||
0,
|
||||
single_block_test_seams(),
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
|
||||
assert_eq!(output, [0xa5u8; 32]);
|
||||
assert_eq!(d_rep, [1, 4, 8]);
|
||||
assert_eq!(c_rep, [1, 4, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_block_body_serializes_raw_entropy_fallback() {
|
||||
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
||||
let mut d_rep = [1, 4, 8];
|
||||
let mut c_rep = d_rep;
|
||||
let mut prev_block = zeroed_state();
|
||||
let mut next_block = zeroed_state();
|
||||
let mut prev_c_block = ptr::null_mut();
|
||||
let mut next_c_block = ptr::null_mut();
|
||||
let mut seq_collector = SeqCollector {
|
||||
collectSequences: 0,
|
||||
seqStart: ptr::null_mut(),
|
||||
seqIndex: 0,
|
||||
maxSequences: 0,
|
||||
};
|
||||
let state = single_block_test_state(
|
||||
&seq_store,
|
||||
&mut d_rep,
|
||||
&mut c_rep,
|
||||
&mut prev_block,
|
||||
&mut next_block,
|
||||
&mut prev_c_block,
|
||||
&mut next_c_block,
|
||||
&mut seq_collector,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
let source = *b"raw fallback";
|
||||
let mut output = [0xa5u8; 32];
|
||||
|
||||
let result = unsafe {
|
||||
compress_seq_store_single_block_body_with(
|
||||
&state,
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
1,
|
||||
0,
|
||||
single_block_test_seams(),
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, source.len() + ZSTD_BLOCK_HEADER_SIZE);
|
||||
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &source);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_block_body_gates_rle_on_first_block_and_confirms_compressed_state() {
|
||||
for is_first_block in [0, 1] {
|
||||
let (seq_store, _sequences, _literals) = target_block_test_seq_store();
|
||||
let mut d_rep = [1, 4, 8];
|
||||
let mut c_rep = d_rep;
|
||||
let mut prev_block = zeroed_state();
|
||||
let mut next_block = zeroed_state();
|
||||
next_block.entropy.fse.offcode_repeatMode = 2;
|
||||
let mut prev_c_block = ptr::null_mut();
|
||||
let mut next_c_block = ptr::null_mut();
|
||||
let mut seq_collector = SeqCollector {
|
||||
collectSequences: 0,
|
||||
seqStart: ptr::null_mut(),
|
||||
seqIndex: 0,
|
||||
maxSequences: 0,
|
||||
};
|
||||
let state = single_block_test_state(
|
||||
&seq_store,
|
||||
&mut d_rep,
|
||||
&mut c_rep,
|
||||
&mut prev_block,
|
||||
&mut next_block,
|
||||
&mut prev_c_block,
|
||||
&mut next_c_block,
|
||||
&mut seq_collector,
|
||||
1,
|
||||
is_first_block,
|
||||
);
|
||||
let source = [0x5au8; 16];
|
||||
let mut output = [0xa5u8; 32];
|
||||
|
||||
let result = unsafe {
|
||||
compress_seq_store_single_block_body_with(
|
||||
&state,
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
1,
|
||||
0,
|
||||
single_block_test_seams(),
|
||||
)
|
||||
};
|
||||
|
||||
if is_first_block == 0 {
|
||||
assert_eq!(result, 4);
|
||||
assert_eq!(output[3], source[0]);
|
||||
assert!(std::ptr::eq(prev_c_block, &prev_block));
|
||||
} else {
|
||||
assert_eq!(result, ZSTD_BLOCK_HEADER_SIZE + 2);
|
||||
assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &[0x11, 0x22]);
|
||||
assert!(std::ptr::eq(prev_c_block, &next_block));
|
||||
assert!(std::ptr::eq(next_c_block, &prev_block));
|
||||
}
|
||||
let expected_repeat_mode = if is_first_block == 0 { 0 } else { 1 };
|
||||
assert_eq!(
|
||||
unsafe { (*prev_c_block).entropy.fse.offcode_repeatMode },
|
||||
expected_repeat_mode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_sequence_block_preserves_header_and_capacity_contract() {
|
||||
let mut output = [0xa5; 4];
|
||||
|
||||
Reference in New Issue
Block a user