diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index bb37402fc..e4a623b94 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -79,16 +79,47 @@ 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); -enum { - ZSTD_RUST_TARGET_CBLOCK_ACTION_RAW = 0, - ZSTD_RUST_TARGET_CBLOCK_ACTION_RLE = 1, - ZSTD_RUST_TARGET_CBLOCK_ACTION_COMPRESSED = 2, - ZSTD_RUST_TARGET_CBLOCK_ACTION_ERROR = 3 -}; -int ZSTD_rust_targetCBlockSizeAction(int bss, int isFirstBlock, - int maybeRLE, int isRLE, - size_t cSize, size_t srcSize, - int strategy); +/* 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. */ +typedef struct { + SeqStore_t* seqStore; + ZSTD_compressedBlockState_t** prevCBlock; + ZSTD_compressedBlockState_t** nextCBlock; + void* tmpWorkspace; + size_t tmpWkspSize; + int strategy; + int disableLiteralCompression; + int bmi2; + U32 windowLog; + size_t targetCBlockSize; + int isFirstBlock; +} ZSTD_rust_targetCBlockSizeState; +size_t ZSTD_rust_compressBlockTargetCBlockSize( + const ZSTD_rust_targetCBlockSizeState* state, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int bss, U32 lastBlock); +typedef char ZSTD_rust_target_cblock_state_layout[ + (offsetof(ZSTD_rust_targetCBlockSizeState, seqStore) == 0 + && offsetof(ZSTD_rust_targetCBlockSizeState, prevCBlock) == sizeof(void*) + && offsetof(ZSTD_rust_targetCBlockSizeState, nextCBlock) == 2 * sizeof(void*) + && offsetof(ZSTD_rust_targetCBlockSizeState, tmpWorkspace) == 3 * sizeof(void*) + && offsetof(ZSTD_rust_targetCBlockSizeState, tmpWkspSize) == 4 * sizeof(void*) + && offsetof(ZSTD_rust_targetCBlockSizeState, strategy) == 5 * sizeof(void*) + && offsetof(ZSTD_rust_targetCBlockSizeState, disableLiteralCompression) + == 5 * sizeof(void*) + sizeof(int) + && offsetof(ZSTD_rust_targetCBlockSizeState, bmi2) + == 5 * sizeof(void*) + 2 * sizeof(int) + && offsetof(ZSTD_rust_targetCBlockSizeState, windowLog) + == 5 * sizeof(void*) + 3 * sizeof(int) + && offsetof(ZSTD_rust_targetCBlockSizeState, targetCBlockSize) + == 5 * sizeof(void*) + 4 * sizeof(int) + && offsetof(ZSTD_rust_targetCBlockSizeState, isFirstBlock) + == 5 * sizeof(void*) + 4 * sizeof(int) + sizeof(size_t) + && sizeof(ZSTD_rust_targetCBlockSizeState) + == (sizeof(void*) == 8 ? 72 : 44)) + ? 1 : -1]; size_t ZSTD_rust_nextInputSizeHint(int inBufferMode, size_t blockSizeMax, size_t stableInNotConsumed, @@ -373,7 +404,6 @@ size_t ZSTD_rust_convertSequencesNoRepcodes( BlockSummary ZSTD_rust_get1BlockSummary(const ZSTD_Sequence* seqs, size_t nbSeqs); int ZSTD_rust_isRLE(const BYTE* src, size_t length); -int ZSTD_rust_maybeRLE(const SeqStore_t* seqStore); size_t ZSTD_rust_postProcessSequenceProducerResult( ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, size_t outSeqsCapacity, size_t srcSize); @@ -2634,15 +2664,6 @@ static int ZSTD_isRLE(const BYTE* src, size_t length) { return ZSTD_rust_isRLE(src, length); } -/* Returns true if the given block may be RLE. - * This is just a heuristic based on the compressibility. - * It may return both false positives and false negatives. - */ -static int ZSTD_maybeRLE(SeqStore_t const* seqStore) -{ - return ZSTD_rust_maybeRLE(seqStore); -} - static void ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) { @@ -3065,70 +3086,6 @@ out: return cSize; } -static size_t ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx* zc, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - const size_t bss, U32 lastBlock) -{ - int const isCompress = bss == ZSTDbss_compress; - int maybeRLE = 0; - int isRLE = 0; - int action; - - DEBUGLOG(6, "Attempting ZSTD_compressSuperBlock()"); - if (isCompress) { - maybeRLE = ZSTD_maybeRLE(&zc->seqStore); - isRLE = ZSTD_isRLE((BYTE const*)src, srcSize); - } - - action = ZSTD_rust_targetCBlockSizeAction( - (int)bss, (int)zc->isFirstBlock, maybeRLE, isRLE, - 0, srcSize, (int)zc->appliedParams.cParams.strategy); - if (action == ZSTD_RUST_TARGET_CBLOCK_ACTION_RLE) { - return ZSTD_rust_rleCompressBlock(dst, dstCapacity, *(BYTE const*)src, srcSize, lastBlock); - } - - if (isCompress) { - /* Attempt superblock compression. - * - * Note that compressed size of ZSTD_compressSuperBlock() is not bound by the - * standard ZSTD_compressBound(). This is a problem, because even if we have - * space now, taking an extra byte now could cause us to run out of space later - * and violate ZSTD_compressBound(). - * - * Define blockBound(blockSize) = blockSize + ZSTD_blockHeaderSize. - * - * In order to respect ZSTD_compressBound() we must attempt to emit a raw - * uncompressed block in these cases: - * * cSize == 0: Return code for an uncompressed block. - * * cSize == dstSize_tooSmall: We may have expanded beyond blockBound(srcSize). - * ZSTD_noCompressBlock() will return dstSize_tooSmall if we are really out of - * output space. - * * cSize >= blockBound(srcSize): We have expanded the block too much so - * emit an uncompressed block. - */ - { size_t const cSize = - ZSTD_compressSuperBlock(zc, dst, dstCapacity, src, srcSize, lastBlock); - action = ZSTD_rust_targetCBlockSizeAction( - (int)bss, (int)zc->isFirstBlock, 0, 0, - cSize, srcSize, (int)zc->appliedParams.cParams.strategy); - if (action == ZSTD_RUST_TARGET_CBLOCK_ACTION_ERROR) { - FORWARD_IF_ERROR(cSize, "ZSTD_compressSuperBlock failed"); - } - if (action == ZSTD_RUST_TARGET_CBLOCK_ACTION_COMPRESSED) { - ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); - return cSize; - } - } - } /* if (bss == ZSTDbss_compress)*/ - - DEBUGLOG(6, "Resorting to ZSTD_noCompressBlock()"); - /* Superblock compression failed, attempt to emit a single no compress block. - * The decoder will be able to stream this block since it is uncompressed. - */ - return ZSTD_rust_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); -} - static size_t ZSTD_compressBlock_targetCBlockSize(ZSTD_CCtx* zc, void* dst, size_t dstCapacity, const void* src, size_t srcSize, @@ -3140,7 +3097,21 @@ static size_t ZSTD_compressBlock_targetCBlockSize(ZSTD_CCtx* zc, (unsigned)dstCapacity, (unsigned)zc->blockState.matchState.window.dictLimit, (unsigned)zc->blockState.matchState.nextToUpdate, srcSize); FORWARD_IF_ERROR(bss, "ZSTD_buildSeqStore failed"); - cSize = ZSTD_compressBlock_targetCBlockSize_body(zc, dst, dstCapacity, src, srcSize, bss, lastBlock); + { ZSTD_rust_targetCBlockSizeState state; + state.seqStore = &zc->seqStore; + state.prevCBlock = &zc->blockState.prevCBlock; + state.nextCBlock = &zc->blockState.nextCBlock; + state.tmpWorkspace = zc->tmpWorkspace; + state.tmpWkspSize = zc->tmpWkspSize; + state.strategy = (int)zc->appliedParams.cParams.strategy; + state.disableLiteralCompression = ZSTD_literalsCompressionIsDisabled(&zc->appliedParams); + state.bmi2 = zc->bmi2; + state.windowLog = zc->appliedParams.cParams.windowLog; + state.targetCBlockSize = zc->appliedParams.targetCBlockSize; + state.isFirstBlock = zc->isFirstBlock; + cSize = ZSTD_rust_compressBlockTargetCBlockSize( + &state, dst, dstCapacity, src, srcSize, (int)bss, lastBlock); + } FORWARD_IF_ERROR(cSize, "ZSTD_compressBlock_targetCBlockSize_body failed"); if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index bae204bb8..bf9bf2d97 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -33,6 +33,7 @@ use crate::zstd_compress_stats::{ ZSTD_rust_resetSeqStore, ZSTD_rust_transferSequencesNoDelim, ZSTD_rust_transferSequencesWBlockDelim, }; +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}; @@ -163,6 +164,59 @@ const _: () = { ); }; +/// Explicit projection of the state used by the target-sized block body. +/// +/// Sequence-store construction and the matchfinder remain in C. This state +/// contains only the already-Rust-owned block-compression inputs and the two +/// pointer slots that the compressed-block confirmation leaf swaps. +#[repr(C)] +pub struct ZSTD_rust_targetCBlockSizeState { + seq_store: *mut SeqStore_t, + 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, + strategy: c_int, + disable_literal_compression: c_int, + bmi2: c_int, + window_log: c_uint, + target_c_block_size: usize, + is_first_block: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, seq_store) == 0); + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, prev_c_block) == size_of::()); + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, next_c_block) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_workspace) == 3 * size_of::()); + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, tmp_wksp_size) == 4 * size_of::()); + assert!(offset_of!(ZSTD_rust_targetCBlockSizeState, strategy) == 5 * size_of::()); + assert!( + offset_of!(ZSTD_rust_targetCBlockSizeState, disable_literal_compression) + == 5 * size_of::() + size_of::() + ); + assert!( + offset_of!(ZSTD_rust_targetCBlockSizeState, bmi2) + == 5 * size_of::() + 2 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_targetCBlockSizeState, window_log) + == 5 * size_of::() + 3 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_targetCBlockSizeState, target_c_block_size) + == 5 * size_of::() + 4 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_targetCBlockSizeState, is_first_block) + == 5 * size_of::() + 4 * size_of::() + size_of::() + ); + assert!( + size_of::() + == if size_of::() == 8 { 72 } else { 44 } + ); +}; + #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum TargetCBlockAction { @@ -227,6 +281,149 @@ fn target_c_block_size_action( } } +type TargetCBlockSuperBlockFn = unsafe extern "C" fn( + *const c_void, + *const c_void, + *mut c_void, + c_int, + c_int, + *mut c_void, + usize, + c_int, + c_uint, + usize, + *mut c_void, + usize, + *const c_void, + usize, + c_uint, +) -> usize; + +/// Rust implementation of `ZSTD_compressBlock_targetCBlockSize_body()`. +/// +/// The C caller has already built the sequence store. This function owns +/// only the RLE/superblock/raw decision and leaves context construction, +/// matchfinder state, frame progress, and outer repeat-mode cleanup in C. +#[allow(clippy::too_many_arguments)] +unsafe fn compress_block_target_c_block_size_body_with( + state: &ZSTD_rust_targetCBlockSizeState, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + bss: c_int, + last_block: c_uint, + compress_super_block: TargetCBlockSuperBlockFn, +) -> usize { + if state.seq_store.is_null() || state.prev_c_block.is_null() || state.next_c_block.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); + } + + let is_compress = bss == ZSTD_TARGET_CBLOCK_BSS_COMPRESS; + let (maybe_rle, is_rle) = if is_compress { + let maybe_rle = unsafe { ZSTD_rust_maybeRLE(state.seq_store) }; + let is_rle = unsafe { ZSTD_rust_isRLE(src.cast(), src_size) }; + (maybe_rle, is_rle) + } else { + (0, 0) + }; + + let action = target_c_block_size_action( + bss, + state.is_first_block, + maybe_rle, + is_rle, + 0, + src_size, + state.strategy, + ); + if action == TargetCBlockAction::Rle { + return unsafe { + ZSTD_rust_rleCompressBlock(dst, dst_capacity, *src.cast::(), src_size, last_block) + }; + } + + if is_compress { + /* The superblock result is not bounded by ZSTD_compressBound(). The + * policy helper therefore falls back to a raw block for zero, + * dstSize_tooSmall, or an expansion beyond blockBound(srcSize). */ + let c_size = unsafe { + compress_super_block( + state.seq_store.cast(), + prev_c_block.cast(), + next_c_block.cast(), + state.strategy, + state.disable_literal_compression, + state.tmp_workspace, + state.tmp_wksp_size, + state.bmi2, + state.window_log, + state.target_c_block_size, + dst, + dst_capacity, + src, + src_size, + last_block, + ) + }; + let action = target_c_block_size_action( + bss, + state.is_first_block, + 0, + 0, + c_size, + src_size, + state.strategy, + ); + if action == TargetCBlockAction::Error { + return c_size; + } + if action == TargetCBlockAction::Compressed { + unsafe { + ZSTD_rust_confirmRepcodesAndEntropyTables(state.prev_c_block, state.next_c_block); + } + return c_size; + } + } + + unsafe { ZSTD_rust_noCompressBlock(dst, dst_capacity, src, src_size, last_block) } +} + +/// C ABI entry point for the target-sized block body. The public and outer +/// block APIs remain C-owned; this is only the body after `ZSTD_buildSeqStore`. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressBlockTargetCBlockSize( + state: *const ZSTD_rust_targetCBlockSizeState, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + bss: c_int, + last_block: c_uint, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + unsafe { + compress_block_target_c_block_size_body_with( + &*state, + dst, + dst_capacity, + src, + src_size, + bss, + last_block, + ZSTD_rust_compressSuperBlock, + ) + } +} + /// Select the strategy used by the simple compression entry points. /// /// This is the Rust equivalent of the strategy portion of @@ -2167,6 +2364,285 @@ mod tests { ); } + #[allow(clippy::too_many_arguments)] + unsafe extern "C" fn target_block_test_superblock_error( + _seq_store: *const c_void, + _prev_cblock: *const c_void, + _next_cblock: *mut c_void, + _strategy: c_int, + _disable_literal_compression: c_int, + _workspace: *mut c_void, + _wksp_size: usize, + _bmi2: c_int, + _window_log: c_uint, + _target_cblock_size: usize, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + _last_block: c_uint, + ) -> usize { + ERROR(ZstdErrorCode::Generic) + } + + #[allow(clippy::too_many_arguments)] + unsafe extern "C" fn target_block_test_superblock_empty( + _seq_store: *const c_void, + _prev_cblock: *const c_void, + _next_cblock: *mut c_void, + _strategy: c_int, + _disable_literal_compression: c_int, + _workspace: *mut c_void, + _wksp_size: usize, + _bmi2: c_int, + _window_log: c_uint, + _target_cblock_size: usize, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + _last_block: c_uint, + ) -> usize { + 0 + } + + #[allow(clippy::too_many_arguments)] + unsafe extern "C" fn target_block_test_superblock_too_small( + _seq_store: *const c_void, + _prev_cblock: *const c_void, + _next_cblock: *mut c_void, + _strategy: c_int, + _disable_literal_compression: c_int, + _workspace: *mut c_void, + _wksp_size: usize, + _bmi2: c_int, + _window_log: c_uint, + _target_cblock_size: usize, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + _last_block: c_uint, + ) -> usize { + ERROR(ZstdErrorCode::DstSizeTooSmall) + } + + #[allow(clippy::too_many_arguments)] + unsafe extern "C" fn target_block_test_superblock_compressed( + _seq_store: *const c_void, + _prev_cblock: *const c_void, + _next_cblock: *mut c_void, + _strategy: c_int, + _disable_literal_compression: c_int, + _workspace: *mut c_void, + _wksp_size: usize, + _bmi2: c_int, + _window_log: c_uint, + _target_cblock_size: usize, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + _last_block: c_uint, + ) -> usize { + 1 + } + + fn target_block_test_state( + seq_store: &mut SeqStore_t, + 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, + is_first_block: c_int, + ) -> ZSTD_rust_targetCBlockSizeState { + *prev_c_block = prev_block as *mut ZSTD_compressedBlockState_t; + *next_c_block = next_block as *mut ZSTD_compressedBlockState_t; + ZSTD_rust_targetCBlockSizeState { + seq_store, + prev_c_block, + next_c_block, + tmp_workspace: ptr::null_mut(), + tmp_wksp_size: 0, + strategy: ZSTD_FAST, + disable_literal_compression: 0, + bmi2: 0, + window_log: 20, + target_c_block_size: 0, + is_first_block, + } + } + + fn target_block_test_seq_store() -> (SeqStore_t, [SeqDef; 1], [u8; 16]) { + let mut sequences = [SeqDef::default(); 1]; + let mut literals = [0u8; 16]; + let sequences_start = sequences.as_mut_ptr(); + let literals_start = literals.as_mut_ptr(); + let seq_store = SeqStore_t { + sequencesStart: sequences_start, + sequences: sequences_start, + litStart: literals_start, + lit: literals_start, + llCode: ptr::null_mut(), + mlCode: ptr::null_mut(), + ofCode: ptr::null_mut(), + maxNbSeq: sequences.len(), + maxNbLit: literals.len(), + longLengthType: 0, + longLengthPos: 0, + }; + (seq_store, sequences, literals) + } + + #[test] + fn target_block_body_emits_rle_only_for_nonfirst_repeated_blocks() { + let (mut seq_store, _sequences, _literals) = target_block_test_seq_store(); + 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 state = target_block_test_state( + &mut seq_store, + &mut prev_block, + &mut next_block, + &mut prev_c_block, + &mut next_c_block, + 0, + ); + let source = [0x5au8; 16]; + let mut output = [0xa5u8; 4]; + + let result = unsafe { + compress_block_target_c_block_size_body_with( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ZSTD_TARGET_CBLOCK_BSS_COMPRESS, + 1, + target_block_test_superblock_error, + ) + }; + + assert_eq!(result, 4); + assert_eq!(output[3], source[0]); + } + + #[test] + fn target_block_body_suppresses_first_block_rle_and_propagates_superblock_error() { + let (mut seq_store, _sequences, _literals) = target_block_test_seq_store(); + 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 state = target_block_test_state( + &mut seq_store, + &mut prev_block, + &mut next_block, + &mut prev_c_block, + &mut next_c_block, + 1, + ); + let source = [0x5au8; 16]; + let mut output = [0xa5u8; 16]; + + let result = unsafe { + compress_block_target_c_block_size_body_with( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ZSTD_TARGET_CBLOCK_BSS_COMPRESS, + 1, + target_block_test_superblock_error, + ) + }; + + assert_eq!(result, ERROR(ZstdErrorCode::Generic)); + assert_eq!(output, [0xa5u8; 16]); + } + + #[test] + fn target_block_body_falls_back_to_raw_for_empty_or_small_superblocks() { + let (mut seq_store, _sequences, _literals) = target_block_test_seq_store(); + 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 state = target_block_test_state( + &mut seq_store, + &mut prev_block, + &mut next_block, + &mut prev_c_block, + &mut next_c_block, + 1, + ); + let source = *b"raw fallback"; + + for superblock in [ + target_block_test_superblock_empty as TargetCBlockSuperBlockFn, + target_block_test_superblock_too_small, + ] { + let mut output = [0xa5u8; 32]; + let result = unsafe { + compress_block_target_c_block_size_body_with( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ZSTD_TARGET_CBLOCK_BSS_COMPRESS, + 0, + superblock, + ) + }; + + assert_eq!(result, source.len() + ZSTD_BLOCK_HEADER_SIZE); + assert_eq!(&output[ZSTD_BLOCK_HEADER_SIZE..result], &source); + } + } + + #[test] + fn target_block_body_swaps_state_only_for_compressed_output() { + let (mut seq_store, _sequences, _literals) = target_block_test_seq_store(); + let mut prev_block = zeroed_state(); + let mut next_block = zeroed_state(); + let mut prev_c_block = &mut prev_block as *mut ZSTD_compressedBlockState_t; + let mut next_c_block = &mut next_block as *mut ZSTD_compressedBlockState_t; + let state = target_block_test_state( + &mut seq_store, + &mut prev_block, + &mut next_block, + &mut prev_c_block, + &mut next_c_block, + 1, + ); + let prev_ptr = prev_c_block; + let next_ptr = next_c_block; + let source = [0x3cu8; 128]; + let mut output = [0xa5u8; 128]; + + let result = unsafe { + compress_block_target_c_block_size_body_with( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + ZSTD_TARGET_CBLOCK_BSS_COMPRESS, + 0, + target_block_test_superblock_compressed, + ) + }; + + assert_eq!(result, 1); + assert_eq!(unsafe { *state.prev_c_block }, next_ptr); + assert_eq!(unsafe { *state.next_c_block }, prev_ptr); + assert_eq!(output, [0xa5u8; 128]); + } + #[test] fn frame_progress_unknown_pledge_updates_counters() { let mut consumed = 7;