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:
2026-07-18 20:16:17 +02:00
parent 57e3399930
commit 20bbb66676
2 changed files with 559 additions and 98 deletions
+56 -93
View File
@@ -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.