feat(compress): move external sequence literal loop into Rust
Move the external-sequence-and-literals block loop into the Rust compression module while preserving the C-owned CCtx initialization and sequence conversion callback. Rust now owns block summaries, literal cursor movement, entropy emission, block framing, repcode state updates, and completion validation; C remains the private-context ABI adapter. Test Plan: - cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1 - cargo clippy --manifest-path rust/Cargo.toml --tests -- -D warnings - make -B -C lib -j2 lib - make -B -C tests -j2 test-cli-tests - FUZZERTEST=-T5s make -B -C tests -j2 test-fuzzer
This commit is contained in:
+77
-102
@@ -664,6 +664,54 @@ typedef char ZSTD_rust_sequence_state_layout[
|
||||
== offsetof(ZSTD_rust_sequenceCompressionState, isFirstBlock) + sizeof(void*))
|
||||
? 1 : -1];
|
||||
|
||||
/* The external-sequence/literals block loop keeps sequence conversion as a
|
||||
* C-private callback because it still reads the opaque CCtx. Everything
|
||||
* else it mutates is passed through this explicit projection. */
|
||||
typedef size_t (*ZSTD_rust_sequenceLiteralsConvert_f)(
|
||||
void* context, const ZSTD_Sequence* inSeqs, size_t nbSequences,
|
||||
int repcodeResolution);
|
||||
typedef struct {
|
||||
SeqStore_t* seqStore;
|
||||
ZSTD_compressedBlockState_t** prevCBlock;
|
||||
ZSTD_compressedBlockState_t** nextCBlock;
|
||||
void* tmpWorkspace;
|
||||
size_t tmpWkspSize;
|
||||
size_t blockSizeMax;
|
||||
int bmi2;
|
||||
int strategy;
|
||||
int disableLiteralCompression;
|
||||
int repcodeResolution;
|
||||
int* isFirstBlock;
|
||||
void* callbackContext;
|
||||
ZSTD_rust_sequenceLiteralsConvert_f convertBlockSequences;
|
||||
} ZSTD_rust_sequenceLiteralsState;
|
||||
|
||||
size_t ZSTD_rust_compressSequencesAndLiteralsInternal(
|
||||
const ZSTD_rust_sequenceLiteralsState* state,
|
||||
void* dst, size_t dstCapacity,
|
||||
const ZSTD_Sequence* inSeqs, size_t nbSequences,
|
||||
const void* literals, size_t litSize, size_t srcSize);
|
||||
typedef char ZSTD_rust_sequence_literals_state_layout[
|
||||
(offsetof(ZSTD_rust_sequenceLiteralsState, seqStore) == 0
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, prevCBlock) == sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, nextCBlock) == 2 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, tmpWorkspace) == 3 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, tmpWkspSize) == 4 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, blockSizeMax) == 5 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, bmi2) == 6 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, strategy) == 6 * sizeof(void*) + sizeof(int)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, disableLiteralCompression) == 6 * sizeof(void*) + 2 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, repcodeResolution) == 6 * sizeof(void*) + 3 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, isFirstBlock)
|
||||
== 6 * sizeof(void*) + 4 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, callbackContext)
|
||||
== 6 * sizeof(void*) + 4 * sizeof(int) + sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_sequenceLiteralsState, convertBlockSequences)
|
||||
== 6 * sizeof(void*) + 4 * sizeof(int) + 2 * sizeof(void*)
|
||||
&& sizeof(ZSTD_rust_sequenceLiteralsState)
|
||||
== 6 * sizeof(void*) + 4 * sizeof(int) + 3 * sizeof(void*))
|
||||
? 1 : -1];
|
||||
|
||||
typedef char ZSTD_rust_stats_seqdef_layout[(sizeof(SeqDef) == 8) ? 1 : -1];
|
||||
typedef char ZSTD_rust_stats_block_summary_layout[
|
||||
(sizeof(BlockSummary) == 3 * sizeof(size_t)) ? 1 : -1];
|
||||
@@ -2804,13 +2852,6 @@ size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs,
|
||||
|
||||
/* ZSTD_mergeBlockDelimiters() lives in rust/src/zstd_compress_api.rs. */
|
||||
|
||||
static void
|
||||
ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs)
|
||||
{
|
||||
ZSTD_rust_confirmRepcodesAndEntropyTables(
|
||||
&bs->prevCBlock, &bs->nextCBlock);
|
||||
}
|
||||
|
||||
/** ZSTD_buildBlockEntropyStats() :
|
||||
* Builds entropy for the block.
|
||||
* Requires workspace size ENTROPY_WORKSPACE_SIZE
|
||||
@@ -5198,6 +5239,14 @@ size_t ZSTD_convertBlockSequences(ZSTD_CCtx* cctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t ZSTD_convertBlockSequencesForRust(
|
||||
void* context, const ZSTD_Sequence* inSeqs, size_t nbSequences,
|
||||
int repcodeResolution)
|
||||
{
|
||||
return ZSTD_convertBlockSequences((ZSTD_CCtx*)context, inSeqs, nbSequences,
|
||||
repcodeResolution);
|
||||
}
|
||||
|
||||
BlockSummary ZSTD_get1BlockSummary(const ZSTD_Sequence* seqs, size_t nbSeqs)
|
||||
{
|
||||
return ZSTD_rust_get1BlockSummary(seqs, nbSeqs);
|
||||
@@ -5210,103 +5259,29 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx,
|
||||
const ZSTD_Sequence* inSeqs, size_t nbSequences,
|
||||
const void* literals, size_t litSize, size_t srcSize)
|
||||
{
|
||||
size_t remaining = srcSize;
|
||||
size_t cSize = 0;
|
||||
BYTE* op = (BYTE*)dst;
|
||||
int const repcodeResolution = (cctx->appliedParams.searchForExternalRepcodes == ZSTD_ps_enable);
|
||||
ZSTD_rust_sequenceLiteralsState state;
|
||||
int const repcodeResolution =
|
||||
(cctx->appliedParams.searchForExternalRepcodes == ZSTD_ps_enable);
|
||||
|
||||
assert(cctx->appliedParams.searchForExternalRepcodes != ZSTD_ps_auto);
|
||||
state.seqStore = &cctx->seqStore;
|
||||
state.prevCBlock = &cctx->blockState.prevCBlock;
|
||||
state.nextCBlock = &cctx->blockState.nextCBlock;
|
||||
state.tmpWorkspace = cctx->tmpWorkspace;
|
||||
state.tmpWkspSize = cctx->tmpWkspSize;
|
||||
state.blockSizeMax = cctx->blockSizeMax;
|
||||
state.bmi2 = cctx->bmi2;
|
||||
state.strategy = (int)cctx->appliedParams.cParams.strategy;
|
||||
state.disableLiteralCompression =
|
||||
ZSTD_literalsCompressionIsDisabled(&cctx->appliedParams);
|
||||
state.repcodeResolution = repcodeResolution;
|
||||
state.isFirstBlock = &cctx->isFirstBlock;
|
||||
state.callbackContext = cctx;
|
||||
state.convertBlockSequences = ZSTD_convertBlockSequencesForRust;
|
||||
|
||||
DEBUGLOG(4, "ZSTD_compressSequencesAndLiterals_internal: nbSeqs=%zu, litSize=%zu", nbSequences, litSize);
|
||||
RETURN_ERROR_IF(nbSequences == 0, externalSequences_invalid, "Requires at least 1 end-of-block");
|
||||
|
||||
/* Special case: empty frame */
|
||||
if ((nbSequences == 1) && (inSeqs[0].litLength == 0)) {
|
||||
U32 const cBlockHeader24 = 1 /* last block */ + (((U32)bt_raw)<<1);
|
||||
RETURN_ERROR_IF(dstCapacity<3, dstSize_tooSmall, "No room for empty frame block header");
|
||||
MEM_writeLE24(op, cBlockHeader24);
|
||||
op += ZSTD_blockHeaderSize;
|
||||
dstCapacity -= ZSTD_blockHeaderSize;
|
||||
cSize += ZSTD_blockHeaderSize;
|
||||
}
|
||||
|
||||
while (nbSequences) {
|
||||
size_t compressedSeqsSize, cBlockSize, conversionStatus;
|
||||
BlockSummary const block = ZSTD_get1BlockSummary(inSeqs, nbSequences);
|
||||
U32 const lastBlock = (block.nbSequences == nbSequences);
|
||||
FORWARD_IF_ERROR(block.nbSequences, "Error while trying to determine nb of sequences for a block");
|
||||
assert(block.nbSequences <= nbSequences);
|
||||
RETURN_ERROR_IF(block.litSize > litSize, externalSequences_invalid, "discrepancy: Sequences require more literals than present in buffer");
|
||||
ZSTD_resetSeqStore(&cctx->seqStore);
|
||||
|
||||
conversionStatus = ZSTD_convertBlockSequences(cctx,
|
||||
inSeqs, block.nbSequences,
|
||||
repcodeResolution);
|
||||
FORWARD_IF_ERROR(conversionStatus, "Bad sequence conversion");
|
||||
inSeqs += block.nbSequences;
|
||||
nbSequences -= block.nbSequences;
|
||||
remaining -= block.blockSize;
|
||||
|
||||
/* Note: when blockSize is very small, other variant send it uncompressed.
|
||||
* Here, we still send the sequences, because we don't have the original source to send it uncompressed.
|
||||
* One could imagine in theory reproducing the source from the sequences,
|
||||
* but that's complex and costly memory intensive, and goes against the objectives of this variant. */
|
||||
|
||||
RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize, dstSize_tooSmall, "not enough dstCapacity to write a new compressed block");
|
||||
|
||||
compressedSeqsSize = ZSTD_entropyCompressSeqStore_internal(
|
||||
op + ZSTD_blockHeaderSize /* Leave space for block header */, dstCapacity - ZSTD_blockHeaderSize,
|
||||
literals, block.litSize,
|
||||
&cctx->seqStore,
|
||||
&cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy,
|
||||
&cctx->appliedParams,
|
||||
cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */,
|
||||
cctx->bmi2);
|
||||
FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed");
|
||||
/* note: the spec forbids for any compressed block to be larger than maximum block size */
|
||||
if (compressedSeqsSize > cctx->blockSizeMax) compressedSeqsSize = 0;
|
||||
DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize);
|
||||
litSize -= block.litSize;
|
||||
literals = (const char*)literals + block.litSize;
|
||||
|
||||
/* Note: difficult to check source for RLE block when only Literals are provided,
|
||||
* but it could be considered from analyzing the sequence directly */
|
||||
|
||||
if (compressedSeqsSize == 0) {
|
||||
/* Sending uncompressed blocks is out of reach, because the source is not provided.
|
||||
* In theory, one could use the sequences to regenerate the source, like a decompressor,
|
||||
* but it's complex, and memory hungry, killing the purpose of this variant.
|
||||
* Current outcome: generate an error code.
|
||||
*/
|
||||
RETURN_ERROR(cannotProduce_uncompressedBlock, "ZSTD_compressSequencesAndLiterals cannot generate an uncompressed block");
|
||||
} else {
|
||||
assert(compressedSeqsSize > 1); /* no RLE */
|
||||
/* Error checking and repcodes update */
|
||||
ZSTD_blockState_confirmRepcodesAndEntropyTables(&cctx->blockState);
|
||||
if (cctx->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid)
|
||||
cctx->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check;
|
||||
|
||||
/* Write block header into beginning of block*/
|
||||
ZSTD_rust_writeBlockHeader(op, compressedSeqsSize, block.blockSize, lastBlock);
|
||||
cBlockSize = ZSTD_blockHeaderSize + compressedSeqsSize;
|
||||
DEBUGLOG(5, "Writing out compressed block, size: %zu", cBlockSize);
|
||||
}
|
||||
|
||||
cSize += cBlockSize;
|
||||
op += cBlockSize;
|
||||
dstCapacity -= cBlockSize;
|
||||
cctx->isFirstBlock = 0;
|
||||
DEBUGLOG(5, "cSize running total: %zu (remaining dstCapacity=%zu)", cSize, dstCapacity);
|
||||
|
||||
if (lastBlock) {
|
||||
assert(nbSequences == 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_ERROR_IF(litSize != 0, externalSequences_invalid, "literals must be entirely and exactly consumed");
|
||||
RETURN_ERROR_IF(remaining != 0, externalSequences_invalid, "Sequences must represent a total of exactly srcSize=%zu", srcSize);
|
||||
DEBUGLOG(4, "cSize final total: %zu", cSize);
|
||||
return cSize;
|
||||
return ZSTD_rust_compressSequencesAndLiteralsInternal(
|
||||
&state, dst, dstCapacity, inSeqs, nbSequences, literals, litSize,
|
||||
srcSize);
|
||||
}
|
||||
|
||||
size_t
|
||||
|
||||
Reference in New Issue
Block a user