feat(compress): move split-block emission into Rust
Move the post-split partition loop out of zstd_compress.c while keeping the private CCtx, matchfinder sequence-store construction, and split discovery in C. Rust now receives a layout-asserted projection containing sequence-store views, block-state slots, workspace and scalar policy, then mirrors the C loop's dRep/cRep histories, final-literal accounting, repeated single-block serialization, and final dRep publication. Remove the obsolete C sequence-store/count/chunk wrappers and the debug-only C size-estimation path that was coupled to the old loop. Add a focused Rust fixture covering partition payloads and final literals, and document the new ownership boundary for the split search and emission paths. Test Plan: - cargo test --manifest-path rust/Cargo.toml --lib -- --test-threads=1 (448 passed) - cargo clippy --manifest-path rust/Cargo.toml --lib -- -D warnings (passed) - cargo clippy --manifest-path rust/Cargo.toml -- -D warnings (passed) - cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check (passed) - cargo clippy --tests/--benches remain blocked only by the pre-existing manual_repeat_n lint in one_shot_promotes_nonfirst_rle_blocks - make -B -C lib -j2 lib (passed) - make -B -C tests -j2 test-zstd (passed) - make -B -C tests -j2 test-cli-tests (41 passed) - ZSTREAM_TESTTIME=-T2s make -B -C tests -j2 test-zstream (passed) - FUZZERTEST=-T5s make -B -C tests -j2 test-fuzzer (252 passed) - make -B -C tests/fuzz -j2 all and sequence_compression_api (passed)
This commit is contained in:
+49
-219
@@ -120,46 +120,50 @@ 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. */
|
||||
/* The post-split partition loop only needs this projection of ZSTD_CCtx.
|
||||
* Split discovery remains in the surrounding C function; the Rust body owns
|
||||
* partition accounting, repcode simulation, and per-partition emission. */
|
||||
typedef struct {
|
||||
const SeqStore_t* seqStore;
|
||||
U32* dRep;
|
||||
U32* cRep;
|
||||
const U32* partitions;
|
||||
SeqStore_t* nextSeqStore;
|
||||
SeqStore_t* currSeqStore;
|
||||
ZSTD_compressedBlockState_t** prevCBlock;
|
||||
ZSTD_compressedBlockState_t** nextCBlock;
|
||||
void* tmpWorkspace;
|
||||
size_t tmpWkspSize;
|
||||
SeqCollector* seqCollector;
|
||||
size_t blockSizeMax;
|
||||
int strategy;
|
||||
int disableLiteralCompression;
|
||||
int bmi2;
|
||||
int isFirstBlock;
|
||||
} ZSTD_rust_seqStoreSingleBlockState;
|
||||
size_t ZSTD_rust_compressSeqStoreSingleBlock(
|
||||
const ZSTD_rust_seqStoreSingleBlockState* state,
|
||||
} ZSTD_rust_splitBlockState;
|
||||
size_t ZSTD_rust_compressBlockSplit(
|
||||
const ZSTD_rust_splitBlockState* 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))
|
||||
const void* src, size_t blockSize,
|
||||
U32 lastBlock, size_t numSplits);
|
||||
typedef char ZSTD_rust_split_block_state_layout[
|
||||
(offsetof(ZSTD_rust_splitBlockState, seqStore) == 0
|
||||
&& offsetof(ZSTD_rust_splitBlockState, partitions) == sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, nextSeqStore) == 2 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, currSeqStore) == 3 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, prevCBlock) == 4 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, nextCBlock) == 5 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, tmpWorkspace) == 6 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, tmpWkspSize) == 7 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, seqCollector) == 8 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, blockSizeMax) == 9 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, strategy) == 10 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, disableLiteralCompression)
|
||||
== 10 * sizeof(void*) + sizeof(int)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, bmi2)
|
||||
== 10 * sizeof(void*) + 2 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_splitBlockState, isFirstBlock)
|
||||
== 10 * sizeof(void*) + 3 * sizeof(int)
|
||||
&& sizeof(ZSTD_rust_splitBlockState)
|
||||
== 10 * sizeof(void*) + 4 * sizeof(int))
|
||||
? 1 : -1];
|
||||
size_t ZSTD_rust_nextInputSizeHint(int inBufferMode,
|
||||
size_t blockSizeMax,
|
||||
@@ -417,14 +421,6 @@ size_t ZSTD_rust_buildBlockEntropyStats(
|
||||
int strategy, int disableLiteralCompression,
|
||||
ZSTD_entropyCTablesMetadata_t* entropyMetadata,
|
||||
void* workspace, size_t wkspSize);
|
||||
size_t ZSTD_rust_estimateBlockSize(
|
||||
const BYTE* literals, size_t litSize,
|
||||
const BYTE* ofCodeTable, const BYTE* llCodeTable,
|
||||
const BYTE* mlCodeTable, size_t nbSeq,
|
||||
const ZSTD_entropyCTables_t* entropy,
|
||||
const ZSTD_entropyCTablesMetadata_t* entropyMetadata,
|
||||
void* workspace, size_t wkspSize,
|
||||
int writeLitEntropy, int writeSeqEntropy);
|
||||
size_t ZSTD_rust_copyBlockSequences(
|
||||
SeqCollector* seqCollector, const SeqStore_t* seqStore,
|
||||
const U32 prevRepcodes[ZSTD_REP_NUM]);
|
||||
@@ -448,11 +444,6 @@ int ZSTD_rust_isRLE(const BYTE* src, size_t length);
|
||||
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,
|
||||
const SeqStore_t* originalSeqStore,
|
||||
size_t startIdx, size_t endIdx);
|
||||
size_t ZSTD_rust_deriveBlockSplits(
|
||||
U32* partitions, U32 nbSeq,
|
||||
const SeqStore_t* originalSeqStore,
|
||||
@@ -2733,105 +2724,6 @@ size_t ZSTD_buildBlockEntropyStats(
|
||||
workspace, wkspSize);
|
||||
}
|
||||
|
||||
/* The block-size estimator is implemented in Rust; C retains the stateful
|
||||
* block-splitting recursion and the entropy-table ownership. */
|
||||
static size_t
|
||||
ZSTD_estimateBlockSize(const BYTE* literals, size_t litSize,
|
||||
const BYTE* ofCodeTable,
|
||||
const BYTE* llCodeTable,
|
||||
const BYTE* mlCodeTable,
|
||||
size_t nbSeq,
|
||||
const ZSTD_entropyCTables_t* entropy,
|
||||
const ZSTD_entropyCTablesMetadata_t* entropyMetadata,
|
||||
void* workspace, size_t wkspSize,
|
||||
int writeLitEntropy, int writeSeqEntropy)
|
||||
{
|
||||
return ZSTD_rust_estimateBlockSize(
|
||||
literals, litSize,
|
||||
ofCodeTable, llCodeTable, mlCodeTable, nbSeq,
|
||||
entropy, entropyMetadata,
|
||||
workspace, wkspSize,
|
||||
writeLitEntropy, writeSeqEntropy);
|
||||
}
|
||||
|
||||
/* Builds entropy statistics and uses them for blocksize estimation.
|
||||
*
|
||||
* @return: estimated compressed size of the seqStore, or a zstd error.
|
||||
*/
|
||||
UNUSED_ATTR static size_t
|
||||
ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(SeqStore_t* seqStore, ZSTD_CCtx* zc)
|
||||
{
|
||||
ZSTD_entropyCTablesMetadata_t* const entropyMetadata = &zc->blockSplitCtx.entropyMetadata;
|
||||
DEBUGLOG(6, "ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize()");
|
||||
FORWARD_IF_ERROR(ZSTD_buildBlockEntropyStats(seqStore,
|
||||
&zc->blockState.prevCBlock->entropy,
|
||||
&zc->blockState.nextCBlock->entropy,
|
||||
&zc->appliedParams,
|
||||
entropyMetadata,
|
||||
zc->tmpWorkspace, zc->tmpWkspSize), "");
|
||||
return ZSTD_estimateBlockSize(
|
||||
seqStore->litStart, (size_t)(seqStore->lit - seqStore->litStart),
|
||||
seqStore->ofCode, seqStore->llCode, seqStore->mlCode,
|
||||
(size_t)(seqStore->sequences - seqStore->sequencesStart),
|
||||
&zc->blockState.nextCBlock->entropy,
|
||||
entropyMetadata,
|
||||
zc->tmpWorkspace, zc->tmpWkspSize,
|
||||
(int)(entropyMetadata->hufMetadata.hType == set_compressed), 1);
|
||||
}
|
||||
|
||||
/* Returns literals bytes represented in a seqStore */
|
||||
static size_t ZSTD_countSeqStoreLiteralsBytes(const SeqStore_t* const seqStore)
|
||||
{
|
||||
return ZSTD_rust_countSeqStoreLiteralsBytes(seqStore);
|
||||
}
|
||||
|
||||
/* Returns match bytes represented in a seqStore */
|
||||
static size_t ZSTD_countSeqStoreMatchBytes(const SeqStore_t* const seqStore)
|
||||
{
|
||||
return ZSTD_rust_countSeqStoreMatchBytes(seqStore);
|
||||
}
|
||||
|
||||
/* Derives the seqStore that is a chunk of the originalSeqStore from [startIdx, endIdx).
|
||||
* Stores the result in resultSeqStore.
|
||||
*/
|
||||
static void ZSTD_deriveSeqStoreChunk(SeqStore_t* resultSeqStore,
|
||||
const SeqStore_t* originalSeqStore,
|
||||
size_t startIdx, size_t endIdx)
|
||||
{
|
||||
ZSTD_rust_deriveSeqStoreChunk(resultSeqStore, originalSeqStore,
|
||||
startIdx, endIdx);
|
||||
}
|
||||
|
||||
/* ZSTD_compressSeqStore_singleBlock():
|
||||
* Compresses a seqStore into a block with a block header, into the buffer dst.
|
||||
*
|
||||
* Returns the total size of that block (including header) or a ZSTD error code.
|
||||
*/
|
||||
static size_t
|
||||
ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc,
|
||||
const SeqStore_t* const seqStore,
|
||||
Repcodes_t* const dRep, Repcodes_t* const cRep,
|
||||
void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize,
|
||||
U32 lastBlock, U32 isPartition)
|
||||
{
|
||||
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.
|
||||
* Populates a table with intra-block partition indices that can improve compression ratio.
|
||||
*
|
||||
@@ -2865,91 +2757,29 @@ ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc,
|
||||
const void* src, size_t blockSize,
|
||||
U32 lastBlock, U32 nbSeq)
|
||||
{
|
||||
size_t cSize = 0;
|
||||
const BYTE* ip = (const BYTE*)src;
|
||||
BYTE* op = (BYTE*)dst;
|
||||
size_t i = 0;
|
||||
size_t srcBytesTotal = 0;
|
||||
ZSTD_rust_splitBlockState state;
|
||||
U32* const partitions = zc->blockSplitCtx.partitions; /* splits plus the terminal boundary */
|
||||
SeqStore_t* const nextSeqStore = &zc->blockSplitCtx.nextSeqStore;
|
||||
SeqStore_t* const currSeqStore = &zc->blockSplitCtx.currSeqStore;
|
||||
size_t const numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq);
|
||||
|
||||
/* If a block is split and some partitions are emitted as RLE/uncompressed, then repcode history
|
||||
* may become invalid. In order to reconcile potentially invalid repcodes, we keep track of two
|
||||
* separate repcode histories that simulate repcode history on compression and decompression side,
|
||||
* and use the histories to determine whether we must replace a particular repcode with its raw offset.
|
||||
*
|
||||
* 1) cRep gets updated for each partition, regardless of whether the block was emitted as uncompressed
|
||||
* or RLE. This allows us to retrieve the offset value that an invalid repcode references within
|
||||
* a nocompress/RLE block.
|
||||
* 2) dRep gets updated only for compressed partitions, and when a repcode gets replaced, will use
|
||||
* the replacement offset value rather than the original repcode to update the repcode history.
|
||||
* dRep also will be the final repcode history sent to the next block.
|
||||
*
|
||||
* See ZSTD_seqStore_resolveOffCodes() for more details.
|
||||
*/
|
||||
Repcodes_t dRep;
|
||||
Repcodes_t cRep;
|
||||
ZSTD_memcpy(dRep.rep, zc->blockState.prevCBlock->rep, sizeof(Repcodes_t));
|
||||
ZSTD_memcpy(cRep.rep, zc->blockState.prevCBlock->rep, sizeof(Repcodes_t));
|
||||
ZSTD_memset(nextSeqStore, 0, sizeof(SeqStore_t));
|
||||
|
||||
DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal (dstCapacity=%u, dictLimit=%u, nextToUpdate=%u)",
|
||||
(unsigned)dstCapacity, (unsigned)zc->blockState.matchState.window.dictLimit,
|
||||
(unsigned)zc->blockState.matchState.nextToUpdate);
|
||||
|
||||
if (numSplits == 0) {
|
||||
size_t cSizeSingleBlock =
|
||||
ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore,
|
||||
&dRep, &cRep,
|
||||
op, dstCapacity,
|
||||
ip, blockSize,
|
||||
lastBlock, 0 /* isPartition */);
|
||||
FORWARD_IF_ERROR(cSizeSingleBlock, "Compressing single block from splitBlock_internal() failed!");
|
||||
DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal: No splits");
|
||||
assert(zc->blockSizeMax <= ZSTD_BLOCKSIZE_MAX);
|
||||
assert(cSizeSingleBlock <= zc->blockSizeMax + ZSTD_blockHeaderSize);
|
||||
return cSizeSingleBlock;
|
||||
}
|
||||
|
||||
ZSTD_deriveSeqStoreChunk(currSeqStore, &zc->seqStore, 0, partitions[0]);
|
||||
for (i = 0; i <= numSplits; ++i) {
|
||||
size_t cSizeChunk;
|
||||
U32 const lastPartition = (i == numSplits);
|
||||
U32 lastBlockEntireSrc = 0;
|
||||
|
||||
size_t srcBytes = ZSTD_countSeqStoreLiteralsBytes(currSeqStore) + ZSTD_countSeqStoreMatchBytes(currSeqStore);
|
||||
srcBytesTotal += srcBytes;
|
||||
if (lastPartition) {
|
||||
/* This is the final partition, need to account for possible last literals */
|
||||
srcBytes += blockSize - srcBytesTotal;
|
||||
lastBlockEntireSrc = lastBlock;
|
||||
} else {
|
||||
ZSTD_deriveSeqStoreChunk(nextSeqStore, &zc->seqStore, partitions[i], partitions[i+1]);
|
||||
}
|
||||
|
||||
cSizeChunk = ZSTD_compressSeqStore_singleBlock(zc, currSeqStore,
|
||||
&dRep, &cRep,
|
||||
op, dstCapacity,
|
||||
ip, srcBytes,
|
||||
lastBlockEntireSrc, 1 /* isPartition */);
|
||||
DEBUGLOG(5, "Estimated size: %zu vs %zu : actual size",
|
||||
ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(currSeqStore, zc), cSizeChunk);
|
||||
FORWARD_IF_ERROR(cSizeChunk, "Compressing chunk failed!");
|
||||
|
||||
ip += srcBytes;
|
||||
op += cSizeChunk;
|
||||
dstCapacity -= cSizeChunk;
|
||||
cSize += cSizeChunk;
|
||||
*currSeqStore = *nextSeqStore;
|
||||
assert(cSizeChunk <= zc->blockSizeMax + ZSTD_blockHeaderSize);
|
||||
}
|
||||
/* cRep and dRep may have diverged during the compression.
|
||||
* If so, we use the dRep repcodes for the next block.
|
||||
*/
|
||||
ZSTD_memcpy(zc->blockState.prevCBlock->rep, dRep.rep, sizeof(Repcodes_t));
|
||||
return cSize;
|
||||
state.seqStore = &zc->seqStore;
|
||||
state.partitions = partitions;
|
||||
state.nextSeqStore = &zc->blockSplitCtx.nextSeqStore;
|
||||
state.currSeqStore = &zc->blockSplitCtx.currSeqStore;
|
||||
state.prevCBlock = &zc->blockState.prevCBlock;
|
||||
state.nextCBlock = &zc->blockState.nextCBlock;
|
||||
state.tmpWorkspace = zc->tmpWorkspace;
|
||||
state.tmpWkspSize = zc->tmpWkspSize;
|
||||
state.seqCollector = &zc->seqCollector;
|
||||
state.blockSizeMax = zc->blockSizeMax;
|
||||
state.strategy = (int)zc->appliedParams.cParams.strategy;
|
||||
state.disableLiteralCompression = ZSTD_literalsCompressionIsDisabled(&zc->appliedParams);
|
||||
state.bmi2 = zc->bmi2;
|
||||
state.isFirstBlock = zc->isFirstBlock;
|
||||
return ZSTD_rust_compressBlockSplit(
|
||||
&state, dst, dstCapacity, src, blockSize, lastBlock, numSplits);
|
||||
}
|
||||
|
||||
static size_t
|
||||
|
||||
Reference in New Issue
Block a user