feat(compress): move frame-chunk loop into Rust
Port ZSTD_compress_frameChunk's per-block orchestration into Rust behind an explicit C-layout projection and callback table. Rust now owns block sizing, target/split/internal dispatch, block framing and accounting, checksum sequencing, and terminal frame-state updates. C retains CCtx and match-state preparation plus the codec-specific callbacks. Test Plan: - cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1 - cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - make -B -C lib -j2 lib - make -B -C programs -j2 zstd - make -B -C tests -j2 test-zstd
This commit is contained in:
+142
-97
@@ -79,6 +79,74 @@ void ZSTD_rust_copyCDictTableIntoCCtx(U32* dst, U32 const* src,
|
||||
U64 ZSTD_rust_advanceHashSalt(U64 hashSalt, U64 hashSaltEntropy);
|
||||
int ZSTD_rust_indexTooCloseToMax(size_t nextSrcBaseOffset);
|
||||
int ZSTD_rust_dictTooBig(size_t loadedDictSize);
|
||||
|
||||
/* The frame-chunk loop is Rust-owned. Its callbacks keep the private
|
||||
* ZSTD_CCtx and match-state layout in C: Rust only drives the block loop and
|
||||
* passes this context back to these C-owned state-preparation/dispatch seams. */
|
||||
typedef void (*ZSTD_rust_frameChunkPrepare_f)(void* context,
|
||||
const void* src,
|
||||
size_t blockSize);
|
||||
typedef size_t (*ZSTD_rust_frameChunkCompress_f)(void* context,
|
||||
void* dst,
|
||||
size_t dstCapacity,
|
||||
const void* src,
|
||||
size_t srcSize,
|
||||
U32 lastBlock);
|
||||
typedef void (*ZSTD_rust_frameChunkChecksum_f)(void* state,
|
||||
const void* src,
|
||||
size_t srcSize);
|
||||
typedef struct {
|
||||
void* callbackContext;
|
||||
void* tmpWorkspace;
|
||||
void* checksumState;
|
||||
int* isFirstBlock;
|
||||
ZSTD_compressionStage_e* stage;
|
||||
size_t tmpWkspSize;
|
||||
size_t blockSizeMax;
|
||||
S64 savings;
|
||||
int preBlockSplitterLevel;
|
||||
int strategy;
|
||||
int useTargetCBlockSize;
|
||||
int blockSplitterEnabled;
|
||||
int checksumFlag;
|
||||
int endingStage;
|
||||
ZSTD_rust_frameChunkPrepare_f prepareBlock;
|
||||
ZSTD_rust_frameChunkCompress_f compressTarget;
|
||||
ZSTD_rust_frameChunkCompress_f compressSplit;
|
||||
ZSTD_rust_frameChunkCompress_f compressInternal;
|
||||
ZSTD_rust_frameChunkChecksum_f updateChecksum;
|
||||
} ZSTD_rust_frameChunkState;
|
||||
size_t ZSTD_rust_compressFrameChunk(
|
||||
const ZSTD_rust_frameChunkState* state,
|
||||
void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize,
|
||||
U32 lastFrameChunk);
|
||||
typedef char ZSTD_rust_frame_chunk_state_layout[
|
||||
(offsetof(ZSTD_rust_frameChunkState, callbackContext) == 0
|
||||
&& offsetof(ZSTD_rust_frameChunkState, tmpWorkspace) == sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, checksumState) == 2 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, isFirstBlock) == 3 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, stage) == 4 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, tmpWkspSize) == 5 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, blockSizeMax) == 6 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, savings) == 7 * sizeof(void*)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, preBlockSplitterLevel)
|
||||
== 7 * sizeof(void*) + sizeof(S64)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, strategy)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + sizeof(int)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, useTargetCBlockSize)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + 2 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, blockSplitterEnabled)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + 3 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, checksumFlag)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + 4 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, endingStage)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + 5 * sizeof(int)
|
||||
&& offsetof(ZSTD_rust_frameChunkState, prepareBlock)
|
||||
== 7 * sizeof(void*) + sizeof(S64) + 6 * sizeof(int)
|
||||
&& sizeof(ZSTD_rust_frameChunkState)
|
||||
== 12 * sizeof(void*) + sizeof(S64) + 6 * sizeof(int))
|
||||
? 1 : -1];
|
||||
/* The target-sized block body only needs this narrow projection of ZSTD_CCtx.
|
||||
* Matchfinder/window state, sequence-store construction, and outer repeat-mode
|
||||
* cleanup remain in C. */
|
||||
@@ -2896,11 +2964,59 @@ static void ZSTD_overflowCorrectIfNeeded(ZSTD_MatchState_t* ms,
|
||||
|
||||
#include "zstd_preSplit.h"
|
||||
|
||||
static size_t ZSTD_optimalBlockSize(ZSTD_CCtx* cctx, const void* src, size_t srcSize, size_t blockSizeMax, int splitLevel, ZSTD_strategy strat, S64 savings)
|
||||
static void ZSTD_rust_frameChunk_prepareBlock(void* context,
|
||||
const void* src,
|
||||
size_t blockSize)
|
||||
{
|
||||
return ZSTD_rust_optimalBlockSize(src, srcSize, blockSizeMax, splitLevel,
|
||||
(int)strat, savings,
|
||||
cctx->tmpWorkspace, cctx->tmpWkspSize);
|
||||
ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context;
|
||||
ZSTD_MatchState_t* const ms = &cctx->blockState.matchState;
|
||||
U32 const maxDist = (U32)1 << cctx->appliedParams.cParams.windowLog;
|
||||
|
||||
ZSTD_overflowCorrectIfNeeded(
|
||||
ms, &cctx->workspace, &cctx->appliedParams,
|
||||
src, (const BYTE*)src + blockSize);
|
||||
ZSTD_checkDictValidity(
|
||||
&ms->window, (const BYTE*)src + blockSize, maxDist,
|
||||
&ms->loadedDictEnd, &ms->dictMatchState);
|
||||
ZSTD_window_enforceMaxDist(
|
||||
&ms->window, src, maxDist,
|
||||
&ms->loadedDictEnd, &ms->dictMatchState);
|
||||
|
||||
/* Ensure hash/chain table insertion resumes no sooner than lowlimit. */
|
||||
if (ms->nextToUpdate < ms->window.lowLimit)
|
||||
ms->nextToUpdate = ms->window.lowLimit;
|
||||
}
|
||||
|
||||
static size_t ZSTD_rust_frameChunk_compressTarget(
|
||||
void* context, void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize, U32 lastBlock)
|
||||
{
|
||||
return ZSTD_compressBlock_targetCBlockSize(
|
||||
(ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize, lastBlock);
|
||||
}
|
||||
|
||||
static size_t ZSTD_rust_frameChunk_compressSplit(
|
||||
void* context, void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize, U32 lastBlock)
|
||||
{
|
||||
return ZSTD_compressBlock_splitBlock(
|
||||
(ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize, lastBlock);
|
||||
}
|
||||
|
||||
static size_t ZSTD_rust_frameChunk_compressInternal(
|
||||
void* context, void* dst, size_t dstCapacity,
|
||||
const void* src, size_t srcSize, U32 lastBlock)
|
||||
{
|
||||
(void)lastBlock;
|
||||
return ZSTD_compressBlock_internal(
|
||||
(ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize,
|
||||
1 /* frame */);
|
||||
}
|
||||
|
||||
static void ZSTD_rust_frameChunk_updateChecksum(
|
||||
void* state, const void* src, size_t srcSize)
|
||||
{
|
||||
(void)XXH64_update((XXH64_state_t*)state, src, srcSize);
|
||||
}
|
||||
|
||||
/*! ZSTD_compress_frameChunk() :
|
||||
@@ -2915,99 +3031,28 @@ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx,
|
||||
const void* src, size_t srcSize,
|
||||
U32 lastFrameChunk)
|
||||
{
|
||||
size_t blockSizeMax = cctx->blockSizeMax;
|
||||
size_t remaining = srcSize;
|
||||
const BYTE* ip = (const BYTE*)src;
|
||||
BYTE* const ostart = (BYTE*)dst;
|
||||
BYTE* op = ostart;
|
||||
U32 const maxDist = (U32)1 << cctx->appliedParams.cParams.windowLog;
|
||||
S64 savings = (S64)cctx->consumedSrcSize - (S64)cctx->producedCSize;
|
||||
|
||||
assert(cctx->appliedParams.cParams.windowLog <= ZSTD_WINDOWLOG_MAX);
|
||||
|
||||
DEBUGLOG(5, "ZSTD_compress_frameChunk (srcSize=%u, blockSizeMax=%u)", (unsigned)srcSize, (unsigned)blockSizeMax);
|
||||
if (cctx->appliedParams.fParams.checksumFlag && srcSize)
|
||||
XXH64_update(&cctx->xxhState, src, srcSize);
|
||||
|
||||
while (remaining) {
|
||||
ZSTD_MatchState_t* const ms = &cctx->blockState.matchState;
|
||||
size_t const blockSize = ZSTD_optimalBlockSize(cctx,
|
||||
ip, remaining,
|
||||
blockSizeMax,
|
||||
cctx->appliedParams.preBlockSplitter_level,
|
||||
cctx->appliedParams.cParams.strategy,
|
||||
savings);
|
||||
U32 const lastBlock = lastFrameChunk & (blockSize == remaining);
|
||||
assert(blockSize <= remaining);
|
||||
|
||||
/* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding
|
||||
* additional 1. We need to revisit and change this logic to be more consistent */
|
||||
RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize + MIN_CBLOCK_SIZE + 1,
|
||||
dstSize_tooSmall,
|
||||
"not enough space to store compressed block");
|
||||
|
||||
ZSTD_overflowCorrectIfNeeded(
|
||||
ms, &cctx->workspace, &cctx->appliedParams, ip, ip + blockSize);
|
||||
ZSTD_checkDictValidity(&ms->window, ip + blockSize, maxDist, &ms->loadedDictEnd, &ms->dictMatchState);
|
||||
ZSTD_window_enforceMaxDist(&ms->window, ip, maxDist, &ms->loadedDictEnd, &ms->dictMatchState);
|
||||
|
||||
/* Ensure hash/chain table insertion resumes no sooner than lowlimit */
|
||||
if (ms->nextToUpdate < ms->window.lowLimit) ms->nextToUpdate = ms->window.lowLimit;
|
||||
|
||||
{ size_t cSize;
|
||||
if (ZSTD_useTargetCBlockSize(&cctx->appliedParams)) {
|
||||
cSize = ZSTD_compressBlock_targetCBlockSize(cctx, op, dstCapacity, ip, blockSize, lastBlock);
|
||||
FORWARD_IF_ERROR(cSize, "ZSTD_compressBlock_targetCBlockSize failed");
|
||||
assert(cSize > 0);
|
||||
assert(cSize <= blockSize + ZSTD_blockHeaderSize);
|
||||
} else if (ZSTD_blockSplitterEnabled(&cctx->appliedParams)) {
|
||||
cSize = ZSTD_compressBlock_splitBlock(cctx, op, dstCapacity, ip, blockSize, lastBlock);
|
||||
FORWARD_IF_ERROR(cSize, "ZSTD_compressBlock_splitBlock failed");
|
||||
assert(cSize > 0 || cctx->seqCollector.collectSequences == 1);
|
||||
} else {
|
||||
cSize = ZSTD_compressBlock_internal(cctx,
|
||||
op+ZSTD_blockHeaderSize, dstCapacity-ZSTD_blockHeaderSize,
|
||||
ip, blockSize, 1 /* frame */);
|
||||
FORWARD_IF_ERROR(cSize, "ZSTD_compressBlock_internal failed");
|
||||
|
||||
if (cSize == 0) { /* block is not compressible */
|
||||
cSize = ZSTD_rust_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock);
|
||||
FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed");
|
||||
} else {
|
||||
ZSTD_rust_writeBlockHeader(op, cSize, blockSize, lastBlock);
|
||||
cSize += ZSTD_blockHeaderSize;
|
||||
}
|
||||
} /* if (ZSTD_useTargetCBlockSize(&cctx->appliedParams))*/
|
||||
|
||||
/* @savings is employed to ensure that splitting doesn't worsen expansion of incompressible data.
|
||||
* Without splitting, the maximum expansion is 3 bytes per full block.
|
||||
* An adversarial input could attempt to fudge the split detector,
|
||||
* and make it split incompressible data, resulting in more block headers.
|
||||
* Note that, since ZSTD_COMPRESSBOUND() assumes a worst case scenario of 1KB per block,
|
||||
* and the splitter never creates blocks that small (current lower limit is 8 KB),
|
||||
* there is already no risk to expand beyond ZSTD_COMPRESSBOUND() limit.
|
||||
* But if the goal is to not expand by more than 3-bytes per 128 KB full block,
|
||||
* then yes, it becomes possible to make the block splitter oversplit incompressible data.
|
||||
* Using @savings, we enforce an even more conservative condition,
|
||||
* requiring the presence of enough savings (at least 3 bytes) to authorize splitting,
|
||||
* otherwise only full blocks are used.
|
||||
* But being conservative is fine,
|
||||
* since splitting barely compressible blocks is not fruitful anyway */
|
||||
savings += (S64)blockSize - (S64)cSize;
|
||||
|
||||
ip += blockSize;
|
||||
assert(remaining >= blockSize);
|
||||
remaining -= blockSize;
|
||||
op += cSize;
|
||||
assert(dstCapacity >= cSize);
|
||||
dstCapacity -= cSize;
|
||||
cctx->isFirstBlock = 0;
|
||||
DEBUGLOG(5, "ZSTD_compress_frameChunk: adding a block of size %u",
|
||||
(unsigned)cSize);
|
||||
} }
|
||||
|
||||
if (lastFrameChunk && (op>ostart)) cctx->stage = ZSTDcs_ending;
|
||||
return (size_t)(op-ostart);
|
||||
ZSTD_rust_frameChunkState state;
|
||||
state.callbackContext = cctx;
|
||||
state.tmpWorkspace = cctx->tmpWorkspace;
|
||||
state.checksumState = &cctx->xxhState;
|
||||
state.isFirstBlock = &cctx->isFirstBlock;
|
||||
state.stage = &cctx->stage;
|
||||
state.tmpWkspSize = cctx->tmpWkspSize;
|
||||
state.blockSizeMax = cctx->blockSizeMax;
|
||||
state.savings = (S64)cctx->consumedSrcSize - (S64)cctx->producedCSize;
|
||||
state.preBlockSplitterLevel = cctx->appliedParams.preBlockSplitter_level;
|
||||
state.strategy = (int)cctx->appliedParams.cParams.strategy;
|
||||
state.useTargetCBlockSize = ZSTD_useTargetCBlockSize(&cctx->appliedParams);
|
||||
state.blockSplitterEnabled = ZSTD_blockSplitterEnabled(&cctx->appliedParams);
|
||||
state.checksumFlag = cctx->appliedParams.fParams.checksumFlag;
|
||||
state.endingStage = (int)ZSTDcs_ending;
|
||||
state.prepareBlock = ZSTD_rust_frameChunk_prepareBlock;
|
||||
state.compressTarget = ZSTD_rust_frameChunk_compressTarget;
|
||||
state.compressSplit = ZSTD_rust_frameChunk_compressSplit;
|
||||
state.compressInternal = ZSTD_rust_frameChunk_compressInternal;
|
||||
state.updateChecksum = ZSTD_rust_frameChunk_updateChecksum;
|
||||
return ZSTD_rust_compressFrameChunk(
|
||||
&state, dst, dstCapacity, src, srcSize, lastFrameChunk);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+488
-2
@@ -38,7 +38,7 @@ use crate::zstd_compress_stats::{
|
||||
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::{offset_of, size_of, MaybeUninit};
|
||||
use std::os::raw::{c_int, c_uint};
|
||||
use std::os::raw::{c_int, c_longlong, c_uint};
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(not(test))]
|
||||
@@ -92,7 +92,8 @@ const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
|
||||
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
|
||||
const ZSTD_TARGET_CBLOCK_BSS_COMPRESS: c_int = 0;
|
||||
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
|
||||
const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = 2 + ZSTD_BLOCK_HEADER_SIZE + 1 + 1;
|
||||
const MIN_CBLOCK_SIZE: usize = 2;
|
||||
const MIN_COMPRESSIBLE_BLOCK_SIZE: usize = MIN_CBLOCK_SIZE + ZSTD_BLOCK_HEADER_SIZE + 1 + 1;
|
||||
const ZSTD_ROWSIZE: usize = 16;
|
||||
const ZSTD_CSTREAM_STAGE_INIT: c_int = 0;
|
||||
const ZSTD_WINDOW_START_INDEX: u32 = 2;
|
||||
@@ -108,6 +109,237 @@ const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX;
|
||||
#[cfg(not(test))]
|
||||
const ZSTD_E_END: c_int = 2;
|
||||
|
||||
type FrameChunkPrepareFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
||||
type FrameChunkCompressFn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize;
|
||||
type FrameChunkChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
||||
|
||||
/// Explicit projection of the state used by `ZSTD_compress_frameChunk`.
|
||||
///
|
||||
/// The Rust side owns the per-frame block loop and its savings/dispatch
|
||||
/// policy. The callback context is opaque to Rust and is only returned to
|
||||
/// C-owned callbacks, which retain private window, workspace, and CCtx
|
||||
/// layout-sensitive operations.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_frameChunkState {
|
||||
callback_context: *mut c_void,
|
||||
tmp_workspace: *mut c_void,
|
||||
checksum_state: *mut c_void,
|
||||
is_first_block: *mut c_int,
|
||||
stage: *mut c_int,
|
||||
tmp_wksp_size: usize,
|
||||
block_size_max: usize,
|
||||
savings: c_longlong,
|
||||
pre_block_splitter_level: c_int,
|
||||
strategy: c_int,
|
||||
use_target_c_block_size: c_int,
|
||||
block_splitter_enabled: c_int,
|
||||
checksum_flag: c_int,
|
||||
ending_stage: c_int,
|
||||
prepare_block: FrameChunkPrepareFn,
|
||||
compress_target: FrameChunkCompressFn,
|
||||
compress_split: FrameChunkCompressFn,
|
||||
compress_internal: FrameChunkCompressFn,
|
||||
update_checksum: FrameChunkChecksumFn,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, callback_context) == 0);
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_workspace) == size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, checksum_state) == 2 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, is_first_block) == 3 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, stage) == 4 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, tmp_wksp_size) == 5 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, block_size_max) == 6 * size_of::<usize>());
|
||||
assert!(offset_of!(ZSTD_rust_frameChunkState, savings) == 7 * size_of::<usize>());
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, pre_block_splitter_level)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, strategy)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, use_target_c_block_size)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, block_splitter_enabled)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 3 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, checksum_flag)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 4 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, ending_stage)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 5 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_frameChunkState, prepare_block)
|
||||
== 7 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
size_of::<ZSTD_rust_frameChunkState>()
|
||||
== 12 * size_of::<usize>() + size_of::<c_longlong>() + 6 * size_of::<c_int>()
|
||||
);
|
||||
};
|
||||
|
||||
/// Rust implementation of `ZSTD_compress_frameChunk`.
|
||||
///
|
||||
/// C still prepares match-state windows and invokes the selected block body
|
||||
/// through callbacks. Rust owns the block-size heuristic call, output
|
||||
/// framing, savings accounting, checksum sequencing, and frame-state update.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn compress_frame_chunk_body_with(
|
||||
state: &ZSTD_rust_frameChunkState,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
last_frame_chunk: c_uint,
|
||||
) -> usize {
|
||||
if state.is_first_block.is_null() || state.stage.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
if state.checksum_flag != 0 && src_size != 0 {
|
||||
if state.checksum_state.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
unsafe { (state.update_checksum)(state.checksum_state, src, src_size) };
|
||||
}
|
||||
|
||||
let mut remaining = src_size;
|
||||
let mut ip = src.cast::<u8>();
|
||||
let mut op = dst.cast::<u8>();
|
||||
let mut remaining_capacity = dst_capacity;
|
||||
let mut savings = state.savings;
|
||||
let mut compressed_size_total = 0usize;
|
||||
|
||||
while remaining != 0 {
|
||||
let block_size = unsafe {
|
||||
crate::zstd_compress_frame::ZSTD_rust_optimalBlockSize(
|
||||
ip.cast(),
|
||||
remaining,
|
||||
state.block_size_max,
|
||||
state.pre_block_splitter_level,
|
||||
state.strategy,
|
||||
savings,
|
||||
state.tmp_workspace,
|
||||
state.tmp_wksp_size,
|
||||
)
|
||||
};
|
||||
if block_size == 0 || block_size > remaining {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
let last_block = last_frame_chunk & u32::from(block_size == remaining);
|
||||
|
||||
/* Keep the original early capacity guard: even a raw block needs the
|
||||
* minimum block header plus the minimum compressible payload budget. */
|
||||
if remaining_capacity < ZSTD_BLOCK_HEADER_SIZE + MIN_CBLOCK_SIZE + 1 {
|
||||
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
||||
}
|
||||
|
||||
unsafe { (state.prepare_block)(state.callback_context, ip.cast(), block_size) };
|
||||
|
||||
let c_size = if state.use_target_c_block_size != 0 {
|
||||
unsafe {
|
||||
(state.compress_target)(
|
||||
state.callback_context,
|
||||
op.cast(),
|
||||
remaining_capacity,
|
||||
ip.cast(),
|
||||
block_size,
|
||||
last_block,
|
||||
)
|
||||
}
|
||||
} else if state.block_splitter_enabled != 0 {
|
||||
unsafe {
|
||||
(state.compress_split)(
|
||||
state.callback_context,
|
||||
op.cast(),
|
||||
remaining_capacity,
|
||||
ip.cast(),
|
||||
block_size,
|
||||
last_block,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let compressed_size = unsafe {
|
||||
(state.compress_internal)(
|
||||
state.callback_context,
|
||||
op.add(ZSTD_BLOCK_HEADER_SIZE).cast(),
|
||||
remaining_capacity - ZSTD_BLOCK_HEADER_SIZE,
|
||||
ip.cast(),
|
||||
block_size,
|
||||
last_block,
|
||||
)
|
||||
};
|
||||
if ERR_isError(compressed_size) {
|
||||
return compressed_size;
|
||||
}
|
||||
|
||||
if compressed_size == 0 {
|
||||
unsafe {
|
||||
ZSTD_rust_noCompressBlock(
|
||||
op.cast(),
|
||||
remaining_capacity,
|
||||
ip.cast(),
|
||||
block_size,
|
||||
last_block,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
ZSTD_rust_writeBlockHeader(op.cast(), compressed_size, block_size, last_block)
|
||||
};
|
||||
compressed_size + ZSTD_BLOCK_HEADER_SIZE
|
||||
}
|
||||
};
|
||||
|
||||
if ERR_isError(c_size) {
|
||||
return c_size;
|
||||
}
|
||||
|
||||
savings =
|
||||
savings.wrapping_add((block_size as c_longlong).wrapping_sub(c_size as c_longlong));
|
||||
unsafe {
|
||||
ip = ip.add(block_size);
|
||||
op = op.add(c_size);
|
||||
}
|
||||
remaining -= block_size;
|
||||
debug_assert!(c_size <= remaining_capacity);
|
||||
remaining_capacity = remaining_capacity.wrapping_sub(c_size);
|
||||
compressed_size_total = compressed_size_total.wrapping_add(c_size);
|
||||
unsafe { *state.is_first_block = 0 };
|
||||
}
|
||||
|
||||
if last_frame_chunk != 0 && compressed_size_total != 0 {
|
||||
unsafe { *state.stage = state.ending_stage };
|
||||
}
|
||||
compressed_size_total
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_compressFrameChunk(
|
||||
state: *const ZSTD_rust_frameChunkState,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
last_frame_chunk: c_uint,
|
||||
) -> usize {
|
||||
if state.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
unsafe {
|
||||
compress_frame_chunk_body_with(&*state, dst, dst_capacity, src, src_size, last_frame_chunk)
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
|
||||
///
|
||||
/// The C context and its function-pointer-bearing parameter structure remain
|
||||
@@ -2675,6 +2907,260 @@ mod tests {
|
||||
output
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FrameChunkTestContext {
|
||||
prepare_calls: usize,
|
||||
prepared_sizes: [usize; 4],
|
||||
target_calls: usize,
|
||||
split_calls: usize,
|
||||
internal_calls: usize,
|
||||
last_blocks: [c_uint; 4],
|
||||
checksum_calls: usize,
|
||||
checksum_size: usize,
|
||||
target_result: usize,
|
||||
split_result: usize,
|
||||
internal_result: usize,
|
||||
}
|
||||
|
||||
unsafe fn frame_chunk_test_context(context: *mut c_void) -> &'static mut FrameChunkTestContext {
|
||||
unsafe { &mut *context.cast::<FrameChunkTestContext>() }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frame_chunk_test_prepare(
|
||||
context: *mut c_void,
|
||||
_src: *const c_void,
|
||||
block_size: usize,
|
||||
) {
|
||||
let context = unsafe { frame_chunk_test_context(context) };
|
||||
if context.prepare_calls < context.prepared_sizes.len() {
|
||||
context.prepared_sizes[context.prepare_calls] = block_size;
|
||||
}
|
||||
context.prepare_calls += 1;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frame_chunk_test_target(
|
||||
context: *mut c_void,
|
||||
_dst: *mut c_void,
|
||||
_dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
last_block: c_uint,
|
||||
) -> usize {
|
||||
let context = unsafe { frame_chunk_test_context(context) };
|
||||
if context.target_calls < context.last_blocks.len() {
|
||||
context.last_blocks[context.target_calls] = last_block;
|
||||
}
|
||||
context.target_calls += 1;
|
||||
context.target_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frame_chunk_test_split(
|
||||
context: *mut c_void,
|
||||
_dst: *mut c_void,
|
||||
_dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
last_block: c_uint,
|
||||
) -> usize {
|
||||
let context = unsafe { frame_chunk_test_context(context) };
|
||||
if context.split_calls < context.last_blocks.len() {
|
||||
context.last_blocks[context.split_calls] = last_block;
|
||||
}
|
||||
context.split_calls += 1;
|
||||
context.split_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frame_chunk_test_internal(
|
||||
context: *mut c_void,
|
||||
_dst: *mut c_void,
|
||||
_dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
last_block: c_uint,
|
||||
) -> usize {
|
||||
let context = unsafe { frame_chunk_test_context(context) };
|
||||
if context.internal_calls < context.last_blocks.len() {
|
||||
context.last_blocks[context.internal_calls] = last_block;
|
||||
}
|
||||
context.internal_calls += 1;
|
||||
context.internal_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frame_chunk_test_checksum(
|
||||
context: *mut c_void,
|
||||
_src: *const c_void,
|
||||
src_size: usize,
|
||||
) {
|
||||
let context = unsafe { frame_chunk_test_context(context) };
|
||||
context.checksum_calls += 1;
|
||||
context.checksum_size = context.checksum_size.wrapping_add(src_size);
|
||||
}
|
||||
|
||||
fn frame_chunk_test_state(
|
||||
context: &mut FrameChunkTestContext,
|
||||
is_first_block: &mut c_int,
|
||||
stage: &mut c_int,
|
||||
use_target_c_block_size: c_int,
|
||||
block_splitter_enabled: c_int,
|
||||
checksum_flag: c_int,
|
||||
) -> ZSTD_rust_frameChunkState {
|
||||
let context = context as *mut FrameChunkTestContext as *mut c_void;
|
||||
ZSTD_rust_frameChunkState {
|
||||
callback_context: context,
|
||||
tmp_workspace: ptr::null_mut(),
|
||||
checksum_state: context,
|
||||
is_first_block,
|
||||
stage,
|
||||
tmp_wksp_size: 0,
|
||||
block_size_max: 4,
|
||||
savings: 0,
|
||||
pre_block_splitter_level: 1,
|
||||
strategy: ZSTD_FAST,
|
||||
use_target_c_block_size,
|
||||
block_splitter_enabled,
|
||||
checksum_flag,
|
||||
ending_stage: 77,
|
||||
prepare_block: frame_chunk_test_prepare,
|
||||
compress_target: frame_chunk_test_target,
|
||||
compress_split: frame_chunk_test_split,
|
||||
compress_internal: frame_chunk_test_internal,
|
||||
update_checksum: frame_chunk_test_checksum,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_chunk_internal_path_emits_blocks_and_updates_state_once() {
|
||||
let mut context = FrameChunkTestContext {
|
||||
internal_result: 2,
|
||||
..FrameChunkTestContext::default()
|
||||
};
|
||||
let mut is_first_block = 1;
|
||||
let mut stage = 0;
|
||||
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
|
||||
let source = [0x11u8; 8];
|
||||
let mut output = [0xa5u8; 16];
|
||||
|
||||
let result = unsafe {
|
||||
compress_frame_chunk_body_with(
|
||||
&state,
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
1,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, 10);
|
||||
assert_eq!(context.prepare_calls, 2);
|
||||
assert_eq!(context.prepared_sizes[..2], [4, 4]);
|
||||
assert_eq!(context.internal_calls, 2);
|
||||
assert_eq!(context.last_blocks[..2], [0, 1]);
|
||||
assert_eq!(context.checksum_calls, 1);
|
||||
assert_eq!(context.checksum_size, source.len());
|
||||
assert_eq!(is_first_block, 0);
|
||||
assert_eq!(stage, 77);
|
||||
assert_ne!(output[..3], [0xa5; 3]);
|
||||
assert_ne!(output[5..8], [0xa5; 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_chunk_dispatches_target_and_split_paths_before_internal() {
|
||||
let source = [0x22u8; 4];
|
||||
|
||||
let mut target_context = FrameChunkTestContext {
|
||||
target_result: 4,
|
||||
split_result: 5,
|
||||
internal_result: 6,
|
||||
..FrameChunkTestContext::default()
|
||||
};
|
||||
let mut target_first = 1;
|
||||
let mut target_stage = 0;
|
||||
let target_state = frame_chunk_test_state(
|
||||
&mut target_context,
|
||||
&mut target_first,
|
||||
&mut target_stage,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
);
|
||||
let mut target_output = [0xa5u8; 8];
|
||||
let target_result = unsafe {
|
||||
compress_frame_chunk_body_with(
|
||||
&target_state,
|
||||
target_output.as_mut_ptr().cast(),
|
||||
target_output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
assert_eq!(target_result, 4);
|
||||
assert_eq!(target_context.target_calls, 1);
|
||||
assert_eq!(target_context.split_calls, 0);
|
||||
assert_eq!(target_context.internal_calls, 0);
|
||||
|
||||
let mut split_context = FrameChunkTestContext {
|
||||
split_result: 5,
|
||||
internal_result: 6,
|
||||
..FrameChunkTestContext::default()
|
||||
};
|
||||
let mut split_first = 1;
|
||||
let mut split_stage = 0;
|
||||
let split_state = frame_chunk_test_state(
|
||||
&mut split_context,
|
||||
&mut split_first,
|
||||
&mut split_stage,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
);
|
||||
let mut split_output = [0xa5u8; 8];
|
||||
let split_result = unsafe {
|
||||
compress_frame_chunk_body_with(
|
||||
&split_state,
|
||||
split_output.as_mut_ptr().cast(),
|
||||
split_output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
assert_eq!(split_result, 5);
|
||||
assert_eq!(split_context.target_calls, 0);
|
||||
assert_eq!(split_context.split_calls, 1);
|
||||
assert_eq!(split_context.internal_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_chunk_keeps_state_unchanged_on_early_capacity_error() {
|
||||
let mut context = FrameChunkTestContext::default();
|
||||
let mut is_first_block = 1;
|
||||
let mut stage = 23;
|
||||
let state = frame_chunk_test_state(&mut context, &mut is_first_block, &mut stage, 0, 0, 1);
|
||||
let source = [0x33u8; 4];
|
||||
let mut output = [0xa5u8; 5];
|
||||
|
||||
let result = unsafe {
|
||||
compress_frame_chunk_body_with(
|
||||
&state,
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
source.as_ptr().cast(),
|
||||
source.len(),
|
||||
1,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
||||
assert_eq!(context.checksum_calls, 1);
|
||||
assert_eq!(context.prepare_calls, 0);
|
||||
assert_eq!(context.internal_calls, 0);
|
||||
assert_eq!(is_first_block, 1);
|
||||
assert_eq!(stage, 23);
|
||||
assert_eq!(output, [0xa5; 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_strategy_follows_source_size_tiers() {
|
||||
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);
|
||||
|
||||
Reference in New Issue
Block a user