Compare commits

..
Author SHA1 Message Date
ddidderr 24488e4eaa feat(rust): port legacy v0.1 decoder
Port lib/legacy/zstd_v01.c (the frozen zstd v0.1 decoder) to
rust/src/legacy/zstd_v01.rs as the first legacy-format port on the new
scaffolding, and reduce the C file to a declaration-only shim that
keeps its header includes for configuration and platform preprocessor
behavior.

Frozen-decoder policy: zstd_v01.c embeds its own v0.1-era FSE and
Huff0 snapshot, distinct from every other release. The Rust port is a
line-by-line translation with the same table layouts (FSE_DTable as a
u32 header word plus packed newState/symbol/nbBits entries, the Huff0
u16 DTable with byte/nbBits pairs), the same arithmetic including
wrap-around and pointer-comparison quirks (e.g. the offset-vs-base
address check in ZSTD_execSequence), the same internal FSE error space
(size_t)-1..-7, and the same public ZSTD error codes. It reuses no
modern Rust entropy module; its only crate dependency is `errors`,
matching the C file's error_private.h include. The 32-bit-only reload
points are kept as compile-time conditions on usize::BITS.

Symbol takeover boundary: all nine ZSTDv01_* entry points from
zstd_v01.h now come from Rust as context-free #[no_mangle] extern "C"
functions (isError, decompress, decompressDCtx,
findFrameSizeInfoLegacy, createDCtx, freeDCtx, resetDCtx,
nextSrcSizeToDecompress, decompressContinue). zstd_legacy.h only uses
the first four for v0.1; streaming for v0.1-v0.3 intentionally returns
version_unsupported there, unchanged. The ZSTDv01_Dctx struct
definition moves entirely into Rust: C code only ever holds an opaque
pointer (zstd_v01.h forward-declares the type), and the context is
malloc/free-allocated exactly like the C version so create/free may
pair across the language boundary.

Byte-identity verification against the pristine pre-migration C build
(f8745da6, pure C, ZSTD_LEGACY_SUPPORT=1):

- Real v0.1 frames were generated by building the v0.1.0 git tag and
  compressing text, random, and 426 KB multi-block inputs. A one-shot
  ZSTD_decompress harness linked once against the pristine C libzstd.a
  and once against the Rust-backed libzstd.a produced bit-identical
  outputs for all frames.
- A direct ZSTDv01_* probe (one-shot decode, dst-too-small, truncated
  input, bad magic, findFrameSizeInfoLegacy, and the streaming
  continue loop) printed identical results, including exact error
  codes (-70 dstSize_tooSmall, -72 srcSize_wrong, -10 prefix_unknown)
  and identical dBound values.
- zstd -l -v on v0.1 files matches the pristine binary; CLI streaming
  decode of v0.1 fails with the same "Version not supported" in both,
  by design of zstd_legacy.h.

Unit tests embed three v0.1.0-generated fixtures (entropy-coded,
raw-block, and four-block frames) plus the truncation, bad-magic,
small-destination, and streaming-API cases, all asserting the exact C
error codes above. Note that `make -C tests test-legacy` only covers
v0.4+ frames, so the embedded fixtures and the harness comparison are
the actual v0.1 coverage.

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  --features legacy-v01 -- -D warnings && cargo test --all-targets
  --features legacy-v01 (127 tests, 9 for v0.1)
- cargo clippy/test --no-default-features --features
  decompression,legacy-v01 (module builds standalone)
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests, also with
  ZSTD_LEGACY_SUPPORT=1 (mixed Rust v0.1 + C v0.2-0.7 link)
- make -C tests test-rust-lib-smoke && make -C tests test-legacy
- make -C programs zstd (default and ZSTD_LEGACY_SUPPORT=1); nm shows
  the nine ZSTDv01_* symbols provided by Rust at level 1
- make -C lib libzstd.a ZSTD_LEGACY_SUPPORT=0 (no legacy symbols) and
  meson -Dlegacy_level=1 shared library exporting all nine
2026-07-11 14:25:23 +02:00
ddidderr c0a1b0bae1 build(rust): add legacy feature scaffolding
The legacy decoders (lib/legacy/zstd_v01.c .. zstd_v07.c) are next in
the Rust migration. Each of those files is a frozen snapshot of the
FSE/Huff0 entropy coders and frame logic of one historical release, so
their ports must not reuse the modern Rust entropy modules and must not
share code with each other: outputs and error codes have to stay
byte-identical to the frozen C forever. This commit installs the
build-system scaffolding so seven per-version ports can land
independently, each adding only its own module file plus a one-line
registration in rust/src/legacy/mod.rs.

Cargo grows features legacy-v01 .. legacy-v07. They are never default
features: the C build defaults differ per build system, so each build
system passes the list explicitly, derived from its own legacy
configuration:

- lib/Makefile and programs/Makefile map ZSTD_LEGACY_SUPPORT=N to the
  features for versions N..7 (0 disables legacy), mirroring the
  ZSTD_LEGACY_FILES selection in lib/libzstd.mk.
- tests/Makefile always enables all seven features because its
  ZSTDLEGACY_FILES wildcard compiles every lib/legacy/*.c regardless of
  the dispatch level.
- build/meson maps legacy_level exactly like the makefiles; build/cmake
  enables all seven whenever ZSTD_LEGACY_SUPPORT is ON because it
  always compiles all seven C files (ZSTD_LEGACY_LEVEL only selects the
  C dispatch).

Every build system also encodes the legacy selection in the Rust target
directory name (e.g. c1-d1-default-legacy5), for the same reason the
HUF mode is encoded there: a cached archive built for one configuration
must never be linked into a build expecting another. In tests/Makefile
the legacy level additionally flows into the existing HUF C-mode stamp,
so the flat C test objects (which bake -DZSTD_LEGACY_SUPPORT into the
dispatch) are rebuilt whenever the level changes. In programs/Makefile
the compress-only, decompress-only, and CLI archives keep
level-independent directories (RUST_HUF_MODE) because they are only
linked into ZSTD_LEGACY_SUPPORT=0 program variants and carry no legacy
features.

A feature whose version has not been ported yet gates nothing: the
module registration in rust/src/legacy/mod.rs is added by each port,
so enabling e.g. legacy-v05 today simply leaves that decoder in C.
This is what makes mixed C/Rust legacy levels link cleanly while the
seven ports land in any order.

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  -- -D warnings && cargo test --all-targets
- cargo clippy with --no-default-features --features
  decompression,legacy-v01 and with all seven legacy features
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests
- make -C tests test-rust-lib-smoke; make -C tests test-legacy
- make -C lib libzstd.a with ZSTD_LEGACY_SUPPORT=0, 1 and default (5)
- cmake configure and meson setup (including -Dlegacy_level=1) emit the
  expected --features lists and legacy-suffixed target directories
2026-07-11 14:24:47 +02:00
17 changed files with 3584 additions and 4988 deletions
+13 -1
View File
@@ -164,6 +164,18 @@ elseif(_zstd_huf_force_x2)
endif()
endif()
# This CMake build compiles every lib/legacy/zstd_v0N.c whenever legacy
# support is enabled (ZSTD_LEGACY_LEVEL only selects the C dispatch), so the
# Rust archive enables every per-version legacy feature to match. The build
# configuration encodes the switch so archives never mix.
set(_zstd_rust_legacy 0)
if(ZSTD_LEGACY_SUPPORT)
set(_zstd_rust_legacy 1)
list(APPEND _zstd_rust_features
legacy-v01 legacy-v02 legacy-v03 legacy-v04
legacy-v05 legacy-v06 legacy-v07)
endif()
set(_zstd_rust_target "${ZSTD_RUST_TARGET}")
if(NOT _zstd_rust_target AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_SIZEOF_VOID_P EQUAL 4)
@@ -190,7 +202,7 @@ if(_zstd_rust_features)
endif()
set(_zstd_rust_build_config
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-${_zstd_rust_huf_mode}")
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-${_zstd_rust_huf_mode}-legacy${_zstd_rust_legacy}")
set(ZSTD_RUST_MANIFEST "${ZSTD_SOURCE_DIR}/rust/Cargo.toml")
set(ZSTD_RUST_TARGET_DIR
"${CMAKE_CURRENT_BINARY_DIR}/rust-target/${_zstd_rust_build_config}")
+11 -1
View File
@@ -86,6 +86,16 @@ elif rust_huf_force_x2
rust_huf_c_args += '-DHUF_FORCE_DECOMPRESS_X2'
endif
# Mirror the legacy source selection below: legacy_level N compiles
# lib/legacy/zstd_v0N.c .. zstd_v07.c, so the Rust archive enables the
# matching per-version features. The build configuration encodes the level
# so archives built for different legacy levels never mix.
foreach i : [1, 2, 3, 4, 5, 6, 7]
if legacy_level != 0 and legacy_level <= i
rust_features += 'legacy-v0@0@'.format(i)
endif
endforeach
rust_target = get_option('zstd_rust_target')
if rust_target == ''
if host_machine_os == os_linux and \
@@ -96,7 +106,7 @@ if rust_target == ''
endif
endif
rust_build_config = 'c1-d1-' + rust_huf_mode
rust_build_config = 'c1-d1-' + rust_huf_mode + '-legacy@0@'.format(legacy_level)
rust_target_dir = join_paths(meson.current_build_dir(), 'rust-target', rust_build_config)
is_msvc = cc_id == compiler_msvc or cc_id == 'clang-cl'
rust_staticlib_name = is_msvc ? 'zstd_rs.lib' : 'libzstd_rs.a'
+12
View File
@@ -100,6 +100,18 @@ ifneq ($(ZSTD_LIB_DECOMPRESSION),0)
RUST_CARGO_FEATURES += $(RUST_HUF_FEATURE)
endif
endif
# Legacy decoders follow the ZSTD_LEGACY_FILES selection exactly: level N
# enables versions v0.N .. v0.7 (0 disables legacy). The build directory
# also encodes the level, so archives for different legacy levels never mix.
RUST_LEGACY_FEATURES :=
ifneq ($(ZSTD_LEGACY_SUPPORT), 0)
ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0)
RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7))
endif
endif
RUST_CARGO_FEATURES += $(RUST_LEGACY_FEATURES)
RUST_BUILD_CONFIG := $(RUST_BUILD_CONFIG)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_CARGO_FEATURES := $(subst $(space),$(comma),$(strip $(RUST_CARGO_FEATURES)))
RUST_TARGET ?=
+858 -194
View File
@@ -38,123 +38,6 @@ size_t ZSTD_rust_writeFrameHeader(void* dst, size_t dstCapacity,
U32 windowLog, U64 pledgedSrcSize,
U32 dictID);
/* Context-free compression-parameter selection and sizing leaves live in
* Rust (rust/src/zstd_compress_params.rs). This file retains
* configuration-sensitive policy: the excluded-block-compressor strategy
* cascade, private ZSTD_CCtx_params handling, and sanitizer workspace
* policy, which it feeds to the leaves as explicit scalar inputs. The
* ZSTD_CParamMode_e and ZSTD_ParamSwitch_e enums are passed as int; the
* Rust side mirrors their values. */
int ZSTD_rust_params_maxCLevel(void);
int ZSTD_rust_params_minCLevel(void);
int ZSTD_rust_params_defaultCLevel(void);
ZSTD_bounds ZSTD_rust_params_getBounds(int param);
size_t ZSTD_rust_params_checkCParams(ZSTD_compressionParameters cParams);
ZSTD_compressionParameters
ZSTD_rust_params_clampCParams(ZSTD_compressionParameters cParams);
U32 ZSTD_rust_params_cycleLog(U32 hashLog, int strategy);
ZSTD_compressionParameters
ZSTD_rust_params_selectCParams(int compressionLevel, U64 srcSizeHint,
size_t dictSize, int mode);
ZSTD_compressionParameters
ZSTD_rust_params_adjustCParams(ZSTD_compressionParameters cParams, U64 srcSize,
size_t dictSize, int mode,
int useRowMatchFinder);
ZSTD_parameters ZSTD_rust_params_makeParams(ZSTD_compressionParameters cParams);
size_t ZSTD_rust_params_maxNbSeq(size_t blockSize, U32 minMatch,
int useSequenceProducer);
size_t ZSTD_rust_params_resolveMaxBlockSize(size_t maxBlockSize);
typedef struct {
U32 hashLog3Max;
size_t matchTSize;
size_t optimalTSize;
size_t asanRedzoneSize;
} ZSTD_rustMatchStateSizing;
size_t ZSTD_rust_params_estimateMatchStateSize(
ZSTD_compressionParameters cParams, int useRowMatchFinder,
int enableDedicatedDictSearch, U32 forCCtx,
const ZSTD_rustMatchStateSizing* sizing);
typedef struct {
size_t cdictSize;
size_t hufWorkspaceSize;
U32 hashLog3Max;
size_t matchTSize;
size_t optimalTSize;
size_t asanRedzoneSize;
} ZSTD_rustCDictSizing;
size_t ZSTD_rust_params_estimateCDictSizeFromCParams(
size_t dictSize, ZSTD_compressionParameters cParams,
int dictLoadMethod, const ZSTD_rustCDictSizing* sizing);
/* Sequence statistics and seqStore entropy compression live in Rust
* (rust/src/zstd_compress_stats.rs), which also exports ZSTD_seqToCodes()
* under its original name. The shims below extract the sequence store, the
* entropy-table leaves, and the only two ZSTD_CCtx_params fields these paths
* read (the strategy and the literals-compression switch), so the private
* parameter structure layout never crosses the language boundary. The
* shared leaf layouts are pinned by the compile-time asserts that follow. */
size_t ZSTD_rust_entropyCompressSeqStore_internal(
void* dst, size_t dstCapacity,
const void* literals, size_t litSize,
const SeqStore_t* seqStorePtr,
const ZSTD_entropyCTables_t* prevEntropy,
ZSTD_entropyCTables_t* nextEntropy,
int strategy, int disableLiteralCompression,
void* entropyWorkspace, size_t entropyWkspSize,
int bmi2);
size_t ZSTD_rust_entropyCompressSeqStore(
const SeqStore_t* seqStorePtr,
const ZSTD_entropyCTables_t* prevEntropy,
ZSTD_entropyCTables_t* nextEntropy,
int strategy, int disableLiteralCompression,
void* dst, size_t dstCapacity,
size_t srcSize,
void* entropyWorkspace, size_t entropyWkspSize,
int bmi2);
size_t ZSTD_rust_buildBlockEntropyStats(
const SeqStore_t* seqStorePtr,
const ZSTD_entropyCTables_t* prevEntropy,
ZSTD_entropyCTables_t* nextEntropy,
int strategy, int disableLiteralCompression,
ZSTD_entropyCTablesMetadata_t* entropyMetadata,
void* workspace, size_t wkspSize);
size_t ZSTD_rust_copyBlockSequences(
SeqCollector* seqCollector, const SeqStore_t* seqStore,
const U32 prevRepcodes[ZSTD_REP_NUM]);
typedef char ZSTD_rust_stats_seqdef_layout[(sizeof(SeqDef) == 8) ? 1 : -1];
typedef char ZSTD_rust_stats_seqstore_long_length_pos[
(offsetof(SeqStore_t, longLengthPos) == 9 * sizeof(size_t) + 4) ? 1 : -1];
typedef char ZSTD_rust_stats_seqstore_layout[
(sizeof(SeqStore_t) == 9 * sizeof(size_t) + 8) ? 1 : -1];
typedef char ZSTD_rust_stats_huf_layout[
(sizeof(ZSTD_hufCTables_t) == 258 * sizeof(size_t)) ? 1 : -1];
typedef char ZSTD_rust_stats_fse_layout[(sizeof(ZSTD_fseCTables_t) == 3552) ? 1 : -1];
typedef char ZSTD_rust_stats_entropy_fse_offset[
(offsetof(ZSTD_entropyCTables_t, fse) == sizeof(ZSTD_hufCTables_t)) ? 1 : -1];
typedef char ZSTD_rust_stats_huf_metadata_buffer[
(offsetof(ZSTD_hufCTablesMetadata_t, hufDesBuffer) == 4
&& ZSTD_MAX_HUF_HEADER_SIZE == 128) ? 1 : -1];
typedef char ZSTD_rust_stats_fse_metadata_buffer[
(offsetof(ZSTD_fseCTablesMetadata_t, fseTablesBuffer) == 12
&& ZSTD_MAX_FSE_HEADERS_SIZE == 133) ? 1 : -1];
typedef char ZSTD_rust_stats_entropy_metadata_layout[
(offsetof(ZSTD_entropyCTablesMetadata_t, fseMetadata)
== sizeof(ZSTD_hufCTablesMetadata_t)) ? 1 : -1];
typedef char ZSTD_rust_stats_sequence_layout[(sizeof(ZSTD_Sequence) == 16) ? 1 : -1];
typedef char ZSTD_rust_stats_seqcollector_layout[
(offsetof(SeqCollector, seqStart) == sizeof(size_t)
&& offsetof(SeqCollector, seqIndex) == 2 * sizeof(size_t)
&& sizeof(SeqCollector) == 4 * sizeof(size_t)) ? 1 : -1];
#if ZSTD_ADDRESS_SANITIZER && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE)
# define ZSTD_RUST_ASAN_REDZONE_SIZE ((size_t)ZSTD_CWKSP_ASAN_REDZONE_SIZE)
#else
# define ZSTD_RUST_ASAN_REDZONE_SIZE ((size_t)0)
#endif
/* ***************************************************************
* Tuning parameters
*****************************************************************/
@@ -393,7 +276,11 @@ static int ZSTD_resolveExternalSequenceValidation(int mode) {
/* Resolves maxBlockSize to the default if no value is present. */
static size_t ZSTD_resolveMaxBlockSize(size_t maxBlockSize) {
return ZSTD_rust_params_resolveMaxBlockSize(maxBlockSize);
if (maxBlockSize == 0) {
return ZSTD_BLOCKSIZE_MAX;
} else {
return maxBlockSize;
}
}
static ZSTD_ParamSwitch_e ZSTD_resolveExternalRepcodeSearch(ZSTD_ParamSwitch_e value, int cLevel) {
@@ -533,17 +420,45 @@ ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param)
switch(param)
{
/* The compression level and the seven core compression parameters are
* bounded by the Rust leaf. */
case ZSTD_c_compressionLevel:
bounds.lowerBound = ZSTD_minCLevel();
bounds.upperBound = ZSTD_maxCLevel();
return bounds;
case ZSTD_c_windowLog:
bounds.lowerBound = ZSTD_WINDOWLOG_MIN;
bounds.upperBound = ZSTD_WINDOWLOG_MAX;
return bounds;
case ZSTD_c_hashLog:
bounds.lowerBound = ZSTD_HASHLOG_MIN;
bounds.upperBound = ZSTD_HASHLOG_MAX;
return bounds;
case ZSTD_c_chainLog:
bounds.lowerBound = ZSTD_CHAINLOG_MIN;
bounds.upperBound = ZSTD_CHAINLOG_MAX;
return bounds;
case ZSTD_c_searchLog:
bounds.lowerBound = ZSTD_SEARCHLOG_MIN;
bounds.upperBound = ZSTD_SEARCHLOG_MAX;
return bounds;
case ZSTD_c_minMatch:
bounds.lowerBound = ZSTD_MINMATCH_MIN;
bounds.upperBound = ZSTD_MINMATCH_MAX;
return bounds;
case ZSTD_c_targetLength:
bounds.lowerBound = ZSTD_TARGETLENGTH_MIN;
bounds.upperBound = ZSTD_TARGETLENGTH_MAX;
return bounds;
case ZSTD_c_strategy:
return ZSTD_rust_params_getBounds((int)param);
bounds.lowerBound = ZSTD_STRATEGY_MIN;
bounds.upperBound = ZSTD_STRATEGY_MAX;
return bounds;
case ZSTD_c_contentSizeFlag:
bounds.lowerBound = 0;
@@ -1470,7 +1385,14 @@ size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset)
@return : 0, or an error code if one value is beyond authorized range */
size_t ZSTD_checkCParams(ZSTD_compressionParameters cParams)
{
return ZSTD_rust_params_checkCParams(cParams);
BOUNDCHECK(ZSTD_c_windowLog, (int)cParams.windowLog);
BOUNDCHECK(ZSTD_c_chainLog, (int)cParams.chainLog);
BOUNDCHECK(ZSTD_c_hashLog, (int)cParams.hashLog);
BOUNDCHECK(ZSTD_c_searchLog, (int)cParams.searchLog);
BOUNDCHECK(ZSTD_c_minMatch, (int)cParams.minMatch);
BOUNDCHECK(ZSTD_c_targetLength,(int)cParams.targetLength);
BOUNDCHECK(ZSTD_c_strategy, (int)cParams.strategy);
return 0;
}
/** ZSTD_clampCParams() :
@@ -1479,14 +1401,63 @@ size_t ZSTD_checkCParams(ZSTD_compressionParameters cParams)
static ZSTD_compressionParameters
ZSTD_clampCParams(ZSTD_compressionParameters cParams)
{
return ZSTD_rust_params_clampCParams(cParams);
# define CLAMP_TYPE(cParam, val, type) \
do { \
ZSTD_bounds const bounds = ZSTD_cParam_getBounds(cParam); \
if ((int)val<bounds.lowerBound) val=(type)bounds.lowerBound; \
else if ((int)val>bounds.upperBound) val=(type)bounds.upperBound; \
} while (0)
# define CLAMP(cParam, val) CLAMP_TYPE(cParam, val, unsigned)
CLAMP(ZSTD_c_windowLog, cParams.windowLog);
CLAMP(ZSTD_c_chainLog, cParams.chainLog);
CLAMP(ZSTD_c_hashLog, cParams.hashLog);
CLAMP(ZSTD_c_searchLog, cParams.searchLog);
CLAMP(ZSTD_c_minMatch, cParams.minMatch);
CLAMP(ZSTD_c_targetLength,cParams.targetLength);
CLAMP_TYPE(ZSTD_c_strategy,cParams.strategy, ZSTD_strategy);
return cParams;
}
/** ZSTD_cycleLog() :
* condition for correct operation : hashLog > 1 */
U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat)
{
return ZSTD_rust_params_cycleLog(hashLog, (int)strat);
U32 const btScale = ((U32)strat >= (U32)ZSTD_btlazy2);
return hashLog - btScale;
}
/** ZSTD_dictAndWindowLog() :
* Returns an adjusted window log that is large enough to fit the source and the dictionary.
* The zstd format says that the entire dictionary is valid if one byte of the dictionary
* is within the window. So the hashLog and chainLog should be large enough to reference both
* the dictionary and the window. So we must use this adjusted dictAndWindowLog when downsizing
* the hashLog and windowLog.
* NOTE: srcSize must not be ZSTD_CONTENTSIZE_UNKNOWN.
*/
static U32 ZSTD_dictAndWindowLog(U32 windowLog, U64 srcSize, U64 dictSize)
{
const U64 maxWindowSize = 1ULL << ZSTD_WINDOWLOG_MAX;
/* No dictionary ==> No change */
if (dictSize == 0) {
return windowLog;
}
assert(windowLog <= ZSTD_WINDOWLOG_MAX);
assert(srcSize != ZSTD_CONTENTSIZE_UNKNOWN); /* Handled in ZSTD_adjustCParams_internal() */
{
U64 const windowSize = 1ULL << windowLog;
U64 const dictAndWindowSize = dictSize + windowSize;
/* If the window size is already large enough to fit both the source and the dictionary
* then just use the window size. Otherwise adjust so that it fits the dictionary and
* the window.
*/
if (windowSize >= dictSize + srcSize) {
return windowLog; /* Window size large enough already */
} else if (dictAndWindowSize >= maxWindowSize) {
return ZSTD_WINDOWLOG_MAX; /* Larger than max window log */
} else {
return ZSTD_highbit32((U32)dictAndWindowSize - 1) + 1;
}
}
}
/** ZSTD_adjustCParams_internal() :
@@ -1503,6 +1474,8 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar,
ZSTD_CParamMode_e mode,
ZSTD_ParamSwitch_e useRowMatchFinder)
{
const U64 minSrcSize = 513; /* (1<<9) + 1 */
const U64 maxWindowResize = 1ULL << (ZSTD_WINDOWLOG_MAX-1);
assert(ZSTD_checkCParams(cPar)==0);
/* Cascade the selected strategy down to the next-highest one built into
@@ -1547,13 +1520,90 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar,
}
#endif
/* The remaining adjustment logic is context-free and lives in Rust. The
* short-cache and row-hash tag widths the leaf assumes are fixed
* private-header constants; keep them checked here. */
ZSTD_STATIC_ASSERT(ZSTD_SHORT_CACHE_TAG_BITS == 8);
ZSTD_STATIC_ASSERT(ZSTD_ROW_HASH_TAG_BITS == 8);
return ZSTD_rust_params_adjustCParams(cPar, (U64)srcSize, dictSize,
(int)mode, (int)useRowMatchFinder);
switch (mode) {
case ZSTD_cpm_unknown:
case ZSTD_cpm_noAttachDict:
/* If we don't know the source size, don't make any
* assumptions about it. We will already have selected
* smaller parameters if a dictionary is in use.
*/
break;
case ZSTD_cpm_createCDict:
/* Assume a small source size when creating a dictionary
* with an unknown source size.
*/
if (dictSize && srcSize == ZSTD_CONTENTSIZE_UNKNOWN)
srcSize = minSrcSize;
break;
case ZSTD_cpm_attachDict:
/* Dictionary has its own dedicated parameters which have
* already been selected. We are selecting parameters
* for only the source.
*/
dictSize = 0;
break;
default:
assert(0);
break;
}
/* resize windowLog if input is small enough, to use less memory */
if ( (srcSize <= maxWindowResize)
&& (dictSize <= maxWindowResize) ) {
U32 const tSize = (U32)(srcSize + dictSize);
static U32 const hashSizeMin = 1 << ZSTD_HASHLOG_MIN;
U32 const srcLog = (tSize < hashSizeMin) ? ZSTD_HASHLOG_MIN :
ZSTD_highbit32(tSize-1) + 1;
if (cPar.windowLog > srcLog) cPar.windowLog = srcLog;
}
if (srcSize != ZSTD_CONTENTSIZE_UNKNOWN) {
U32 const dictAndWindowLog = ZSTD_dictAndWindowLog(cPar.windowLog, (U64)srcSize, (U64)dictSize);
U32 const cycleLog = ZSTD_cycleLog(cPar.chainLog, cPar.strategy);
if (cPar.hashLog > dictAndWindowLog+1) cPar.hashLog = dictAndWindowLog+1;
if (cycleLog > dictAndWindowLog)
cPar.chainLog -= (cycleLog - dictAndWindowLog);
}
if (cPar.windowLog < ZSTD_WINDOWLOG_ABSOLUTEMIN)
cPar.windowLog = ZSTD_WINDOWLOG_ABSOLUTEMIN; /* minimum wlog required for valid frame header */
/* We can't use more than 32 bits of hash in total, so that means that we require:
* (hashLog + 8) <= 32 && (chainLog + 8) <= 32
*/
if (mode == ZSTD_cpm_createCDict && ZSTD_CDictIndicesAreTagged(&cPar)) {
U32 const maxShortCacheHashLog = 32 - ZSTD_SHORT_CACHE_TAG_BITS;
if (cPar.hashLog > maxShortCacheHashLog) {
cPar.hashLog = maxShortCacheHashLog;
}
if (cPar.chainLog > maxShortCacheHashLog) {
cPar.chainLog = maxShortCacheHashLog;
}
}
/* At this point, we aren't 100% sure if we are using the row match finder.
* Unless it is explicitly disabled, conservatively assume that it is enabled.
* In this case it will only be disabled for small sources, so shrinking the
* hash log a little bit shouldn't result in any ratio loss.
*/
if (useRowMatchFinder == ZSTD_ps_auto)
useRowMatchFinder = ZSTD_ps_enable;
/* We can't hash more than 32-bits in total. So that means that we require:
* (hashLog - rowLog + 8) <= 32
*/
if (ZSTD_rowMatchFinderUsed(cPar.strategy, useRowMatchFinder)) {
/* Switch to 32-entry rows if searchLog is 5 (or more) */
U32 const rowLog = BOUNDED(4, cPar.searchLog, 6);
U32 const maxRowHashLog = 32 - ZSTD_ROW_HASH_TAG_BITS;
U32 const maxHashLog = maxRowHashLog + rowLog;
assert(cPar.hashLog >= rowLog);
if (cPar.hashLog > maxHashLog) {
cPar.hashLog = maxHashLog;
}
}
return cPar;
}
ZSTD_compressionParameters
@@ -1604,31 +1654,47 @@ ZSTD_sizeof_matchState(const ZSTD_compressionParameters* const cParams,
const int enableDedicatedDictSearch,
const U32 forCCtx)
{
ZSTD_rustMatchStateSizing sizing;
sizing.hashLog3Max = ZSTD_HASHLOG3_MAX;
sizing.matchTSize = sizeof(ZSTD_match_t);
sizing.optimalTSize = sizeof(ZSTD_optimal_t);
sizing.asanRedzoneSize = ZSTD_RUST_ASAN_REDZONE_SIZE;
/* chain table size should be 0 for fast or row-hash strategies */
size_t const chainSize = ZSTD_allocateChainTable(cParams->strategy, useRowMatchFinder, enableDedicatedDictSearch && !forCCtx)
? ((size_t)1 << cParams->chainLog)
: 0;
size_t const hSize = ((size_t)1) << cParams->hashLog;
U32 const hashLog3 = (forCCtx && cParams->minMatch==3) ? MIN(ZSTD_HASHLOG3_MAX, cParams->windowLog) : 0;
size_t const h3Size = hashLog3 ? ((size_t)1) << hashLog3 : 0;
/* We don't use ZSTD_cwksp_alloc_size() here because the tables aren't
* surrounded by redzones in ASAN. */
size_t const tableSpace = chainSize * sizeof(U32)
+ hSize * sizeof(U32)
+ h3Size * sizeof(U32);
size_t const optPotentialSpace =
ZSTD_cwksp_aligned64_alloc_size((MaxML+1) * sizeof(U32))
+ ZSTD_cwksp_aligned64_alloc_size((MaxLL+1) * sizeof(U32))
+ ZSTD_cwksp_aligned64_alloc_size((MaxOff+1) * sizeof(U32))
+ ZSTD_cwksp_aligned64_alloc_size((1<<Litbits) * sizeof(U32))
+ ZSTD_cwksp_aligned64_alloc_size(ZSTD_OPT_SIZE * sizeof(ZSTD_match_t))
+ ZSTD_cwksp_aligned64_alloc_size(ZSTD_OPT_SIZE * sizeof(ZSTD_optimal_t));
size_t const lazyAdditionalSpace = ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)
? ZSTD_cwksp_aligned64_alloc_size(hSize)
: 0;
size_t const optSpace = (forCCtx && (cParams->strategy >= ZSTD_btopt))
? optPotentialSpace
: 0;
size_t const slackSpace = ZSTD_cwksp_slack_space_required();
/* The Rust leaf hardcodes the frozen format bounds and workspace rules;
* keep them checked against the private headers here. */
ZSTD_STATIC_ASSERT(MaxML == 52 && MaxLL == 35 && MaxOff == 31);
ZSTD_STATIC_ASSERT(Litbits == 8 && ZSTD_OPT_SIZE == 4099);
ZSTD_STATIC_ASSERT(ZSTD_CWKSP_ALIGNMENT_BYTES == 64);
/* tables are guaranteed to be sized in multiples of 64 bytes (or 16 uint32_t) */
ZSTD_STATIC_ASSERT(ZSTD_HASHLOG_MIN >= 4 && ZSTD_WINDOWLOG_MIN >= 4 && ZSTD_CHAINLOG_MIN >= 4);
assert(useRowMatchFinder != ZSTD_ps_auto);
return ZSTD_rust_params_estimateMatchStateSize(*cParams,
(int)useRowMatchFinder,
enableDedicatedDictSearch,
forCCtx, &sizing);
DEBUGLOG(4, "chainSize: %u - hSize: %u - h3Size: %u",
(U32)chainSize, (U32)hSize, (U32)h3Size);
return tableSpace + optSpace + slackSpace + lazyAdditionalSpace;
}
/* Helper function for calculating memory requirements.
* Gives a tighter bound than ZSTD_sequenceBound() by taking minMatch into account. */
static size_t ZSTD_maxNbSeq(size_t blockSize, unsigned minMatch, int useSequenceProducer) {
return ZSTD_rust_params_maxNbSeq(blockSize, minMatch, useSequenceProducer);
U32 const divider = (minMatch==3 || useSequenceProducer) ? 3 : 4;
return blockSize / divider;
}
static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal(
@@ -2622,7 +2688,33 @@ static void ZSTD_reduceIndex (ZSTD_MatchState_t* ms, ZSTD_CCtx_params const* par
/* See doc/zstd_compression_format.md for detailed format description */
/* ZSTD_seqToCodes() lives in rust/src/zstd_compress_stats.rs. */
int ZSTD_seqToCodes(const SeqStore_t* seqStorePtr)
{
const SeqDef* const sequences = seqStorePtr->sequencesStart;
BYTE* const llCodeTable = seqStorePtr->llCode;
BYTE* const ofCodeTable = seqStorePtr->ofCode;
BYTE* const mlCodeTable = seqStorePtr->mlCode;
U32 const nbSeq = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart);
U32 u;
int longOffsets = 0;
assert(nbSeq <= seqStorePtr->maxNbSeq);
for (u=0; u<nbSeq; u++) {
U32 const llv = sequences[u].litLength;
U32 const ofCode = ZSTD_highbit32(sequences[u].offBase);
U32 const mlv = sequences[u].mlBase;
llCodeTable[u] = (BYTE)ZSTD_LLcode(llv);
ofCodeTable[u] = (BYTE)ofCode;
mlCodeTable[u] = (BYTE)ZSTD_MLcode(mlv);
assert(!(MEM_64bits() && ofCode >= STREAM_ACCUMULATOR_MIN));
if (MEM_32bits() && ofCode >= STREAM_ACCUMULATOR_MIN)
longOffsets = 1;
}
if (seqStorePtr->longLengthType==ZSTD_llt_literalLength)
llCodeTable[seqStorePtr->longLengthPos] = MaxLL;
if (seqStorePtr->longLengthType==ZSTD_llt_matchLength)
mlCodeTable[seqStorePtr->longLengthPos] = MaxML;
return longOffsets;
}
/* ZSTD_useTargetCBlockSize():
* Returns if target compressed block size param is being used.
@@ -2646,11 +2738,150 @@ static int ZSTD_blockSplitterEnabled(ZSTD_CCtx_params* cctxParams)
return (cctxParams->postBlockSplitter == ZSTD_ps_enable);
}
/* ZSTD_buildSequencesStatistics() and the ZSTD_entropyCompressSeqStore*()
* implementations live in rust/src/zstd_compress_stats.rs. The wrappers
* below keep the original signatures and extract the only two
* ZSTD_CCtx_params fields those paths read. */
/* Type returned by ZSTD_buildSequencesStatistics containing finalized symbol encoding types
* and size of the sequences statistics
*/
typedef struct {
U32 LLtype;
U32 Offtype;
U32 MLtype;
size_t size;
size_t lastCountSize; /* Accounts for bug in 1.3.4. More detail in ZSTD_entropyCompressSeqStore_internal() */
int longOffsets;
} ZSTD_symbolEncodingTypeStats_t;
/* ZSTD_buildSequencesStatistics():
* Returns a ZSTD_symbolEncodingTypeStats_t, or a zstd error code in the `size` field.
* Modifies `nextEntropy` to have the appropriate values as a side effect.
* nbSeq must be greater than 0.
*
* entropyWkspSize must be of size at least ENTROPY_WORKSPACE_SIZE - (MaxSeq + 1)*sizeof(U32)
*/
static ZSTD_symbolEncodingTypeStats_t
ZSTD_buildSequencesStatistics(
const SeqStore_t* seqStorePtr, size_t nbSeq,
const ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy,
BYTE* dst, const BYTE* const dstEnd,
ZSTD_strategy strategy, unsigned* countWorkspace,
void* entropyWorkspace, size_t entropyWkspSize)
{
BYTE* const ostart = dst;
const BYTE* const oend = dstEnd;
BYTE* op = ostart;
FSE_CTable* CTable_LitLength = nextEntropy->litlengthCTable;
FSE_CTable* CTable_OffsetBits = nextEntropy->offcodeCTable;
FSE_CTable* CTable_MatchLength = nextEntropy->matchlengthCTable;
const BYTE* const ofCodeTable = seqStorePtr->ofCode;
const BYTE* const llCodeTable = seqStorePtr->llCode;
const BYTE* const mlCodeTable = seqStorePtr->mlCode;
ZSTD_symbolEncodingTypeStats_t stats;
stats.lastCountSize = 0;
/* convert length/distances into codes */
stats.longOffsets = ZSTD_seqToCodes(seqStorePtr);
assert(op <= oend);
assert(nbSeq != 0); /* ZSTD_selectEncodingType() divides by nbSeq */
/* build CTable for Literal Lengths */
{ unsigned max = MaxLL;
size_t const mostFrequent = HIST_countFast_wksp(countWorkspace, &max, llCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); /* can't fail */
DEBUGLOG(5, "Building LL table");
nextEntropy->litlength_repeatMode = prevEntropy->litlength_repeatMode;
stats.LLtype = ZSTD_selectEncodingType(&nextEntropy->litlength_repeatMode,
countWorkspace, max, mostFrequent, nbSeq,
LLFSELog, prevEntropy->litlengthCTable,
LL_defaultNorm, LL_defaultNormLog,
ZSTD_defaultAllowed, strategy);
assert(set_basic < set_compressed && set_rle < set_compressed);
assert(!(stats.LLtype < set_compressed && nextEntropy->litlength_repeatMode != FSE_repeat_none)); /* We don't copy tables */
{ size_t const countSize = ZSTD_buildCTable(
op, (size_t)(oend - op),
CTable_LitLength, LLFSELog, (SymbolEncodingType_e)stats.LLtype,
countWorkspace, max, llCodeTable, nbSeq,
LL_defaultNorm, LL_defaultNormLog, MaxLL,
prevEntropy->litlengthCTable,
sizeof(prevEntropy->litlengthCTable),
entropyWorkspace, entropyWkspSize);
if (ZSTD_isError(countSize)) {
DEBUGLOG(3, "ZSTD_buildCTable for LitLens failed");
stats.size = countSize;
return stats;
}
if (stats.LLtype == set_compressed)
stats.lastCountSize = countSize;
op += countSize;
assert(op <= oend);
} }
/* build CTable for Offsets */
{ unsigned max = MaxOff;
size_t const mostFrequent = HIST_countFast_wksp(
countWorkspace, &max, ofCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); /* can't fail */
/* We can only use the basic table if max <= DefaultMaxOff, otherwise the offsets are too large */
ZSTD_DefaultPolicy_e const defaultPolicy = (max <= DefaultMaxOff) ? ZSTD_defaultAllowed : ZSTD_defaultDisallowed;
DEBUGLOG(5, "Building OF table");
nextEntropy->offcode_repeatMode = prevEntropy->offcode_repeatMode;
stats.Offtype = ZSTD_selectEncodingType(&nextEntropy->offcode_repeatMode,
countWorkspace, max, mostFrequent, nbSeq,
OffFSELog, prevEntropy->offcodeCTable,
OF_defaultNorm, OF_defaultNormLog,
defaultPolicy, strategy);
assert(!(stats.Offtype < set_compressed && nextEntropy->offcode_repeatMode != FSE_repeat_none)); /* We don't copy tables */
{ size_t const countSize = ZSTD_buildCTable(
op, (size_t)(oend - op),
CTable_OffsetBits, OffFSELog, (SymbolEncodingType_e)stats.Offtype,
countWorkspace, max, ofCodeTable, nbSeq,
OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff,
prevEntropy->offcodeCTable,
sizeof(prevEntropy->offcodeCTable),
entropyWorkspace, entropyWkspSize);
if (ZSTD_isError(countSize)) {
DEBUGLOG(3, "ZSTD_buildCTable for Offsets failed");
stats.size = countSize;
return stats;
}
if (stats.Offtype == set_compressed)
stats.lastCountSize = countSize;
op += countSize;
assert(op <= oend);
} }
/* build CTable for MatchLengths */
{ unsigned max = MaxML;
size_t const mostFrequent = HIST_countFast_wksp(
countWorkspace, &max, mlCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); /* can't fail */
DEBUGLOG(5, "Building ML table (remaining space : %i)", (int)(oend-op));
nextEntropy->matchlength_repeatMode = prevEntropy->matchlength_repeatMode;
stats.MLtype = ZSTD_selectEncodingType(&nextEntropy->matchlength_repeatMode,
countWorkspace, max, mostFrequent, nbSeq,
MLFSELog, prevEntropy->matchlengthCTable,
ML_defaultNorm, ML_defaultNormLog,
ZSTD_defaultAllowed, strategy);
assert(!(stats.MLtype < set_compressed && nextEntropy->matchlength_repeatMode != FSE_repeat_none)); /* We don't copy tables */
{ size_t const countSize = ZSTD_buildCTable(
op, (size_t)(oend - op),
CTable_MatchLength, MLFSELog, (SymbolEncodingType_e)stats.MLtype,
countWorkspace, max, mlCodeTable, nbSeq,
ML_defaultNorm, ML_defaultNormLog, MaxML,
prevEntropy->matchlengthCTable,
sizeof(prevEntropy->matchlengthCTable),
entropyWorkspace, entropyWkspSize);
if (ZSTD_isError(countSize)) {
DEBUGLOG(3, "ZSTD_buildCTable for MatchLengths failed");
stats.size = countSize;
return stats;
}
if (stats.MLtype == set_compressed)
stats.lastCountSize = countSize;
op += countSize;
assert(op <= oend);
} }
stats.size = (size_t)(op-ostart);
return stats;
}
/* ZSTD_entropyCompressSeqStore_internal():
* compresses both literals and sequences
* Returns compressed size of block, or a zstd error.
*/
#define SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO 20
MEM_STATIC size_t
ZSTD_entropyCompressSeqStore_internal(
void* dst, size_t dstCapacity,
@@ -2662,13 +2893,150 @@ ZSTD_entropyCompressSeqStore_internal(
void* entropyWorkspace, size_t entropyWkspSize,
const int bmi2)
{
return ZSTD_rust_entropyCompressSeqStore_internal(
dst, dstCapacity,
literals, litSize,
seqStorePtr, prevEntropy, nextEntropy,
(int)cctxParams->cParams.strategy,
ZSTD_literalsCompressionIsDisabled(cctxParams),
entropyWorkspace, entropyWkspSize, bmi2);
ZSTD_strategy const strategy = cctxParams->cParams.strategy;
unsigned* count = (unsigned*)entropyWorkspace;
FSE_CTable* CTable_LitLength = nextEntropy->fse.litlengthCTable;
FSE_CTable* CTable_OffsetBits = nextEntropy->fse.offcodeCTable;
FSE_CTable* CTable_MatchLength = nextEntropy->fse.matchlengthCTable;
const SeqDef* const sequences = seqStorePtr->sequencesStart;
const size_t nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart);
const BYTE* const ofCodeTable = seqStorePtr->ofCode;
const BYTE* const llCodeTable = seqStorePtr->llCode;
const BYTE* const mlCodeTable = seqStorePtr->mlCode;
BYTE* const ostart = (BYTE*)dst;
BYTE* const oend = ostart + dstCapacity;
BYTE* op = ostart;
size_t lastCountSize;
int longOffsets = 0;
entropyWorkspace = count + (MaxSeq + 1);
entropyWkspSize -= (MaxSeq + 1) * sizeof(*count);
DEBUGLOG(5, "ZSTD_entropyCompressSeqStore_internal (nbSeq=%zu, dstCapacity=%zu)", nbSeq, dstCapacity);
ZSTD_STATIC_ASSERT(HUF_WORKSPACE_SIZE >= (1<<MAX(MLFSELog,LLFSELog)));
assert(entropyWkspSize >= HUF_WORKSPACE_SIZE);
/* Compress literals */
{ size_t const numSequences = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart);
/* Base suspicion of uncompressibility on ratio of literals to sequences */
int const suspectUncompressible = (numSequences == 0) || (litSize / numSequences >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO);
size_t const cSize = ZSTD_compressLiterals(
op, dstCapacity,
literals, litSize,
entropyWorkspace, entropyWkspSize,
&prevEntropy->huf, &nextEntropy->huf,
cctxParams->cParams.strategy,
ZSTD_literalsCompressionIsDisabled(cctxParams),
suspectUncompressible, bmi2);
FORWARD_IF_ERROR(cSize, "ZSTD_compressLiterals failed");
assert(cSize <= dstCapacity);
op += cSize;
}
/* Sequences Header */
RETURN_ERROR_IF((oend-op) < 3 /*max nbSeq Size*/ + 1 /*seqHead*/,
dstSize_tooSmall, "Can't fit seq hdr in output buf!");
if (nbSeq < 128) {
*op++ = (BYTE)nbSeq;
} else if (nbSeq < LONGNBSEQ) {
op[0] = (BYTE)((nbSeq>>8) + 0x80);
op[1] = (BYTE)nbSeq;
op+=2;
} else {
op[0]=0xFF;
MEM_writeLE16(op+1, (U16)(nbSeq - LONGNBSEQ));
op+=3;
}
assert(op <= oend);
if (nbSeq==0) {
/* Copy the old tables over as if we repeated them */
ZSTD_memcpy(&nextEntropy->fse, &prevEntropy->fse, sizeof(prevEntropy->fse));
return (size_t)(op - ostart);
}
{ BYTE* const seqHead = op++;
/* build stats for sequences */
const ZSTD_symbolEncodingTypeStats_t stats =
ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq,
&prevEntropy->fse, &nextEntropy->fse,
op, oend,
strategy, count,
entropyWorkspace, entropyWkspSize);
FORWARD_IF_ERROR(stats.size, "ZSTD_buildSequencesStatistics failed!");
*seqHead = (BYTE)((stats.LLtype<<6) + (stats.Offtype<<4) + (stats.MLtype<<2));
lastCountSize = stats.lastCountSize;
op += stats.size;
longOffsets = stats.longOffsets;
}
{ size_t const bitstreamSize = ZSTD_encodeSequences(
op, (size_t)(oend - op),
CTable_MatchLength, mlCodeTable,
CTable_OffsetBits, ofCodeTable,
CTable_LitLength, llCodeTable,
sequences, nbSeq,
longOffsets, bmi2);
FORWARD_IF_ERROR(bitstreamSize, "ZSTD_encodeSequences failed");
op += bitstreamSize;
assert(op <= oend);
/* zstd versions <= 1.3.4 mistakenly report corruption when
* FSE_readNCount() receives a buffer < 4 bytes.
* Fixed by https://github.com/facebook/zstd/pull/1146.
* This can happen when the last set_compressed table present is 2
* bytes and the bitstream is only one byte.
* In this exceedingly rare case, we will simply emit an uncompressed
* block, since it isn't worth optimizing.
*/
if (lastCountSize && (lastCountSize + bitstreamSize) < 4) {
/* lastCountSize >= 2 && bitstreamSize > 0 ==> lastCountSize == 3 */
assert(lastCountSize + bitstreamSize == 3);
DEBUGLOG(5, "Avoiding bug in zstd decoder in versions <= 1.3.4 by "
"emitting an uncompressed block.");
return 0;
}
}
DEBUGLOG(5, "compressed block size : %u", (unsigned)(op - ostart));
return (size_t)(op - ostart);
}
static size_t
ZSTD_entropyCompressSeqStore_wExtLitBuffer(
void* dst, size_t dstCapacity,
const void* literals, size_t litSize,
size_t blockSize,
const SeqStore_t* seqStorePtr,
const ZSTD_entropyCTables_t* prevEntropy,
ZSTD_entropyCTables_t* nextEntropy,
const ZSTD_CCtx_params* cctxParams,
void* entropyWorkspace, size_t entropyWkspSize,
int bmi2)
{
size_t const cSize = ZSTD_entropyCompressSeqStore_internal(
dst, dstCapacity,
literals, litSize,
seqStorePtr, prevEntropy, nextEntropy, cctxParams,
entropyWorkspace, entropyWkspSize, bmi2);
if (cSize == 0) return 0;
/* When srcSize <= dstCapacity, there is enough space to write a raw uncompressed block.
* Since we ran out of space, block must be not compressible, so fall back to raw uncompressed block.
*/
if ((cSize == ERROR(dstSize_tooSmall)) & (blockSize <= dstCapacity)) {
DEBUGLOG(4, "not enough dstCapacity (%zu) for ZSTD_entropyCompressSeqStore_internal()=> do not compress block", dstCapacity);
return 0; /* block not compressed */
}
FORWARD_IF_ERROR(cSize, "ZSTD_entropyCompressSeqStore_internal failed");
/* Check compressibility */
{ size_t const maxCSize = blockSize - ZSTD_minGain(blockSize, cctxParams->cParams.strategy);
if (cSize >= maxCSize) return 0; /* block not compressed */
}
DEBUGLOG(5, "ZSTD_entropyCompressSeqStore() cSize: %zu", cSize);
/* libzstd decoder before > v1.5.4 is not compatible with compressed blocks of size ZSTD_BLOCKSIZE_MAX exactly.
* This restriction is indirectly already fulfilled by respecting ZSTD_minGain() condition above.
*/
assert(cSize < ZSTD_BLOCKSIZE_MAX);
return cSize;
}
static size_t
@@ -2682,12 +3050,13 @@ ZSTD_entropyCompressSeqStore(
void* entropyWorkspace, size_t entropyWkspSize,
int bmi2)
{
return ZSTD_rust_entropyCompressSeqStore(
seqStorePtr, prevEntropy, nextEntropy,
(int)cctxParams->cParams.strategy,
ZSTD_literalsCompressionIsDisabled(cctxParams),
return ZSTD_entropyCompressSeqStore_wExtLitBuffer(
dst, dstCapacity,
seqStorePtr->litStart, (size_t)(seqStorePtr->lit - seqStorePtr->litStart),
srcSize,
seqStorePtr,
prevEntropy, nextEntropy,
cctxParams,
entropyWorkspace, entropyWkspSize,
bmi2);
}
@@ -3057,8 +3426,87 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize)
static size_t ZSTD_copyBlockSequences(SeqCollector* seqCollector, const SeqStore_t* seqStore, const U32 prevRepcodes[ZSTD_REP_NUM])
{
/* The implementation lives in rust/src/zstd_compress_stats.rs. */
return ZSTD_rust_copyBlockSequences(seqCollector, seqStore, prevRepcodes);
const SeqDef* inSeqs = seqStore->sequencesStart;
const size_t nbInSequences = (size_t)(seqStore->sequences - inSeqs);
const size_t nbInLiterals = (size_t)(seqStore->lit - seqStore->litStart);
ZSTD_Sequence* outSeqs = seqCollector->seqIndex == 0 ? seqCollector->seqStart : seqCollector->seqStart + seqCollector->seqIndex;
const size_t nbOutSequences = nbInSequences + 1;
size_t nbOutLiterals = 0;
Repcodes_t repcodes;
size_t i;
/* Bounds check that we have enough space for every input sequence
* and the block delimiter
*/
assert(seqCollector->seqIndex <= seqCollector->maxSequences);
RETURN_ERROR_IF(
nbOutSequences > (size_t)(seqCollector->maxSequences - seqCollector->seqIndex),
dstSize_tooSmall,
"Not enough space to copy sequences");
ZSTD_memcpy(&repcodes, prevRepcodes, sizeof(repcodes));
for (i = 0; i < nbInSequences; ++i) {
U32 rawOffset;
outSeqs[i].litLength = inSeqs[i].litLength;
outSeqs[i].matchLength = inSeqs[i].mlBase + MINMATCH;
outSeqs[i].rep = 0;
/* Handle the possible single length >= 64K
* There can only be one because we add MINMATCH to every match length,
* and blocks are at most 128K.
*/
if (i == seqStore->longLengthPos) {
if (seqStore->longLengthType == ZSTD_llt_literalLength) {
outSeqs[i].litLength += 0x10000;
} else if (seqStore->longLengthType == ZSTD_llt_matchLength) {
outSeqs[i].matchLength += 0x10000;
}
}
/* Determine the raw offset given the offBase, which may be a repcode. */
if (OFFBASE_IS_REPCODE(inSeqs[i].offBase)) {
const U32 repcode = OFFBASE_TO_REPCODE(inSeqs[i].offBase);
assert(repcode > 0);
outSeqs[i].rep = repcode;
if (outSeqs[i].litLength != 0) {
rawOffset = repcodes.rep[repcode - 1];
} else {
if (repcode == 3) {
assert(repcodes.rep[0] > 1);
rawOffset = repcodes.rep[0] - 1;
} else {
rawOffset = repcodes.rep[repcode];
}
}
} else {
rawOffset = OFFBASE_TO_OFFSET(inSeqs[i].offBase);
}
outSeqs[i].offset = rawOffset;
/* Update repcode history for the sequence */
ZSTD_updateRep(repcodes.rep,
inSeqs[i].offBase,
inSeqs[i].litLength == 0);
nbOutLiterals += outSeqs[i].litLength;
}
/* Insert last literals (if any exist) in the block as a sequence with ml == off == 0.
* If there are no last literals, then we'll emit (of: 0, ml: 0, ll: 0), which is a marker
* for the block boundary, according to the API.
*/
assert(nbInLiterals >= nbOutLiterals);
{
const size_t lastLLSize = nbInLiterals - nbOutLiterals;
outSeqs[nbInSequences].litLength = (U32)lastLLSize;
outSeqs[nbInSequences].matchLength = 0;
outSeqs[nbInSequences].offset = 0;
assert(nbOutSequences == nbInSequences + 1);
}
seqCollector->seqIndex += nbOutSequences;
assert(seqCollector->seqIndex <= seqCollector->maxSequences);
return 0;
}
/* ZSTD_sequenceBound() lives in rust/src/zstd_compress_api.rs. */
@@ -3154,14 +3602,178 @@ writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock)
DEBUGLOG(5, "writeBlockHeader: cSize: %zu blockSize: %zu lastBlock: %u", cSize, blockSize, lastBlock);
}
/** ZSTD_buildBlockEntropyStats_literals() :
* Builds entropy for the literals.
* Stores literals block type (raw, rle, compressed, repeat) and
* huffman description table to hufMetadata.
* Requires ENTROPY_WORKSPACE_SIZE workspace
* @return : size of huffman description table, or an error code
*/
static size_t
ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSize,
const ZSTD_hufCTables_t* prevHuf,
ZSTD_hufCTables_t* nextHuf,
ZSTD_hufCTablesMetadata_t* hufMetadata,
const int literalsCompressionIsDisabled,
void* workspace, size_t wkspSize,
int hufFlags)
{
BYTE* const wkspStart = (BYTE*)workspace;
BYTE* const wkspEnd = wkspStart + wkspSize;
BYTE* const countWkspStart = wkspStart;
unsigned* const countWksp = (unsigned*)workspace;
const size_t countWkspSize = (HUF_SYMBOLVALUE_MAX + 1) * sizeof(unsigned);
BYTE* const nodeWksp = countWkspStart + countWkspSize;
const size_t nodeWkspSize = (size_t)(wkspEnd - nodeWksp);
unsigned maxSymbolValue = HUF_SYMBOLVALUE_MAX;
unsigned huffLog = LitHufLog;
HUF_repeat repeat = prevHuf->repeatMode;
DEBUGLOG(5, "ZSTD_buildBlockEntropyStats_literals (srcSize=%zu)", srcSize);
/* Prepare nextEntropy assuming reusing the existing table */
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
if (literalsCompressionIsDisabled) {
DEBUGLOG(5, "set_basic - disabled");
hufMetadata->hType = set_basic;
return 0;
}
/* small ? don't even attempt compression (speed opt) */
#ifndef COMPRESS_LITERALS_SIZE_MIN
# define COMPRESS_LITERALS_SIZE_MIN 63 /* heuristic */
#endif
{ size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN;
if (srcSize <= minLitSize) {
DEBUGLOG(5, "set_basic - too small");
hufMetadata->hType = set_basic;
return 0;
} }
/* Scan input and build symbol stats */
{ size_t const largest =
HIST_count_wksp (countWksp, &maxSymbolValue,
(const BYTE*)src, srcSize,
workspace, wkspSize);
FORWARD_IF_ERROR(largest, "HIST_count_wksp failed");
if (largest == srcSize) {
/* only one literal symbol */
DEBUGLOG(5, "set_rle");
hufMetadata->hType = set_rle;
return 0;
}
if (largest <= (srcSize >> 7)+4) {
/* heuristic: likely not compressible */
DEBUGLOG(5, "set_basic - no gain");
hufMetadata->hType = set_basic;
return 0;
} }
/* Validate the previous Huffman table */
if (repeat == HUF_repeat_check
&& !HUF_validateCTable((HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue)) {
repeat = HUF_repeat_none;
}
/* Build Huffman Tree */
ZSTD_memset(nextHuf->CTable, 0, sizeof(nextHuf->CTable));
huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue, nodeWksp, nodeWkspSize, nextHuf->CTable, countWksp, hufFlags);
assert(huffLog <= LitHufLog);
{ size_t const maxBits = HUF_buildCTable_wksp((HUF_CElt*)nextHuf->CTable, countWksp,
maxSymbolValue, huffLog,
nodeWksp, nodeWkspSize);
FORWARD_IF_ERROR(maxBits, "HUF_buildCTable_wksp");
huffLog = (U32)maxBits;
}
{ /* Build and write the CTable */
size_t const newCSize = HUF_estimateCompressedSize(
(HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue);
size_t const hSize = HUF_writeCTable_wksp(
hufMetadata->hufDesBuffer, sizeof(hufMetadata->hufDesBuffer),
(HUF_CElt*)nextHuf->CTable, maxSymbolValue, huffLog,
nodeWksp, nodeWkspSize);
/* Check against repeating the previous CTable */
if (repeat != HUF_repeat_none) {
size_t const oldCSize = HUF_estimateCompressedSize(
(HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue);
if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) {
DEBUGLOG(5, "set_repeat - smaller");
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
hufMetadata->hType = set_repeat;
return 0;
} }
if (newCSize + hSize >= srcSize) {
DEBUGLOG(5, "set_basic - no gains");
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
hufMetadata->hType = set_basic;
return 0;
}
DEBUGLOG(5, "set_compressed (hSize=%u)", (U32)hSize);
hufMetadata->hType = set_compressed;
nextHuf->repeatMode = HUF_repeat_check;
return hSize;
}
}
/* ZSTD_buildDummySequencesStatistics():
* Returns a ZSTD_symbolEncodingTypeStats_t with all encoding types as set_basic,
* and updates nextEntropy to the appropriate repeatMode.
*/
static ZSTD_symbolEncodingTypeStats_t
ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy)
{
ZSTD_symbolEncodingTypeStats_t stats = {set_basic, set_basic, set_basic, 0, 0, 0};
nextEntropy->litlength_repeatMode = FSE_repeat_none;
nextEntropy->offcode_repeatMode = FSE_repeat_none;
nextEntropy->matchlength_repeatMode = FSE_repeat_none;
return stats;
}
/** ZSTD_buildBlockEntropyStats_sequences() :
* Builds entropy for the sequences.
* Stores symbol compression modes and fse table to fseMetadata.
* Requires ENTROPY_WORKSPACE_SIZE wksp.
* @return : size of fse tables or error code */
static size_t
ZSTD_buildBlockEntropyStats_sequences(
const SeqStore_t* seqStorePtr,
const ZSTD_fseCTables_t* prevEntropy,
ZSTD_fseCTables_t* nextEntropy,
const ZSTD_CCtx_params* cctxParams,
ZSTD_fseCTablesMetadata_t* fseMetadata,
void* workspace, size_t wkspSize)
{
ZSTD_strategy const strategy = cctxParams->cParams.strategy;
size_t const nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart);
BYTE* const ostart = fseMetadata->fseTablesBuffer;
BYTE* const oend = ostart + sizeof(fseMetadata->fseTablesBuffer);
BYTE* op = ostart;
unsigned* countWorkspace = (unsigned*)workspace;
unsigned* entropyWorkspace = countWorkspace + (MaxSeq + 1);
size_t entropyWorkspaceSize = wkspSize - (MaxSeq + 1) * sizeof(*countWorkspace);
ZSTD_symbolEncodingTypeStats_t stats;
DEBUGLOG(5, "ZSTD_buildBlockEntropyStats_sequences (nbSeq=%zu)", nbSeq);
stats = nbSeq != 0 ? ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq,
prevEntropy, nextEntropy, op, oend,
strategy, countWorkspace,
entropyWorkspace, entropyWorkspaceSize)
: ZSTD_buildDummySequencesStatistics(nextEntropy);
FORWARD_IF_ERROR(stats.size, "ZSTD_buildSequencesStatistics failed!");
fseMetadata->llType = (SymbolEncodingType_e) stats.LLtype;
fseMetadata->ofType = (SymbolEncodingType_e) stats.Offtype;
fseMetadata->mlType = (SymbolEncodingType_e) stats.MLtype;
fseMetadata->lastCountSize = stats.lastCountSize;
return stats.size;
}
/** ZSTD_buildBlockEntropyStats() :
* Builds entropy for the block.
* Requires workspace size ENTROPY_WORKSPACE_SIZE
* @return : 0 on success, or an error code
* Note : also employed in superblock
*
* The implementation, together with its literals/sequences/dummy helpers,
* lives in rust/src/zstd_compress_stats.rs.
*/
size_t ZSTD_buildBlockEntropyStats(
const SeqStore_t* seqStorePtr,
@@ -3171,12 +3783,26 @@ size_t ZSTD_buildBlockEntropyStats(
ZSTD_entropyCTablesMetadata_t* entropyMetadata,
void* workspace, size_t wkspSize)
{
return ZSTD_rust_buildBlockEntropyStats(
seqStorePtr, prevEntropy, nextEntropy,
(int)cctxParams->cParams.strategy,
ZSTD_literalsCompressionIsDisabled(cctxParams),
entropyMetadata,
workspace, wkspSize);
size_t const litSize = (size_t)(seqStorePtr->lit - seqStorePtr->litStart);
int const huf_useOptDepth = (cctxParams->cParams.strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD);
int const hufFlags = huf_useOptDepth ? HUF_flags_optimalDepth : 0;
entropyMetadata->hufMetadata.hufDesSize =
ZSTD_buildBlockEntropyStats_literals(seqStorePtr->litStart, litSize,
&prevEntropy->huf, &nextEntropy->huf,
&entropyMetadata->hufMetadata,
ZSTD_literalsCompressionIsDisabled(cctxParams),
workspace, wkspSize, hufFlags);
FORWARD_IF_ERROR(entropyMetadata->hufMetadata.hufDesSize, "ZSTD_buildBlockEntropyStats_literals failed");
entropyMetadata->fseMetadata.fseTablesSize =
ZSTD_buildBlockEntropyStats_sequences(seqStorePtr,
&prevEntropy->fse, &nextEntropy->fse,
cctxParams,
&entropyMetadata->fseMetadata,
workspace, wkspSize);
FORWARD_IF_ERROR(entropyMetadata->fseMetadata.fseTablesSize, "ZSTD_buildBlockEntropyStats_sequences failed");
return 0;
}
/* Returns the size estimate for the literals section (header + content) of a block */
@@ -4804,15 +5430,15 @@ size_t ZSTD_estimateCDictSize_advanced(
size_t dictSize, ZSTD_compressionParameters cParams,
ZSTD_dictLoadMethod_e dictLoadMethod)
{
ZSTD_rustCDictSizing sizing;
sizing.cdictSize = sizeof(ZSTD_CDict);
sizing.hufWorkspaceSize = HUF_WORKSPACE_SIZE;
sizing.hashLog3Max = ZSTD_HASHLOG3_MAX;
sizing.matchTSize = sizeof(ZSTD_match_t);
sizing.optimalTSize = sizeof(ZSTD_optimal_t);
sizing.asanRedzoneSize = ZSTD_RUST_ASAN_REDZONE_SIZE;
return ZSTD_rust_params_estimateCDictSizeFromCParams(
dictSize, cParams, (int)dictLoadMethod, &sizing);
DEBUGLOG(5, "sizeof(ZSTD_CDict) : %u", (unsigned)sizeof(ZSTD_CDict));
return ZSTD_cwksp_alloc_size(sizeof(ZSTD_CDict))
+ ZSTD_cwksp_alloc_size(HUF_WORKSPACE_SIZE)
/* enableDedicatedDictSearch == 1 ensures that CDict estimation will not be too small
* in case we are using DDS with row-hash. */
+ ZSTD_sizeof_matchState(&cParams, ZSTD_resolveRowMatchFinderMode(ZSTD_ps_auto, &cParams),
/* enableDedicatedDictSearch */ 1, /* forCCtx */ 0)
+ (dictLoadMethod == ZSTD_dlm_byRef ? 0
: ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, sizeof(void *))));
}
size_t ZSTD_estimateCDictSize(size_t dictSize, int compressionLevel)
@@ -6950,12 +7576,11 @@ size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output)
/*-===== Pre-defined compression levels =====-*/
/* The compression-level tables (formerly included from clevels.h) live in
* rust/src/zstd_compress_params.rs. */
#include "clevels.h"
int ZSTD_maxCLevel(void) { return ZSTD_rust_params_maxCLevel(); }
int ZSTD_minCLevel(void) { return ZSTD_rust_params_minCLevel(); }
int ZSTD_defaultCLevel(void) { return ZSTD_rust_params_defaultCLevel(); }
int ZSTD_maxCLevel(void) { return ZSTD_MAX_CLEVEL; }
int ZSTD_minCLevel(void) { return (int)-ZSTD_TARGETLENGTH_MAX; }
int ZSTD_defaultCLevel(void) { return ZSTD_CLEVEL_DEFAULT; }
static ZSTD_compressionParameters ZSTD_dedicatedDictSearch_getCParams(int const compressionLevel, size_t const dictSize)
{
@@ -7014,6 +7639,26 @@ static void ZSTD_dedicatedDictSearch_revertCParams(
}
}
static U64 ZSTD_getCParamRowSize(U64 srcSizeHint, size_t dictSize, ZSTD_CParamMode_e mode)
{
switch (mode) {
case ZSTD_cpm_unknown:
case ZSTD_cpm_noAttachDict:
case ZSTD_cpm_createCDict:
break;
case ZSTD_cpm_attachDict:
dictSize = 0;
break;
default:
assert(0);
break;
}
{ int const unknown = srcSizeHint == ZSTD_CONTENTSIZE_UNKNOWN;
size_t const addedSize = unknown && dictSize > 0 ? 500 : 0;
return unknown && dictSize == 0 ? ZSTD_CONTENTSIZE_UNKNOWN : srcSizeHint+dictSize+addedSize;
}
}
/*! ZSTD_getCParams_internal() :
* @return ZSTD_compressionParameters structure for a selected compression level, srcSize and dictSize.
* Note: srcSizeHint 0 means 0, use ZSTD_CONTENTSIZE_UNKNOWN for unknown.
@@ -7021,12 +7666,27 @@ static void ZSTD_dedicatedDictSearch_revertCParams(
* Note: `mode` controls how we treat the `dictSize`. See docs for `ZSTD_CParamMode_e`. */
static ZSTD_compressionParameters ZSTD_getCParams_internal(int compressionLevel, unsigned long long srcSizeHint, size_t dictSize, ZSTD_CParamMode_e mode)
{
/* Table selection is context-free and lives in Rust; the adjustment step
* stays behind ZSTD_adjustCParams_internal() so this build's strategy
* cascade applies. */
ZSTD_compressionParameters const cp = ZSTD_rust_params_selectCParams(
compressionLevel, srcSizeHint, dictSize, (int)mode);
return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode, ZSTD_ps_auto);
U64 const rSize = ZSTD_getCParamRowSize(srcSizeHint, dictSize, mode);
U32 const tableID = (rSize <= 256 KB) + (rSize <= 128 KB) + (rSize <= 16 KB);
int row;
DEBUGLOG(5, "ZSTD_getCParams_internal (cLevel=%i)", compressionLevel);
/* row */
if (compressionLevel == 0) row = ZSTD_CLEVEL_DEFAULT; /* 0 == default */
else if (compressionLevel < 0) row = 0; /* entry 0 is baseline for fast mode */
else if (compressionLevel > ZSTD_MAX_CLEVEL) row = ZSTD_MAX_CLEVEL;
else row = compressionLevel;
{ ZSTD_compressionParameters cp = ZSTD_defaultCParameters[tableID][row];
DEBUGLOG(5, "ZSTD_getCParams_internal selected tableID: %u row: %u strat: %u", tableID, row, (U32)cp.strategy);
/* acceleration factor */
if (compressionLevel < 0) {
int const clampedCompressionLevel = MAX(ZSTD_minCLevel(), compressionLevel);
cp.targetLength = (unsigned)(-clampedCompressionLevel);
}
/* refine parameters based on srcSize & dictSize */
return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode, ZSTD_ps_auto);
}
}
/*! ZSTD_getCParams() :
@@ -7045,9 +7705,13 @@ ZSTD_compressionParameters ZSTD_getCParams(int compressionLevel, unsigned long l
static ZSTD_parameters
ZSTD_getParams_internal(int compressionLevel, unsigned long long srcSizeHint, size_t dictSize, ZSTD_CParamMode_e mode)
{
ZSTD_parameters params;
ZSTD_compressionParameters const cParams = ZSTD_getCParams_internal(compressionLevel, srcSizeHint, dictSize, mode);
DEBUGLOG(5, "ZSTD_getParams (cLevel=%i)", compressionLevel);
return ZSTD_rust_params_makeParams(cParams);
ZSTD_memset(&params, 0, sizeof(params));
params.cParams = cParams;
params.fParams.contentSizeFlag = 1;
return params;
}
/*! ZSTD_getParams() :
+2 -3
View File
@@ -29,7 +29,7 @@ size_t ZSTD_rust_compressSuperBlock(
const SeqStore_t* seqStore,
const ZSTD_compressedBlockState_t* prevCBlock,
ZSTD_compressedBlockState_t* nextCBlock,
int strategy, int disableLiteralCompression,
const ZSTD_CCtx_params* cctxParams,
void* workspace, size_t wkspSize,
int bmi2, U32 windowLog, size_t targetCBlockSize,
void* dst, size_t dstCapacity,
@@ -45,8 +45,7 @@ size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc,
&zc->seqStore,
zc->blockState.prevCBlock,
zc->blockState.nextCBlock,
(int)zc->appliedParams.cParams.strategy,
ZSTD_literalsCompressionIsDisabled(&zc->appliedParams),
&zc->appliedParams,
zc->tmpWorkspace, zc->tmpWkspSize,
zc->bmi2, zc->appliedParams.cParams.windowLog,
zc->appliedParams.targetCBlockSize,
+4 -2108
View File
@@ -17,2111 +17,7 @@
#include "../common/compiler.h"
#include "../common/error_private.h"
/******************************************
* Static allocation
******************************************/
/* You can statically allocate FSE CTable/DTable as a table of unsigned using below macro */
#define FSE_DTABLE_SIZE_U32(maxTableLog) (1 + (1<<maxTableLog))
/* You can statically allocate Huff0 DTable as a table of unsigned short using below macro */
#define HUF_DTABLE_SIZE_U16(maxTableLog) (1 + (1<<maxTableLog))
#define HUF_CREATE_STATIC_DTABLE(DTable, maxTableLog) \
unsigned short DTable[HUF_DTABLE_SIZE_U16(maxTableLog)] = { maxTableLog }
/******************************************
* Error Management
******************************************/
#define FSE_LIST_ERRORS(ITEM) \
ITEM(FSE_OK_NoError) ITEM(FSE_ERROR_GENERIC) \
ITEM(FSE_ERROR_tableLog_tooLarge) ITEM(FSE_ERROR_maxSymbolValue_tooLarge) ITEM(FSE_ERROR_maxSymbolValue_tooSmall) \
ITEM(FSE_ERROR_dstSize_tooSmall) ITEM(FSE_ERROR_srcSize_wrong)\
ITEM(FSE_ERROR_corruptionDetected) \
ITEM(FSE_ERROR_maxCode)
#define FSE_GENERATE_ENUM(ENUM) ENUM,
typedef enum { FSE_LIST_ERRORS(FSE_GENERATE_ENUM) } FSE_errorCodes; /* enum is exposed, to detect & handle specific errors; compare function result to -enum value */
/******************************************
* FSE symbol compression API
******************************************/
/*
This API consists of small unitary functions, which highly benefit from being inlined.
You will want to enable link-time-optimization to ensure these functions are properly inlined in your binary.
Visual seems to do it automatically.
For gcc or clang, you'll need to add -flto flag at compilation and linking stages.
If none of these solutions is applicable, include "fse.c" directly.
*/
typedef unsigned FSE_CTable; /* don't allocate that. It's just a way to be more restrictive than void* */
typedef unsigned FSE_DTable; /* don't allocate that. It's just a way to be more restrictive than void* */
typedef struct
{
size_t bitContainer;
int bitPos;
char* startPtr;
char* ptr;
char* endPtr;
} FSE_CStream_t;
typedef struct
{
ptrdiff_t value;
const void* stateTable;
const void* symbolTT;
unsigned stateLog;
} FSE_CState_t;
typedef struct
{
size_t bitContainer;
unsigned bitsConsumed;
const char* ptr;
const char* start;
} FSE_DStream_t;
typedef struct
{
size_t state;
const void* table; /* precise table may vary, depending on U16 */
} FSE_DState_t;
typedef enum { FSE_DStream_unfinished = 0,
FSE_DStream_endOfBuffer = 1,
FSE_DStream_completed = 2,
FSE_DStream_tooFar = 3 } FSE_DStream_status; /* result of FSE_reloadDStream() */
/* 1,2,4,8 would be better for bitmap combinations, but slows down performance a bit ... ?! */
/****************************************************************
* Tuning parameters
****************************************************************/
/* MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio
* Reduced memory usage can improve speed, due to cache effect
* Recommended max value is 14, for 16KB, which nicely fits into Intel x86 L1 cache */
#define FSE_MAX_MEMORY_USAGE 14
#define FSE_DEFAULT_MEMORY_USAGE 13
/* FSE_MAX_SYMBOL_VALUE :
* Maximum symbol value authorized.
* Required for proper stack allocation */
#define FSE_MAX_SYMBOL_VALUE 255
/****************************************************************
* template functions type & suffix
****************************************************************/
#define FSE_FUNCTION_TYPE BYTE
#define FSE_FUNCTION_EXTENSION
/****************************************************************
* Byte symbol type
****************************************************************/
typedef struct
{
unsigned short newState;
unsigned char symbol;
unsigned char nbBits;
} FSE_decode_t; /* size == U32 */
/****************************************************************
* Compiler specifics
****************************************************************/
#ifdef _MSC_VER /* Visual Studio */
# define FORCE_INLINE static __forceinline
# include <intrin.h> /* For Visual 2005 */
# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
# pragma warning(disable : 4214) /* disable: C4214: non-int bitfields */
#else
# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# ifdef __GNUC__
# define FORCE_INLINE static inline __attribute__((always_inline))
# else
# define FORCE_INLINE static inline
# endif
# else
# define FORCE_INLINE static
# endif /* __STDC_VERSION__ */
#endif
/****************************************************************
* Includes
****************************************************************/
#include <stdlib.h> /* malloc, free, qsort */
#include <string.h> /* memcpy, memset */
#include <stdio.h> /* printf (debug) */
#ifndef MEM_ACCESS_MODULE
#define MEM_ACCESS_MODULE
/****************************************************************
* Basic Types
*****************************************************************/
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# include <stdint.h>
typedef uint8_t BYTE;
typedef uint16_t U16;
typedef int16_t S16;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
typedef int64_t S64;
#else
typedef unsigned char BYTE;
typedef unsigned short U16;
typedef signed short S16;
typedef unsigned int U32;
typedef signed int S32;
typedef unsigned long long U64;
typedef signed long long S64;
#endif
#endif /* MEM_ACCESS_MODULE */
/****************************************************************
* Memory I/O
*****************************************************************/
static unsigned FSE_32bits(void)
{
return sizeof(void*)==4;
}
static unsigned FSE_isLittleEndian(void)
{
const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
return one.c[0];
}
static U16 FSE_read16(const void* memPtr)
{
U16 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U32 FSE_read32(const void* memPtr)
{
U32 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U64 FSE_read64(const void* memPtr)
{
U64 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U16 FSE_readLE16(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read16(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U16)(p[0] + (p[1]<<8));
}
}
static U32 FSE_readLE32(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read32(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U32)((U32)p[0] + ((U32)p[1]<<8) + ((U32)p[2]<<16) + ((U32)p[3]<<24));
}
}
static U64 FSE_readLE64(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read64(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U64)((U64)p[0] + ((U64)p[1]<<8) + ((U64)p[2]<<16) + ((U64)p[3]<<24)
+ ((U64)p[4]<<32) + ((U64)p[5]<<40) + ((U64)p[6]<<48) + ((U64)p[7]<<56));
}
}
static size_t FSE_readLEST(const void* memPtr)
{
if (FSE_32bits())
return (size_t)FSE_readLE32(memPtr);
else
return (size_t)FSE_readLE64(memPtr);
}
/****************************************************************
* Constants
*****************************************************************/
#define FSE_MAX_TABLELOG (FSE_MAX_MEMORY_USAGE-2)
#define FSE_MAX_TABLESIZE (1U<<FSE_MAX_TABLELOG)
#define FSE_MAXTABLESIZE_MASK (FSE_MAX_TABLESIZE-1)
#define FSE_DEFAULT_TABLELOG (FSE_DEFAULT_MEMORY_USAGE-2)
#define FSE_MIN_TABLELOG 5
#define FSE_TABLELOG_ABSOLUTE_MAX 15
#if FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX
#error "FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX is not supported"
#endif
/****************************************************************
* Error Management
****************************************************************/
#define FSE_STATIC_ASSERT(c) { enum { FSE_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */
/****************************************************************
* Complex types
****************************************************************/
typedef struct
{
int deltaFindState;
U32 deltaNbBits;
} FSE_symbolCompressionTransform; /* total 8 bytes */
typedef U32 DTable_max_t[FSE_DTABLE_SIZE_U32(FSE_MAX_TABLELOG)];
/****************************************************************
* Internal functions
****************************************************************/
FORCE_INLINE unsigned FSE_highbit32 (U32 val)
{
# if defined(_MSC_VER) /* Visual */
unsigned long r;
return _BitScanReverse(&r, val) ? (unsigned)r : 0;
# elif defined(__GNUC__) && (GCC_VERSION >= 304) /* GCC Intrinsic */
return __builtin_clz (val) ^ 31;
# else /* Software version */
static const unsigned DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 };
U32 v = val;
unsigned r;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
r = DeBruijnClz[ (U32) (v * 0x07C4ACDDU) >> 27];
return r;
# endif
}
/****************************************************************
* Templates
****************************************************************/
/*
designed to be included
for type-specific functions (template emulation in C)
Objective is to write these functions only once, for improved maintenance
*/
/* safety checks */
#ifndef FSE_FUNCTION_EXTENSION
# error "FSE_FUNCTION_EXTENSION must be defined"
#endif
#ifndef FSE_FUNCTION_TYPE
# error "FSE_FUNCTION_TYPE must be defined"
#endif
/* Function names */
#define FSE_CAT(X,Y) X##Y
#define FSE_FUNCTION_NAME(X,Y) FSE_CAT(X,Y)
#define FSE_TYPE_NAME(X,Y) FSE_CAT(X,Y)
static U32 FSE_tableStep(U32 tableSize) { return (tableSize>>1) + (tableSize>>3) + 3; }
#define FSE_DECODE_TYPE FSE_decode_t
typedef struct {
U16 tableLog;
U16 fastMode;
} FSE_DTableHeader; /* sizeof U32 */
static size_t FSE_buildDTable
(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_DECODE_TYPE* const tableDecode = (FSE_DECODE_TYPE*)(ptr) + 1; /* because dt is unsigned, 32-bits aligned on 32-bits */
const U32 tableSize = 1 << tableLog;
const U32 tableMask = tableSize-1;
const U32 step = FSE_tableStep(tableSize);
U16 symbolNext[FSE_MAX_SYMBOL_VALUE+1];
U32 position = 0;
U32 highThreshold = tableSize-1;
const S16 largeLimit= (S16)(1 << (tableLog-1));
U32 noLarge = 1;
U32 s;
/* Sanity Checks */
if (maxSymbolValue > FSE_MAX_SYMBOL_VALUE) return (size_t)-FSE_ERROR_maxSymbolValue_tooLarge;
if (tableLog > FSE_MAX_TABLELOG) return (size_t)-FSE_ERROR_tableLog_tooLarge;
/* Init, lay down lowprob symbols */
DTableH[0].tableLog = (U16)tableLog;
for (s=0; s<=maxSymbolValue; s++)
{
if (normalizedCounter[s]==-1)
{
tableDecode[highThreshold--].symbol = (FSE_FUNCTION_TYPE)s;
symbolNext[s] = 1;
}
else
{
if (normalizedCounter[s] >= largeLimit) noLarge=0;
symbolNext[s] = normalizedCounter[s];
}
}
/* Spread symbols */
for (s=0; s<=maxSymbolValue; s++)
{
int i;
for (i=0; i<normalizedCounter[s]; i++)
{
tableDecode[position].symbol = (FSE_FUNCTION_TYPE)s;
position = (position + step) & tableMask;
while (position > highThreshold) position = (position + step) & tableMask; /* lowprob area */
}
}
if (position!=0) return (size_t)-FSE_ERROR_GENERIC; /* position must reach all cells once, otherwise normalizedCounter is incorrect */
/* Build Decoding table */
{
U32 i;
for (i=0; i<tableSize; i++)
{
FSE_FUNCTION_TYPE symbol = (FSE_FUNCTION_TYPE)(tableDecode[i].symbol);
U16 nextState = symbolNext[symbol]++;
tableDecode[i].nbBits = (BYTE) (tableLog - FSE_highbit32 ((U32)nextState) );
tableDecode[i].newState = (U16) ( (nextState << tableDecode[i].nbBits) - tableSize);
}
}
DTableH->fastMode = (U16)noLarge;
return 0;
}
/******************************************
* FSE byte symbol
******************************************/
#ifndef FSE_COMMONDEFS_ONLY
static unsigned FSE_isError(size_t code) { return (code > (size_t)(-FSE_ERROR_maxCode)); }
static short FSE_abs(short a)
{
return a<0? -a : a;
}
/****************************************************************
* Header bitstream management
****************************************************************/
static size_t FSE_readNCount (short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
const void* headerBuffer, size_t hbSize)
{
const BYTE* const istart = (const BYTE*) headerBuffer;
const BYTE* const iend = istart + hbSize;
const BYTE* ip = istart;
int nbBits;
int remaining;
int threshold;
U32 bitStream;
int bitCount;
unsigned charnum = 0;
int previous0 = 0;
if (hbSize < 4) return (size_t)-FSE_ERROR_srcSize_wrong;
bitStream = FSE_readLE32(ip);
nbBits = (bitStream & 0xF) + FSE_MIN_TABLELOG; /* extract tableLog */
if (nbBits > FSE_TABLELOG_ABSOLUTE_MAX) return (size_t)-FSE_ERROR_tableLog_tooLarge;
bitStream >>= 4;
bitCount = 4;
*tableLogPtr = nbBits;
remaining = (1<<nbBits)+1;
threshold = 1<<nbBits;
nbBits++;
while ((remaining>1) && (charnum<=*maxSVPtr))
{
if (previous0)
{
unsigned n0 = charnum;
while ((bitStream & 0xFFFF) == 0xFFFF)
{
n0+=24;
if (ip < iend-5)
{
ip+=2;
bitStream = FSE_readLE32(ip) >> bitCount;
}
else
{
bitStream >>= 16;
bitCount+=16;
}
}
while ((bitStream & 3) == 3)
{
n0+=3;
bitStream>>=2;
bitCount+=2;
}
n0 += bitStream & 3;
bitCount += 2;
if (n0 > *maxSVPtr) return (size_t)-FSE_ERROR_maxSymbolValue_tooSmall;
while (charnum < n0) normalizedCounter[charnum++] = 0;
if ((ip <= iend-7) || (ip + (bitCount>>3) <= iend-4))
{
ip += bitCount>>3;
bitCount &= 7;
bitStream = FSE_readLE32(ip) >> bitCount;
}
else
bitStream >>= 2;
}
{
const short max = (short)((2*threshold-1)-remaining);
short count;
if ((bitStream & (threshold-1)) < (U32)max)
{
count = (short)(bitStream & (threshold-1));
bitCount += nbBits-1;
}
else
{
count = (short)(bitStream & (2*threshold-1));
if (count >= threshold) count -= max;
bitCount += nbBits;
}
count--; /* extra accuracy */
remaining -= FSE_abs(count);
normalizedCounter[charnum++] = count;
previous0 = !count;
while (remaining < threshold)
{
nbBits--;
threshold >>= 1;
}
{
if ((ip <= iend-7) || (ip + (bitCount>>3) <= iend-4))
{
ip += bitCount>>3;
bitCount &= 7;
}
else
{
bitCount -= (int)(8 * (iend - 4 - ip));
ip = iend - 4;
}
bitStream = FSE_readLE32(ip) >> (bitCount & 31);
}
}
}
if (remaining != 1) return (size_t)-FSE_ERROR_GENERIC;
*maxSVPtr = charnum-1;
ip += (bitCount+7)>>3;
if ((size_t)(ip-istart) > hbSize) return (size_t)-FSE_ERROR_srcSize_wrong;
return ip-istart;
}
/*********************************************************
* Decompression (Byte symbols)
*********************************************************/
static size_t FSE_buildDTable_rle (FSE_DTable* dt, BYTE symbolValue)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_decode_t* const cell = (FSE_decode_t*)(ptr) + 1; /* because dt is unsigned */
DTableH->tableLog = 0;
DTableH->fastMode = 0;
cell->newState = 0;
cell->symbol = symbolValue;
cell->nbBits = 0;
return 0;
}
static size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_decode_t* const dinfo = (FSE_decode_t*)(ptr) + 1; /* because dt is unsigned */
const unsigned tableSize = 1 << nbBits;
const unsigned tableMask = tableSize - 1;
const unsigned maxSymbolValue = tableMask;
unsigned s;
/* Sanity checks */
if (nbBits < 1) return (size_t)-FSE_ERROR_GENERIC; /* min size */
/* Build Decoding Table */
DTableH->tableLog = (U16)nbBits;
DTableH->fastMode = 1;
for (s=0; s<=maxSymbolValue; s++)
{
dinfo[s].newState = 0;
dinfo[s].symbol = (BYTE)s;
dinfo[s].nbBits = (BYTE)nbBits;
}
return 0;
}
/* FSE_initDStream
* Initialize a FSE_DStream_t.
* srcBuffer must point at the beginning of an FSE block.
* The function result is the size of the FSE_block (== srcSize).
* If srcSize is too small, the function will return an errorCode;
*/
static size_t FSE_initDStream(FSE_DStream_t* bitD, const void* srcBuffer, size_t srcSize)
{
if (srcSize < 1) return (size_t)-FSE_ERROR_srcSize_wrong;
if (srcSize >= sizeof(size_t))
{
U32 contain32;
bitD->start = (const char*)srcBuffer;
bitD->ptr = (const char*)srcBuffer + srcSize - sizeof(size_t);
bitD->bitContainer = FSE_readLEST(bitD->ptr);
contain32 = ((const BYTE*)srcBuffer)[srcSize-1];
if (contain32 == 0) return (size_t)-FSE_ERROR_GENERIC; /* stop bit not present */
bitD->bitsConsumed = 8 - FSE_highbit32(contain32);
}
else
{
U32 contain32;
bitD->start = (const char*)srcBuffer;
bitD->ptr = bitD->start;
bitD->bitContainer = *(const BYTE*)(bitD->start);
switch(srcSize)
{
case 7: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[6]) << (sizeof(size_t)*8 - 16);
/* fallthrough */
case 6: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[5]) << (sizeof(size_t)*8 - 24);
/* fallthrough */
case 5: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[4]) << (sizeof(size_t)*8 - 32);
/* fallthrough */
case 4: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[3]) << 24;
/* fallthrough */
case 3: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[2]) << 16;
/* fallthrough */
case 2: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[1]) << 8;
/* fallthrough */
default:;
}
contain32 = ((const BYTE*)srcBuffer)[srcSize-1];
if (contain32 == 0) return (size_t)-FSE_ERROR_GENERIC; /* stop bit not present */
bitD->bitsConsumed = 8 - FSE_highbit32(contain32);
bitD->bitsConsumed += (U32)(sizeof(size_t) - srcSize)*8;
}
return srcSize;
}
/*!FSE_lookBits
* Provides next n bits from the bitContainer.
* bitContainer is not modified (bits are still present for next read/look)
* On 32-bits, maxNbBits==25
* On 64-bits, maxNbBits==57
* return : value extracted.
*/
static size_t FSE_lookBits(FSE_DStream_t* bitD, U32 nbBits)
{
const U32 bitMask = sizeof(bitD->bitContainer)*8 - 1;
return ((bitD->bitContainer << (bitD->bitsConsumed & bitMask)) >> 1) >> ((bitMask-nbBits) & bitMask);
}
static size_t FSE_lookBitsFast(FSE_DStream_t* bitD, U32 nbBits) /* only if nbBits >= 1 !! */
{
const U32 bitMask = sizeof(bitD->bitContainer)*8 - 1;
return (bitD->bitContainer << (bitD->bitsConsumed & bitMask)) >> (((bitMask+1)-nbBits) & bitMask);
}
static void FSE_skipBits(FSE_DStream_t* bitD, U32 nbBits)
{
bitD->bitsConsumed += nbBits;
}
/*!FSE_readBits
* Read next n bits from the bitContainer.
* On 32-bits, don't read more than maxNbBits==25
* On 64-bits, don't read more than maxNbBits==57
* Use the fast variant *only* if n >= 1.
* return : value extracted.
*/
static size_t FSE_readBits(FSE_DStream_t* bitD, U32 nbBits)
{
size_t value = FSE_lookBits(bitD, nbBits);
FSE_skipBits(bitD, nbBits);
return value;
}
static size_t FSE_readBitsFast(FSE_DStream_t* bitD, U32 nbBits) /* only if nbBits >= 1 !! */
{
size_t value = FSE_lookBitsFast(bitD, nbBits);
FSE_skipBits(bitD, nbBits);
return value;
}
static unsigned FSE_reloadDStream(FSE_DStream_t* bitD)
{
if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* should never happen */
return FSE_DStream_tooFar;
if (bitD->ptr >= bitD->start + sizeof(bitD->bitContainer))
{
bitD->ptr -= bitD->bitsConsumed >> 3;
bitD->bitsConsumed &= 7;
bitD->bitContainer = FSE_readLEST(bitD->ptr);
return FSE_DStream_unfinished;
}
if (bitD->ptr == bitD->start)
{
if (bitD->bitsConsumed < sizeof(bitD->bitContainer)*8) return FSE_DStream_endOfBuffer;
return FSE_DStream_completed;
}
{
U32 nbBytes = bitD->bitsConsumed >> 3;
U32 result = FSE_DStream_unfinished;
if (bitD->ptr - nbBytes < bitD->start)
{
nbBytes = (U32)(bitD->ptr - bitD->start); /* ptr > start */
result = FSE_DStream_endOfBuffer;
}
bitD->ptr -= nbBytes;
bitD->bitsConsumed -= nbBytes*8;
bitD->bitContainer = FSE_readLEST(bitD->ptr); /* reminder : srcSize > sizeof(bitD) */
return result;
}
}
static void FSE_initDState(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD, const FSE_DTable* dt)
{
const void* ptr = dt;
const FSE_DTableHeader* const DTableH = (const FSE_DTableHeader*)ptr;
DStatePtr->state = FSE_readBits(bitD, DTableH->tableLog);
FSE_reloadDStream(bitD);
DStatePtr->table = dt + 1;
}
static BYTE FSE_decodeSymbol(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD)
{
const FSE_decode_t DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
const U32 nbBits = DInfo.nbBits;
BYTE symbol = DInfo.symbol;
size_t lowBits = FSE_readBits(bitD, nbBits);
DStatePtr->state = DInfo.newState + lowBits;
return symbol;
}
static BYTE FSE_decodeSymbolFast(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD)
{
const FSE_decode_t DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
const U32 nbBits = DInfo.nbBits;
BYTE symbol = DInfo.symbol;
size_t lowBits = FSE_readBitsFast(bitD, nbBits);
DStatePtr->state = DInfo.newState + lowBits;
return symbol;
}
/* FSE_endOfDStream
Tells if bitD has reached end of bitStream or not */
static unsigned FSE_endOfDStream(const FSE_DStream_t* bitD)
{
return ((bitD->ptr == bitD->start) && (bitD->bitsConsumed == sizeof(bitD->bitContainer)*8));
}
static unsigned FSE_endOfDState(const FSE_DState_t* DStatePtr)
{
return DStatePtr->state == 0;
}
FORCE_INLINE size_t FSE_decompress_usingDTable_generic(
void* dst, size_t maxDstSize,
const void* cSrc, size_t cSrcSize,
const FSE_DTable* dt, const unsigned fast)
{
BYTE* const ostart = (BYTE*) dst;
BYTE* op = ostart;
BYTE* const omax = op + maxDstSize;
BYTE* const olimit = omax-3;
FSE_DStream_t bitD;
FSE_DState_t state1;
FSE_DState_t state2;
size_t errorCode;
/* Init */
errorCode = FSE_initDStream(&bitD, cSrc, cSrcSize); /* replaced last arg by maxCompressed Size */
if (FSE_isError(errorCode)) return errorCode;
FSE_initDState(&state1, &bitD, dt);
FSE_initDState(&state2, &bitD, dt);
#define FSE_GETSYMBOL(statePtr) fast ? FSE_decodeSymbolFast(statePtr, &bitD) : FSE_decodeSymbol(statePtr, &bitD)
/* 4 symbols per loop */
for ( ; (FSE_reloadDStream(&bitD)==FSE_DStream_unfinished) && (op<olimit) ; op+=4)
{
op[0] = FSE_GETSYMBOL(&state1);
if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
FSE_reloadDStream(&bitD);
op[1] = FSE_GETSYMBOL(&state2);
if (FSE_MAX_TABLELOG*4+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
{ if (FSE_reloadDStream(&bitD) > FSE_DStream_unfinished) { op+=2; break; } }
op[2] = FSE_GETSYMBOL(&state1);
if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
FSE_reloadDStream(&bitD);
op[3] = FSE_GETSYMBOL(&state2);
}
/* tail */
/* note : FSE_reloadDStream(&bitD) >= FSE_DStream_partiallyFilled; Ends at exactly FSE_DStream_completed */
while (1)
{
if ( (FSE_reloadDStream(&bitD)>FSE_DStream_completed) || (op==omax) || (FSE_endOfDStream(&bitD) && (fast || FSE_endOfDState(&state1))) )
break;
*op++ = FSE_GETSYMBOL(&state1);
if ( (FSE_reloadDStream(&bitD)>FSE_DStream_completed) || (op==omax) || (FSE_endOfDStream(&bitD) && (fast || FSE_endOfDState(&state2))) )
break;
*op++ = FSE_GETSYMBOL(&state2);
}
/* end ? */
if (FSE_endOfDStream(&bitD) && FSE_endOfDState(&state1) && FSE_endOfDState(&state2))
return op-ostart;
if (op==omax) return (size_t)-FSE_ERROR_dstSize_tooSmall; /* dst buffer is full, but cSrc unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
}
static size_t FSE_decompress_usingDTable(void* dst, size_t originalSize,
const void* cSrc, size_t cSrcSize,
const FSE_DTable* dt)
{
FSE_DTableHeader DTableH;
memcpy(&DTableH, dt, sizeof(DTableH)); /* memcpy() into local variable, to avoid strict aliasing warning */
/* select fast mode (static) */
if (DTableH.fastMode) return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 1);
return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 0);
}
static size_t FSE_decompress(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize)
{
const BYTE* const istart = (const BYTE*)cSrc;
const BYTE* ip = istart;
short counting[FSE_MAX_SYMBOL_VALUE+1];
DTable_max_t dt; /* Static analyzer seems unable to understand this table will be properly initialized later */
unsigned tableLog;
unsigned maxSymbolValue = FSE_MAX_SYMBOL_VALUE;
size_t errorCode;
if (cSrcSize<2) return (size_t)-FSE_ERROR_srcSize_wrong; /* too small input size */
/* normal FSE decoding mode */
errorCode = FSE_readNCount (counting, &maxSymbolValue, &tableLog, istart, cSrcSize);
if (FSE_isError(errorCode)) return errorCode;
if (errorCode >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong; /* too small input size */
ip += errorCode;
cSrcSize -= errorCode;
errorCode = FSE_buildDTable (dt, counting, maxSymbolValue, tableLog);
if (FSE_isError(errorCode)) return errorCode;
/* always return, even if it is an error code */
return FSE_decompress_usingDTable (dst, maxDstSize, ip, cSrcSize, dt);
}
/* *******************************************************
* Huff0 : Huffman block compression
*********************************************************/
#define HUF_MAX_SYMBOL_VALUE 255
#define HUF_DEFAULT_TABLELOG 12 /* used by default, when not specified */
#define HUF_MAX_TABLELOG 12 /* max possible tableLog; for allocation purpose; can be modified */
#define HUF_ABSOLUTEMAX_TABLELOG 16 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */
#if (HUF_MAX_TABLELOG > HUF_ABSOLUTEMAX_TABLELOG)
# error "HUF_MAX_TABLELOG is too large !"
#endif
typedef struct HUF_CElt_s {
U16 val;
BYTE nbBits;
} HUF_CElt ;
typedef struct nodeElt_s {
U32 count;
U16 parent;
BYTE byte;
BYTE nbBits;
} nodeElt;
/* *******************************************************
* Huff0 : Huffman block decompression
*********************************************************/
typedef struct {
BYTE byte;
BYTE nbBits;
} HUF_DElt;
static size_t HUF_readDTable (U16* DTable, const void* src, size_t srcSize)
{
BYTE huffWeight[HUF_MAX_SYMBOL_VALUE + 1];
U32 rankVal[HUF_ABSOLUTEMAX_TABLELOG + 1]; /* large enough for values from 0 to 16 */
U32 weightTotal;
U32 maxBits;
const BYTE* ip = (const BYTE*) src;
size_t iSize;
size_t oSize;
U32 n;
U32 nextRankStart;
void* ptr = DTable+1;
HUF_DElt* const dt = (HUF_DElt*)ptr;
if (!srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
iSize = ip[0];
FSE_STATIC_ASSERT(sizeof(HUF_DElt) == sizeof(U16)); /* if compilation fails here, assertion is false */
//memset(huffWeight, 0, sizeof(huffWeight)); /* should not be necessary, but some analyzer complain ... */
if (iSize >= 128) /* special header */
{
if (iSize >= (242)) /* RLE */
{
static int l[14] = { 1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128 };
oSize = l[iSize-242];
memset(huffWeight, 1, sizeof(huffWeight));
iSize = 0;
}
else /* Incompressible */
{
oSize = iSize - 127;
iSize = ((oSize+1)/2);
if (iSize+1 > srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
ip += 1;
for (n=0; n<oSize; n+=2)
{
huffWeight[n] = ip[n/2] >> 4;
huffWeight[n+1] = ip[n/2] & 15;
}
}
}
else /* header compressed with FSE (normal case) */
{
if (iSize+1 > srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
oSize = FSE_decompress(huffWeight, HUF_MAX_SYMBOL_VALUE, ip+1, iSize); /* max 255 values decoded, last one is implied */
if (FSE_isError(oSize)) return oSize;
}
/* collect weight stats */
memset(rankVal, 0, sizeof(rankVal));
weightTotal = 0;
for (n=0; n<oSize; n++)
{
if (huffWeight[n] >= HUF_ABSOLUTEMAX_TABLELOG) return (size_t)-FSE_ERROR_corruptionDetected;
rankVal[huffWeight[n]]++;
weightTotal += (1 << huffWeight[n]) >> 1;
}
if (weightTotal == 0) return (size_t)-FSE_ERROR_corruptionDetected;
/* get last non-null symbol weight (implied, total must be 2^n) */
maxBits = FSE_highbit32(weightTotal) + 1;
if (maxBits > DTable[0]) return (size_t)-FSE_ERROR_tableLog_tooLarge; /* DTable is too small */
DTable[0] = (U16)maxBits;
{
U32 total = 1 << maxBits;
U32 rest = total - weightTotal;
U32 verif = 1 << FSE_highbit32(rest);
U32 lastWeight = FSE_highbit32(rest) + 1;
if (verif != rest) return (size_t)-FSE_ERROR_corruptionDetected; /* last value must be a clean power of 2 */
huffWeight[oSize] = (BYTE)lastWeight;
rankVal[lastWeight]++;
}
/* check tree construction validity */
if ((rankVal[1] < 2) || (rankVal[1] & 1)) return (size_t)-FSE_ERROR_corruptionDetected; /* by construction : at least 2 elts of rank 1, must be even */
/* Prepare ranks */
nextRankStart = 0;
for (n=1; n<=maxBits; n++)
{
U32 current = nextRankStart;
nextRankStart += (rankVal[n] << (n-1));
rankVal[n] = current;
}
/* fill DTable */
for (n=0; n<=oSize; n++)
{
const U32 w = huffWeight[n];
const U32 length = (1 << w) >> 1;
U32 i;
HUF_DElt D;
D.byte = (BYTE)n; D.nbBits = (BYTE)(maxBits + 1 - w);
for (i = rankVal[w]; i < rankVal[w] + length; i++)
dt[i] = D;
rankVal[w] += length;
}
return iSize+1;
}
static BYTE HUF_decodeSymbol(FSE_DStream_t* Dstream, const HUF_DElt* dt, const U32 dtLog)
{
const size_t val = FSE_lookBitsFast(Dstream, dtLog); /* note : dtLog >= 1 */
const BYTE c = dt[val].byte;
FSE_skipBits(Dstream, dt[val].nbBits);
return c;
}
static size_t HUF_decompress_usingDTable( /* -3% slower when non static */
void* dst, size_t maxDstSize,
const void* cSrc, size_t cSrcSize,
const U16* DTable)
{
if (cSrcSize < 6) return (size_t)-FSE_ERROR_srcSize_wrong;
{
BYTE* const ostart = (BYTE*) dst;
BYTE* op = ostart;
BYTE* const omax = op + maxDstSize;
BYTE* const olimit = maxDstSize < 15 ? op : omax-15;
const void* ptr = DTable;
const HUF_DElt* const dt = (const HUF_DElt*)(ptr)+1;
const U32 dtLog = DTable[0];
size_t errorCode;
U32 reloadStatus;
/* Init */
const U16* jumpTable = (const U16*)cSrc;
const size_t length1 = FSE_readLE16(jumpTable);
const size_t length2 = FSE_readLE16(jumpTable+1);
const size_t length3 = FSE_readLE16(jumpTable+2);
const size_t length4 = cSrcSize - 6 - length1 - length2 - length3; /* check coherency !! */
const char* const start1 = (const char*)(cSrc) + 6;
const char* const start2 = start1 + length1;
const char* const start3 = start2 + length2;
const char* const start4 = start3 + length3;
FSE_DStream_t bitD1, bitD2, bitD3, bitD4;
if (length1+length2+length3+6 >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
errorCode = FSE_initDStream(&bitD1, start1, length1);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD2, start2, length2);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD3, start3, length3);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD4, start4, length4);
if (FSE_isError(errorCode)) return errorCode;
reloadStatus=FSE_reloadDStream(&bitD2);
/* 16 symbols per loop */
for ( ; (reloadStatus<FSE_DStream_completed) && (op<olimit); /* D2-3-4 are supposed to be synchronized and finish together */
op+=16, reloadStatus = FSE_reloadDStream(&bitD2) | FSE_reloadDStream(&bitD3) | FSE_reloadDStream(&bitD4), FSE_reloadDStream(&bitD1))
{
#define HUF_DECODE_SYMBOL_0(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog);
#define HUF_DECODE_SYMBOL_1(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog); \
if (FSE_32bits() && (HUF_MAX_TABLELOG>12)) FSE_reloadDStream(&Dstream)
#define HUF_DECODE_SYMBOL_2(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog); \
if (FSE_32bits()) FSE_reloadDStream(&Dstream)
HUF_DECODE_SYMBOL_1( 0, bitD1);
HUF_DECODE_SYMBOL_1( 1, bitD2);
HUF_DECODE_SYMBOL_1( 2, bitD3);
HUF_DECODE_SYMBOL_1( 3, bitD4);
HUF_DECODE_SYMBOL_2( 4, bitD1);
HUF_DECODE_SYMBOL_2( 5, bitD2);
HUF_DECODE_SYMBOL_2( 6, bitD3);
HUF_DECODE_SYMBOL_2( 7, bitD4);
HUF_DECODE_SYMBOL_1( 8, bitD1);
HUF_DECODE_SYMBOL_1( 9, bitD2);
HUF_DECODE_SYMBOL_1(10, bitD3);
HUF_DECODE_SYMBOL_1(11, bitD4);
HUF_DECODE_SYMBOL_0(12, bitD1);
HUF_DECODE_SYMBOL_0(13, bitD2);
HUF_DECODE_SYMBOL_0(14, bitD3);
HUF_DECODE_SYMBOL_0(15, bitD4);
}
if (reloadStatus!=FSE_DStream_completed) /* not complete : some bitStream might be FSE_DStream_unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
/* tail */
{
/* bitTail = bitD1; */ /* *much* slower : -20% !??! */
FSE_DStream_t bitTail;
bitTail.ptr = bitD1.ptr;
bitTail.bitsConsumed = bitD1.bitsConsumed;
bitTail.bitContainer = bitD1.bitContainer; /* required in case of FSE_DStream_endOfBuffer */
bitTail.start = start1;
for ( ; (FSE_reloadDStream(&bitTail) < FSE_DStream_completed) && (op<omax) ; op++)
{
HUF_DECODE_SYMBOL_0(0, bitTail);
}
if (FSE_endOfDStream(&bitTail))
return op-ostart;
}
if (op==omax) return (size_t)-FSE_ERROR_dstSize_tooSmall; /* dst buffer is full, but cSrc unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
}
}
static size_t HUF_decompress (void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize)
{
HUF_CREATE_STATIC_DTABLE(DTable, HUF_MAX_TABLELOG);
const BYTE* ip = (const BYTE*) cSrc;
size_t errorCode;
errorCode = HUF_readDTable (DTable, cSrc, cSrcSize);
if (FSE_isError(errorCode)) return errorCode;
if (errorCode >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
ip += errorCode;
cSrcSize -= errorCode;
return HUF_decompress_usingDTable (dst, maxDstSize, ip, cSrcSize, DTable);
}
#endif /* FSE_COMMONDEFS_ONLY */
/*
zstd - standard compression library
Copyright (C) 2014-2015, Yann Collet.
BSD 2-Clause License (https://opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- zstd source repository : https://github.com/Cyan4973/zstd
- ztsd public forum : https://groups.google.com/forum/#!forum/lz4c
*/
/****************************************************************
* Tuning parameters
*****************************************************************/
/* MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio
* Reduced memory usage can improve speed, due to cache effect */
#define ZSTD_MEMORY_USAGE 17
/**************************************
CPU Feature Detection
**************************************/
/*
* Automated efficient unaligned memory access detection
* Based on known hardware architectures
* This list will be updated thanks to feedbacks
*/
#if defined(CPU_HAS_EFFICIENT_UNALIGNED_MEMORY_ACCESS) \
|| defined(__ARM_FEATURE_UNALIGNED) \
|| defined(__i386__) || defined(__x86_64__) \
|| defined(_M_IX86) || defined(_M_X64) \
|| defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_8__) \
|| (defined(_M_ARM) && (_M_ARM >= 7))
# define ZSTD_UNALIGNED_ACCESS 1
#else
# define ZSTD_UNALIGNED_ACCESS 0
#endif
/********************************************************
* Includes
*********************************************************/
#include <stdlib.h> /* calloc */
#include <string.h> /* memcpy, memmove */
#include <stdio.h> /* debug : printf */
/********************************************************
* Compiler specifics
*********************************************************/
#ifdef __AVX2__
# include <immintrin.h> /* AVX2 intrinsics */
#endif
#ifdef _MSC_VER /* Visual Studio */
# include <intrin.h> /* For Visual 2005 */
# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
# pragma warning(disable : 4324) /* disable: C4324: padded structure */
#endif
#ifndef MEM_ACCESS_MODULE
#define MEM_ACCESS_MODULE
/********************************************************
* Basic Types
*********************************************************/
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# if defined(_AIX)
# include <inttypes.h>
# else
# include <stdint.h> /* intptr_t */
# endif
typedef uint8_t BYTE;
typedef uint16_t U16;
typedef int16_t S16;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
#else
typedef unsigned char BYTE;
typedef unsigned short U16;
typedef signed short S16;
typedef unsigned int U32;
typedef signed int S32;
typedef unsigned long long U64;
#endif
#endif /* MEM_ACCESS_MODULE */
/********************************************************
* Constants
*********************************************************/
static const U32 ZSTD_magicNumber = 0xFD2FB51E; /* 3rd version : seqNb header */
#define HASH_LOG (ZSTD_MEMORY_USAGE - 2)
#define HASH_TABLESIZE (1 << HASH_LOG)
#define HASH_MASK (HASH_TABLESIZE - 1)
#define KNUTH 2654435761
#define BIT7 128
#define BIT6 64
#define BIT5 32
#define BIT4 16
#define KB *(1 <<10)
#define MB *(1 <<20)
#define GB *(1U<<30)
#define BLOCKSIZE (128 KB) /* define, for static allocation */
#define WORKPLACESIZE (BLOCKSIZE*3)
#define MINMATCH 4
#define MLbits 7
#define LLbits 6
#define Offbits 5
#define MaxML ((1<<MLbits )-1)
#define MaxLL ((1<<LLbits )-1)
#define MaxOff ((1<<Offbits)-1)
#define LitFSELog 11
#define MLFSELog 10
#define LLFSELog 10
#define OffFSELog 9
#define MAX(a,b) ((a)<(b)?(b):(a))
#define MaxSeq MAX(MaxLL, MaxML)
#define LITERAL_NOENTROPY 63
#define COMMAND_NOENTROPY 7 /* to remove */
#define ZSTD_CONTENTSIZE_ERROR (0ULL - 2)
static const size_t ZSTD_blockHeaderSize = 3;
static const size_t ZSTD_frameHeaderSize = 4;
/********************************************************
* Memory operations
*********************************************************/
static unsigned ZSTD_32bits(void) { return sizeof(void*)==4; }
static unsigned ZSTD_isLittleEndian(void)
{
const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
return one.c[0];
}
static U16 ZSTD_read16(const void* p) { U16 r; memcpy(&r, p, sizeof(r)); return r; }
static void ZSTD_copy4(void* dst, const void* src) { memcpy(dst, src, 4); }
static void ZSTD_copy8(void* dst, const void* src) { memcpy(dst, src, 8); }
#define COPY8(d,s) { ZSTD_copy8(d,s); d+=8; s+=8; }
static void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length)
{
const BYTE* ip = (const BYTE*)src;
BYTE* op = (BYTE*)dst;
BYTE* const oend = op + length;
while (op < oend) COPY8(op, ip);
}
static U16 ZSTD_readLE16(const void* memPtr)
{
if (ZSTD_isLittleEndian()) return ZSTD_read16(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U16)((U16)p[0] + ((U16)p[1]<<8));
}
}
static U32 ZSTD_readLE24(const void* memPtr)
{
return ZSTD_readLE16(memPtr) + (((const BYTE*)memPtr)[2] << 16);
}
static U32 ZSTD_readBE32(const void* memPtr)
{
const BYTE* p = (const BYTE*)memPtr;
return (U32)(((U32)p[0]<<24) + ((U32)p[1]<<16) + ((U32)p[2]<<8) + ((U32)p[3]<<0));
}
/**************************************
* Local structures
***************************************/
typedef struct ZSTD_Cctx_s ZSTD_Cctx;
typedef enum { bt_compressed, bt_raw, bt_rle, bt_end } blockType_t;
typedef struct
{
blockType_t blockType;
U32 origSize;
} blockProperties_t;
typedef struct {
void* buffer;
U32* offsetStart;
U32* offset;
BYTE* offCodeStart;
BYTE* offCode;
BYTE* litStart;
BYTE* lit;
BYTE* litLengthStart;
BYTE* litLength;
BYTE* matchLengthStart;
BYTE* matchLength;
BYTE* dumpsStart;
BYTE* dumps;
} SeqStore_t;
typedef struct ZSTD_Cctx_s
{
const BYTE* base;
U32 current;
U32 nextUpdate;
SeqStore_t seqStore;
#ifdef __AVX2__
__m256i hashTable[HASH_TABLESIZE>>3];
#else
U32 hashTable[HASH_TABLESIZE];
#endif
BYTE buffer[WORKPLACESIZE];
} cctxi_t;
/**************************************
* Error Management
**************************************/
/* published entry point */
unsigned ZSTDv01_isError(size_t code) { return ERR_isError(code); }
/**************************************
* Tool functions
**************************************/
#define ZSTD_VERSION_MAJOR 0 /* for breaking interface changes */
#define ZSTD_VERSION_MINOR 1 /* for new (non-breaking) interface capabilities */
#define ZSTD_VERSION_RELEASE 3 /* for tweaks, bug-fixes, or development */
#define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE)
/**************************************************************
* Decompression code
**************************************************************/
static size_t ZSTDv01_getcBlockSize(const void* src, size_t srcSize, blockProperties_t* bpPtr)
{
const BYTE* const in = (const BYTE* const)src;
BYTE headerFlags;
U32 cSize;
if (srcSize < 3) return ERROR(srcSize_wrong);
headerFlags = *in;
cSize = in[2] + (in[1]<<8) + ((in[0] & 7)<<16);
bpPtr->blockType = (blockType_t)(headerFlags >> 6);
bpPtr->origSize = (bpPtr->blockType == bt_rle) ? cSize : 0;
if (bpPtr->blockType == bt_end) return 0;
if (bpPtr->blockType == bt_rle) return 1;
return cSize;
}
static size_t ZSTD_copyUncompressedBlock(void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
if (srcSize > maxDstSize) return ERROR(dstSize_tooSmall);
if (srcSize > 0) {
memcpy(dst, src, srcSize);
}
return srcSize;
}
static size_t ZSTD_decompressLiterals(void* ctx,
void* dst, size_t maxDstSize,
const void* src, size_t srcSize)
{
BYTE* op = (BYTE*)dst;
BYTE* const oend = op + maxDstSize;
const BYTE* ip = (const BYTE*)src;
size_t errorCode;
size_t litSize;
/* check : minimum 2, for litSize, +1, for content */
if (srcSize <= 3) return ERROR(corruption_detected);
litSize = ip[1] + (ip[0]<<8);
litSize += ((ip[-3] >> 3) & 7) << 16; /* mmmmh.... */
op = oend - litSize;
(void)ctx;
if (litSize > maxDstSize) return ERROR(dstSize_tooSmall);
errorCode = HUF_decompress(op, litSize, ip+2, srcSize-2);
if (FSE_isError(errorCode)) return ERROR(GENERIC);
return litSize;
}
static size_t ZSTDv01_decodeLiteralsBlock(void* ctx,
void* dst, size_t maxDstSize,
const BYTE** litStart, size_t* litSize,
const void* src, size_t srcSize)
{
const BYTE* const istart = (const BYTE* const)src;
const BYTE* ip = istart;
BYTE* const ostart = (BYTE* const)dst;
BYTE* const oend = ostart + maxDstSize;
blockProperties_t litbp;
size_t litcSize = ZSTDv01_getcBlockSize(src, srcSize, &litbp);
if (ZSTDv01_isError(litcSize)) return litcSize;
if (litcSize > srcSize - ZSTD_blockHeaderSize) return ERROR(srcSize_wrong);
ip += ZSTD_blockHeaderSize;
switch(litbp.blockType)
{
case bt_raw:
*litStart = ip;
ip += litcSize;
*litSize = litcSize;
break;
case bt_rle:
{
size_t rleSize = litbp.origSize;
if (rleSize>maxDstSize) return ERROR(dstSize_tooSmall);
if (!srcSize) return ERROR(srcSize_wrong);
if (rleSize > 0) {
memset(oend - rleSize, *ip, rleSize);
}
*litStart = oend - rleSize;
*litSize = rleSize;
ip++;
break;
}
case bt_compressed:
{
size_t decodedLitSize = ZSTD_decompressLiterals(ctx, dst, maxDstSize, ip, litcSize);
if (ZSTDv01_isError(decodedLitSize)) return decodedLitSize;
*litStart = oend - decodedLitSize;
*litSize = decodedLitSize;
ip += litcSize;
break;
}
case bt_end:
default:
return ERROR(GENERIC);
}
return ip-istart;
}
static size_t ZSTDv01_decodeSeqHeaders(int* nbSeq, const BYTE** dumpsPtr, size_t* dumpsLengthPtr,
FSE_DTable* DTableLL, FSE_DTable* DTableML, FSE_DTable* DTableOffb,
const void* src, size_t srcSize)
{
const BYTE* const istart = (const BYTE* const)src;
const BYTE* ip = istart;
const BYTE* const iend = istart + srcSize;
U32 LLtype, Offtype, MLtype;
U32 LLlog, Offlog, MLlog;
size_t dumpsLength;
/* check */
if (srcSize < 5) return ERROR(srcSize_wrong);
/* SeqHead */
*nbSeq = ZSTD_readLE16(ip); ip+=2;
LLtype = *ip >> 6;
Offtype = (*ip >> 4) & 3;
MLtype = (*ip >> 2) & 3;
if (*ip & 2)
{
dumpsLength = ip[2];
dumpsLength += ip[1] << 8;
ip += 3;
}
else
{
dumpsLength = ip[1];
dumpsLength += (ip[0] & 1) << 8;
ip += 2;
}
*dumpsPtr = ip;
ip += dumpsLength;
*dumpsLengthPtr = dumpsLength;
/* check */
if (ip > iend-3) return ERROR(srcSize_wrong); /* min : all 3 are "raw", hence no header, but at least xxLog bits per type */
/* sequences */
{
S16 norm[MaxML+1]; /* assumption : MaxML >= MaxLL and MaxOff */
size_t headerSize;
/* Build DTables */
switch(LLtype)
{
case bt_rle :
LLlog = 0;
FSE_buildDTable_rle(DTableLL, *ip++); break;
case bt_raw :
LLlog = LLbits;
FSE_buildDTable_raw(DTableLL, LLbits); break;
default :
{ U32 max = MaxLL;
headerSize = FSE_readNCount(norm, &max, &LLlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (LLlog > LLFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableLL, norm, max, LLlog);
} }
switch(Offtype)
{
case bt_rle :
Offlog = 0;
if (ip > iend-2) return ERROR(srcSize_wrong); /* min : "raw", hence no header, but at least xxLog bits */
FSE_buildDTable_rle(DTableOffb, *ip++); break;
case bt_raw :
Offlog = Offbits;
FSE_buildDTable_raw(DTableOffb, Offbits); break;
default :
{ U32 max = MaxOff;
headerSize = FSE_readNCount(norm, &max, &Offlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (Offlog > OffFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableOffb, norm, max, Offlog);
} }
switch(MLtype)
{
case bt_rle :
MLlog = 0;
if (ip > iend-2) return ERROR(srcSize_wrong); /* min : "raw", hence no header, but at least xxLog bits */
FSE_buildDTable_rle(DTableML, *ip++); break;
case bt_raw :
MLlog = MLbits;
FSE_buildDTable_raw(DTableML, MLbits); break;
default :
{ U32 max = MaxML;
headerSize = FSE_readNCount(norm, &max, &MLlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (MLlog > MLFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableML, norm, max, MLlog);
} } }
return ip-istart;
}
typedef struct {
size_t litLength;
size_t offset;
size_t matchLength;
} seq_t;
typedef struct {
FSE_DStream_t DStream;
FSE_DState_t stateLL;
FSE_DState_t stateOffb;
FSE_DState_t stateML;
size_t prevOffset;
const BYTE* dumps;
const BYTE* dumpsEnd;
} seqState_t;
static void ZSTD_decodeSequence(seq_t* seq, seqState_t* seqState)
{
size_t litLength;
size_t prevOffset;
size_t offset;
size_t matchLength;
const BYTE* dumps = seqState->dumps;
const BYTE* const de = seqState->dumpsEnd;
/* Literal length */
litLength = FSE_decodeSymbol(&(seqState->stateLL), &(seqState->DStream));
prevOffset = litLength ? seq->offset : seqState->prevOffset;
seqState->prevOffset = seq->offset;
if (litLength == MaxLL)
{
const U32 add = dumps<de ? *dumps++ : 0;
if (add < 255) litLength += add;
else
{
if (dumps<=(de-3))
{
litLength = ZSTD_readLE24(dumps);
dumps += 3;
}
}
}
/* Offset */
{
U32 offsetCode, nbBits;
offsetCode = FSE_decodeSymbol(&(seqState->stateOffb), &(seqState->DStream));
if (ZSTD_32bits()) FSE_reloadDStream(&(seqState->DStream));
nbBits = offsetCode - 1;
if (offsetCode==0) nbBits = 0; /* cmove */
offset = ((size_t)1 << (nbBits & ((sizeof(offset)*8)-1))) + FSE_readBits(&(seqState->DStream), nbBits);
if (ZSTD_32bits()) FSE_reloadDStream(&(seqState->DStream));
if (offsetCode==0) offset = prevOffset;
}
/* MatchLength */
matchLength = FSE_decodeSymbol(&(seqState->stateML), &(seqState->DStream));
if (matchLength == MaxML)
{
const U32 add = dumps<de ? *dumps++ : 0;
if (add < 255) matchLength += add;
else
{
if (dumps<=(de-3))
{
matchLength = ZSTD_readLE24(dumps);
dumps += 3;
}
}
}
matchLength += MINMATCH;
/* save result */
seq->litLength = litLength;
seq->offset = offset;
seq->matchLength = matchLength;
seqState->dumps = dumps;
}
static size_t ZSTD_execSequence(BYTE* op,
seq_t sequence,
const BYTE** litPtr, const BYTE* const litLimit,
BYTE* const base, BYTE* const oend)
{
static const int dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4}; /* added */
static const int dec64table[] = {8, 8, 8, 7, 8, 9,10,11}; /* subtracted */
const BYTE* const ostart = op;
BYTE* const oLitEnd = op + sequence.litLength;
const size_t litLength = sequence.litLength;
BYTE* const endMatch = op + litLength + sequence.matchLength; /* risk : address space overflow (32-bits) */
const BYTE* const litEnd = *litPtr + litLength;
/* checks */
size_t const seqLength = sequence.litLength + sequence.matchLength;
if (seqLength > (size_t)(oend - op)) return ERROR(dstSize_tooSmall);
if (sequence.litLength > (size_t)(litLimit - *litPtr)) return ERROR(corruption_detected);
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected);
if (endMatch > oend) return ERROR(dstSize_tooSmall); /* overwrite beyond dst buffer */
if (litEnd > litLimit) return ERROR(corruption_detected); /* overRead beyond lit buffer */
if (sequence.matchLength > (size_t)(*litPtr-op)) return ERROR(dstSize_tooSmall); /* overwrite literal segment */
/* copy Literals */
ZSTD_memmove(op, *litPtr, sequence.litLength); /* note : v0.1 seems to allow scenarios where output or input are close to end of buffer */
op += litLength;
*litPtr = litEnd; /* update for next sequence */
/* check : last match must be at a minimum distance of 8 from end of dest buffer */
if (oend-op < 8) return ERROR(dstSize_tooSmall);
/* copy Match */
{
const U32 overlapRisk = (((size_t)(litEnd - endMatch)) < 12);
const BYTE* match = op - sequence.offset; /* possible underflow at op - offset ? */
size_t qutt = 12;
U64 saved[2];
/* check */
if (match < base) return ERROR(corruption_detected);
if (sequence.offset > (size_t)base) return ERROR(corruption_detected);
/* save beginning of literal sequence, in case of write overlap */
if (overlapRisk)
{
if ((endMatch + qutt) > oend) qutt = oend-endMatch;
memcpy(saved, endMatch, qutt);
}
if (sequence.offset < 8)
{
const int dec64 = dec64table[sequence.offset];
op[0] = match[0];
op[1] = match[1];
op[2] = match[2];
op[3] = match[3];
match += dec32table[sequence.offset];
ZSTD_copy4(op+4, match);
match -= dec64;
} else { ZSTD_copy8(op, match); }
op += 8; match += 8;
if (endMatch > oend-(16-MINMATCH))
{
if (op < oend-8)
{
ZSTD_wildcopy(op, match, (oend-8) - op);
match += (oend-8) - op;
op = oend-8;
}
while (op<endMatch) *op++ = *match++;
}
else
ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength-8); /* works even if matchLength < 8 */
/* restore, in case of overlap */
if (overlapRisk) memcpy(endMatch, saved, qutt);
}
return endMatch-ostart;
}
typedef struct ZSTDv01_Dctx_s
{
U32 LLTable[FSE_DTABLE_SIZE_U32(LLFSELog)];
U32 OffTable[FSE_DTABLE_SIZE_U32(OffFSELog)];
U32 MLTable[FSE_DTABLE_SIZE_U32(MLFSELog)];
void* previousDstEnd;
void* base;
size_t expected;
blockType_t bType;
U32 phase;
} dctx_t;
static size_t ZSTD_decompressSequences(
void* ctx,
void* dst, size_t maxDstSize,
const void* seqStart, size_t seqSize,
const BYTE* litStart, size_t litSize)
{
dctx_t* dctx = (dctx_t*)ctx;
const BYTE* ip = (const BYTE*)seqStart;
const BYTE* const iend = ip + seqSize;
BYTE* const ostart = (BYTE* const)dst;
BYTE* op = ostart;
BYTE* const oend = ostart + maxDstSize;
size_t errorCode, dumpsLength;
const BYTE* litPtr = litStart;
const BYTE* const litEnd = litStart + litSize;
int nbSeq;
const BYTE* dumps;
U32* DTableLL = dctx->LLTable;
U32* DTableML = dctx->MLTable;
U32* DTableOffb = dctx->OffTable;
BYTE* const base = (BYTE*) (dctx->base);
/* Build Decoding Tables */
errorCode = ZSTDv01_decodeSeqHeaders(&nbSeq, &dumps, &dumpsLength,
DTableLL, DTableML, DTableOffb,
ip, iend-ip);
if (ZSTDv01_isError(errorCode)) return errorCode;
ip += errorCode;
/* Regen sequences */
{
seq_t sequence;
seqState_t seqState;
memset(&sequence, 0, sizeof(sequence));
seqState.dumps = dumps;
seqState.dumpsEnd = dumps + dumpsLength;
seqState.prevOffset = 1;
errorCode = FSE_initDStream(&(seqState.DStream), ip, iend-ip);
if (FSE_isError(errorCode)) return ERROR(corruption_detected);
FSE_initDState(&(seqState.stateLL), &(seqState.DStream), DTableLL);
FSE_initDState(&(seqState.stateOffb), &(seqState.DStream), DTableOffb);
FSE_initDState(&(seqState.stateML), &(seqState.DStream), DTableML);
for ( ; (FSE_reloadDStream(&(seqState.DStream)) <= FSE_DStream_completed) && (nbSeq>0) ; )
{
size_t oneSeqSize;
nbSeq--;
ZSTD_decodeSequence(&sequence, &seqState);
oneSeqSize = ZSTD_execSequence(op, sequence, &litPtr, litEnd, base, oend);
if (ZSTDv01_isError(oneSeqSize)) return oneSeqSize;
op += oneSeqSize;
}
/* check if reached exact end */
if ( !FSE_endOfDStream(&(seqState.DStream)) ) return ERROR(corruption_detected); /* requested too much : data is corrupted */
if (nbSeq<0) return ERROR(corruption_detected); /* requested too many sequences : data is corrupted */
/* last literal segment */
{
size_t lastLLSize = litEnd - litPtr;
if (op+lastLLSize > oend) return ERROR(dstSize_tooSmall);
if (lastLLSize > 0) {
if (op != litPtr) memmove(op, litPtr, lastLLSize);
op += lastLLSize;
}
}
}
return op-ostart;
}
static size_t ZSTD_decompressBlock(
void* ctx,
void* dst, size_t maxDstSize,
const void* src, size_t srcSize)
{
/* blockType == blockCompressed, srcSize is trusted */
const BYTE* ip = (const BYTE*)src;
const BYTE* litPtr = NULL;
size_t litSize = 0;
size_t errorCode;
/* Decode literals sub-block */
errorCode = ZSTDv01_decodeLiteralsBlock(ctx, dst, maxDstSize, &litPtr, &litSize, src, srcSize);
if (ZSTDv01_isError(errorCode)) return errorCode;
ip += errorCode;
srcSize -= errorCode;
return ZSTD_decompressSequences(ctx, dst, maxDstSize, ip, srcSize, litPtr, litSize);
}
size_t ZSTDv01_decompressDCtx(void* ctx, void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
const BYTE* ip = (const BYTE*)src;
const BYTE* iend = ip + srcSize;
BYTE* const ostart = (BYTE* const)dst;
BYTE* op = ostart;
BYTE* const oend = ostart + maxDstSize;
size_t remainingSize = srcSize;
U32 magicNumber;
size_t errorCode=0;
blockProperties_t blockProperties;
/* Frame Header */
if (srcSize < ZSTD_frameHeaderSize+ZSTD_blockHeaderSize) return ERROR(srcSize_wrong);
magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) return ERROR(prefix_unknown);
ip += ZSTD_frameHeaderSize; remainingSize -= ZSTD_frameHeaderSize;
/* Loop on each block */
while (1)
{
size_t blockSize = ZSTDv01_getcBlockSize(ip, iend-ip, &blockProperties);
if (ZSTDv01_isError(blockSize)) return blockSize;
ip += ZSTD_blockHeaderSize;
remainingSize -= ZSTD_blockHeaderSize;
if (blockSize > remainingSize) return ERROR(srcSize_wrong);
switch(blockProperties.blockType)
{
case bt_compressed:
errorCode = ZSTD_decompressBlock(ctx, op, oend-op, ip, blockSize);
break;
case bt_raw :
errorCode = ZSTD_copyUncompressedBlock(op, oend-op, ip, blockSize);
break;
case bt_rle :
return ERROR(GENERIC); /* not yet supported */
break;
case bt_end :
/* end of frame */
if (remainingSize) return ERROR(srcSize_wrong);
break;
default:
return ERROR(GENERIC);
}
if (blockSize == 0) break; /* bt_end */
if (ZSTDv01_isError(errorCode)) return errorCode;
op += errorCode;
ip += blockSize;
remainingSize -= blockSize;
}
return op-ostart;
}
size_t ZSTDv01_decompress(void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
dctx_t ctx;
ctx.base = dst;
return ZSTDv01_decompressDCtx(&ctx, dst, maxDstSize, src, srcSize);
}
/* ZSTD_errorFrameSizeInfoLegacy() :
assumes `cSize` and `dBound` are _not_ NULL */
static void ZSTD_errorFrameSizeInfoLegacy(size_t* cSize, unsigned long long* dBound, size_t ret)
{
*cSize = ret;
*dBound = ZSTD_CONTENTSIZE_ERROR;
}
void ZSTDv01_findFrameSizeInfoLegacy(const void *src, size_t srcSize, size_t* cSize, unsigned long long* dBound)
{
const BYTE* ip = (const BYTE*)src;
size_t remainingSize = srcSize;
size_t nbBlocks = 0;
U32 magicNumber;
blockProperties_t blockProperties;
/* Frame Header */
if (srcSize < ZSTD_frameHeaderSize+ZSTD_blockHeaderSize) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(srcSize_wrong));
return;
}
magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(prefix_unknown));
return;
}
ip += ZSTD_frameHeaderSize; remainingSize -= ZSTD_frameHeaderSize;
/* Loop on each block */
while (1)
{
size_t blockSize = ZSTDv01_getcBlockSize(ip, remainingSize, &blockProperties);
if (ZSTDv01_isError(blockSize)) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, blockSize);
return;
}
ip += ZSTD_blockHeaderSize;
remainingSize -= ZSTD_blockHeaderSize;
if (blockSize > remainingSize) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(srcSize_wrong));
return;
}
if (blockSize == 0) break; /* bt_end */
ip += blockSize;
remainingSize -= blockSize;
nbBlocks++;
}
*cSize = ip - (const BYTE*)src;
*dBound = nbBlocks * BLOCKSIZE;
}
/*******************************
* Streaming Decompression API
*******************************/
size_t ZSTDv01_resetDCtx(ZSTDv01_Dctx* dctx)
{
dctx->expected = ZSTD_frameHeaderSize;
dctx->phase = 0;
dctx->previousDstEnd = NULL;
dctx->base = NULL;
return 0;
}
ZSTDv01_Dctx* ZSTDv01_createDCtx(void)
{
ZSTDv01_Dctx* dctx = (ZSTDv01_Dctx*)malloc(sizeof(ZSTDv01_Dctx));
if (dctx==NULL) return NULL;
ZSTDv01_resetDCtx(dctx);
return dctx;
}
size_t ZSTDv01_freeDCtx(ZSTDv01_Dctx* dctx)
{
free(dctx);
return 0;
}
size_t ZSTDv01_nextSrcSizeToDecompress(ZSTDv01_Dctx* dctx)
{
return ((dctx_t*)dctx)->expected;
}
size_t ZSTDv01_decompressContinue(ZSTDv01_Dctx* dctx, void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
dctx_t* ctx = (dctx_t*)dctx;
/* Sanity check */
if (srcSize != ctx->expected) return ERROR(srcSize_wrong);
if (dst != ctx->previousDstEnd) /* not contiguous */
ctx->base = dst;
/* Decompress : frame header */
if (ctx->phase == 0)
{
/* Check frame magic header */
U32 magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) return ERROR(prefix_unknown);
ctx->phase = 1;
ctx->expected = ZSTD_blockHeaderSize;
return 0;
}
/* Decompress : block header */
if (ctx->phase == 1)
{
blockProperties_t bp;
size_t blockSize = ZSTDv01_getcBlockSize(src, ZSTD_blockHeaderSize, &bp);
if (ZSTDv01_isError(blockSize)) return blockSize;
if (bp.blockType == bt_end)
{
ctx->expected = 0;
ctx->phase = 0;
}
else
{
ctx->expected = blockSize;
ctx->bType = bp.blockType;
ctx->phase = 2;
}
return 0;
}
/* Decompress : block content */
{
size_t rSize;
switch(ctx->bType)
{
case bt_compressed:
rSize = ZSTD_decompressBlock(ctx, dst, maxDstSize, src, srcSize);
break;
case bt_raw :
rSize = ZSTD_copyUncompressedBlock(dst, maxDstSize, src, srcSize);
break;
case bt_rle :
return ERROR(GENERIC); /* not yet handled */
break;
case bt_end : /* should never happen (filtered at phase 1) */
rSize = 0;
break;
default:
return ERROR(GENERIC);
}
ctx->phase = 1;
ctx->expected = ZSTD_blockHeaderSize;
if (ZSTDv01_isError(rSize)) return rSize;
ctx->previousDstEnd = (void*)( ((char*)dst) + rSize);
return rSize;
}
}
/* Implementation moved to Rust (rust/src/legacy/zstd_v01.rs).
* The frozen v0.1 decoder, including its embedded FSE/Huff0 snapshot and the
* ZSTDv01_Dctx streaming state, now lives entirely in Rust; C code only ever
* holds an opaque ZSTDv01_Dctx pointer. */
+28 -8
View File
@@ -55,16 +55,33 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_BUILD_CONFIG := default
RUST_HUF_MODE := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_BUILD_CONFIG := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
endif
# The full-featured zstd program mirrors libzstd.mk's legacy file selection:
# ZSTD_LEGACY_SUPPORT=N compiles v0.N .. v0.7, so the Rust archive enables the
# matching per-version features. The archive directory encodes the level so
# builds for different legacy levels never share cached Rust outputs. The
# compress-only, decompress-only, and CLI archives further below are used
# solely by ZSTD_LEGACY_SUPPORT=0 program variants and stay legacy-free.
empty :=
space := $(empty) $(empty)
comma := ,
RUST_LEGACY_FEATURES :=
ifneq ($(ZSTD_LEGACY_SUPPORT), 0)
ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0)
RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7))
endif
endif
RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG)
RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a
RUST_TARGET_32 ?= i686-unknown-linux-gnu
@@ -75,6 +92,9 @@ RUST_CARGO_FLAGS += --features compression,decompression
ifneq ($(RUST_HUF_FEATURE),)
RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif
ifneq ($(RUST_LEGACY_FEATURES),)
RUST_CARGO_FLAGS += --features $(subst $(space),$(comma),$(strip $(RUST_LEGACY_FEATURES)))
endif
$(RUST_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS)
@@ -82,7 +102,7 @@ $(RUST_STATICLIB): $(RUST_SOURCES)
$(RUST_STATICLIB_32): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS) --target $(RUST_TARGET_32)
RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_BUILD_CONFIG)
RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_HUF_MODE)
RUST_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_CLI_BUILD_CONFIG)
RUST_CLI_STATICLIB := $(RUST_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_CLI_STATICLIB_32 := $(RUST_CLI_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
@@ -96,7 +116,7 @@ $(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(RUST_CLI_STATICLIB_32): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_CLI_CARGO_FLAGS) --target $(RUST_TARGET_32)
RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-$(RUST_BUILD_CONFIG)
RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-$(RUST_HUF_MODE)
RUST_DECOMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_BUILD_CONFIG)
RUST_DECOMPRESS_STATICLIB := $(RUST_DECOMPRESS_TARGET_DIR)/release/libzstd_rs.a
RUST_DECOMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
@@ -109,7 +129,7 @@ endif
$(RUST_DECOMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CARGO_FLAGS)
RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_BUILD_CONFIG)
RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_HUF_MODE)
RUST_DECOMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_CLI_BUILD_CONFIG)
RUST_DECOMPRESS_CLI_STATICLIB := $(RUST_DECOMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
@@ -119,7 +139,7 @@ RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --releas
$(RUST_DECOMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CLI_CARGO_FLAGS)
RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-$(RUST_BUILD_CONFIG)
RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-$(RUST_HUF_MODE)
RUST_COMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_BUILD_CONFIG)
RUST_COMPRESS_STATICLIB := $(RUST_COMPRESS_TARGET_DIR)/release/libzstd_rs.a
RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
@@ -129,7 +149,7 @@ RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
$(RUST_COMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_COMPRESS_CARGO_FLAGS)
RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_BUILD_CONFIG)
RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_HUF_MODE)
RUST_COMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_CLI_BUILD_CONFIG)
RUST_COMPRESS_CLI_STATICLIB := $(RUST_COMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_COMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
+12
View File
@@ -12,6 +12,18 @@ compression = []
decompression = []
huf-force-decompress-x1 = []
huf-force-decompress-x2 = []
# Legacy-format decoders (zstd v0.1 .. v0.7). Never default features: the
# build systems map ZSTD_LEGACY_SUPPORT=N to the features for versions >= N,
# exactly mirroring which lib/legacy/zstd_v0N.c files the C build compiles.
# A feature whose version is not yet ported to Rust gates nothing; the
# original C file still provides that decoder.
legacy-v01 = []
legacy-v02 = []
legacy-v03 = []
legacy-v04 = []
legacy-v05 = []
legacy-v06 = []
legacy-v07 = []
[dependencies]
libc = "0.2"
+44 -16
View File
@@ -31,22 +31,9 @@ zstd ABI:
- `zstd_presplit` chooses split points for full compression blocks.
- `zstd_compress_literals` emits raw, RLE, and Huffman literal sections
while preserving the compressor's Huffman-table repeat state.
- `zstd_compress_stats` converts stored sequences into symbol codes,
selects each block's symbol encoding types, compresses a seqStore's
literals and sequences into a compressed-block body, builds the block
entropy statistics shared with the superblock writer and the block
splitter, and exports collected sequences in the public `ZSTD_Sequence`
format. Its C shims extract the sequence store, the entropy-table
leaves, and the two `ZSTD_CCtx_params` scalars these paths read.
- `zstd_compress_frame` serializes frame headers, skippable frames, and the
last empty block; it takes scalar frame parameters so the C-owned
`ZSTD_CCtx_params` layout never crosses the language boundary.
- `zstd_compress_params` owns the compression-level tables (formerly
`clevels.h`), parameter bounds, clamping, validation, table selection,
source/dictionary adjustment, and match-state/CDict size estimation.
The C integration layer keeps the public `ZSTD_*` symbols and feeds the
leaves configuration-owned scalars: the excluded-block-compressor
strategy cascade, struct sizes, and sanitizer redzone policy.
- `zstd_fast` and `zstd_double_fast` implement the single- and two-table
fast block match finders, including attached and external dictionary paths.
- `zstd_lazy` implements greedy, lazy, lazy2, and binary-tree matching,
@@ -60,6 +47,10 @@ zstd ABI:
- `pool` implements the bounded worker pool used by multithreaded compression.
- Dictionary support
- `zstd_ddict` owns, loads, copies, and references decode dictionaries.
- Legacy decoding
- `legacy` hosts one frozen module per historical format; `legacy::zstd_v01`
ports the self-contained v0.1 decoder. Versions v0.2 through v0.7 are
still C.
- Block decompression
- `zstd_decompress_block` decodes literal and sequence sections, maintains
FSE/Huffman repeat state, and executes compressed-block sequences.
@@ -74,9 +65,46 @@ zstd ABI:
metadata, and streaming I/O.
The optimal block matcher, high-level frame compression, dictionary-building,
legacy decoding callbacks, and the CLI file-I/O backend are still C. They must
move before the rewrite is complete. Keeping that boundary explicit prevents a
passing hybrid build from being mistaken for the final all-Rust result.
the legacy v0.2-v0.7 decoders, and the CLI file-I/O backend are still C. They
must move before the rewrite is complete. Keeping that boundary explicit
prevents a passing hybrid build from being mistaken for the final all-Rust
result.
## Legacy decoding
Each `lib/legacy/zstd_v0N.c` file is a frozen snapshot of the entropy coders
and frame logic of one historical release. The Rust ports in `src/legacy/`
keep that property: every version owns its own frozen FSE/Huff0 and frame
logic, ported line by line, and must never reuse the modern entropy modules
or share code with other legacy versions. Outputs and error codes must be
byte-identical to the original C files. Their only shared dependency is the
`errors` module, matching the C files' `error_private.h` include.
Cargo features `legacy-v01` .. `legacy-v07` gate the per-version modules and
are never default features. The build systems derive the feature list from
the C configuration:
- `lib/Makefile` and `programs/Makefile` map `ZSTD_LEGACY_SUPPORT=N` to the
features for versions >= N (0 disables legacy), matching the
`ZSTD_LEGACY_FILES` selection in `lib/libzstd.mk`.
- `tests/Makefile` always enables all seven features because the test
objects compile every `lib/legacy/*.c` file regardless of dispatch level.
- `build/meson` maps `legacy_level` like the makefiles; `build/cmake`
enables all seven whenever `ZSTD_LEGACY_SUPPORT` is on because it always
compiles all seven C files.
Every build system also encodes the legacy selection in the Rust target
directory name (for example `c1-d1-default-legacy5`), for the same reason the
HUF mode is encoded there: a cached archive built for one configuration must
never be linked into a build that expects another.
A feature whose version has not been ported yet gates nothing; the original
C file still provides that decoder, so mixed C/Rust legacy levels link
cleanly. Porting a version means adding `src/legacy/zstd_v0N.rs`, registering
it in `src/legacy/mod.rs` behind its feature, and reducing
`lib/legacy/zstd_v0N.c` to a declaration-only shim. For v0.1 the streaming
`ZSTDv01_Dctx` state lives entirely in Rust: C code only ever holds an opaque
pointer, so the C-side struct definition is gone.
## Compatibility boundary
+40
View File
@@ -0,0 +1,40 @@
//! Frozen decoders for the legacy zstd formats (v0.1 through v0.7).
//!
//! Frozen-decoder policy
//! =====================
//!
//! Each `lib/legacy/zstd_v0N.c` translation unit is a self-contained snapshot
//! of the entropy coders and frame logic of that historical release. The
//! Rust ports mirror that property:
//!
//! - Every version keeps its own frozen FSE/Huff0 and frame logic. The
//! modern `fse_decompress`, `huf_decompress`, `bitstream`, or `mem`
//! modules must NOT be reused here, and legacy versions must not share
//! code with each other, even where functions look identical. The legacy
//! formats are frozen; the modern modules keep evolving.
//! - Ports are line-by-line translations of the corresponding C file: same
//! table layouts, same arithmetic, same error codes. Outputs must be
//! byte-identical to the C implementation, including error behavior.
//! - The only shared dependency is `crate::errors`, because the C files
//! include `error_private.h` for the public `ZSTD_ErrorCode` values.
//!
//! Registration
//! ============
//!
//! Cargo features `legacy-v01` .. `legacy-v07` are all declared in
//! `Cargo.toml`. The build systems always pass the feature list derived
//! from the C configuration (`ZSTD_LEGACY_SUPPORT=N` enables versions N
//! and newer), so a feature may be enabled before its port exists. A version
//! without a Rust module simply stays implemented by its C file.
//!
//! To port version `v0N`: add `zstd_v0N.rs` next to this file, reduce
//! `lib/legacy/zstd_v0N.c` to a declaration-only shim, and register the
//! module here with exactly one line:
//!
//! ```text
//! #[cfg(feature = "legacy-v0N")]
//! pub mod zstd_v0N;
//! ```
#[cfg(feature = "legacy-v01")]
pub mod zstd_v01;
+2354
View File
@@ -0,0 +1,2354 @@
#![allow(non_snake_case)]
//! Frozen decoder for the zstd v0.1 format.
//!
//! This is a line-by-line port of `lib/legacy/zstd_v01.c`: the same table
//! layouts, the same arithmetic, and the same error codes. The C file is a
//! self-contained snapshot of the v0.1-era FSE and Huff0 coders, so this
//! module deliberately reimplements them instead of reusing the modern
//! `fse_decompress`/`huf_decompress` modules (see `legacy/mod.rs` for the
//! frozen-decoder policy). The only shared dependency is `crate::errors`,
//! mirroring the C file's `error_private.h` include.
//!
//! The streaming `ZSTDv01_Dctx` state lives entirely in Rust; C callers only
//! ever hold an opaque pointer to it. It is allocated with `libc::malloc`
//! and released with `libc::free`, exactly like the original C context.
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use std::os::raw::{c_uint, c_void};
use std::ptr;
/* ******************************************
* Error management (frozen v0.1 FSE codes)
********************************************/
/* FSE_LIST_ERRORS in zstd_v01.c; values are returned as `(size_t)-code`. */
const FSE_ERROR_GENERIC: usize = 1;
const FSE_ERROR_TABLELOG_TOO_LARGE: usize = 2;
const FSE_ERROR_MAX_SYMBOL_VALUE_TOO_LARGE: usize = 3;
const FSE_ERROR_MAX_SYMBOL_VALUE_TOO_SMALL: usize = 4;
const FSE_ERROR_DST_SIZE_TOO_SMALL: usize = 5;
const FSE_ERROR_SRC_SIZE_WRONG: usize = 6;
const FSE_ERROR_CORRUPTION_DETECTED: usize = 7;
const FSE_ERROR_MAX_CODE: usize = 8;
#[inline]
fn fse_error(code: usize) -> usize {
code.wrapping_neg()
}
#[inline]
fn fse_is_error(code: usize) -> bool {
code > fse_error(FSE_ERROR_MAX_CODE)
}
/* ******************************************
* Tuning parameters (frozen)
********************************************/
const FSE_MAX_MEMORY_USAGE: u32 = 14;
const FSE_MAX_SYMBOL_VALUE: u32 = 255;
const FSE_MAX_TABLELOG: u32 = FSE_MAX_MEMORY_USAGE - 2;
const FSE_MIN_TABLELOG: u32 = 5;
const FSE_TABLELOG_ABSOLUTE_MAX: u32 = 15;
const HUF_MAX_SYMBOL_VALUE: u32 = 255;
const HUF_MAX_TABLELOG: u32 = 12;
const HUF_ABSOLUTEMAX_TABLELOG: u32 = 16;
const IS_32BITS: bool = std::mem::size_of::<usize>() == 4;
const USIZE_BITS: u32 = usize::BITS;
/* ******************************************
* Memory I/O (FSE_read* / ZSTD_read* helpers)
********************************************/
#[inline]
unsafe fn fse_read_le16(mem_ptr: *const u8) -> u16 {
u16::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 2]))
}
#[inline]
unsafe fn fse_read_le32(mem_ptr: *const u8) -> u32 {
u32::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 4]))
}
#[inline]
unsafe fn fse_read_le64(mem_ptr: *const u8) -> u64 {
u64::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 8]))
}
#[inline]
unsafe fn fse_read_lest(mem_ptr: *const u8) -> usize {
if IS_32BITS {
fse_read_le32(mem_ptr) as usize
} else {
fse_read_le64(mem_ptr) as usize
}
}
/// `FSE_highbit32`; the caller guarantees `val != 0`, as in C.
#[inline]
fn fse_highbit32(val: u32) -> u32 {
val.leading_zeros() ^ 31
}
/* ******************************************
* FSE structures
********************************************/
#[repr(C)]
#[derive(Clone, Copy)]
struct FseDecode {
new_state: u16,
symbol: u8,
nb_bits: u8,
}
#[repr(C)]
struct FseDTableHeader {
table_log: u16,
fast_mode: u16,
}
struct FseDStream {
bit_container: usize,
bits_consumed: u32,
ptr: *const u8,
start: *const u8,
}
struct FseDState {
state: usize,
table: *const FseDecode,
}
const FSE_DSTREAM_UNFINISHED: u32 = 0;
const FSE_DSTREAM_END_OF_BUFFER: u32 = 1;
const FSE_DSTREAM_COMPLETED: u32 = 2;
const FSE_DSTREAM_TOO_FAR: u32 = 3;
#[inline]
fn fse_table_step(table_size: u32) -> u32 {
(table_size >> 1) + (table_size >> 3) + 3
}
/* An FSE_DTable is an opaque u32 array: one header word followed by
* `1 << tableLog` FseDecode entries, exactly as in C. */
unsafe fn fse_build_dtable(
dt: *mut u32,
normalized_counter: *const i16,
max_symbol_value: u32,
table_log: u32,
) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let table_decode = dt.add(1) as *mut FseDecode;
/* Sanity checks */
if max_symbol_value > FSE_MAX_SYMBOL_VALUE {
return fse_error(FSE_ERROR_MAX_SYMBOL_VALUE_TOO_LARGE);
}
if table_log > FSE_MAX_TABLELOG {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE);
}
let table_size: u32 = 1 << table_log;
let table_mask = table_size - 1;
let step = fse_table_step(table_size);
let mut symbol_next = [0u16; (FSE_MAX_SYMBOL_VALUE + 1) as usize];
let mut position: u32 = 0;
let mut high_threshold = table_size - 1;
let large_limit = (1i32 << (table_log - 1)) as i16;
let mut no_large: u32 = 1;
/* Init, lay down lowprob symbols */
(*dtable_h).table_log = table_log as u16;
for s in 0..=max_symbol_value {
let count = *normalized_counter.add(s as usize);
if count == -1 {
(*table_decode.add(high_threshold as usize)).symbol = s as u8;
high_threshold = high_threshold.wrapping_sub(1);
symbol_next[s as usize] = 1;
} else {
if count >= large_limit {
no_large = 0;
}
symbol_next[s as usize] = count as u16;
}
}
/* Spread symbols */
for s in 0..=max_symbol_value {
let count = *normalized_counter.add(s as usize);
let mut i = 0i32;
while i < count as i32 {
(*table_decode.add(position as usize)).symbol = s as u8;
position = (position + step) & table_mask;
while position > high_threshold {
position = (position + step) & table_mask; /* lowprob area */
}
i += 1;
}
}
if position != 0 {
/* position must reach all cells once, otherwise normalizedCounter is incorrect */
return fse_error(FSE_ERROR_GENERIC);
}
/* Build Decoding table */
for i in 0..table_size as usize {
let symbol = (*table_decode.add(i)).symbol;
let next_state = symbol_next[symbol as usize];
symbol_next[symbol as usize] = next_state.wrapping_add(1);
let nb_bits = (table_log - fse_highbit32(next_state as u32)) as u8;
(*table_decode.add(i)).nb_bits = nb_bits;
(*table_decode.add(i)).new_state =
(((next_state as u32) << nb_bits).wrapping_sub(table_size)) as u16;
}
(*dtable_h).fast_mode = no_large as u16;
0
}
/* ******************************************
* FSE header bitstream (FSE_readNCount)
********************************************/
unsafe fn fse_read_ncount(
normalized_counter: *mut i16,
max_sv_ptr: &mut u32,
table_log_ptr: &mut u32,
header_buffer: *const u8,
hb_size: usize,
) -> usize {
let istart = header_buffer;
let iend_addr = (istart as usize).wrapping_add(hb_size);
let mut ip = istart;
let mut charnum: u32 = 0;
let mut previous0 = false;
if hb_size < 4 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut bit_stream = fse_read_le32(ip);
let mut nb_bits: i32 = ((bit_stream & 0xF) + FSE_MIN_TABLELOG) as i32; /* extract tableLog */
if nb_bits > FSE_TABLELOG_ABSOLUTE_MAX as i32 {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE);
}
bit_stream >>= 4;
let mut bit_count: i32 = 4;
*table_log_ptr = nb_bits as u32;
let mut remaining: i32 = (1 << nb_bits) + 1;
let mut threshold: i32 = 1 << nb_bits;
nb_bits += 1;
while remaining > 1 && charnum <= *max_sv_ptr {
if previous0 {
let mut n0 = charnum;
while (bit_stream & 0xFFFF) == 0xFFFF {
n0 += 24;
if (ip as usize) < iend_addr.wrapping_sub(5) {
ip = ip.add(2);
bit_stream = fse_read_le32(ip).wrapping_shr(bit_count as u32);
} else {
bit_stream >>= 16;
bit_count += 16;
}
}
while (bit_stream & 3) == 3 {
n0 += 3;
bit_stream >>= 2;
bit_count += 2;
}
n0 += bit_stream & 3;
bit_count += 2;
if n0 > *max_sv_ptr {
return fse_error(FSE_ERROR_MAX_SYMBOL_VALUE_TOO_SMALL);
}
while charnum < n0 {
*normalized_counter.add(charnum as usize) = 0;
charnum += 1;
}
if (ip as usize) <= iend_addr.wrapping_sub(7)
|| (ip as usize).wrapping_add((bit_count >> 3) as usize)
<= iend_addr.wrapping_sub(4)
{
ip = ip.add((bit_count >> 3) as usize);
bit_count &= 7;
bit_stream = fse_read_le32(ip).wrapping_shr(bit_count as u32);
} else {
bit_stream >>= 2;
}
}
{
let max: i16 = ((2 * threshold - 1) - remaining) as i16;
let mut count: i16;
if (bit_stream & (threshold - 1) as u32) < max as i32 as u32 {
count = (bit_stream & (threshold - 1) as u32) as u16 as i16;
bit_count += nb_bits - 1;
} else {
count = (bit_stream & (2 * threshold - 1) as u32) as u16 as i16;
if count as i32 >= threshold {
count = ((count as i32) - (max as i32)) as i16;
}
bit_count += nb_bits;
}
count = count.wrapping_sub(1); /* extra accuracy */
remaining -= (count as i32).abs();
*normalized_counter.add(charnum as usize) = count;
charnum += 1;
previous0 = count == 0;
while remaining < threshold {
nb_bits -= 1;
threshold >>= 1;
}
if (ip as usize) <= iend_addr.wrapping_sub(7)
|| (ip as usize).wrapping_add((bit_count >> 3) as usize)
<= iend_addr.wrapping_sub(4)
{
ip = ip.add((bit_count >> 3) as usize);
bit_count &= 7;
} else {
bit_count -=
(8 * (iend_addr.wrapping_sub(4) as isize - ip as usize as isize)) as i32;
ip = (iend_addr - 4) as *const u8;
}
bit_stream = fse_read_le32(ip).wrapping_shr((bit_count & 31) as u32);
}
}
if remaining != 1 {
return fse_error(FSE_ERROR_GENERIC);
}
*max_sv_ptr = charnum - 1;
ip = ip.wrapping_offset(((bit_count + 7) >> 3) as isize);
if (ip as usize).wrapping_sub(istart as usize) > hb_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
(ip as usize).wrapping_sub(istart as usize)
}
/* ******************************************
* FSE decompression, byte symbols
********************************************/
unsafe fn fse_build_dtable_rle(dt: *mut u32, symbol_value: u8) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let cell = dt.add(1) as *mut FseDecode;
(*dtable_h).table_log = 0;
(*dtable_h).fast_mode = 0;
(*cell).new_state = 0;
(*cell).symbol = symbol_value;
(*cell).nb_bits = 0;
0
}
unsafe fn fse_build_dtable_raw(dt: *mut u32, nb_bits: u32) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let dinfo = dt.add(1) as *mut FseDecode;
/* Sanity checks */
if nb_bits < 1 {
return fse_error(FSE_ERROR_GENERIC); /* min size */
}
let table_size: u32 = 1 << nb_bits;
let table_mask = table_size - 1;
let max_symbol_value = table_mask;
(*dtable_h).table_log = nb_bits as u16;
(*dtable_h).fast_mode = 1;
for s in 0..=max_symbol_value {
let cell = dinfo.add(s as usize);
(*cell).new_state = 0;
(*cell).symbol = s as u8;
(*cell).nb_bits = nb_bits as u8;
}
0
}
unsafe fn fse_init_dstream(
bit_d: &mut FseDStream,
src_buffer: *const u8,
src_size: usize,
) -> usize {
let word = std::mem::size_of::<usize>();
if src_size < 1 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
if src_size >= word {
bit_d.start = src_buffer;
bit_d.ptr = src_buffer.add(src_size - word);
bit_d.bit_container = fse_read_lest(bit_d.ptr);
let contain32 = *src_buffer.add(src_size - 1) as u32;
if contain32 == 0 {
return fse_error(FSE_ERROR_GENERIC); /* stop bit not present */
}
bit_d.bits_consumed = 8 - fse_highbit32(contain32);
} else {
bit_d.start = src_buffer;
bit_d.ptr = bit_d.start;
bit_d.bit_container = *bit_d.start as usize;
/* C switch with fallthrough over srcSize 7..2 */
if src_size >= 7 {
bit_d.bit_container += (*src_buffer.add(6) as usize) << (USIZE_BITS as usize - 16);
}
if src_size >= 6 {
bit_d.bit_container += (*src_buffer.add(5) as usize) << (USIZE_BITS as usize - 24);
}
if src_size >= 5 {
bit_d.bit_container += (*src_buffer.add(4) as usize) << (USIZE_BITS as usize - 32);
}
if src_size >= 4 {
bit_d.bit_container += (*src_buffer.add(3) as usize) << 24;
}
if src_size >= 3 {
bit_d.bit_container += (*src_buffer.add(2) as usize) << 16;
}
if src_size >= 2 {
bit_d.bit_container += (*src_buffer.add(1) as usize) << 8;
}
let contain32 = *src_buffer.add(src_size - 1) as u32;
if contain32 == 0 {
return fse_error(FSE_ERROR_GENERIC); /* stop bit not present */
}
bit_d.bits_consumed = 8 - fse_highbit32(contain32);
bit_d.bits_consumed += ((word - src_size) * 8) as u32;
}
src_size
}
#[inline]
unsafe fn fse_look_bits(bit_d: &FseDStream, nb_bits: u32) -> usize {
let bit_mask = USIZE_BITS - 1;
((bit_d.bit_container << (bit_d.bits_consumed & bit_mask)) >> 1)
>> (bit_mask.wrapping_sub(nb_bits) & bit_mask)
}
#[inline]
unsafe fn fse_look_bits_fast(bit_d: &FseDStream, nb_bits: u32) -> usize {
/* only if nb_bits >= 1 */
let bit_mask = USIZE_BITS - 1;
(bit_d.bit_container << (bit_d.bits_consumed & bit_mask))
>> ((bit_mask + 1).wrapping_sub(nb_bits) & bit_mask)
}
#[inline]
fn fse_skip_bits(bit_d: &mut FseDStream, nb_bits: u32) {
bit_d.bits_consumed = bit_d.bits_consumed.wrapping_add(nb_bits);
}
#[inline]
unsafe fn fse_read_bits(bit_d: &mut FseDStream, nb_bits: u32) -> usize {
let value = fse_look_bits(bit_d, nb_bits);
fse_skip_bits(bit_d, nb_bits);
value
}
#[inline]
unsafe fn fse_read_bits_fast(bit_d: &mut FseDStream, nb_bits: u32) -> usize {
/* only if nb_bits >= 1 */
let value = fse_look_bits_fast(bit_d, nb_bits);
fse_skip_bits(bit_d, nb_bits);
value
}
unsafe fn fse_reload_dstream(bit_d: &mut FseDStream) -> u32 {
let word = std::mem::size_of::<usize>();
if bit_d.bits_consumed > (word * 8) as u32 {
/* should never happen */
return FSE_DSTREAM_TOO_FAR;
}
if (bit_d.ptr as usize) >= (bit_d.start as usize).wrapping_add(word) {
bit_d.ptr = bit_d.ptr.sub((bit_d.bits_consumed >> 3) as usize);
bit_d.bits_consumed &= 7;
bit_d.bit_container = fse_read_lest(bit_d.ptr);
return FSE_DSTREAM_UNFINISHED;
}
if bit_d.ptr == bit_d.start {
if bit_d.bits_consumed < (word * 8) as u32 {
return FSE_DSTREAM_END_OF_BUFFER;
}
return FSE_DSTREAM_COMPLETED;
}
{
let mut nb_bytes = bit_d.bits_consumed >> 3;
let mut result = FSE_DSTREAM_UNFINISHED;
if (bit_d.ptr as usize).wrapping_sub(nb_bytes as usize) < (bit_d.start as usize) {
nb_bytes = ((bit_d.ptr as usize) - (bit_d.start as usize)) as u32; /* ptr > start */
result = FSE_DSTREAM_END_OF_BUFFER;
}
bit_d.ptr = bit_d.ptr.sub(nb_bytes as usize);
bit_d.bits_consumed -= nb_bytes * 8;
bit_d.bit_container = fse_read_lest(bit_d.ptr); /* reminder : srcSize > sizeof(bitD) */
result
}
}
unsafe fn fse_init_dstate(d_state: &mut FseDState, bit_d: &mut FseDStream, dt: *const u32) {
let dtable_h = dt as *const FseDTableHeader;
d_state.state = fse_read_bits(bit_d, (*dtable_h).table_log as u32);
fse_reload_dstream(bit_d);
d_state.table = dt.add(1) as *const FseDecode;
}
unsafe fn fse_decode_symbol(d_state: &mut FseDState, bit_d: &mut FseDStream) -> u8 {
let d_info = *d_state.table.add(d_state.state);
let low_bits = fse_read_bits(bit_d, d_info.nb_bits as u32);
d_state.state = (d_info.new_state as usize).wrapping_add(low_bits);
d_info.symbol
}
unsafe fn fse_decode_symbol_fast(d_state: &mut FseDState, bit_d: &mut FseDStream) -> u8 {
let d_info = *d_state.table.add(d_state.state);
let low_bits = fse_read_bits_fast(bit_d, d_info.nb_bits as u32);
d_state.state = (d_info.new_state as usize).wrapping_add(low_bits);
d_info.symbol
}
#[inline]
fn fse_end_of_dstream(bit_d: &FseDStream) -> bool {
bit_d.ptr == bit_d.start && bit_d.bits_consumed == USIZE_BITS
}
#[inline]
fn fse_end_of_dstate(d_state: &FseDState) -> bool {
d_state.state == 0
}
unsafe fn fse_decompress_using_dtable_generic(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
dt: *const u32,
fast: bool,
) -> usize {
let ostart = dst;
let mut op = ostart;
let omax_addr = (op as usize).wrapping_add(max_dst_size);
let olimit_addr = omax_addr.wrapping_sub(3);
let mut bit_d = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut state1 = FseDState {
state: 0,
table: ptr::null(),
};
let mut state2 = FseDState {
state: 0,
table: ptr::null(),
};
/* Init */
let error_code = fse_init_dstream(&mut bit_d, c_src, c_src_size);
if fse_is_error(error_code) {
return error_code;
}
fse_init_dstate(&mut state1, &mut bit_d, dt);
fse_init_dstate(&mut state2, &mut bit_d, dt);
macro_rules! fse_getsymbol {
($state:expr) => {
if fast {
fse_decode_symbol_fast($state, &mut bit_d)
} else {
fse_decode_symbol($state, &mut bit_d)
}
};
}
/* Constant conditions from the C source; on 64-bit both are false, on
* 32-bit only the *4 variant reloads. */
const RELOAD_2: bool = FSE_MAX_TABLELOG * 2 + 7 > USIZE_BITS;
const RELOAD_4: bool = FSE_MAX_TABLELOG * 4 + 7 > USIZE_BITS;
/* 4 symbols per loop */
while fse_reload_dstream(&mut bit_d) == FSE_DSTREAM_UNFINISHED && (op as usize) < olimit_addr {
*op = fse_getsymbol!(&mut state1);
if RELOAD_2 {
/* This test must be static */
fse_reload_dstream(&mut bit_d);
}
*op.add(1) = fse_getsymbol!(&mut state2);
if RELOAD_4 {
/* This test must be static */
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_UNFINISHED {
op = op.add(2);
break;
}
}
*op.add(2) = fse_getsymbol!(&mut state1);
if RELOAD_2 {
/* This test must be static */
fse_reload_dstream(&mut bit_d);
}
*op.add(3) = fse_getsymbol!(&mut state2);
op = op.add(4);
}
/* tail */
loop {
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_COMPLETED
|| (op as usize) == omax_addr
|| (fse_end_of_dstream(&bit_d) && (fast || fse_end_of_dstate(&state1)))
{
break;
}
*op = fse_getsymbol!(&mut state1);
op = op.add(1);
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_COMPLETED
|| (op as usize) == omax_addr
|| (fse_end_of_dstream(&bit_d) && (fast || fse_end_of_dstate(&state2)))
{
break;
}
*op = fse_getsymbol!(&mut state2);
op = op.add(1);
}
/* end ? */
if fse_end_of_dstream(&bit_d) && fse_end_of_dstate(&state1) && fse_end_of_dstate(&state2) {
return (op as usize) - (ostart as usize);
}
if (op as usize) == omax_addr {
/* dst buffer is full, but cSrc unfinished */
return fse_error(FSE_ERROR_DST_SIZE_TOO_SMALL);
}
fse_error(FSE_ERROR_CORRUPTION_DETECTED)
}
unsafe fn fse_decompress_using_dtable(
dst: *mut u8,
original_size: usize,
c_src: *const u8,
c_src_size: usize,
dt: *const u32,
) -> usize {
let fast_mode = (*(dt as *const FseDTableHeader)).fast_mode;
/* select fast mode (static) */
if fast_mode != 0 {
return fse_decompress_using_dtable_generic(
dst,
original_size,
c_src,
c_src_size,
dt,
true,
);
}
fse_decompress_using_dtable_generic(dst, original_size, c_src, c_src_size, dt, false)
}
unsafe fn fse_decompress(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
) -> usize {
let istart = c_src;
let mut ip = istart;
let mut counting = [0i16; (FSE_MAX_SYMBOL_VALUE + 1) as usize];
let mut dt = [0u32; 1 + (1 << FSE_MAX_TABLELOG)]; /* DTable_max_t */
let mut table_log: u32 = 0;
let mut max_symbol_value: u32 = FSE_MAX_SYMBOL_VALUE;
let mut remaining_size = c_src_size;
if c_src_size < 2 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG); /* too small input size */
}
/* normal FSE decoding mode */
let error_code = fse_read_ncount(
counting.as_mut_ptr(),
&mut max_symbol_value,
&mut table_log,
istart,
c_src_size,
);
if fse_is_error(error_code) {
return error_code;
}
if error_code >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG); /* too small input size */
}
ip = ip.add(error_code);
remaining_size -= error_code;
let error_code = fse_build_dtable(
dt.as_mut_ptr(),
counting.as_ptr(),
max_symbol_value,
table_log,
);
if fse_is_error(error_code) {
return error_code;
}
/* always return, even if it is an error code */
fse_decompress_using_dtable(dst, max_dst_size, ip, remaining_size, dt.as_ptr())
}
/* ******************************************
* Huff0 : Huffman block decompression
********************************************/
#[repr(C)]
#[derive(Clone, Copy)]
struct HufDElt {
byte: u8,
nb_bits: u8,
}
/* Loop shapes and arithmetic below intentionally mirror the frozen C. */
#[allow(clippy::needless_range_loop, clippy::manual_div_ceil)]
unsafe fn huf_read_dtable(dtable: *mut u16, src: *const u8, src_size: usize) -> usize {
let mut huff_weight = [0u8; (HUF_MAX_SYMBOL_VALUE + 1) as usize];
let mut rank_val = [0u32; (HUF_ABSOLUTEMAX_TABLELOG + 1) as usize];
let ip = src;
let dt = dtable.add(1) as *mut HufDElt;
if src_size == 0 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut i_size = *ip as usize;
let o_size: usize;
if i_size >= 128 {
/* special header */
if i_size >= 242 {
/* RLE */
const L: [usize; 14] = [1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128];
o_size = L[i_size - 242];
huff_weight = [1u8; (HUF_MAX_SYMBOL_VALUE + 1) as usize];
i_size = 0;
} else {
/* Incompressible */
o_size = i_size - 127;
i_size = (o_size + 1) / 2;
if i_size + 1 > src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let ip = ip.add(1);
let mut n = 0usize;
while n < o_size {
huff_weight[n] = *ip.add(n / 2) >> 4;
huff_weight[n + 1] = *ip.add(n / 2) & 15;
n += 2;
}
}
} else {
/* header compressed with FSE (normal case) */
if i_size + 1 > src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
/* max 255 values decoded, last one is implied */
let decoded = fse_decompress(
huff_weight.as_mut_ptr(),
HUF_MAX_SYMBOL_VALUE as usize,
ip.add(1),
i_size,
);
if fse_is_error(decoded) {
return decoded;
}
o_size = decoded;
}
/* collect weight stats */
let mut weight_total: u32 = 0;
for n in 0..o_size {
if huff_weight[n] >= HUF_ABSOLUTEMAX_TABLELOG as u8 {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
rank_val[huff_weight[n] as usize] += 1;
weight_total += (1u32 << huff_weight[n]) >> 1;
}
if weight_total == 0 {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* get last non-null symbol weight (implied, total must be 2^n) */
let max_bits = fse_highbit32(weight_total) + 1;
if max_bits > *dtable as u32 {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE); /* DTable is too small */
}
*dtable = max_bits as u16;
{
let total = 1u32 << max_bits;
let rest = total - weight_total;
let verif = 1u32 << fse_highbit32(rest);
let last_weight = fse_highbit32(rest) + 1;
if verif != rest {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED); /* last value must be a clean power of 2 */
}
huff_weight[o_size] = last_weight as u8;
rank_val[last_weight as usize] += 1;
}
/* check tree construction validity */
if rank_val[1] < 2 || (rank_val[1] & 1) != 0 {
/* by construction : at least 2 elts of rank 1, must be even */
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* Prepare ranks */
let mut next_rank_start: u32 = 0;
for n in 1..=(max_bits as usize) {
let current = next_rank_start;
next_rank_start += rank_val[n] << (n - 1);
rank_val[n] = current;
}
/* fill DTable */
for n in 0..=o_size {
let w = huff_weight[n] as usize;
let length = (1u32 << w) >> 1;
let d = HufDElt {
byte: n as u8,
nb_bits: (max_bits + 1 - w as u32) as u8,
};
for i in rank_val[w]..(rank_val[w] + length) {
*dt.add(i as usize) = d;
}
rank_val[w] += length;
}
i_size + 1
}
unsafe fn huf_decode_symbol(d_stream: &mut FseDStream, dt: *const HufDElt, dt_log: u32) -> u8 {
let val = fse_look_bits_fast(d_stream, dt_log); /* note : dtLog >= 1 */
let entry = *dt.add(val);
fse_skip_bits(d_stream, entry.nb_bits as u32);
entry.byte
}
/* The C decode macros: SYMBOL_1 reloads on 32-bit only when
* HUF_MAX_TABLELOG > 12 (never here); SYMBOL_2 reloads on 32-bit. */
#[inline]
unsafe fn huf_decode_symbol_1(
op: *mut u8,
d_stream: &mut FseDStream,
dt: *const HufDElt,
dt_log: u32,
) {
*op = huf_decode_symbol(d_stream, dt, dt_log);
if IS_32BITS && HUF_MAX_TABLELOG > 12 {
fse_reload_dstream(d_stream);
}
}
#[inline]
unsafe fn huf_decode_symbol_2(
op: *mut u8,
d_stream: &mut FseDStream,
dt: *const HufDElt,
dt_log: u32,
) {
*op = huf_decode_symbol(d_stream, dt, dt_log);
if IS_32BITS {
fse_reload_dstream(d_stream);
}
}
unsafe fn huf_decompress_using_dtable(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
dtable: *const u16,
) -> usize {
if c_src_size < 6 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let ostart = dst;
let mut op = ostart;
let omax_addr = (op as usize).wrapping_add(max_dst_size);
let olimit_addr = if max_dst_size < 15 {
op as usize
} else {
omax_addr - 15
};
let dt = dtable.add(1) as *const HufDElt;
let dt_log = *dtable as u32;
/* Init */
let length1 = fse_read_le16(c_src) as usize;
let length2 = fse_read_le16(c_src.add(2)) as usize;
let length3 = fse_read_le16(c_src.add(4)) as usize;
let length4 = c_src_size
.wrapping_sub(6)
.wrapping_sub(length1)
.wrapping_sub(length2)
.wrapping_sub(length3); /* check coherency !! */
let start1 = c_src.add(6);
let start2 = start1.wrapping_add(length1);
let start3 = start2.wrapping_add(length2);
let start4 = start3.wrapping_add(length3);
if length1 + length2 + length3 + 6 >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut bit_d1 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d2 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d3 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d4 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let error_code = fse_init_dstream(&mut bit_d1, start1, length1);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d2, start2, length2);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d3, start3, length3);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d4, start4, length4);
if fse_is_error(error_code) {
return error_code;
}
let mut reload_status = fse_reload_dstream(&mut bit_d2);
/* 16 symbols per loop; D2-3-4 are supposed to be synchronized and finish together */
while reload_status < FSE_DSTREAM_COMPLETED && (op as usize) < olimit_addr {
huf_decode_symbol_1(op, &mut bit_d1, dt, dt_log);
huf_decode_symbol_1(op.add(1), &mut bit_d2, dt, dt_log);
huf_decode_symbol_1(op.add(2), &mut bit_d3, dt, dt_log);
huf_decode_symbol_1(op.add(3), &mut bit_d4, dt, dt_log);
huf_decode_symbol_2(op.add(4), &mut bit_d1, dt, dt_log);
huf_decode_symbol_2(op.add(5), &mut bit_d2, dt, dt_log);
huf_decode_symbol_2(op.add(6), &mut bit_d3, dt, dt_log);
huf_decode_symbol_2(op.add(7), &mut bit_d4, dt, dt_log);
huf_decode_symbol_1(op.add(8), &mut bit_d1, dt, dt_log);
huf_decode_symbol_1(op.add(9), &mut bit_d2, dt, dt_log);
huf_decode_symbol_1(op.add(10), &mut bit_d3, dt, dt_log);
huf_decode_symbol_1(op.add(11), &mut bit_d4, dt, dt_log);
*op.add(12) = huf_decode_symbol(&mut bit_d1, dt, dt_log);
*op.add(13) = huf_decode_symbol(&mut bit_d2, dt, dt_log);
*op.add(14) = huf_decode_symbol(&mut bit_d3, dt, dt_log);
*op.add(15) = huf_decode_symbol(&mut bit_d4, dt, dt_log);
op = op.add(16);
reload_status = fse_reload_dstream(&mut bit_d2)
| fse_reload_dstream(&mut bit_d3)
| fse_reload_dstream(&mut bit_d4);
fse_reload_dstream(&mut bit_d1);
}
if reload_status != FSE_DSTREAM_COMPLETED {
/* not complete : some bitStream might be FSE_DStream_unfinished */
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* tail */
{
let mut bit_tail = FseDStream {
bit_container: bit_d1.bit_container, /* required in case of FSE_DStream_endOfBuffer */
bits_consumed: bit_d1.bits_consumed,
ptr: bit_d1.ptr,
start: start1,
};
while fse_reload_dstream(&mut bit_tail) < FSE_DSTREAM_COMPLETED && (op as usize) < omax_addr
{
*op = huf_decode_symbol(&mut bit_tail, dt, dt_log);
op = op.add(1);
}
if fse_end_of_dstream(&bit_tail) {
return (op as usize) - (ostart as usize);
}
}
if (op as usize) == omax_addr {
return fse_error(FSE_ERROR_DST_SIZE_TOO_SMALL); /* dst buffer is full, but cSrc unfinished */
}
fse_error(FSE_ERROR_CORRUPTION_DETECTED)
}
unsafe fn huf_decompress(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
) -> usize {
/* HUF_CREATE_STATIC_DTABLE(DTable, HUF_MAX_TABLELOG) */
let mut dtable = [0u16; 1 + (1 << HUF_MAX_TABLELOG)];
dtable[0] = HUF_MAX_TABLELOG as u16;
let mut ip = c_src;
let error_code = huf_read_dtable(dtable.as_mut_ptr(), c_src, c_src_size);
if fse_is_error(error_code) {
return error_code;
}
if error_code >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
ip = ip.add(error_code);
huf_decompress_using_dtable(
dst,
max_dst_size,
ip,
c_src_size - error_code,
dtable.as_ptr(),
)
}
/* ******************************************
* zstd v0.1 frame decoding
********************************************/
const ZSTD_MAGIC_NUMBER: u32 = 0xFD2FB51E; /* 3rd version : seqNb header */
const KB: usize = 1 << 10;
const BLOCKSIZE: usize = 128 * KB; /* define, for static allocation */
const MINMATCH: usize = 4;
const MLBITS: u32 = 7;
const LLBITS: u32 = 6;
const OFFBITS: u32 = 5;
const MAX_ML: u32 = (1 << MLBITS) - 1;
const MAX_LL: u32 = (1 << LLBITS) - 1;
const MAX_OFF: u32 = (1 << OFFBITS) - 1;
#[allow(dead_code)] /* part of the frozen v0.1 constant set; used only by the compressor */
const LIT_FSE_LOG: u32 = 11;
const ML_FSE_LOG: u32 = 10;
const LL_FSE_LOG: u32 = 10;
const OFF_FSE_LOG: u32 = 9;
const ZSTD_CONTENTSIZE_ERROR: u64 = 0u64.wrapping_sub(2);
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
const ZSTD_FRAME_HEADER_SIZE: usize = 4;
/* FSE_DTABLE_SIZE_U32(maxTableLog) == 1 + (1 << maxTableLog) */
const LL_DTABLE_SIZE_U32: usize = 1 + (1 << LL_FSE_LOG as usize);
const OFF_DTABLE_SIZE_U32: usize = 1 + (1 << OFF_FSE_LOG as usize);
const ML_DTABLE_SIZE_U32: usize = 1 + (1 << ML_FSE_LOG as usize);
#[inline]
unsafe fn zstd_copy4(dst: *mut u8, src: *const u8) {
ptr::copy_nonoverlapping(src, dst, 4);
}
#[inline]
unsafe fn zstd_copy8(dst: *mut u8, src: *const u8) {
ptr::copy_nonoverlapping(src, dst, 8);
}
unsafe fn zstd_wildcopy(dst: *mut u8, src: *const u8, length: isize) {
let mut ip = src;
let mut op = dst;
let oend_addr = (op as usize).wrapping_add(length as usize);
while (op as usize) < oend_addr {
zstd_copy8(op, ip);
op = op.add(8);
ip = ip.add(8);
}
}
#[inline]
unsafe fn zstd_read_le16(mem_ptr: *const u8) -> u16 {
u16::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 2]))
}
#[inline]
unsafe fn zstd_read_le24(mem_ptr: *const u8) -> u32 {
zstd_read_le16(mem_ptr) as u32 + ((*mem_ptr.add(2) as u32) << 16)
}
#[inline]
unsafe fn zstd_read_be32(mem_ptr: *const u8) -> u32 {
u32::from_be_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 4]))
}
/* blockType_t */
const BT_COMPRESSED: u32 = 0;
const BT_RAW: u32 = 1;
const BT_RLE: u32 = 2;
const BT_END: u32 = 3;
struct BlockProperties {
block_type: u32,
orig_size: u32,
}
/// The v0.1 streaming decompression context (`dctx_t` in C). The struct is
/// opaque to C: `zstd_v01.h` only forward-declares it, so the definition now
/// lives here.
#[repr(C)]
pub struct ZSTDv01_Dctx {
ll_table: [u32; LL_DTABLE_SIZE_U32],
off_table: [u32; OFF_DTABLE_SIZE_U32],
ml_table: [u32; ML_DTABLE_SIZE_U32],
previous_dst_end: *const u8,
base: *const u8,
expected: usize,
b_type: u32,
phase: u32,
}
unsafe fn zstdv01_getc_block_size(
src: *const u8,
src_size: usize,
bp_ptr: &mut BlockProperties,
) -> usize {
if src_size < 3 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let header_flags = *src;
let c_size =
(*src.add(2) as u32) + ((*src.add(1) as u32) << 8) + (((header_flags & 7) as u32) << 16);
bp_ptr.block_type = (header_flags >> 6) as u32;
bp_ptr.orig_size = if bp_ptr.block_type == BT_RLE {
c_size
} else {
0
};
if bp_ptr.block_type == BT_END {
return 0;
}
if bp_ptr.block_type == BT_RLE {
return 1;
}
c_size as usize
}
unsafe fn zstd_copy_uncompressed_block(
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
if src_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if src_size > 0 {
ptr::copy_nonoverlapping(src, dst, src_size);
}
src_size
}
unsafe fn zstd_decompress_literals(
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
let oend = dst.add(max_dst_size);
let ip = src;
/* check : minimum 2, for litSize, +1, for content */
if src_size <= 3 {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
let mut lit_size = (*ip.add(1) as usize) + ((*ip as usize) << 8);
lit_size += (((*ip.offset(-3) as usize) >> 3) & 7) << 16; /* mmmmh.... */
if lit_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let op = oend.sub(lit_size);
let error_code = huf_decompress(op, lit_size, ip.add(2), src_size - 2);
if fse_is_error(error_code) {
return ERROR(ZstdErrorCode::Generic);
}
lit_size
}
unsafe fn zstdv01_decode_literals_block(
dst: *mut u8,
max_dst_size: usize,
lit_start: &mut *const u8,
lit_size: &mut usize,
src: *const u8,
src_size: usize,
) -> usize {
let istart = src;
let mut ip = istart;
let ostart = dst;
let oend = ostart.add(max_dst_size);
let mut litbp = BlockProperties {
block_type: 0,
orig_size: 0,
};
let litc_size = zstdv01_getc_block_size(src, src_size, &mut litbp);
if ERR_isError(litc_size) {
return litc_size;
}
if litc_size > src_size - ZSTD_BLOCK_HEADER_SIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
match litbp.block_type {
BT_RAW => {
*lit_start = ip;
ip = ip.add(litc_size);
*lit_size = litc_size;
}
BT_RLE => {
let rle_size = litbp.orig_size as usize;
if rle_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if src_size == 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if rle_size > 0 {
ptr::write_bytes(oend.sub(rle_size), *ip, rle_size);
}
*lit_start = oend.sub(rle_size);
*lit_size = rle_size;
ip = ip.add(1);
}
BT_COMPRESSED => {
let decoded_lit_size = zstd_decompress_literals(dst, max_dst_size, ip, litc_size);
if ERR_isError(decoded_lit_size) {
return decoded_lit_size;
}
*lit_start = oend.sub(decoded_lit_size);
*lit_size = decoded_lit_size;
ip = ip.add(litc_size);
}
_ => {
/* bt_end and impossible values */
return ERROR(ZstdErrorCode::Generic);
}
}
(ip as usize) - (istart as usize)
}
#[allow(clippy::too_many_arguments)]
unsafe fn zstdv01_decode_seq_headers(
nb_seq: &mut i32,
dumps_ptr: &mut *const u8,
dumps_length_ptr: &mut usize,
dtable_ll: *mut u32,
dtable_ml: *mut u32,
dtable_offb: *mut u32,
src: *const u8,
src_size: usize,
) -> usize {
let istart = src;
let mut ip = istart;
let iend_addr = (istart as usize).wrapping_add(src_size);
/* check */
if src_size < 5 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
/* SeqHead */
*nb_seq = zstd_read_le16(ip) as i32;
ip = ip.add(2);
let ll_type = (*ip >> 6) as u32;
let off_type = ((*ip >> 4) & 3) as u32;
let ml_type = ((*ip >> 2) & 3) as u32;
let dumps_length: usize;
if (*ip & 2) != 0 {
dumps_length = (*ip.add(2) as usize) + ((*ip.add(1) as usize) << 8);
ip = ip.add(3);
} else {
dumps_length = (*ip.add(1) as usize) + (((*ip as usize) & 1) << 8);
ip = ip.add(2);
}
*dumps_ptr = ip;
ip = ip.wrapping_add(dumps_length);
*dumps_length_ptr = dumps_length;
/* check */
if (ip as usize) > iend_addr.wrapping_sub(3) {
/* min : all 3 are "raw", hence no header, but at least xxLog bits per type */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
/* sequences */
{
let mut norm = [0i16; (MAX_ML + 1) as usize]; /* assumption : MaxML >= MaxLL and MaxOff */
/* Build DTables */
match ll_type {
BT_RLE => {
fse_build_dtable_rle(dtable_ll, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_ll, LLBITS);
}
_ => {
let mut max = MAX_LL;
let mut ll_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut ll_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if ll_log > LL_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_ll, norm.as_ptr(), max, ll_log);
}
}
match off_type {
BT_RLE => {
if (ip as usize) > iend_addr.wrapping_sub(2) {
/* min : "raw", hence no header, but at least xxLog bits */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
fse_build_dtable_rle(dtable_offb, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_offb, OFFBITS);
}
_ => {
let mut max = MAX_OFF;
let mut off_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut off_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if off_log > OFF_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_offb, norm.as_ptr(), max, off_log);
}
}
match ml_type {
BT_RLE => {
if (ip as usize) > iend_addr.wrapping_sub(2) {
/* min : "raw", hence no header, but at least xxLog bits */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
fse_build_dtable_rle(dtable_ml, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_ml, MLBITS);
}
_ => {
let mut max = MAX_ML;
let mut ml_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut ml_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if ml_log > ML_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_ml, norm.as_ptr(), max, ml_log);
}
}
}
(ip as usize) - (istart as usize)
}
#[derive(Clone, Copy)]
struct Seq {
lit_length: usize,
offset: usize,
match_length: usize,
}
struct SeqState {
d_stream: FseDStream,
state_ll: FseDState,
state_offb: FseDState,
state_ml: FseDState,
prev_offset: usize,
dumps: *const u8,
dumps_end: *const u8,
}
unsafe fn zstd_decode_sequence(seq: &mut Seq, seq_state: &mut SeqState) {
let mut dumps = seq_state.dumps;
let de = seq_state.dumps_end;
/* Literal length */
let mut lit_length =
fse_decode_symbol(&mut seq_state.state_ll, &mut seq_state.d_stream) as usize;
let prev_offset = if lit_length != 0 {
seq.offset
} else {
seq_state.prev_offset
};
seq_state.prev_offset = seq.offset;
if lit_length == MAX_LL as usize {
let add = if (dumps as usize) < (de as usize) {
let v = *dumps as u32;
dumps = dumps.add(1);
v
} else {
0
};
if add < 255 {
lit_length += add as usize;
} else if (dumps as usize) <= (de as usize).wrapping_sub(3) {
lit_length = zstd_read_le24(dumps) as usize;
dumps = dumps.add(3);
}
}
/* Offset */
let mut offset: usize;
{
let offset_code =
fse_decode_symbol(&mut seq_state.state_offb, &mut seq_state.d_stream) as u32;
if IS_32BITS {
fse_reload_dstream(&mut seq_state.d_stream);
}
let mut nb_bits = offset_code.wrapping_sub(1);
if offset_code == 0 {
nb_bits = 0; /* cmove */
}
offset = (1usize << (nb_bits & (USIZE_BITS - 1)))
.wrapping_add(fse_read_bits(&mut seq_state.d_stream, nb_bits));
if IS_32BITS {
fse_reload_dstream(&mut seq_state.d_stream);
}
if offset_code == 0 {
offset = prev_offset;
}
}
/* MatchLength */
let mut match_length =
fse_decode_symbol(&mut seq_state.state_ml, &mut seq_state.d_stream) as usize;
if match_length == MAX_ML as usize {
let add = if (dumps as usize) < (de as usize) {
let v = *dumps as u32;
dumps = dumps.add(1);
v
} else {
0
};
if add < 255 {
match_length += add as usize;
} else if (dumps as usize) <= (de as usize).wrapping_sub(3) {
match_length = zstd_read_le24(dumps) as usize;
dumps = dumps.add(3);
}
}
match_length += MINMATCH;
/* save result */
seq.lit_length = lit_length;
seq.offset = offset;
seq.match_length = match_length;
seq_state.dumps = dumps;
}
unsafe fn zstd_exec_sequence(
op: *mut u8,
sequence: Seq,
lit_ptr: &mut *const u8,
lit_limit: *const u8,
base: *const u8,
oend: *mut u8,
) -> usize {
static DEC32TABLE: [usize; 8] = [0, 1, 2, 1, 4, 4, 4, 4]; /* added */
static DEC64TABLE: [usize; 8] = [8, 8, 8, 7, 8, 9, 10, 11]; /* subtracted */
let ostart = op;
let mut op = op;
let o_lit_end = op.wrapping_add(sequence.lit_length);
let lit_length = sequence.lit_length;
/* risk : address space overflow (32-bits) */
let end_match = op
.wrapping_add(lit_length)
.wrapping_add(sequence.match_length);
let lit_end = (*lit_ptr).wrapping_add(lit_length);
/* checks */
let seq_length = sequence.lit_length.wrapping_add(sequence.match_length);
if seq_length > (oend as usize).wrapping_sub(op as usize) {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if sequence.lit_length > (lit_limit as usize).wrapping_sub(*lit_ptr as usize) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */
if sequence.offset > ((o_lit_end as usize).wrapping_sub(base as usize)) as u32 as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if (end_match as usize) > (oend as usize) {
/* overwrite beyond dst buffer */
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if (lit_end as usize) > (lit_limit as usize) {
/* overRead beyond lit buffer */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if sequence.match_length > (*lit_ptr as usize).wrapping_sub(op as usize) {
/* overwrite literal segment */
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
/* copy Literals */
/* note : v0.1 seems to allow scenarios where output or input are close to end of buffer */
ptr::copy(*lit_ptr, op, sequence.lit_length);
op = op.add(lit_length);
*lit_ptr = lit_end; /* update for next sequence */
/* check : last match must be at a minimum distance of 8 from end of dest buffer */
if (oend as usize) - (op as usize) < 8 {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
/* copy Match */
{
let overlap_risk = (lit_end as usize).wrapping_sub(end_match as usize) < 12;
/* possible underflow at op - offset ? */
let mut match_ptr = (op as usize).wrapping_sub(sequence.offset) as *const u8;
let mut qutt: usize = 12;
let mut saved = [0u8; 16]; /* U64 saved[2] */
/* check */
if (match_ptr as usize) < (base as usize) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if sequence.offset > base as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* save beginning of literal sequence, in case of write overlap */
if overlap_risk {
if (end_match as usize).wrapping_add(qutt) > (oend as usize) {
qutt = (oend as usize) - (end_match as usize);
}
ptr::copy_nonoverlapping(end_match as *const u8, saved.as_mut_ptr(), qutt);
}
if sequence.offset < 8 {
let dec64 = DEC64TABLE[sequence.offset];
*op = *match_ptr;
*op.add(1) = *match_ptr.add(1);
*op.add(2) = *match_ptr.add(2);
*op.add(3) = *match_ptr.add(3);
match_ptr = match_ptr.add(DEC32TABLE[sequence.offset]);
zstd_copy4(op.add(4), match_ptr);
match_ptr = match_ptr.sub(dec64);
} else {
zstd_copy8(op, match_ptr);
}
op = op.add(8);
match_ptr = match_ptr.add(8);
if (end_match as usize) > (oend as usize).wrapping_sub(16 - MINMATCH) {
if (op as usize) < (oend as usize).wrapping_sub(8) {
let dist = ((oend as usize) - 8) - (op as usize);
zstd_wildcopy(op, match_ptr, dist as isize);
match_ptr = match_ptr.add(dist);
op = oend.sub(8);
}
while (op as usize) < (end_match as usize) {
*op = *match_ptr;
op = op.add(1);
match_ptr = match_ptr.add(1);
}
} else {
/* works even if matchLength < 8 */
zstd_wildcopy(op, match_ptr, sequence.match_length as isize - 8);
}
/* restore, in case of overlap */
if overlap_risk {
ptr::copy_nonoverlapping(saved.as_ptr(), end_match, qutt);
}
}
(end_match as usize) - (ostart as usize)
}
unsafe fn zstd_decompress_sequences(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
seq_start: *const u8,
seq_size: usize,
lit_start: *const u8,
lit_size: usize,
) -> usize {
let dctx = ctx;
let mut ip = seq_start;
let iend_addr = (ip as usize).wrapping_add(seq_size);
let ostart = dst;
let mut op = ostart;
let oend = ostart.add(max_dst_size);
let mut lit_ptr = lit_start;
let lit_end = lit_start.add(lit_size);
let mut nb_seq: i32 = 0;
let mut dumps: *const u8 = ptr::null();
let mut dumps_length: usize = 0;
let dtable_ll = (*dctx).ll_table.as_mut_ptr();
let dtable_ml = (*dctx).ml_table.as_mut_ptr();
let dtable_offb = (*dctx).off_table.as_mut_ptr();
let base = (*dctx).base;
/* Build Decoding Tables */
let error_code = zstdv01_decode_seq_headers(
&mut nb_seq,
&mut dumps,
&mut dumps_length,
dtable_ll,
dtable_ml,
dtable_offb,
ip,
iend_addr - ip as usize,
);
if ERR_isError(error_code) {
return error_code;
}
ip = ip.add(error_code);
/* Regen sequences */
{
let mut sequence = Seq {
lit_length: 0,
offset: 0,
match_length: 0,
};
let mut seq_state = SeqState {
d_stream: FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
},
state_ll: FseDState {
state: 0,
table: ptr::null(),
},
state_offb: FseDState {
state: 0,
table: ptr::null(),
},
state_ml: FseDState {
state: 0,
table: ptr::null(),
},
prev_offset: 1,
dumps,
dumps_end: dumps.wrapping_add(dumps_length),
};
let error_code = fse_init_dstream(&mut seq_state.d_stream, ip, iend_addr - ip as usize);
if fse_is_error(error_code) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
fse_init_dstate(&mut seq_state.state_ll, &mut seq_state.d_stream, dtable_ll);
fse_init_dstate(
&mut seq_state.state_offb,
&mut seq_state.d_stream,
dtable_offb,
);
fse_init_dstate(&mut seq_state.state_ml, &mut seq_state.d_stream, dtable_ml);
while fse_reload_dstream(&mut seq_state.d_stream) <= FSE_DSTREAM_COMPLETED && nb_seq > 0 {
nb_seq -= 1;
zstd_decode_sequence(&mut sequence, &mut seq_state);
let one_seq_size = zstd_exec_sequence(op, sequence, &mut lit_ptr, lit_end, base, oend);
if ERR_isError(one_seq_size) {
return one_seq_size;
}
op = op.add(one_seq_size);
}
/* check if reached exact end */
if !fse_end_of_dstream(&seq_state.d_stream) {
/* requested too much : data is corrupted */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if nb_seq < 0 {
/* requested too many sequences : data is corrupted */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* last literal segment */
{
let last_ll_size = (lit_end as usize) - (lit_ptr as usize);
if (op as usize).wrapping_add(last_ll_size) > (oend as usize) {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if last_ll_size > 0 {
if !std::ptr::eq(op as *const u8, lit_ptr) {
ptr::copy(lit_ptr, op, last_ll_size);
}
op = op.add(last_ll_size);
}
}
}
(op as usize) - (ostart as usize)
}
unsafe fn zstd_decompress_block(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
/* blockType == blockCompressed, srcSize is trusted */
let mut ip = src;
let mut lit_ptr: *const u8 = ptr::null();
let mut lit_size: usize = 0;
/* Decode literals sub-block */
let error_code = zstdv01_decode_literals_block(
dst,
max_dst_size,
&mut lit_ptr,
&mut lit_size,
src,
src_size,
);
if ERR_isError(error_code) {
return error_code;
}
ip = ip.add(error_code);
let src_size = src_size - error_code;
zstd_decompress_sequences(ctx, dst, max_dst_size, ip, src_size, lit_ptr, lit_size)
}
unsafe fn zstdv01_decompress_dctx(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
let mut ip = src;
let iend_addr = (ip as usize).wrapping_add(src_size);
let ostart = dst;
let mut op = ostart;
let oend_addr = (ostart as usize).wrapping_add(max_dst_size);
let mut remaining_size = src_size;
let mut error_code: usize = 0;
/* Frame Header */
if src_size < ZSTD_FRAME_HEADER_SIZE + ZSTD_BLOCK_HEADER_SIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let magic_number = zstd_read_be32(src);
if magic_number != ZSTD_MAGIC_NUMBER {
return ERROR(ZstdErrorCode::PrefixUnknown);
}
ip = ip.add(ZSTD_FRAME_HEADER_SIZE);
remaining_size -= ZSTD_FRAME_HEADER_SIZE;
/* Loop on each block */
loop {
let mut block_properties = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size =
zstdv01_getc_block_size(ip, iend_addr - ip as usize, &mut block_properties);
if ERR_isError(block_size) {
return block_size;
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
remaining_size -= ZSTD_BLOCK_HEADER_SIZE;
if block_size > remaining_size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
match block_properties.block_type {
BT_COMPRESSED => {
error_code =
zstd_decompress_block(ctx, op, oend_addr - op as usize, ip, block_size);
}
BT_RAW => {
error_code =
zstd_copy_uncompressed_block(op, oend_addr - op as usize, ip, block_size);
}
BT_RLE => {
return ERROR(ZstdErrorCode::Generic); /* not yet supported */
}
BT_END => {
/* end of frame */
if remaining_size != 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
}
_ => {
return ERROR(ZstdErrorCode::Generic);
}
}
if block_size == 0 {
break; /* bt_end */
}
if ERR_isError(error_code) {
return error_code;
}
op = op.add(error_code);
ip = ip.add(block_size);
remaining_size -= block_size;
}
(op as usize) - (ostart as usize)
}
/* ZSTD_errorFrameSizeInfoLegacy() :
assumes `cSize` and `dBound` are _not_ NULL */
unsafe fn zstd_error_frame_size_info_legacy(c_size: *mut usize, d_bound: *mut u64, ret: usize) {
*c_size = ret;
*d_bound = ZSTD_CONTENTSIZE_ERROR;
}
/* ******************************************
* Exported C ABI (zstd_v01.h)
********************************************/
/// C ABI: `ZSTDv01_isError`.
#[no_mangle]
pub extern "C" fn ZSTDv01_isError(code: usize) -> c_uint {
ERR_isError(code) as c_uint
}
/// C ABI: `ZSTDv01_decompressDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompressDCtx(
ctx: *mut c_void,
dst: *mut c_void,
max_original_size: usize,
src: *const c_void,
compressed_size: usize,
) -> usize {
zstdv01_decompress_dctx(
ctx as *mut ZSTDv01_Dctx,
dst as *mut u8,
max_original_size,
src as *const u8,
compressed_size,
)
}
/// C ABI: `ZSTDv01_decompress`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompress(
dst: *mut c_void,
max_original_size: usize,
src: *const c_void,
compressed_size: usize,
) -> usize {
/* The C version uses an uninitialized on-stack dctx_t; only `base` is
* read before being written, so a zeroed context is equivalent. */
let mut ctx = std::mem::MaybeUninit::<ZSTDv01_Dctx>::zeroed();
let ctx_ptr = ctx.as_mut_ptr();
(*ctx_ptr).base = dst as *const u8;
zstdv01_decompress_dctx(
ctx_ptr,
dst as *mut u8,
max_original_size,
src as *const u8,
compressed_size,
)
}
/// C ABI: `ZSTDv01_findFrameSizeInfoLegacy`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_findFrameSizeInfoLegacy(
src: *const c_void,
src_size: usize,
c_size: *mut usize,
d_bound: *mut u64,
) {
let mut ip = src as *const u8;
let mut remaining_size = src_size;
let mut nb_blocks: usize = 0;
/* Frame Header */
if src_size < ZSTD_FRAME_HEADER_SIZE + ZSTD_BLOCK_HEADER_SIZE {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::SrcSizeWrong));
return;
}
let magic_number = zstd_read_be32(src as *const u8);
if magic_number != ZSTD_MAGIC_NUMBER {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::PrefixUnknown));
return;
}
ip = ip.add(ZSTD_FRAME_HEADER_SIZE);
remaining_size -= ZSTD_FRAME_HEADER_SIZE;
/* Loop on each block */
loop {
let mut block_properties = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size = zstdv01_getc_block_size(ip, remaining_size, &mut block_properties);
if ERR_isError(block_size) {
zstd_error_frame_size_info_legacy(c_size, d_bound, block_size);
return;
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
remaining_size -= ZSTD_BLOCK_HEADER_SIZE;
if block_size > remaining_size {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::SrcSizeWrong));
return;
}
if block_size == 0 {
break; /* bt_end */
}
ip = ip.add(block_size);
remaining_size -= block_size;
nb_blocks += 1;
}
*c_size = (ip as usize) - (src as usize);
*d_bound = (nb_blocks * BLOCKSIZE) as u64;
}
/* ******************************************
* Streaming Decompression API
********************************************/
/// C ABI: `ZSTDv01_resetDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_resetDCtx(dctx: *mut ZSTDv01_Dctx) -> usize {
(*dctx).expected = ZSTD_FRAME_HEADER_SIZE;
(*dctx).phase = 0;
(*dctx).previous_dst_end = ptr::null();
(*dctx).base = ptr::null();
0
}
/// C ABI: `ZSTDv01_createDCtx`. Allocated with `malloc` exactly like the C
/// implementation, so create/free may be paired across the C/Rust boundary.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_createDCtx() -> *mut ZSTDv01_Dctx {
let dctx = libc::malloc(std::mem::size_of::<ZSTDv01_Dctx>()) as *mut ZSTDv01_Dctx;
if dctx.is_null() {
return ptr::null_mut();
}
ZSTDv01_resetDCtx(dctx);
dctx
}
/// C ABI: `ZSTDv01_freeDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_freeDCtx(dctx: *mut ZSTDv01_Dctx) -> usize {
libc::free(dctx as *mut c_void);
0
}
/// C ABI: `ZSTDv01_nextSrcSizeToDecompress`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_nextSrcSizeToDecompress(dctx: *mut ZSTDv01_Dctx) -> usize {
(*dctx).expected
}
/// C ABI: `ZSTDv01_decompressContinue`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompressContinue(
dctx: *mut ZSTDv01_Dctx,
dst: *mut c_void,
max_dst_size: usize,
src: *const c_void,
src_size: usize,
) -> usize {
let ctx = dctx;
let src = src as *const u8;
let dst = dst as *mut u8;
/* Sanity check */
if src_size != (*ctx).expected {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if !std::ptr::eq(dst as *const u8, (*ctx).previous_dst_end) {
/* not contiguous */
(*ctx).base = dst as *const u8;
}
/* Decompress : frame header */
if (*ctx).phase == 0 {
/* Check frame magic header */
let magic_number = zstd_read_be32(src);
if magic_number != ZSTD_MAGIC_NUMBER {
return ERROR(ZstdErrorCode::PrefixUnknown);
}
(*ctx).phase = 1;
(*ctx).expected = ZSTD_BLOCK_HEADER_SIZE;
return 0;
}
/* Decompress : block header */
if (*ctx).phase == 1 {
let mut bp = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size = zstdv01_getc_block_size(src, ZSTD_BLOCK_HEADER_SIZE, &mut bp);
if ERR_isError(block_size) {
return block_size;
}
if bp.block_type == BT_END {
(*ctx).expected = 0;
(*ctx).phase = 0;
} else {
(*ctx).expected = block_size;
(*ctx).b_type = bp.block_type;
(*ctx).phase = 2;
}
return 0;
}
/* Decompress : block content */
{
let r_size = match (*ctx).b_type {
BT_COMPRESSED => zstd_decompress_block(ctx, dst, max_dst_size, src, src_size),
BT_RAW => zstd_copy_uncompressed_block(dst, max_dst_size, src, src_size),
BT_RLE => {
return ERROR(ZstdErrorCode::Generic); /* not yet handled */
}
BT_END => {
/* should never happen (filtered at phase 1) */
0
}
_ => {
return ERROR(ZstdErrorCode::Generic);
}
};
(*ctx).phase = 1;
(*ctx).expected = ZSTD_BLOCK_HEADER_SIZE;
if ERR_isError(r_size) {
return r_size;
}
(*ctx).previous_dst_end = (dst as usize + r_size) as *const u8;
r_size
}
}
#[cfg(test)]
mod tests {
use super::*;
/// 237-byte base text; the fixtures compress repetitions of it.
const SAMPLE: &str = "snowden is snowed in / he's now then in his snow den / when does the snow end?\ngoodbye little dog / you dug some holes in your day / they'll be hard to fill.\nwhen life shuts a door, / just open it. it's a door. / that is how doors work.\n";
/// `SAMPLE` repeated 3 times (711 bytes), compressed by a zstd binary
/// built from the v0.1.0 tag. Single compressed block (FSE + Huff0),
/// verified byte-identical against the pristine C decoder.
const FRAME_ENTROPY: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x93, 0x00, 0xD6, 0x19, 0x80, 0x87,
0xB1, 0x00, 0xC0, 0x73, 0xC5, 0x8A, 0xA5, 0x6C, 0x6F, 0x4B, 0xF2, 0x9F, 0x94, 0xC4, 0x2B,
0xE4, 0x6B, 0x2C, 0x96, 0xAE, 0x5F, 0xC8, 0x9C, 0x1F, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x43,
0x31, 0x70, 0xE4, 0xC9, 0xA3, 0x46, 0xD9, 0xAD, 0xFD, 0x78, 0x37, 0x66, 0xA6, 0x4D, 0x15,
0xCA, 0x33, 0xF5, 0xA7, 0x31, 0x83, 0x02, 0x96, 0x7E, 0xD0, 0x5E, 0xAC, 0xE9, 0x48, 0x03,
0xC7, 0xEF, 0x44, 0x81, 0x0B, 0x00, 0x90, 0xEA, 0x55, 0x41, 0xD8, 0x4E, 0x36, 0x10, 0xAC,
0x95, 0x92, 0x42, 0x5D, 0x92, 0xF4, 0x64, 0x7F, 0x96, 0x44, 0x59, 0x0B, 0x03, 0x4A, 0xF6,
0x16, 0x9F, 0xC8, 0xB9, 0x11, 0x70, 0xB8, 0x35, 0x06, 0xC4, 0x26, 0xC7, 0x6A, 0x89, 0xE4,
0x6F, 0x65, 0xC0, 0x9B, 0x55, 0x08, 0xEB, 0xE8, 0x37, 0x66, 0x67, 0x9E, 0x7D, 0x57, 0xA0,
0x25, 0x0D, 0xA4, 0x3E, 0x26, 0x8F, 0x32, 0xF6, 0xC9, 0x49, 0x50, 0x40, 0x0A, 0xED, 0x34,
0x94, 0x15, 0x5E, 0xE3, 0x1B, 0xD6, 0x27, 0x05, 0x00, 0x54, 0x05, 0x41, 0xFF, 0xD6, 0x01,
0x00, 0x80, 0x36, 0x00, 0xFE, 0x8D, 0x38, 0x42, 0x51, 0x7E, 0x40, 0x38, 0xD5, 0x60, 0x46,
0x03, 0x90, 0x25, 0xC0, 0x00, 0x00,
];
/// 48 random bytes compressed by v0.1.0: a raw (uncompressed) block.
const FRAME_RAW: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x40, 0x00, 0x30, 0x77, 0xE9, 0x9A, 0x70, 0x2F, 0x77, 0x16, 0x9A,
0x92, 0xD4, 0xEE, 0x72, 0xFD, 0xDD, 0x0F, 0x39, 0x33, 0x8B, 0x8B, 0x3C, 0x6A, 0x91, 0xB2,
0x2B, 0x3A, 0xF0, 0x0E, 0xA6, 0x4C, 0x51, 0xA0, 0xC9, 0x5F, 0x69, 0x1A, 0xCE, 0x93, 0x5C,
0x7F, 0x43, 0x0C, 0xD9, 0x25, 0xBC, 0x91, 0xDD, 0x6E, 0xD7, 0xC0, 0x00, 0x00,
];
const RAW_PAYLOAD: &[u8] = &[
0x77, 0xE9, 0x9A, 0x70, 0x2F, 0x77, 0x16, 0x9A, 0x92, 0xD4, 0xEE, 0x72, 0xFD, 0xDD, 0x0F,
0x39, 0x33, 0x8B, 0x8B, 0x3C, 0x6A, 0x91, 0xB2, 0x2B, 0x3A, 0xF0, 0x0E, 0xA6, 0x4C, 0x51,
0xA0, 0xC9, 0x5F, 0x69, 0x1A, 0xCE, 0x93, 0x5C, 0x7F, 0x43, 0x0C, 0xD9, 0x25, 0xBC, 0x91,
0xDD, 0x6E, 0xD7,
];
/// `SAMPLE` repeated 1800 times (426600 bytes) compressed by v0.1.0:
/// four data blocks, so it exercises multi-block frames and repeated
/// offsets.
const FRAME_MULTI_BLOCK: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x93, 0x00, 0xD6, 0x19, 0x80, 0x87,
0xB1, 0x00, 0xC0, 0x73, 0xC5, 0x8A, 0xA5, 0x6C, 0x6F, 0x4B, 0xF2, 0x9F, 0x94, 0xC4, 0x2B,
0xE4, 0x6B, 0x2C, 0x96, 0xAE, 0x5F, 0xC8, 0x9C, 0x1F, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x43,
0x31, 0x70, 0xE4, 0xC9, 0xA3, 0x46, 0xD9, 0xAD, 0xFD, 0x78, 0x37, 0x66, 0xA6, 0x4D, 0x15,
0xCA, 0x33, 0xF5, 0xA7, 0x31, 0x83, 0x02, 0x96, 0x7E, 0xD0, 0x5E, 0xAC, 0xE9, 0x48, 0x03,
0xC7, 0xEF, 0x44, 0x81, 0x0B, 0x00, 0x90, 0xEA, 0x55, 0x41, 0xD8, 0x4E, 0x36, 0x10, 0xAC,
0x95, 0x92, 0x42, 0x5D, 0x92, 0xF4, 0x64, 0x7F, 0x96, 0x44, 0x59, 0x0B, 0x03, 0x4A, 0xF6,
0x16, 0x9F, 0xC8, 0xB9, 0x11, 0x70, 0xB8, 0x35, 0x06, 0xC4, 0x26, 0xC7, 0x6A, 0x89, 0xE4,
0x6F, 0x65, 0xC0, 0x9B, 0x55, 0x08, 0xEB, 0xE8, 0x37, 0x66, 0x67, 0x9E, 0x7D, 0x57, 0xA0,
0x25, 0x0D, 0xA4, 0x3E, 0x26, 0x8F, 0x32, 0xF6, 0xC9, 0x49, 0x50, 0x40, 0x0A, 0xED, 0x34,
0x94, 0x15, 0x5E, 0xE3, 0x1B, 0xD6, 0x27, 0x05, 0x00, 0x54, 0x05, 0x41, 0xFF, 0x0F, 0xFF,
0x01, 0x80, 0x36, 0x00, 0xFE, 0x8D, 0x38, 0x42, 0x51, 0x7E, 0x40, 0x38, 0xD5, 0x60, 0x46,
0x03, 0x90, 0x25, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x01, 0x00, 0x54, 0x04, 0xFF, 0xFC,
0xFF, 0x01, 0x80, 0xFA, 0x7F, 0x00, 0xFC, 0x23, 0x10, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00,
0x01, 0x00, 0x54, 0x04, 0xFF, 0xFC, 0xFF, 0x01, 0x00, 0xF5, 0xFF, 0x00, 0xF8, 0x4B, 0x20,
0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x01, 0x00, 0x54, 0x04, 0xFF, 0x64, 0x82, 0x00, 0x80,
0xEF, 0xFF, 0x00, 0xF0, 0x9F, 0x40, 0xC0, 0x00, 0x00,
];
fn decompress(frame: &[u8], capacity: usize) -> Result<Vec<u8>, usize> {
let mut out = vec![0u8; capacity];
let code = unsafe {
ZSTDv01_decompress(
out.as_mut_ptr() as *mut c_void,
capacity,
frame.as_ptr() as *const c_void,
frame.len(),
)
};
if ZSTDv01_isError(code) != 0 {
return Err(code);
}
out.truncate(code);
Ok(out)
}
fn frame_size_info(frame: &[u8]) -> (usize, u64) {
let mut c_size = 0usize;
let mut d_bound = 0u64;
unsafe {
ZSTDv01_findFrameSizeInfoLegacy(
frame.as_ptr() as *const c_void,
frame.len(),
&mut c_size,
&mut d_bound,
);
}
(c_size, d_bound)
}
#[test]
fn decodes_entropy_frame_byte_identically() {
let expected = SAMPLE.repeat(3).into_bytes();
let out = decompress(FRAME_ENTROPY, expected.len()).unwrap();
assert_eq!(out, expected);
}
#[test]
fn decodes_raw_block_frame_byte_identically() {
let out = decompress(FRAME_RAW, RAW_PAYLOAD.len()).unwrap();
assert_eq!(out, RAW_PAYLOAD);
}
#[test]
fn decodes_multi_block_frame_byte_identically() {
let expected = SAMPLE.repeat(1800).into_bytes();
assert_eq!(expected.len(), 426600);
let out = decompress(FRAME_MULTI_BLOCK, expected.len()).unwrap();
assert_eq!(out, expected);
}
#[test]
fn reports_frame_size_info_like_c() {
/* Values verified against the pristine C implementation. */
assert_eq!(
frame_size_info(FRAME_ENTROPY),
(FRAME_ENTROPY.len(), 131072)
);
assert_eq!(frame_size_info(FRAME_RAW), (FRAME_RAW.len(), 131072));
assert_eq!(
frame_size_info(FRAME_MULTI_BLOCK),
(FRAME_MULTI_BLOCK.len(), 524288)
);
}
#[test]
fn rejects_bad_magic_with_prefix_unknown() {
let mut frame = FRAME_ENTROPY.to_vec();
frame[0] ^= 0x55;
assert_eq!(
decompress(&frame, 1024).unwrap_err(),
ERROR(ZstdErrorCode::PrefixUnknown)
);
let (c_size, d_bound) = frame_size_info(&frame);
assert_eq!(c_size, ERROR(ZstdErrorCode::PrefixUnknown));
assert_eq!(d_bound, ZSTD_CONTENTSIZE_ERROR);
}
#[test]
fn rejects_truncated_frames_with_src_size_wrong() {
for frame in [FRAME_ENTROPY, FRAME_RAW, FRAME_MULTI_BLOCK] {
let truncated = &frame[..frame.len() - 1];
assert_eq!(
decompress(truncated, 1 << 20).unwrap_err(),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
assert_eq!(
decompress(&frame[..5], 1 << 20).unwrap_err(),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
}
}
#[test]
fn rejects_small_destination_with_dst_size_too_small() {
/* Error codes verified against the pristine C implementation. */
let expected_len = SAMPLE.len() * 3;
assert_eq!(
decompress(FRAME_ENTROPY, expected_len - 1).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
assert_eq!(
decompress(FRAME_ENTROPY, 0).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
assert_eq!(
decompress(FRAME_RAW, RAW_PAYLOAD.len() - 1).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
}
#[test]
fn streaming_api_decodes_the_frame() {
let expected = SAMPLE.repeat(3).into_bytes();
let mut out = vec![0u8; expected.len()];
let mut in_pos = 0usize;
let mut out_pos = 0usize;
unsafe {
let dctx = ZSTDv01_createDCtx();
assert!(!dctx.is_null());
loop {
let needed = ZSTDv01_nextSrcSizeToDecompress(dctx);
if needed == 0 {
break;
}
assert!(in_pos + needed <= FRAME_ENTROPY.len());
let produced = ZSTDv01_decompressContinue(
dctx,
out.as_mut_ptr().add(out_pos) as *mut c_void,
out.len() - out_pos,
FRAME_ENTROPY.as_ptr().add(in_pos) as *const c_void,
needed,
);
assert_eq!(ZSTDv01_isError(produced), 0);
in_pos += needed;
out_pos += produced;
}
assert_eq!(ZSTDv01_freeDCtx(dctx), 0);
}
assert_eq!(in_pos, FRAME_ENTROPY.len());
assert_eq!(out_pos, expected.len());
assert_eq!(out, expected);
}
#[test]
fn streaming_context_reset_and_null_free() {
unsafe {
let dctx = ZSTDv01_createDCtx();
assert_eq!(ZSTDv01_nextSrcSizeToDecompress(dctx), 4);
/* bad magic through the streaming entry point */
let bad = [0u8; 4];
assert_eq!(
ZSTDv01_decompressContinue(
dctx,
ptr::null_mut(),
0,
bad.as_ptr() as *const c_void,
4
),
ERROR(ZstdErrorCode::PrefixUnknown)
);
/* wrong srcSize */
assert_eq!(
ZSTDv01_decompressContinue(
dctx,
ptr::null_mut(),
0,
bad.as_ptr() as *const c_void,
3
),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
assert_eq!(ZSTDv01_resetDCtx(dctx), 0);
assert_eq!(ZSTDv01_nextSrcSizeToDecompress(dctx), 4);
assert_eq!(ZSTDv01_freeDCtx(dctx), 0);
assert_eq!(ZSTDv01_freeDCtx(ptr::null_mut()), 0);
}
}
}
+1 -4
View File
@@ -16,6 +16,7 @@ pub mod hist;
pub mod huf_compress;
#[cfg(feature = "decompression")]
pub mod huf_decompress;
pub mod legacy;
pub mod mem;
pub mod pool;
pub mod threading;
@@ -28,12 +29,8 @@ pub mod zstd_compress_frame;
#[cfg(feature = "compression")]
pub mod zstd_compress_literals;
#[cfg(feature = "compression")]
pub mod zstd_compress_params;
#[cfg(feature = "compression")]
pub mod zstd_compress_sequences;
#[cfg(feature = "compression")]
pub mod zstd_compress_stats;
#[cfg(feature = "compression")]
pub mod zstd_compress_superblock;
#[cfg(feature = "decompression")]
pub mod zstd_ddict;
+2 -4
View File
@@ -65,7 +65,7 @@ unsafe fn copy_bytes(dst: *mut u8, src: *const u8, size: usize) {
}
#[inline]
pub(crate) unsafe fn copy_huf_tables(dst: *mut ZSTD_hufCTables_t, src: *const ZSTD_hufCTables_t) {
unsafe fn copy_huf_tables(dst: *mut ZSTD_hufCTables_t, src: *const ZSTD_hufCTables_t) {
// Copy as bytes, not as a Rust struct: C's memcpy preserves the four tail
// padding bytes that this layout has on 64-bit targets. Some C callers
// copy or compare the complete entropy state, so leaving those bytes from
@@ -192,10 +192,8 @@ fn min_literals_to_compress(strategy: c_int, huf_repeat: c_int) -> usize {
}
}
/// C's `ZSTD_minGain()`: minimum compression gain required to emit a
/// compressed block or compressed literals section.
#[inline]
pub(crate) fn min_gain(src_size: usize, strategy: c_int) -> usize {
fn min_gain(src_size: usize, strategy: c_int) -> usize {
let min_log = if strategy >= ZSTD_BTULTRA {
strategy.saturating_sub(1) as u32
} else {
-1048
View File
@@ -1,1048 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
//! Context-free compression-parameter selection and sizing leaves.
//!
//! This module deliberately does **not** own the public `ZSTD_*` symbols yet.
//! `zstd_compress.c` still owns configuration-sensitive policy: excluded block
//! compressors, private `ZSTD_CCtx_params` layouts, LDM workspace sizing, and
//! ASAN workspace policy. The C integration layer can select a raw table
//! entry here, apply its configured strategy cascade, then use the adjustment
//! and sizing leaves below. That keeps the Rust implementation independent of
//! C preprocessor state while retaining byte-for-byte C policy for reduced
//! builds.
use crate::errors::{ZstdErrorCode, ERROR};
use std::mem::size_of;
use std::os::raw::c_int;
pub const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_CLEVEL_DEFAULT: c_int = 3;
const ZSTD_MAX_CLEVEL: c_int = 22;
const ZSTD_TARGETLENGTH_MAX: c_int = 1 << 17;
const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
const ZSTD_WINDOWLOG_MIN: c_int = 10;
#[cfg(target_pointer_width = "32")]
const ZSTD_WINDOWLOG_MAX: c_int = 30;
#[cfg(not(target_pointer_width = "32"))]
const ZSTD_WINDOWLOG_MAX: c_int = 31;
const ZSTD_HASHLOG_MIN: c_int = 6;
const ZSTD_HASHLOG_MAX: c_int = 30;
const ZSTD_CHAINLOG_MIN: c_int = ZSTD_HASHLOG_MIN;
#[cfg(target_pointer_width = "32")]
const ZSTD_CHAINLOG_MAX: c_int = 29;
#[cfg(not(target_pointer_width = "32"))]
const ZSTD_CHAINLOG_MAX: c_int = 30;
const ZSTD_SEARCHLOG_MIN: c_int = 1;
const ZSTD_SEARCHLOG_MAX: c_int = ZSTD_WINDOWLOG_MAX - 1;
const ZSTD_MINMATCH_MIN: c_int = 3;
const ZSTD_MINMATCH_MAX: c_int = 7;
const ZSTD_TARGETLENGTH_MIN: c_int = 0;
const ZSTD_FAST: c_int = 1;
const ZSTD_DFAST: c_int = 2;
const ZSTD_GREEDY: c_int = 3;
const ZSTD_LAZY: c_int = 4;
const ZSTD_LAZY2: c_int = 5;
const ZSTD_BTLAZY2: c_int = 6;
const ZSTD_BTOPT: c_int = 7;
const ZSTD_BTULTRA: c_int = 8;
const ZSTD_BTULTRA2: c_int = 9;
const ZSTD_C_COMPRESSION_LEVEL: c_int = 100;
const ZSTD_C_WINDOW_LOG: c_int = 101;
const ZSTD_C_HASH_LOG: c_int = 102;
const ZSTD_C_CHAIN_LOG: c_int = 103;
const ZSTD_C_SEARCH_LOG: c_int = 104;
const ZSTD_C_MIN_MATCH: c_int = 105;
const ZSTD_C_TARGET_LENGTH: c_int = 106;
const ZSTD_C_STRATEGY: c_int = 107;
/// Private compression-parameter modes from `zstd_compress_internal.h`.
///
/// They are ABI-compatible with `ZSTD_CParamMode_e` and intentionally kept as
/// integer constants because that enum remains private C API for now.
pub const ZSTD_RUST_CPM_NO_ATTACH_DICT: c_int = 0;
pub const ZSTD_RUST_CPM_ATTACH_DICT: c_int = 1;
pub const ZSTD_RUST_CPM_CREATE_CDICT: c_int = 2;
pub const ZSTD_RUST_CPM_UNKNOWN: c_int = 3;
/// `ZSTD_ParamSwitch_e` values used by the adjustment and sizing leaves.
pub const ZSTD_RUST_PS_AUTO: c_int = 0;
pub const ZSTD_RUST_PS_ENABLE: c_int = 1;
pub const ZSTD_RUST_PS_DISABLE: c_int = 2;
/// ABI-compatible `ZSTD_compressionParameters` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTD_compressionParameters {
pub windowLog: u32,
pub chainLog: u32,
pub hashLog: u32,
pub searchLog: u32,
pub minMatch: u32,
pub targetLength: u32,
pub strategy: c_int,
}
/// ABI-compatible `ZSTD_frameParameters` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTD_frameParameters {
pub contentSizeFlag: c_int,
pub checksumFlag: c_int,
pub noDictIDFlag: c_int,
}
/// ABI-compatible `ZSTD_parameters` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTD_parameters {
pub cParams: ZSTD_compressionParameters,
pub fParams: ZSTD_frameParameters,
}
/// ABI-compatible `ZSTD_bounds` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTD_bounds {
pub error: usize,
pub lowerBound: c_int,
pub upperBound: c_int,
}
/* `clevels.h`, represented as raw fields so the table stays compact and easy
* to compare mechanically against its C source. Field order is W, C, H, S,
* L, TL, strategy. */
const DEFAULT_CPARAMS: [[[u32; 7]; 23]; 4] = [
[
[19, 12, 13, 1, 6, 1, ZSTD_FAST as u32],
[19, 13, 14, 1, 7, 0, ZSTD_FAST as u32],
[20, 15, 16, 1, 6, 0, ZSTD_FAST as u32],
[21, 16, 17, 1, 5, 0, ZSTD_DFAST as u32],
[21, 18, 18, 1, 5, 0, ZSTD_DFAST as u32],
[21, 18, 19, 3, 5, 2, ZSTD_GREEDY as u32],
[21, 18, 19, 3, 5, 4, ZSTD_LAZY as u32],
[21, 19, 20, 4, 5, 8, ZSTD_LAZY as u32],
[21, 19, 20, 4, 5, 16, ZSTD_LAZY2 as u32],
[22, 20, 21, 4, 5, 16, ZSTD_LAZY2 as u32],
[22, 21, 22, 5, 5, 16, ZSTD_LAZY2 as u32],
[22, 21, 22, 6, 5, 16, ZSTD_LAZY2 as u32],
[22, 22, 23, 6, 5, 32, ZSTD_LAZY2 as u32],
[22, 22, 22, 4, 5, 32, ZSTD_BTLAZY2 as u32],
[22, 22, 23, 5, 5, 32, ZSTD_BTLAZY2 as u32],
[22, 23, 23, 6, 5, 32, ZSTD_BTLAZY2 as u32],
[22, 22, 22, 5, 5, 48, ZSTD_BTOPT as u32],
[23, 23, 22, 5, 4, 64, ZSTD_BTOPT as u32],
[23, 23, 22, 6, 3, 64, ZSTD_BTULTRA as u32],
[23, 24, 22, 7, 3, 256, ZSTD_BTULTRA2 as u32],
[25, 25, 23, 7, 3, 256, ZSTD_BTULTRA2 as u32],
[26, 26, 24, 7, 3, 512, ZSTD_BTULTRA2 as u32],
[27, 27, 25, 9, 3, 999, ZSTD_BTULTRA2 as u32],
],
[
[18, 12, 13, 1, 5, 1, ZSTD_FAST as u32],
[18, 13, 14, 1, 6, 0, ZSTD_FAST as u32],
[18, 14, 14, 1, 5, 0, ZSTD_DFAST as u32],
[18, 16, 16, 1, 4, 0, ZSTD_DFAST as u32],
[18, 16, 17, 3, 5, 2, ZSTD_GREEDY as u32],
[18, 17, 18, 5, 5, 2, ZSTD_GREEDY as u32],
[18, 18, 19, 3, 5, 4, ZSTD_LAZY as u32],
[18, 18, 19, 4, 4, 4, ZSTD_LAZY as u32],
[18, 18, 19, 4, 4, 8, ZSTD_LAZY2 as u32],
[18, 18, 19, 5, 4, 8, ZSTD_LAZY2 as u32],
[18, 18, 19, 6, 4, 8, ZSTD_LAZY2 as u32],
[18, 18, 19, 5, 4, 12, ZSTD_BTLAZY2 as u32],
[18, 19, 19, 7, 4, 12, ZSTD_BTLAZY2 as u32],
[18, 18, 19, 4, 4, 16, ZSTD_BTOPT as u32],
[18, 18, 19, 4, 3, 32, ZSTD_BTOPT as u32],
[18, 18, 19, 6, 3, 128, ZSTD_BTOPT as u32],
[18, 19, 19, 6, 3, 128, ZSTD_BTULTRA as u32],
[18, 19, 19, 8, 3, 256, ZSTD_BTULTRA as u32],
[18, 19, 19, 6, 3, 128, ZSTD_BTULTRA2 as u32],
[18, 19, 19, 8, 3, 256, ZSTD_BTULTRA2 as u32],
[18, 19, 19, 10, 3, 512, ZSTD_BTULTRA2 as u32],
[18, 19, 19, 12, 3, 512, ZSTD_BTULTRA2 as u32],
[18, 19, 19, 13, 3, 999, ZSTD_BTULTRA2 as u32],
],
[
[17, 12, 12, 1, 5, 1, ZSTD_FAST as u32],
[17, 12, 13, 1, 6, 0, ZSTD_FAST as u32],
[17, 13, 15, 1, 5, 0, ZSTD_FAST as u32],
[17, 15, 16, 2, 5, 0, ZSTD_DFAST as u32],
[17, 17, 17, 2, 4, 0, ZSTD_DFAST as u32],
[17, 16, 17, 3, 4, 2, ZSTD_GREEDY as u32],
[17, 16, 17, 3, 4, 4, ZSTD_LAZY as u32],
[17, 16, 17, 3, 4, 8, ZSTD_LAZY2 as u32],
[17, 16, 17, 4, 4, 8, ZSTD_LAZY2 as u32],
[17, 16, 17, 5, 4, 8, ZSTD_LAZY2 as u32],
[17, 16, 17, 6, 4, 8, ZSTD_LAZY2 as u32],
[17, 17, 17, 5, 4, 8, ZSTD_BTLAZY2 as u32],
[17, 18, 17, 7, 4, 12, ZSTD_BTLAZY2 as u32],
[17, 18, 17, 3, 4, 12, ZSTD_BTOPT as u32],
[17, 18, 17, 4, 3, 32, ZSTD_BTOPT as u32],
[17, 18, 17, 6, 3, 256, ZSTD_BTOPT as u32],
[17, 18, 17, 6, 3, 128, ZSTD_BTULTRA as u32],
[17, 18, 17, 8, 3, 256, ZSTD_BTULTRA as u32],
[17, 18, 17, 10, 3, 512, ZSTD_BTULTRA as u32],
[17, 18, 17, 5, 3, 256, ZSTD_BTULTRA2 as u32],
[17, 18, 17, 7, 3, 512, ZSTD_BTULTRA2 as u32],
[17, 18, 17, 9, 3, 512, ZSTD_BTULTRA2 as u32],
[17, 18, 17, 11, 3, 999, ZSTD_BTULTRA2 as u32],
],
[
[14, 12, 13, 1, 5, 1, ZSTD_FAST as u32],
[14, 14, 15, 1, 5, 0, ZSTD_FAST as u32],
[14, 14, 15, 1, 4, 0, ZSTD_FAST as u32],
[14, 14, 15, 2, 4, 0, ZSTD_DFAST as u32],
[14, 14, 14, 4, 4, 2, ZSTD_GREEDY as u32],
[14, 14, 14, 3, 4, 4, ZSTD_LAZY as u32],
[14, 14, 14, 4, 4, 8, ZSTD_LAZY2 as u32],
[14, 14, 14, 6, 4, 8, ZSTD_LAZY2 as u32],
[14, 14, 14, 8, 4, 8, ZSTD_LAZY2 as u32],
[14, 15, 14, 5, 4, 8, ZSTD_BTLAZY2 as u32],
[14, 15, 14, 9, 4, 8, ZSTD_BTLAZY2 as u32],
[14, 15, 14, 3, 4, 12, ZSTD_BTOPT as u32],
[14, 15, 14, 4, 3, 24, ZSTD_BTOPT as u32],
[14, 15, 14, 5, 3, 32, ZSTD_BTULTRA as u32],
[14, 15, 15, 6, 3, 64, ZSTD_BTULTRA as u32],
[14, 15, 15, 7, 3, 256, ZSTD_BTULTRA as u32],
[14, 15, 15, 5, 3, 48, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 6, 3, 128, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 7, 3, 256, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 8, 3, 256, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 8, 3, 512, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 9, 3, 512, ZSTD_BTULTRA2 as u32],
[14, 15, 15, 10, 3, 999, ZSTD_BTULTRA2 as u32],
],
];
#[inline]
fn cparams_from_row(row: [u32; 7]) -> ZSTD_compressionParameters {
ZSTD_compressionParameters {
windowLog: row[0],
chainLog: row[1],
hashLog: row[2],
searchLog: row[3],
minMatch: row[4],
targetLength: row[5],
strategy: row[6] as c_int,
}
}
#[inline]
fn bounds(param: c_int) -> ZSTD_bounds {
let (lowerBound, upperBound) = match param {
ZSTD_C_COMPRESSION_LEVEL => (ZSTD_rust_params_minCLevel(), ZSTD_rust_params_maxCLevel()),
ZSTD_C_WINDOW_LOG => (ZSTD_WINDOWLOG_MIN, ZSTD_WINDOWLOG_MAX),
ZSTD_C_HASH_LOG => (ZSTD_HASHLOG_MIN, ZSTD_HASHLOG_MAX),
ZSTD_C_CHAIN_LOG => (ZSTD_CHAINLOG_MIN, ZSTD_CHAINLOG_MAX),
ZSTD_C_SEARCH_LOG => (ZSTD_SEARCHLOG_MIN, ZSTD_SEARCHLOG_MAX),
ZSTD_C_MIN_MATCH => (ZSTD_MINMATCH_MIN, ZSTD_MINMATCH_MAX),
ZSTD_C_TARGET_LENGTH => (ZSTD_TARGETLENGTH_MIN, ZSTD_TARGETLENGTH_MAX),
ZSTD_C_STRATEGY => (ZSTD_FAST, ZSTD_BTULTRA2),
_ => {
return ZSTD_bounds {
error: ERROR(ZstdErrorCode::ParameterUnsupported),
lowerBound: 0,
upperBound: 0,
}
}
};
ZSTD_bounds {
error: 0,
lowerBound,
upperBound,
}
}
#[inline]
fn within_bounds(param: c_int, value: c_int) -> bool {
let bounds = bounds(param);
bounds.error == 0 && value >= bounds.lowerBound && value <= bounds.upperBound
}
#[inline]
fn clamp_unsigned(value: u32, param: c_int) -> u32 {
let bounds = bounds(param);
debug_assert_eq!(bounds.error, 0);
let signed = value as c_int;
if signed < bounds.lowerBound {
bounds.lowerBound as u32
} else if signed > bounds.upperBound {
bounds.upperBound as u32
} else {
value
}
}
#[inline]
fn clamp_cparams(mut cparams: ZSTD_compressionParameters) -> ZSTD_compressionParameters {
cparams.windowLog = clamp_unsigned(cparams.windowLog, ZSTD_C_WINDOW_LOG);
cparams.chainLog = clamp_unsigned(cparams.chainLog, ZSTD_C_CHAIN_LOG);
cparams.hashLog = clamp_unsigned(cparams.hashLog, ZSTD_C_HASH_LOG);
cparams.searchLog = clamp_unsigned(cparams.searchLog, ZSTD_C_SEARCH_LOG);
cparams.minMatch = clamp_unsigned(cparams.minMatch, ZSTD_C_MIN_MATCH);
cparams.targetLength = clamp_unsigned(cparams.targetLength, ZSTD_C_TARGET_LENGTH);
let strategy_bounds = bounds(ZSTD_C_STRATEGY);
if cparams.strategy < strategy_bounds.lowerBound {
cparams.strategy = strategy_bounds.lowerBound;
} else if cparams.strategy > strategy_bounds.upperBound {
cparams.strategy = strategy_bounds.upperBound;
}
cparams
}
#[inline]
fn highbit32(value: u32) -> u32 {
debug_assert_ne!(value, 0);
u32::BITS - 1 - value.leading_zeros()
}
#[inline]
fn cycle_log(hash_log: u32, strategy: c_int) -> u32 {
hash_log.wrapping_sub((strategy >= ZSTD_BTLAZY2) as u32)
}
#[inline]
fn strategy_supports_row_match_finder(strategy: c_int) -> bool {
(ZSTD_GREEDY..=ZSTD_LAZY2).contains(&strategy)
}
#[inline]
fn row_match_finder_used(strategy: c_int, mode: c_int) -> bool {
debug_assert_ne!(mode, ZSTD_RUST_PS_AUTO);
strategy_supports_row_match_finder(strategy) && mode == ZSTD_RUST_PS_ENABLE
}
#[inline]
fn resolve_row_match_finder(mode: c_int, cparams: ZSTD_compressionParameters) -> c_int {
if mode != ZSTD_RUST_PS_AUTO {
return mode;
}
if strategy_supports_row_match_finder(cparams.strategy) && cparams.windowLog > 14 {
ZSTD_RUST_PS_ENABLE
} else {
ZSTD_RUST_PS_DISABLE
}
}
#[inline]
fn dict_and_window_log(window_log: u32, src_size: u64, dict_size: u64) -> u32 {
/* 1ULL << ZSTD_WINDOWLOG_MAX, which is smaller for 32-bit builds. */
const MAX_WINDOW_SIZE: u64 = 1u64 << ZSTD_WINDOWLOG_MAX;
if dict_size == 0 {
return window_log;
}
debug_assert!(window_log <= ZSTD_WINDOWLOG_MAX as u32);
debug_assert_ne!(src_size, ZSTD_CONTENTSIZE_UNKNOWN);
let window_size = 1u64 << window_log;
if window_size >= dict_size.wrapping_add(src_size) {
window_log
} else {
let dict_and_window_size = dict_size.wrapping_add(window_size);
if dict_and_window_size >= MAX_WINDOW_SIZE {
ZSTD_WINDOWLOG_MAX as u32
} else {
highbit32((dict_and_window_size as u32).wrapping_sub(1)) + 1
}
}
}
/// Adjusts a *validated, configuration-resolved* parameter set.
///
/// This is the direct Rust leaf for `ZSTD_adjustCParams_internal()` after the
/// C wrapper has applied its `ZSTD_EXCLUDE_*_BLOCK_COMPRESSOR` cascade. In
/// particular, it does not select a fallback strategy itself. `srcSize == 0`
/// means a known empty input here, just as it does in the C internal helper;
/// the public C wrapper must translate zero to `ZSTD_CONTENTSIZE_UNKNOWN`.
fn adjust_cparams(
mut cparams: ZSTD_compressionParameters,
mut src_size: u64,
mut dict_size: usize,
mode: c_int,
mut use_row_match_finder: c_int,
) -> ZSTD_compressionParameters {
debug_assert_eq!(check_cparams(cparams), 0);
const MIN_SRC_SIZE: u64 = 513;
/* 1ULL << (ZSTD_WINDOWLOG_MAX - 1), which is smaller for 32-bit builds. */
const MAX_WINDOW_RESIZE: u64 = 1u64 << (ZSTD_WINDOWLOG_MAX - 1);
match mode {
ZSTD_RUST_CPM_UNKNOWN | ZSTD_RUST_CPM_NO_ATTACH_DICT => {}
ZSTD_RUST_CPM_CREATE_CDICT => {
if dict_size != 0 && src_size == ZSTD_CONTENTSIZE_UNKNOWN {
src_size = MIN_SRC_SIZE;
}
}
ZSTD_RUST_CPM_ATTACH_DICT => dict_size = 0,
_ => debug_assert!(false, "invalid ZSTD_CParamMode_e"),
}
if src_size <= MAX_WINDOW_RESIZE && (dict_size as u64) <= MAX_WINDOW_RESIZE {
let total_size = src_size.wrapping_add(dict_size as u64) as u32;
let hash_size_min = 1u32 << ZSTD_HASHLOG_MIN;
let src_log = if total_size < hash_size_min {
ZSTD_HASHLOG_MIN as u32
} else {
highbit32(total_size.wrapping_sub(1)) + 1
};
cparams.windowLog = cparams.windowLog.min(src_log);
}
if src_size != ZSTD_CONTENTSIZE_UNKNOWN {
let dict_and_window_log =
dict_and_window_log(cparams.windowLog, src_size, dict_size as u64);
let cycle_log = cycle_log(cparams.chainLog, cparams.strategy);
cparams.hashLog = cparams.hashLog.min(dict_and_window_log + 1);
if cycle_log > dict_and_window_log {
cparams.chainLog = cparams
.chainLog
.wrapping_sub(cycle_log - dict_and_window_log);
}
}
cparams.windowLog = cparams.windowLog.max(ZSTD_WINDOWLOG_MIN as u32);
/* The short-cache tags used for fast and dfast CDicts consume eight bits
* of each 32-bit index. This is a fixed private-header constant today;
* if it becomes configurable, the C shim must pass it as an explicit
* adjustment input rather than silently changing this leaf. */
if mode == ZSTD_RUST_CPM_CREATE_CDICT
&& (cparams.strategy == ZSTD_FAST || cparams.strategy == ZSTD_DFAST)
{
const SHORT_CACHE_TAG_BITS: u32 = 8;
let max_short_cache_hash_log = 32 - SHORT_CACHE_TAG_BITS;
cparams.hashLog = cparams.hashLog.min(max_short_cache_hash_log);
cparams.chainLog = cparams.chainLog.min(max_short_cache_hash_log);
}
if use_row_match_finder == ZSTD_RUST_PS_AUTO {
use_row_match_finder = ZSTD_RUST_PS_ENABLE;
}
if row_match_finder_used(cparams.strategy, use_row_match_finder) {
const ROW_HASH_TAG_BITS: u32 = 8;
let row_log = cparams.searchLog.clamp(4, 6);
let max_hash_log = (32 - ROW_HASH_TAG_BITS) + row_log;
debug_assert!(cparams.hashLog >= row_log);
cparams.hashLog = cparams.hashLog.min(max_hash_log);
}
cparams
}
#[inline]
fn get_cparam_row_size(src_size_hint: u64, dict_size: usize, mode: c_int) -> u64 {
let mut dict_size = dict_size as u64;
match mode {
ZSTD_RUST_CPM_UNKNOWN | ZSTD_RUST_CPM_NO_ATTACH_DICT | ZSTD_RUST_CPM_CREATE_CDICT => {}
ZSTD_RUST_CPM_ATTACH_DICT => dict_size = 0,
_ => debug_assert!(false, "invalid ZSTD_CParamMode_e"),
}
let unknown = src_size_hint == ZSTD_CONTENTSIZE_UNKNOWN;
let added_size = if unknown && dict_size > 0 { 500 } else { 0 };
if unknown && dict_size == 0 {
ZSTD_CONTENTSIZE_UNKNOWN
} else {
src_size_hint
.wrapping_add(dict_size)
.wrapping_add(added_size)
}
}
/// Selects a raw compression-level-table entry, without strategy cascading or
/// source/dictionary adjustment.
///
/// A C wrapper must apply its active excluded-compressor cascade to the
/// returned strategy before calling [`ZSTD_rust_params_adjustCParams`].
fn select_cparams(
compression_level: c_int,
src_size_hint: u64,
dict_size: usize,
mode: c_int,
) -> ZSTD_compressionParameters {
let row_size = get_cparam_row_size(src_size_hint, dict_size, mode);
let table_id = usize::from(row_size <= 256 * 1024)
+ usize::from(row_size <= 128 * 1024)
+ usize::from(row_size <= 16 * 1024);
let row = if compression_level == 0 {
ZSTD_CLEVEL_DEFAULT
} else if compression_level < 0 {
0
} else {
compression_level.min(ZSTD_MAX_CLEVEL)
} as usize;
let mut cparams = cparams_from_row(DEFAULT_CPARAMS[table_id][row]);
if compression_level < 0 {
let clamped = compression_level.max(ZSTD_rust_params_minCLevel());
cparams.targetLength = (-clamped) as u32;
}
cparams
}
#[inline]
fn make_params(cparams: ZSTD_compressionParameters) -> ZSTD_parameters {
ZSTD_parameters {
cParams: cparams,
fParams: ZSTD_frameParameters {
contentSizeFlag: 1,
checksumFlag: 0,
noDictIDFlag: 0,
},
}
}
#[inline]
fn check_cparams(cparams: ZSTD_compressionParameters) -> usize {
if !within_bounds(ZSTD_C_WINDOW_LOG, cparams.windowLog as c_int)
|| !within_bounds(ZSTD_C_CHAIN_LOG, cparams.chainLog as c_int)
|| !within_bounds(ZSTD_C_HASH_LOG, cparams.hashLog as c_int)
|| !within_bounds(ZSTD_C_SEARCH_LOG, cparams.searchLog as c_int)
|| !within_bounds(ZSTD_C_MIN_MATCH, cparams.minMatch as c_int)
|| !within_bounds(ZSTD_C_TARGET_LENGTH, cparams.targetLength as c_int)
|| !within_bounds(ZSTD_C_STRATEGY, cparams.strategy)
{
ERROR(ZstdErrorCode::ParameterOutOfBound)
} else {
0
}
}
/// Returns the highest table-backed compression level (`ZSTD_MAX_CLEVEL`).
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_maxCLevel() -> c_int {
ZSTD_MAX_CLEVEL
}
/// Returns the lowest public fast level (`-ZSTD_TARGETLENGTH_MAX`).
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_minCLevel() -> c_int {
-ZSTD_TARGETLENGTH_MAX
}
/// Returns the default compression level from `zstd.h`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_defaultCLevel() -> c_int {
ZSTD_CLEVEL_DEFAULT
}
/// Returns bounds for the seven core compression parameters and compression
/// level. Other `ZSTD_cParameter` cases remain C-owned.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_getBounds(param: c_int) -> ZSTD_bounds {
bounds(param)
}
/// Checks the seven fields of `ZSTD_compressionParameters`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_checkCParams(cparams: ZSTD_compressionParameters) -> usize {
check_cparams(cparams)
}
/// Clamps the seven fields of `ZSTD_compressionParameters` to public bounds.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_clampCParams(
cparams: ZSTD_compressionParameters,
) -> ZSTD_compressionParameters {
clamp_cparams(cparams)
}
/// C ABI for `ZSTD_cycleLog()`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_cycleLog(hashLog: u32, strategy: c_int) -> u32 {
cycle_log(hashLog, strategy)
}
/// C ABI for private `ZSTD_getCParamRowSize()`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_getCParamRowSize(
srcSizeHint: u64,
dictSize: usize,
mode: c_int,
) -> u64 {
get_cparam_row_size(srcSizeHint, dictSize, mode)
}
/// Returns the unadjusted compression-level-table entry.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_selectCParams(
compressionLevel: c_int,
srcSizeHint: u64,
dictSize: usize,
mode: c_int,
) -> ZSTD_compressionParameters {
select_cparams(compressionLevel, srcSizeHint, dictSize, mode)
}
/// Adjusts a validated, strategy-resolved C parameter set.
///
/// See [`adjust_cparams`] for the required C-side policy step.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_adjustCParams(
cparams: ZSTD_compressionParameters,
srcSize: u64,
dictSize: usize,
mode: c_int,
useRowMatchFinder: c_int,
) -> ZSTD_compressionParameters {
adjust_cparams(cparams, srcSize, dictSize, mode, useRowMatchFinder)
}
/// Builds `ZSTD_parameters` with the public default frame parameters.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_makeParams(
cparams: ZSTD_compressionParameters,
) -> ZSTD_parameters {
make_params(cparams)
}
/// Runtime sizes needed to reproduce `ZSTD_sizeof_matchState()` without
/// exposing private C structures to Rust.
///
/// `asanRedzoneSize` is `ZSTD_CWKSP_ASAN_REDZONE_SIZE` when workspace
/// poisoning is enabled and zero otherwise. `matchTSize` and `optimalTSize`
/// must be `sizeof(ZSTD_match_t)` and `sizeof(ZSTD_optimal_t)` respectively.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ZSTD_rustMatchStateSizing {
pub hashLog3Max: u32,
pub matchTSize: usize,
pub optimalTSize: usize,
pub asanRedzoneSize: usize,
}
/// C-only layout inputs for `ZSTD_estimateCDictSize_advanced()`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ZSTD_rustCDictSizing {
pub cdictSize: usize,
pub hufWorkspaceSize: usize,
pub hashLog3Max: u32,
pub matchTSize: usize,
pub optimalTSize: usize,
pub asanRedzoneSize: usize,
}
#[inline]
fn cwksp_alloc_size(size: usize, asan_redzone_size: usize) -> usize {
if size == 0 {
0
} else {
size.wrapping_add(asan_redzone_size.wrapping_mul(2))
}
}
#[inline]
fn cwksp_align(size: usize, alignment: usize) -> usize {
debug_assert!(alignment.is_power_of_two());
size.wrapping_add(alignment - 1) & !(alignment - 1)
}
#[inline]
fn cwksp_aligned64_alloc_size(size: usize, asan_redzone_size: usize) -> usize {
cwksp_alloc_size(cwksp_align(size, 64), asan_redzone_size)
}
#[inline]
fn shift_size(log: u32) -> usize {
debug_assert!(log < usize::BITS);
1usize << log
}
#[inline]
fn allocate_chain_table(strategy: c_int, use_row_match_finder: c_int, for_dds_dict: bool) -> bool {
for_dds_dict
|| (strategy != ZSTD_FAST && !row_match_finder_used(strategy, use_row_match_finder))
}
fn estimate_match_state_size(
cparams: ZSTD_compressionParameters,
use_row_match_finder: c_int,
enable_dedicated_dict_search: bool,
for_cctx: bool,
sizing: ZSTD_rustMatchStateSizing,
) -> usize {
if check_cparams(cparams) != 0 || use_row_match_finder == ZSTD_RUST_PS_AUTO {
return 0;
}
let chain_size = if allocate_chain_table(
cparams.strategy,
use_row_match_finder,
enable_dedicated_dict_search && !for_cctx,
) {
shift_size(cparams.chainLog)
} else {
0
};
let hash_size = shift_size(cparams.hashLog);
let hash_log_3 = if for_cctx && cparams.minMatch == 3 {
cparams.windowLog.min(sizing.hashLog3Max)
} else {
0
};
let hash3_size = if hash_log_3 == 0 {
0
} else {
shift_size(hash_log_3)
};
let table_space = chain_size
.wrapping_mul(size_of::<u32>())
.wrapping_add(hash_size.wrapping_mul(size_of::<u32>()))
.wrapping_add(hash3_size.wrapping_mul(size_of::<u32>()));
let redzone = sizing.asanRedzoneSize;
let opt_potential_space = cwksp_aligned64_alloc_size((52 + 1) * size_of::<u32>(), redzone)
.wrapping_add(cwksp_aligned64_alloc_size(
(35 + 1) * size_of::<u32>(),
redzone,
))
.wrapping_add(cwksp_aligned64_alloc_size(
(31 + 1) * size_of::<u32>(),
redzone,
))
.wrapping_add(cwksp_aligned64_alloc_size(
(1 << 8) * size_of::<u32>(),
redzone,
))
.wrapping_add(cwksp_aligned64_alloc_size(
4099usize.wrapping_mul(sizing.matchTSize),
redzone,
))
.wrapping_add(cwksp_aligned64_alloc_size(
4099usize.wrapping_mul(sizing.optimalTSize),
redzone,
));
let lazy_additional_space = if row_match_finder_used(cparams.strategy, use_row_match_finder) {
cwksp_aligned64_alloc_size(hash_size, redzone)
} else {
0
};
let opt_space = if for_cctx && cparams.strategy >= ZSTD_BTOPT {
opt_potential_space
} else {
0
};
let slack_space = 2 * 64;
table_space
.wrapping_add(opt_space)
.wrapping_add(slack_space)
.wrapping_add(lazy_additional_space)
}
/// Pure leaf for private `ZSTD_sizeof_matchState()`.
///
/// `useRowMatchFinder` must already be resolved to enable or disable. A NULL
/// `sizing` pointer or invalid C parameters returns zero; C never supplies
/// either in a valid estimator call.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_params_estimateMatchStateSize(
cparams: ZSTD_compressionParameters,
useRowMatchFinder: c_int,
enableDedicatedDictSearch: c_int,
forCCtx: u32,
sizing: *const ZSTD_rustMatchStateSizing,
) -> usize {
if sizing.is_null() {
return 0;
}
let sizing = unsafe { *sizing };
estimate_match_state_size(
cparams,
useRowMatchFinder,
enableDedicatedDictSearch != 0,
forCCtx != 0,
sizing,
)
}
#[inline]
fn max_nb_seq(block_size: usize, min_match: u32, use_sequence_producer: bool) -> usize {
let divider = if min_match == 3 || use_sequence_producer {
3
} else {
4
};
block_size / divider
}
/// Pure leaf for private `ZSTD_maxNbSeq()`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_maxNbSeq(
blockSize: usize,
minMatch: u32,
useSequenceProducer: c_int,
) -> usize {
max_nb_seq(blockSize, minMatch, useSequenceProducer != 0)
}
/// Pure leaf for private `ZSTD_resolveMaxBlockSize()`.
#[no_mangle]
pub extern "C" fn ZSTD_rust_params_resolveMaxBlockSize(maxBlockSize: usize) -> usize {
if maxBlockSize == 0 {
ZSTD_BLOCKSIZE_MAX
} else {
maxBlockSize
}
}
fn estimate_cdict_size_from_cparams(
dict_size: usize,
cparams: ZSTD_compressionParameters,
dict_load_method: c_int,
sizing: ZSTD_rustCDictSizing,
) -> usize {
if check_cparams(cparams) != 0 {
return 0;
}
let match_state_sizing = ZSTD_rustMatchStateSizing {
hashLog3Max: sizing.hashLog3Max,
matchTSize: sizing.matchTSize,
optimalTSize: sizing.optimalTSize,
asanRedzoneSize: sizing.asanRedzoneSize,
};
let row_match_finder = resolve_row_match_finder(ZSTD_RUST_PS_AUTO, cparams);
let copied_dict_space = if dict_load_method == 1 {
0
} else {
cwksp_alloc_size(
cwksp_align(dict_size, size_of::<usize>()),
sizing.asanRedzoneSize,
)
};
cwksp_alloc_size(sizing.cdictSize, sizing.asanRedzoneSize)
.wrapping_add(cwksp_alloc_size(
sizing.hufWorkspaceSize,
sizing.asanRedzoneSize,
))
.wrapping_add(estimate_match_state_size(
cparams,
row_match_finder,
true,
false,
match_state_sizing,
))
.wrapping_add(copied_dict_space)
}
/// Parameter-only leaf for `ZSTD_estimateCDictSize_advanced()`.
///
/// C retains ownership of `sizeof(ZSTD_CDict)`, HUF workspace configuration,
/// and sanitizer workspace policy, then passes them in `sizing`. A NULL sizing
/// pointer returns zero.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_params_estimateCDictSizeFromCParams(
dictSize: usize,
cparams: ZSTD_compressionParameters,
dictLoadMethod: c_int,
sizing: *const ZSTD_rustCDictSizing,
) -> usize {
if sizing.is_null() {
return 0;
}
estimate_cdict_size_from_cparams(dictSize, cparams, dictLoadMethod, unsafe { *sizing })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::ERR_isError;
use std::mem::{align_of, size_of};
fn adjust_public(
cparams: ZSTD_compressionParameters,
src_size: u64,
dict_size: usize,
) -> ZSTD_compressionParameters {
let src_size = if src_size == 0 {
ZSTD_CONTENTSIZE_UNKNOWN
} else {
src_size
};
adjust_cparams(
clamp_cparams(cparams),
src_size,
dict_size,
ZSTD_RUST_CPM_UNKNOWN,
ZSTD_RUST_PS_AUTO,
)
}
#[test]
fn abi_parameter_layouts_match_zstd_h() {
assert_eq!(
size_of::<ZSTD_compressionParameters>(),
7 * size_of::<u32>()
);
assert_eq!(align_of::<ZSTD_compressionParameters>(), align_of::<u32>());
assert_eq!(size_of::<ZSTD_frameParameters>(), 3 * size_of::<c_int>());
assert_eq!(size_of::<ZSTD_parameters>(), 10 * size_of::<u32>());
assert_eq!(
size_of::<ZSTD_bounds>(),
size_of::<usize>() + 2 * size_of::<c_int>()
);
}
#[test]
fn level_tables_match_representative_clevels_entries() {
let large = select_cparams(3, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN);
assert_eq!(
large,
ZSTD_compressionParameters {
windowLog: 21,
chainLog: 16,
hashLog: 17,
searchLog: 1,
minMatch: 5,
targetLength: 0,
strategy: ZSTD_DFAST,
}
);
let small = select_cparams(3, 16 * 1024, 0, ZSTD_RUST_CPM_UNKNOWN);
assert_eq!(
small,
ZSTD_compressionParameters {
windowLog: 14,
chainLog: 14,
hashLog: 15,
searchLog: 2,
minMatch: 4,
targetLength: 0,
strategy: ZSTD_DFAST,
}
);
let fast = select_cparams(-5, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN);
assert_eq!(fast.strategy, ZSTD_FAST);
assert_eq!(fast.targetLength, 5);
assert_eq!(
select_cparams(999, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN),
select_cparams(22, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN)
);
}
#[test]
fn adjustment_clamps_and_downsizes_like_the_c_leaf() {
let input = ZSTD_compressionParameters {
windowLog: 31,
chainLog: 30,
hashLog: 30,
searchLog: 1,
minMatch: 3,
targetLength: 0,
strategy: ZSTD_BTOPT,
};
let adjusted = adjust_public(input, 1, 0);
assert_eq!(adjusted.windowLog, 10);
/* C applies WINDOWLOG_ABSOLUTEMIN only after it has used the smaller
* temporary window to downsize the hash and chain logs. */
assert_eq!(adjusted.hashLog, 7);
assert_eq!(adjusted.chainLog, 7);
assert_eq!(ZSTD_rust_params_checkCParams(adjusted), 0);
let clamped = ZSTD_rust_params_clampCParams(ZSTD_compressionParameters {
windowLog: u32::MAX,
chainLog: 0,
hashLog: 0,
searchLog: 0,
minMatch: 0,
targetLength: u32::MAX,
strategy: 99,
});
assert_eq!(clamped.windowLog, ZSTD_WINDOWLOG_MIN as u32);
assert_eq!(clamped.chainLog, ZSTD_CHAINLOG_MIN as u32);
assert_eq!(clamped.hashLog, ZSTD_HASHLOG_MIN as u32);
assert_eq!(clamped.searchLog, ZSTD_SEARCHLOG_MIN as u32);
assert_eq!(clamped.minMatch, ZSTD_MINMATCH_MIN as u32);
assert_eq!(clamped.targetLength, ZSTD_TARGETLENGTH_MIN as u32);
assert_eq!(clamped.strategy, ZSTD_BTULTRA2);
}
#[test]
fn checking_and_bounds_preserve_the_public_error_contract() {
let valid = select_cparams(1, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN);
assert_eq!(check_cparams(valid), 0);
let mut invalid = valid;
invalid.minMatch = 2;
assert!(ERR_isError(check_cparams(invalid)));
let bounds = ZSTD_rust_params_getBounds(ZSTD_C_HASH_LOG);
assert_eq!(bounds.error, 0);
assert_eq!(bounds.lowerBound, 6);
assert_eq!(bounds.upperBound, 30);
assert!(ERR_isError(ZSTD_rust_params_getBounds(-1).error));
}
#[test]
fn params_default_frame_flags_match_c() {
let cparams = select_cparams(5, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN);
let params = ZSTD_rust_params_makeParams(cparams);
assert_eq!(params.cParams, cparams);
assert_eq!(params.fParams.contentSizeFlag, 1);
assert_eq!(params.fParams.checksumFlag, 0);
assert_eq!(params.fParams.noDictIDFlag, 0);
}
#[test]
fn row_size_preserves_unknown_dictionary_overflow_semantics() {
assert_eq!(
get_cparam_row_size(ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_RUST_CPM_UNKNOWN),
ZSTD_CONTENTSIZE_UNKNOWN
);
assert_eq!(
get_cparam_row_size(ZSTD_CONTENTSIZE_UNKNOWN, 1, ZSTD_RUST_CPM_UNKNOWN),
500
);
assert_eq!(
get_cparam_row_size(128 * 1024, 1, ZSTD_RUST_CPM_ATTACH_DICT),
128 * 1024
);
}
#[test]
fn match_state_sizing_reproduces_table_and_row_rules() {
let sizing = ZSTD_rustMatchStateSizing {
hashLog3Max: 17,
matchTSize: 16,
optimalTSize: 32,
asanRedzoneSize: 0,
};
let fast = ZSTD_compressionParameters {
windowLog: 10,
chainLog: 10,
hashLog: 10,
searchLog: 1,
minMatch: 4,
targetLength: 0,
strategy: ZSTD_FAST,
};
assert_eq!(
estimate_match_state_size(fast, ZSTD_RUST_PS_DISABLE, false, true, sizing),
4096 + 128
);
let row = ZSTD_compressionParameters {
windowLog: 15,
chainLog: 10,
hashLog: 10,
searchLog: 4,
minMatch: 4,
targetLength: 0,
strategy: ZSTD_GREEDY,
};
assert_eq!(
estimate_match_state_size(row, ZSTD_RUST_PS_ENABLE, false, true, sizing),
4096 + 1024 + 128
);
assert_eq!(ZSTD_rust_params_maxNbSeq(100, 3, 0), 33);
assert_eq!(ZSTD_rust_params_maxNbSeq(100, 4, 0), 25);
assert_eq!(ZSTD_rust_params_resolveMaxBlockSize(0), 128 * 1024);
}
}
-1549
View File
@@ -1,1549 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
//! Sequence statistics and seqStore entropy compression.
//!
//! This module translates the block entropy-statistics layer of
//! `zstd_compress.c`: `ZSTD_seqToCodes()`, `ZSTD_buildSequencesStatistics()`,
//! `ZSTD_entropyCompressSeqStore*()`, `ZSTD_buildBlockEntropyStats*()`, and
//! `ZSTD_copyBlockSequences()`. The `ZSTD_CCtx` and `ZSTD_CCtx_params`
//! layouts stay private to C: the C shims extract the sequence store, the
//! entropy-table leaves, and the two parameter scalars these paths read
//! (the compression strategy and the literals-compression switch). Block
//! dispatch, block splitting, and the block-size estimation heuristics also
//! remain in C for a later slice.
use crate::bits::ZSTD_highbit32;
use crate::common::{
DEFAULT_MAX_OFF, LL_DEFAULT_NORM, LL_DEFAULT_NORM_LOG, LL_FSE_LOG, LONGNBSEQ, MAX_LL, MAX_ML,
MAX_OFF, MAX_SEQ, MINMATCH, ML_DEFAULT_NORM, ML_DEFAULT_NORM_LOG, ML_FSE_LOG, OFF_FSE_LOG,
OF_DEFAULT_NORM, OF_DEFAULT_NORM_LOG, ZSTD_MAX_FSE_HEADERS_SIZE, ZSTD_MAX_HUF_HEADER_SIZE,
ZSTD_REP_NUM,
};
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use crate::hist::{HIST_countFast_wksp, HIST_count_wksp};
use crate::huf_compress::{
HUF_buildCTable_wksp, HUF_estimateCompressedSize, HUF_optimalTableLog, HUF_validateCTable,
HUF_writeCTable_wksp,
};
use crate::mem::{MEM_32bits, MEM_writeLE16};
use crate::zstd_compress_literals::{
copy_huf_tables, min_gain, ZSTD_compressLiterals, ZSTD_hufCTables_t,
};
use crate::zstd_compress_sequences::{
SeqDef, ZSTD_buildCTable, ZSTD_encodeSequences, ZSTD_selectEncodingType,
};
use std::ffi::c_void;
use std::mem::{size_of, size_of_val};
use std::os::raw::{c_int, c_uint};
use std::ptr;
const SET_BASIC: c_int = 0;
const SET_RLE: c_int = 1;
const SET_COMPRESSED: c_int = 2;
const SET_REPEAT: c_int = 3;
const FSE_REPEAT_NONE: c_int = 0;
const HUF_REPEAT_NONE: c_int = 0;
const HUF_REPEAT_CHECK: c_int = 1;
const HUF_REPEAT_VALID: c_int = 2;
const HUF_FLAGS_OPTIMAL_DEPTH: c_int = 1 << 1;
const HUF_OPTIMAL_DEPTH_THRESHOLD: c_int = 8; /* ZSTD_btultra */
const ZSTD_DEFAULT_DISALLOWED: c_int = 0;
const ZSTD_DEFAULT_ALLOWED: c_int = 1;
const HUF_SYMBOLVALUE_MAX: c_uint = 255;
const LIT_HUF_LOG: c_uint = 11;
const COMPRESS_LITERALS_SIZE_MIN: usize = 63;
const SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO: usize = 20;
const STREAM_ACCUMULATOR_MIN_32: u32 = 25;
const STREAM_ACCUMULATOR_MIN_64: u32 = 57;
const LL_DELTA_CODE: u32 = 19;
const ML_DELTA_CODE: u32 = 36;
pub(crate) const ZSTD_LLT_LITERAL_LENGTH: c_int = 1;
pub(crate) const ZSTD_LLT_MATCH_LENGTH: c_int = 2;
/// `FSE_CTABLE_SIZE_U32(OffFSELog, MaxOff)` from the C headers.
pub const OFF_CTABLE_SIZE: usize = 1 + (1 << (OFF_FSE_LOG - 1)) + ((MAX_OFF + 1) * 2);
/// `FSE_CTABLE_SIZE_U32(MLFSELog, MaxML)` from the C headers.
pub const ML_CTABLE_SIZE: usize = 1 + (1 << (ML_FSE_LOG - 1)) + ((MAX_ML + 1) * 2);
/// `FSE_CTABLE_SIZE_U32(LLFSELog, MaxLL)` from the C headers.
pub const LL_CTABLE_SIZE: usize = 1 + (1 << (LL_FSE_LOG - 1)) + ((MAX_LL + 1) * 2);
/// ABI-compatible `SeqStore_t` leaf layout from `zstd_compress_internal.h`.
///
/// The C context itself remains opaque; its shims pass the `seqStore` member
/// directly, so this small, stable hot-path structure is the only sequence
/// storage representation crossing into Rust.
#[repr(C)]
pub struct SeqStore_t {
pub sequencesStart: *mut SeqDef,
pub sequences: *mut SeqDef,
pub litStart: *mut u8,
pub lit: *mut u8,
pub llCode: *mut u8,
pub mlCode: *mut u8,
pub ofCode: *mut u8,
pub maxNbSeq: usize,
pub maxNbLit: usize,
pub longLengthType: c_int,
pub longLengthPos: u32,
}
/// C's `ZSTD_fseCTables_t`. The table element type is `FSE_CTable`, an
/// `unsigned`, and the table lengths are the header macros expanded above.
#[repr(C)]
pub struct ZSTD_fseCTables_t {
pub offcodeCTable: [u32; OFF_CTABLE_SIZE],
pub matchlengthCTable: [u32; ML_CTABLE_SIZE],
pub litlengthCTable: [u32; LL_CTABLE_SIZE],
pub offcode_repeatMode: c_int,
pub matchlength_repeatMode: c_int,
pub litlength_repeatMode: c_int,
}
/// C's `ZSTD_entropyCTables_t`.
#[repr(C)]
pub struct ZSTD_entropyCTables_t {
pub huf: ZSTD_hufCTables_t,
pub fse: ZSTD_fseCTables_t,
}
/// C's `ZSTD_compressedBlockState_t`.
#[repr(C)]
pub struct ZSTD_compressedBlockState_t {
pub entropy: ZSTD_entropyCTables_t,
pub rep: [u32; ZSTD_REP_NUM],
}
/// C's `ZSTD_hufCTablesMetadata_t`.
#[repr(C)]
pub struct ZSTD_hufCTablesMetadata_t {
pub hType: c_int,
pub hufDesBuffer: [u8; ZSTD_MAX_HUF_HEADER_SIZE],
pub hufDesSize: usize,
}
/// C's `ZSTD_fseCTablesMetadata_t`.
#[repr(C)]
pub struct ZSTD_fseCTablesMetadata_t {
pub llType: c_int,
pub ofType: c_int,
pub mlType: c_int,
pub fseTablesBuffer: [u8; ZSTD_MAX_FSE_HEADERS_SIZE],
pub fseTablesSize: usize,
pub lastCountSize: usize,
}
/// C's `ZSTD_entropyCTablesMetadata_t`.
#[repr(C)]
pub struct ZSTD_entropyCTablesMetadata_t {
pub hufMetadata: ZSTD_hufCTablesMetadata_t,
pub fseMetadata: ZSTD_fseCTablesMetadata_t,
}
/// Public `ZSTD_Sequence` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ZSTD_Sequence {
pub offset: u32,
pub litLength: u32,
pub matchLength: u32,
pub rep: u32,
}
/// C's `SeqCollector` leaf from `zstd_compress_internal.h`.
#[repr(C)]
pub struct SeqCollector {
pub collectSequences: c_int,
pub seqStart: *mut ZSTD_Sequence,
pub seqIndex: usize,
pub maxSequences: usize,
}
/// Result of `build_sequences_statistics()`; mirrors C's
/// `ZSTD_symbolEncodingTypeStats_t`, which no longer crosses the boundary.
pub(crate) struct SymbolEncodingTypeStats {
pub ll_type: u32,
pub off_type: u32,
pub ml_type: u32,
pub size: usize,
/// Accounts for a bug in zstd 1.3.4; see
/// `entropy_compress_seq_store_internal()`.
pub last_count_size: usize,
pub long_offsets: c_int,
}
/// C's `ZSTD_updateRep()` repeat-offset history update.
#[inline]
pub(crate) fn update_rep(reps: &mut [u32; 3], off_base: u32, literal_length_is_zero: bool) {
if off_base > 3 {
reps[2] = reps[1];
reps[1] = reps[0];
reps[0] = off_base - 3;
return;
}
let rep_code = off_base - 1 + u32::from(literal_length_is_zero);
if rep_code == 0 {
return;
}
let current_offset = if rep_code == 3 {
reps[0].wrapping_sub(1)
} else {
reps[rep_code as usize]
};
reps[2] = if rep_code >= 2 { reps[1] } else { reps[2] };
reps[1] = reps[0];
reps[0] = current_offset;
}
#[inline]
fn ll_code(lit_length: u32) -> u32 {
const LL_CODE: [u8; 64] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
];
if lit_length > 63 {
ZSTD_highbit32(lit_length) + LL_DELTA_CODE
} else {
LL_CODE[lit_length as usize] as u32
}
}
#[inline]
fn ml_code(ml_base: u32) -> u32 {
const ML_CODE: [u8; 128] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37,
38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
];
if ml_base > 127 {
ZSTD_highbit32(ml_base) + ML_DELTA_CODE
} else {
ML_CODE[ml_base as usize] as u32
}
}
#[inline]
unsafe fn copy_fse_tables(dst: *mut ZSTD_fseCTables_t, src: *const ZSTD_fseCTables_t) {
unsafe {
ptr::copy_nonoverlapping(
src.cast::<u8>(),
dst.cast::<u8>(),
size_of::<ZSTD_fseCTables_t>(),
)
};
}
/// Converts the stored lengths and distances into symbol codes.
///
/// Retains its original exported name: the C dictionary builder and the
/// decodecorpus test tool link against it directly.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_seqToCodes(seq_store: *const SeqStore_t) -> c_int {
let store = unsafe { &*seq_store };
let sequences = store.sequencesStart;
let nb_seq = unsafe { store.sequences.offset_from(sequences) } as usize;
let mut long_offsets = 0;
debug_assert!(nb_seq <= store.maxNbSeq);
for index in 0..nb_seq {
let sequence = unsafe { sequences.add(index).read() };
let of_code = ZSTD_highbit32(sequence.offBase);
unsafe {
*store.llCode.add(index) = ll_code(sequence.litLength as u32) as u8;
*store.ofCode.add(index) = of_code as u8;
*store.mlCode.add(index) = ml_code(sequence.mlBase as u32) as u8;
}
debug_assert!(MEM_32bits() || of_code < STREAM_ACCUMULATOR_MIN_64);
if MEM_32bits() && of_code >= STREAM_ACCUMULATOR_MIN_32 {
long_offsets = 1;
}
}
if store.longLengthType == ZSTD_LLT_LITERAL_LENGTH {
unsafe { *store.llCode.add(store.longLengthPos as usize) = MAX_LL as u8 };
}
if store.longLengthType == ZSTD_LLT_MATCH_LENGTH {
unsafe { *store.mlCode.add(store.longLengthPos as usize) = MAX_ML as u8 };
}
long_offsets
}
/// C's `ZSTD_buildSequencesStatistics()`.
///
/// Returns the statistics with a zstd error code in the `size` field on
/// failure, and updates `next_entropy` as a side effect. `nb_seq` must be
/// greater than zero, and the entropy workspace must hold at least
/// `ENTROPY_WORKSPACE_SIZE - (MaxSeq + 1) * sizeof(U32)` bytes.
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn build_sequences_statistics(
seq_store: *const SeqStore_t,
nb_seq: usize,
prev_entropy: *const ZSTD_fseCTables_t,
next_entropy: *mut ZSTD_fseCTables_t,
dst: *mut u8,
dst_capacity: usize,
strategy: c_int,
count_workspace: *mut c_uint,
entropy_workspace: *mut c_void,
entropy_wksp_size: usize,
) -> SymbolEncodingTypeStats {
let store = unsafe { &*seq_store };
let mut stats = SymbolEncodingTypeStats {
ll_type: 0,
off_type: 0,
ml_type: 0,
size: 0,
last_count_size: 0,
long_offsets: 0,
};
let mut written = 0usize;
/* convert length/distances into codes */
stats.long_offsets = unsafe { ZSTD_seqToCodes(seq_store) };
debug_assert!(nb_seq != 0); /* ZSTD_selectEncodingType() divides by nb_seq */
/* build CTable for Literal Lengths */
{
let mut max = MAX_LL as c_uint;
let most_frequent = unsafe {
HIST_countFast_wksp(
count_workspace,
&mut max,
store.llCode.cast::<c_void>(),
nb_seq,
entropy_workspace,
entropy_wksp_size,
)
}; /* can't fail */
unsafe { (*next_entropy).litlength_repeatMode = (*prev_entropy).litlength_repeatMode };
stats.ll_type = unsafe {
ZSTD_selectEncodingType(
ptr::addr_of_mut!((*next_entropy).litlength_repeatMode),
count_workspace,
max,
most_frequent,
nb_seq,
LL_FSE_LOG as c_uint,
ptr::addr_of!((*prev_entropy).litlengthCTable).cast::<u32>(),
LL_DEFAULT_NORM.as_ptr(),
LL_DEFAULT_NORM_LOG,
ZSTD_DEFAULT_ALLOWED,
strategy,
)
} as u32;
let count_size = unsafe {
ZSTD_buildCTable(
dst.add(written).cast::<c_void>(),
dst_capacity - written,
ptr::addr_of_mut!((*next_entropy).litlengthCTable).cast::<u32>(),
LL_FSE_LOG as u32,
stats.ll_type as c_int,
count_workspace,
max,
store.llCode,
nb_seq,
LL_DEFAULT_NORM.as_ptr(),
LL_DEFAULT_NORM_LOG,
MAX_LL as u32,
ptr::addr_of!((*prev_entropy).litlengthCTable).cast::<u32>(),
size_of::<[u32; LL_CTABLE_SIZE]>(),
entropy_workspace,
entropy_wksp_size,
)
};
if ERR_isError(count_size) {
stats.size = count_size;
return stats;
}
if stats.ll_type == SET_COMPRESSED as u32 {
stats.last_count_size = count_size;
}
written += count_size;
}
/* build CTable for Offsets */
{
let mut max = MAX_OFF as c_uint;
let most_frequent = unsafe {
HIST_countFast_wksp(
count_workspace,
&mut max,
store.ofCode.cast::<c_void>(),
nb_seq,
entropy_workspace,
entropy_wksp_size,
)
}; /* can't fail */
/* We can only use the basic table if max <= DefaultMaxOff, otherwise the offsets are too large */
let default_policy = if max <= DEFAULT_MAX_OFF as c_uint {
ZSTD_DEFAULT_ALLOWED
} else {
ZSTD_DEFAULT_DISALLOWED
};
unsafe { (*next_entropy).offcode_repeatMode = (*prev_entropy).offcode_repeatMode };
stats.off_type = unsafe {
ZSTD_selectEncodingType(
ptr::addr_of_mut!((*next_entropy).offcode_repeatMode),
count_workspace,
max,
most_frequent,
nb_seq,
OFF_FSE_LOG as c_uint,
ptr::addr_of!((*prev_entropy).offcodeCTable).cast::<u32>(),
OF_DEFAULT_NORM.as_ptr(),
OF_DEFAULT_NORM_LOG,
default_policy,
strategy,
)
} as u32;
let count_size = unsafe {
ZSTD_buildCTable(
dst.add(written).cast::<c_void>(),
dst_capacity - written,
ptr::addr_of_mut!((*next_entropy).offcodeCTable).cast::<u32>(),
OFF_FSE_LOG as u32,
stats.off_type as c_int,
count_workspace,
max,
store.ofCode,
nb_seq,
OF_DEFAULT_NORM.as_ptr(),
OF_DEFAULT_NORM_LOG,
DEFAULT_MAX_OFF as u32,
ptr::addr_of!((*prev_entropy).offcodeCTable).cast::<u32>(),
size_of::<[u32; OFF_CTABLE_SIZE]>(),
entropy_workspace,
entropy_wksp_size,
)
};
if ERR_isError(count_size) {
stats.size = count_size;
return stats;
}
if stats.off_type == SET_COMPRESSED as u32 {
stats.last_count_size = count_size;
}
written += count_size;
}
/* build CTable for MatchLengths */
{
let mut max = MAX_ML as c_uint;
let most_frequent = unsafe {
HIST_countFast_wksp(
count_workspace,
&mut max,
store.mlCode.cast::<c_void>(),
nb_seq,
entropy_workspace,
entropy_wksp_size,
)
}; /* can't fail */
unsafe { (*next_entropy).matchlength_repeatMode = (*prev_entropy).matchlength_repeatMode };
stats.ml_type = unsafe {
ZSTD_selectEncodingType(
ptr::addr_of_mut!((*next_entropy).matchlength_repeatMode),
count_workspace,
max,
most_frequent,
nb_seq,
ML_FSE_LOG as c_uint,
ptr::addr_of!((*prev_entropy).matchlengthCTable).cast::<u32>(),
ML_DEFAULT_NORM.as_ptr(),
ML_DEFAULT_NORM_LOG,
ZSTD_DEFAULT_ALLOWED,
strategy,
)
} as u32;
let count_size = unsafe {
ZSTD_buildCTable(
dst.add(written).cast::<c_void>(),
dst_capacity - written,
ptr::addr_of_mut!((*next_entropy).matchlengthCTable).cast::<u32>(),
ML_FSE_LOG as u32,
stats.ml_type as c_int,
count_workspace,
max,
store.mlCode,
nb_seq,
ML_DEFAULT_NORM.as_ptr(),
ML_DEFAULT_NORM_LOG,
MAX_ML as u32,
ptr::addr_of!((*prev_entropy).matchlengthCTable).cast::<u32>(),
size_of::<[u32; ML_CTABLE_SIZE]>(),
entropy_workspace,
entropy_wksp_size,
)
};
if ERR_isError(count_size) {
stats.size = count_size;
return stats;
}
if stats.ml_type == SET_COMPRESSED as u32 {
stats.last_count_size = count_size;
}
written += count_size;
}
stats.size = written;
stats
}
/// C's `ZSTD_entropyCompressSeqStore_internal()`: compresses literals and
/// sequences into one compressed-block body.
#[allow(clippy::too_many_arguments)]
unsafe fn entropy_compress_seq_store_internal(
dst: *mut c_void,
dst_capacity: usize,
literals: *const c_void,
lit_size: usize,
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
entropy_workspace: *mut c_void,
entropy_wksp_size: usize,
bmi2: c_int,
) -> usize {
let count = entropy_workspace.cast::<c_uint>();
let entropy_workspace = unsafe { count.add(MAX_SEQ + 1) }.cast::<c_void>();
let entropy_wksp_size = entropy_wksp_size.wrapping_sub((MAX_SEQ + 1) * size_of::<c_uint>());
let store = unsafe { &*seq_store };
let sequences = store.sequencesStart;
let nb_seq = unsafe { store.sequences.offset_from(sequences) } as usize;
let ostart = dst.cast::<u8>();
let mut written = 0usize;
let last_count_size;
let long_offsets;
/* Compress literals */
{
/* Base suspicion of uncompressibility on ratio of literals to sequences */
let suspect_uncompressible =
c_int::from(nb_seq == 0 || lit_size / nb_seq >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO);
let c_size = unsafe {
ZSTD_compressLiterals(
dst,
dst_capacity,
literals,
lit_size,
entropy_workspace,
entropy_wksp_size,
ptr::addr_of!((*prev_entropy).huf),
ptr::addr_of_mut!((*next_entropy).huf),
strategy,
disable_literal_compression,
suspect_uncompressible,
bmi2,
)
};
if ERR_isError(c_size) {
return c_size;
}
debug_assert!(c_size <= dst_capacity);
written += c_size;
}
/* Sequences Header */
if (dst_capacity as isize).wrapping_sub(written as isize) < 3 /* max nbSeq Size */ + 1
/* seqHead */
{
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
unsafe {
if nb_seq < 128 {
*ostart.add(written) = nb_seq as u8;
written += 1;
} else if nb_seq < LONGNBSEQ as usize {
*ostart.add(written) = ((nb_seq >> 8) + 0x80) as u8;
*ostart.add(written + 1) = nb_seq as u8;
written += 2;
} else {
*ostart.add(written) = 0xFF;
MEM_writeLE16(
ostart.add(written + 1).cast::<c_void>(),
(nb_seq - LONGNBSEQ as usize) as u16,
);
written += 3;
}
}
if nb_seq == 0 {
/* Copy the old tables over as if we repeated them */
unsafe {
copy_fse_tables(
ptr::addr_of_mut!((*next_entropy).fse),
ptr::addr_of!((*prev_entropy).fse),
)
};
return written;
}
{
let seq_head = written;
written += 1;
/* build stats for sequences */
let stats = unsafe {
build_sequences_statistics(
seq_store,
nb_seq,
ptr::addr_of!((*prev_entropy).fse),
ptr::addr_of_mut!((*next_entropy).fse),
ostart.add(written),
dst_capacity - written,
strategy,
count,
entropy_workspace,
entropy_wksp_size,
)
};
if ERR_isError(stats.size) {
return stats.size;
}
unsafe {
*ostart.add(seq_head) =
((stats.ll_type << 6) + (stats.off_type << 4) + (stats.ml_type << 2)) as u8;
}
last_count_size = stats.last_count_size;
written += stats.size;
long_offsets = stats.long_offsets;
}
{
let bitstream_size = unsafe {
ZSTD_encodeSequences(
ostart.add(written).cast::<c_void>(),
dst_capacity - written,
ptr::addr_of!((*next_entropy).fse.matchlengthCTable).cast::<u32>(),
store.mlCode,
ptr::addr_of!((*next_entropy).fse.offcodeCTable).cast::<u32>(),
store.ofCode,
ptr::addr_of!((*next_entropy).fse.litlengthCTable).cast::<u32>(),
store.llCode,
sequences,
nb_seq,
long_offsets,
bmi2,
)
};
if ERR_isError(bitstream_size) {
return bitstream_size;
}
written += bitstream_size;
/* zstd versions <= 1.3.4 mistakenly report corruption when
* FSE_readNCount() receives a buffer < 4 bytes.
* Fixed by https://github.com/facebook/zstd/pull/1146.
* This can happen when the last set_compressed table present is 2
* bytes and the bitstream is only one byte.
* In this exceedingly rare case, we will simply emit an uncompressed
* block, since it isn't worth optimizing.
*/
if last_count_size != 0 && last_count_size + bitstream_size < 4 {
/* last_count_size >= 2 && bitstream_size > 0 ==> last_count_size == 3 */
debug_assert!(last_count_size + bitstream_size == 3);
return 0;
}
}
written
}
/// C's `ZSTD_entropyCompressSeqStore_wExtLitBuffer()`: adds the
/// incompressibility fallbacks around the internal entropy pass.
#[allow(clippy::too_many_arguments)]
unsafe fn entropy_compress_seq_store_w_ext_lit_buffer(
dst: *mut c_void,
dst_capacity: usize,
literals: *const c_void,
lit_size: usize,
block_size: usize,
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
entropy_workspace: *mut c_void,
entropy_wksp_size: usize,
bmi2: c_int,
) -> usize {
let c_size = unsafe {
entropy_compress_seq_store_internal(
dst,
dst_capacity,
literals,
lit_size,
seq_store,
prev_entropy,
next_entropy,
strategy,
disable_literal_compression,
entropy_workspace,
entropy_wksp_size,
bmi2,
)
};
if c_size == 0 {
return 0;
}
/* When srcSize <= dstCapacity, there is enough space to write a raw uncompressed block.
* Since we ran out of space, block must be not compressible, so fall back to raw uncompressed block.
*/
if c_size == ERROR(ZstdErrorCode::DstSizeTooSmall) && block_size <= dst_capacity {
return 0; /* block not compressed */
}
if ERR_isError(c_size) {
return c_size;
}
/* Check compressibility */
let max_c_size = block_size.wrapping_sub(min_gain(block_size, strategy));
if c_size >= max_c_size {
return 0; /* block not compressed */
}
c_size
}
/// C ABI wrapper for the external-literal-buffer entropy pass; the C shim
/// keeps the `ZSTD_entropyCompressSeqStore_internal()` name and extracts the
/// two `ZSTD_CCtx_params` scalars.
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_entropyCompressSeqStore_internal(
dst: *mut c_void,
dst_capacity: usize,
literals: *const c_void,
lit_size: usize,
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
entropy_workspace: *mut c_void,
entropy_wksp_size: usize,
bmi2: c_int,
) -> usize {
unsafe {
entropy_compress_seq_store_internal(
dst,
dst_capacity,
literals,
lit_size,
seq_store,
prev_entropy,
next_entropy,
strategy,
disable_literal_compression,
entropy_workspace,
entropy_wksp_size,
bmi2,
)
}
}
/// C ABI implementation behind the `ZSTD_entropyCompressSeqStore()` shim.
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_entropyCompressSeqStore(
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
dst: *mut c_void,
dst_capacity: usize,
src_size: usize,
entropy_workspace: *mut c_void,
entropy_wksp_size: usize,
bmi2: c_int,
) -> usize {
let store = unsafe { &*seq_store };
let lit_size = unsafe { store.lit.offset_from(store.litStart) } as usize;
unsafe {
entropy_compress_seq_store_w_ext_lit_buffer(
dst,
dst_capacity,
store.litStart.cast::<c_void>(),
lit_size,
src_size,
seq_store,
prev_entropy,
next_entropy,
strategy,
disable_literal_compression,
entropy_workspace,
entropy_wksp_size,
bmi2,
)
}
}
/// C's `ZSTD_buildBlockEntropyStats_literals()`.
///
/// Stores the literals block type and Huffman description table into
/// `huf_metadata` and returns the description size, or an error code.
#[allow(clippy::too_many_arguments)]
unsafe fn build_block_entropy_stats_literals(
src: *const c_void,
src_size: usize,
prev_huf: *const ZSTD_hufCTables_t,
next_huf: *mut ZSTD_hufCTables_t,
huf_metadata: *mut ZSTD_hufCTablesMetadata_t,
literals_compression_is_disabled: c_int,
workspace: *mut c_void,
wksp_size: usize,
huf_flags: c_int,
) -> usize {
let count_wksp = workspace.cast::<c_uint>();
let count_wksp_size = (HUF_SYMBOLVALUE_MAX as usize + 1) * size_of::<c_uint>();
let node_wksp = unsafe { workspace.cast::<u8>().add(count_wksp_size) }.cast::<c_void>();
let node_wksp_size = wksp_size.wrapping_sub(count_wksp_size);
let mut max_symbol_value = HUF_SYMBOLVALUE_MAX;
let mut huff_log = LIT_HUF_LOG;
let mut repeat = unsafe { (*prev_huf).repeatMode };
/* Prepare nextEntropy assuming reusing the existing table */
unsafe { copy_huf_tables(next_huf, prev_huf) };
if literals_compression_is_disabled != 0 {
unsafe { (*huf_metadata).hType = SET_BASIC };
return 0;
}
/* small ? don't even attempt compression (speed opt) */
{
let min_lit_size = if repeat == HUF_REPEAT_VALID {
6
} else {
COMPRESS_LITERALS_SIZE_MIN
};
if src_size <= min_lit_size {
unsafe { (*huf_metadata).hType = SET_BASIC };
return 0;
}
}
/* Scan input and build symbol stats */
{
let largest = unsafe {
HIST_count_wksp(
count_wksp,
&mut max_symbol_value,
src,
src_size,
workspace,
wksp_size,
)
};
if ERR_isError(largest) {
return largest;
}
if largest == src_size {
/* only one literal symbol */
unsafe { (*huf_metadata).hType = SET_RLE };
return 0;
}
if largest <= (src_size >> 7) + 4 {
/* heuristic: likely not compressible */
unsafe { (*huf_metadata).hType = SET_BASIC };
return 0;
}
}
/* Validate the previous Huffman table */
if repeat == HUF_REPEAT_CHECK
&& unsafe {
HUF_validateCTable(
ptr::addr_of!((*prev_huf).CTable).cast::<usize>(),
count_wksp,
max_symbol_value,
)
} == 0
{
repeat = HUF_REPEAT_NONE;
}
/* Build Huffman Tree */
unsafe {
ptr::write_bytes(
ptr::addr_of_mut!((*next_huf).CTable).cast::<u8>(),
0,
size_of_val(&(*next_huf).CTable),
);
}
huff_log = unsafe {
HUF_optimalTableLog(
huff_log,
src_size,
max_symbol_value,
node_wksp,
node_wksp_size,
ptr::addr_of_mut!((*next_huf).CTable).cast::<usize>(),
count_wksp,
huf_flags,
)
};
debug_assert!(huff_log <= LIT_HUF_LOG);
{
let max_bits = unsafe {
HUF_buildCTable_wksp(
ptr::addr_of_mut!((*next_huf).CTable).cast::<usize>(),
count_wksp,
max_symbol_value,
huff_log,
node_wksp,
node_wksp_size,
)
};
if ERR_isError(max_bits) {
return max_bits;
}
huff_log = max_bits as c_uint;
}
{
/* Build and write the CTable */
let new_c_size = unsafe {
HUF_estimateCompressedSize(
ptr::addr_of!((*next_huf).CTable).cast::<usize>(),
count_wksp,
max_symbol_value,
)
};
let h_size = unsafe {
HUF_writeCTable_wksp(
ptr::addr_of_mut!((*huf_metadata).hufDesBuffer).cast::<c_void>(),
size_of::<[u8; ZSTD_MAX_HUF_HEADER_SIZE]>(),
ptr::addr_of!((*next_huf).CTable).cast::<usize>(),
max_symbol_value,
huff_log,
node_wksp,
node_wksp_size,
)
};
/* Check against repeating the previous CTable */
if repeat != HUF_REPEAT_NONE {
let old_c_size = unsafe {
HUF_estimateCompressedSize(
ptr::addr_of!((*prev_huf).CTable).cast::<usize>(),
count_wksp,
max_symbol_value,
)
};
if old_c_size < src_size
&& (old_c_size <= h_size.wrapping_add(new_c_size)
|| h_size.wrapping_add(12) >= src_size)
{
unsafe {
copy_huf_tables(next_huf, prev_huf);
(*huf_metadata).hType = SET_REPEAT;
}
return 0;
}
}
if new_c_size.wrapping_add(h_size) >= src_size {
unsafe {
copy_huf_tables(next_huf, prev_huf);
(*huf_metadata).hType = SET_BASIC;
}
return 0;
}
unsafe {
(*huf_metadata).hType = SET_COMPRESSED;
(*next_huf).repeatMode = HUF_REPEAT_CHECK;
}
h_size
}
}
/// C's `ZSTD_buildDummySequencesStatistics()`: marks every encoding type as
/// basic and resets the FSE repeat modes.
unsafe fn build_dummy_sequences_statistics(
next_entropy: *mut ZSTD_fseCTables_t,
) -> SymbolEncodingTypeStats {
unsafe {
(*next_entropy).litlength_repeatMode = FSE_REPEAT_NONE;
(*next_entropy).offcode_repeatMode = FSE_REPEAT_NONE;
(*next_entropy).matchlength_repeatMode = FSE_REPEAT_NONE;
}
SymbolEncodingTypeStats {
ll_type: SET_BASIC as u32,
off_type: SET_BASIC as u32,
ml_type: SET_BASIC as u32,
size: 0,
last_count_size: 0,
long_offsets: 0,
}
}
/// C's `ZSTD_buildBlockEntropyStats_sequences()`.
#[allow(clippy::too_many_arguments)]
unsafe fn build_block_entropy_stats_sequences(
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_fseCTables_t,
next_entropy: *mut ZSTD_fseCTables_t,
strategy: c_int,
fse_metadata: *mut ZSTD_fseCTablesMetadata_t,
workspace: *mut c_void,
wksp_size: usize,
) -> usize {
let store = unsafe { &*seq_store };
let nb_seq = unsafe { store.sequences.offset_from(store.sequencesStart) } as usize;
let count_workspace = workspace.cast::<c_uint>();
let entropy_workspace = unsafe { count_workspace.add(MAX_SEQ + 1) }.cast::<c_void>();
let entropy_workspace_size = wksp_size.wrapping_sub((MAX_SEQ + 1) * size_of::<c_uint>());
let stats = if nb_seq != 0 {
unsafe {
build_sequences_statistics(
seq_store,
nb_seq,
prev_entropy,
next_entropy,
ptr::addr_of_mut!((*fse_metadata).fseTablesBuffer).cast::<u8>(),
ZSTD_MAX_FSE_HEADERS_SIZE,
strategy,
count_workspace,
entropy_workspace,
entropy_workspace_size,
)
}
} else {
unsafe { build_dummy_sequences_statistics(next_entropy) }
};
if ERR_isError(stats.size) {
return stats.size;
}
unsafe {
(*fse_metadata).llType = stats.ll_type as c_int;
(*fse_metadata).ofType = stats.off_type as c_int;
(*fse_metadata).mlType = stats.ml_type as c_int;
(*fse_metadata).lastCountSize = stats.last_count_size;
}
stats.size
}
/// C's `ZSTD_buildBlockEntropyStats()`: builds entropy statistics for one
/// block. Requires an `ENTROPY_WORKSPACE_SIZE` workspace.
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn build_block_entropy_stats(
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
entropy_metadata: *mut ZSTD_entropyCTablesMetadata_t,
workspace: *mut c_void,
wksp_size: usize,
) -> usize {
let store = unsafe { &*seq_store };
let lit_size = unsafe { store.lit.offset_from(store.litStart) } as usize;
let huf_flags = if strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD {
HUF_FLAGS_OPTIMAL_DEPTH
} else {
0
};
let huf_des_size = unsafe {
build_block_entropy_stats_literals(
store.litStart.cast::<c_void>(),
lit_size,
ptr::addr_of!((*prev_entropy).huf),
ptr::addr_of_mut!((*next_entropy).huf),
ptr::addr_of_mut!((*entropy_metadata).hufMetadata),
disable_literal_compression,
workspace,
wksp_size,
huf_flags,
)
};
unsafe { (*entropy_metadata).hufMetadata.hufDesSize = huf_des_size };
if ERR_isError(huf_des_size) {
return huf_des_size;
}
let fse_tables_size = unsafe {
build_block_entropy_stats_sequences(
seq_store,
ptr::addr_of!((*prev_entropy).fse),
ptr::addr_of_mut!((*next_entropy).fse),
strategy,
ptr::addr_of_mut!((*entropy_metadata).fseMetadata),
workspace,
wksp_size,
)
};
unsafe { (*entropy_metadata).fseMetadata.fseTablesSize = fse_tables_size };
if ERR_isError(fse_tables_size) {
return fse_tables_size;
}
0
}
/// C ABI wrapper behind the `ZSTD_buildBlockEntropyStats()` shim, which is
/// still exported from C for the block splitter's size estimation.
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_buildBlockEntropyStats(
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
strategy: c_int,
disable_literal_compression: c_int,
entropy_metadata: *mut ZSTD_entropyCTablesMetadata_t,
workspace: *mut c_void,
wksp_size: usize,
) -> usize {
unsafe {
build_block_entropy_stats(
seq_store,
prev_entropy,
next_entropy,
strategy,
disable_literal_compression,
entropy_metadata,
workspace,
wksp_size,
)
}
}
/// C's `ZSTD_copyBlockSequences()`: exports a block's sequences in the public
/// `ZSTD_Sequence` format, appending the last-literals block delimiter.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_copyBlockSequences(
seq_collector: *mut SeqCollector,
seq_store: *const SeqStore_t,
prev_repcodes: *const u32,
) -> usize {
let collector = unsafe { &mut *seq_collector };
let store = unsafe { &*seq_store };
let in_seqs = store.sequencesStart;
let nb_in_sequences = unsafe { store.sequences.offset_from(in_seqs) } as usize;
let nb_in_literals = unsafe { store.lit.offset_from(store.litStart) } as usize;
let out_seqs = if collector.seqIndex == 0 {
collector.seqStart
} else {
unsafe { collector.seqStart.add(collector.seqIndex) }
};
let nb_out_sequences = nb_in_sequences + 1;
let mut nb_out_literals = 0usize;
/* Bounds check that we have enough space for every input sequence
* and the block delimiter
*/
debug_assert!(collector.seqIndex <= collector.maxSequences);
if nb_out_sequences > collector.maxSequences.wrapping_sub(collector.seqIndex) {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let mut repcodes = [0u32; ZSTD_REP_NUM];
unsafe { ptr::copy_nonoverlapping(prev_repcodes, repcodes.as_mut_ptr(), ZSTD_REP_NUM) };
for index in 0..nb_in_sequences {
let in_seq = unsafe { in_seqs.add(index).read() };
let out = unsafe { out_seqs.add(index) };
let mut lit_length = in_seq.litLength as u32;
let mut match_length = in_seq.mlBase as u32 + MINMATCH as u32;
let mut rep = 0u32;
/* Handle the possible single length >= 64K
* There can only be one because we add MINMATCH to every match length,
* and blocks are at most 128K.
*/
if index == store.longLengthPos as usize {
if store.longLengthType == ZSTD_LLT_LITERAL_LENGTH {
lit_length += 0x10000;
} else if store.longLengthType == ZSTD_LLT_MATCH_LENGTH {
match_length += 0x10000;
}
}
/* Determine the raw offset given the offBase, which may be a repcode. */
let raw_offset = if (1..=ZSTD_REP_NUM as u32).contains(&in_seq.offBase) {
let repcode = in_seq.offBase;
debug_assert!(repcode > 0);
rep = repcode;
if lit_length != 0 {
repcodes[repcode as usize - 1]
} else if repcode == 3 {
debug_assert!(repcodes[0] > 1);
repcodes[0].wrapping_sub(1)
} else {
repcodes[repcode as usize]
}
} else {
in_seq.offBase.wrapping_sub(ZSTD_REP_NUM as u32)
};
unsafe {
(*out).litLength = lit_length;
(*out).matchLength = match_length;
(*out).offset = raw_offset;
(*out).rep = rep;
}
/* Update repcode history for the sequence */
update_rep(&mut repcodes, in_seq.offBase, in_seq.litLength == 0);
nb_out_literals += lit_length as usize;
}
/* Insert last literals (if any exist) in the block as a sequence with ml == off == 0.
* If there are no last literals, then we'll emit (of: 0, ml: 0, ll: 0), which is a marker
* for the block boundary, according to the API.
*/
debug_assert!(nb_in_literals >= nb_out_literals);
{
let last_ll_size = nb_in_literals.wrapping_sub(nb_out_literals);
unsafe {
let last = out_seqs.add(nb_in_sequences);
(*last).litLength = last_ll_size as u32;
(*last).matchLength = 0;
(*last).offset = 0;
}
}
collector.seqIndex += nb_out_sequences;
debug_assert!(collector.seqIndex <= collector.maxSequences);
0
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{align_of, offset_of};
const HUF_CTABLE_SIZE_ST: usize = HUF_SYMBOLVALUE_MAX as usize + 2;
const ENTROPY_WORKSPACE_SIZE: usize = (8 << 10) + 512 + (MAX_SEQ + 2) * size_of::<u32>();
fn empty_huf_tables(repeat: c_int) -> ZSTD_hufCTables_t {
ZSTD_hufCTables_t {
CTable: [0; HUF_CTABLE_SIZE_ST],
repeatMode: repeat,
}
}
fn empty_fse_tables(repeat: c_int) -> ZSTD_fseCTables_t {
ZSTD_fseCTables_t {
offcodeCTable: [0; OFF_CTABLE_SIZE],
matchlengthCTable: [0; ML_CTABLE_SIZE],
litlengthCTable: [0; LL_CTABLE_SIZE],
offcode_repeatMode: repeat,
matchlength_repeatMode: repeat,
litlength_repeatMode: repeat,
}
}
struct TestSeqStore {
sequences: Vec<SeqDef>,
literals: Vec<u8>,
ll_codes: Vec<u8>,
ml_codes: Vec<u8>,
of_codes: Vec<u8>,
}
impl TestSeqStore {
fn new(sequences: Vec<SeqDef>, literals: Vec<u8>) -> Self {
let nb_seq = sequences.len();
TestSeqStore {
sequences,
literals,
ll_codes: vec![0; nb_seq],
ml_codes: vec![0; nb_seq],
of_codes: vec![0; nb_seq],
}
}
fn seq_store(&mut self) -> SeqStore_t {
let nb_seq = self.sequences.len();
let nb_lit = self.literals.len();
SeqStore_t {
sequencesStart: self.sequences.as_mut_ptr(),
sequences: unsafe { self.sequences.as_mut_ptr().add(nb_seq) },
litStart: self.literals.as_mut_ptr(),
lit: unsafe { self.literals.as_mut_ptr().add(nb_lit) },
llCode: self.ll_codes.as_mut_ptr(),
mlCode: self.ml_codes.as_mut_ptr(),
ofCode: self.of_codes.as_mut_ptr(),
maxNbSeq: nb_seq,
maxNbLit: nb_lit,
longLengthType: 0,
longLengthPos: 0,
}
}
}
#[test]
fn c_leaf_layouts_match_supported_abis() {
assert_eq!(size_of::<SeqDef>(), 8);
assert_eq!(align_of::<SeqDef>(), align_of::<u32>());
assert_eq!(offset_of!(SeqStore_t, sequencesStart), 0);
assert_eq!(
offset_of!(SeqStore_t, longLengthPos),
9 * size_of::<usize>() + 4
);
assert_eq!(size_of::<SeqStore_t>(), 9 * size_of::<usize>() + 8);
assert_eq!(size_of::<ZSTD_fseCTables_t>(), 3552);
assert_eq!(offset_of!(ZSTD_compressedBlockState_t, entropy), 0);
assert_eq!(
offset_of!(ZSTD_compressedBlockState_t, rep),
size_of::<ZSTD_entropyCTables_t>()
);
assert_eq!(size_of::<ZSTD_Sequence>(), 16);
assert_eq!(offset_of!(SeqCollector, seqStart), size_of::<usize>());
assert_eq!(offset_of!(SeqCollector, seqIndex), 2 * size_of::<usize>());
assert_eq!(size_of::<SeqCollector>(), 4 * size_of::<usize>());
if size_of::<usize>() == 8 {
assert_eq!(size_of::<ZSTD_hufCTablesMetadata_t>(), 144);
assert_eq!(size_of::<ZSTD_fseCTablesMetadata_t>(), 168);
assert_eq!(size_of::<ZSTD_entropyCTablesMetadata_t>(), 312);
assert_eq!(size_of::<ZSTD_entropyCTables_t>(), 5616);
assert_eq!(size_of::<ZSTD_compressedBlockState_t>(), 5632);
} else {
assert_eq!(size_of::<ZSTD_hufCTablesMetadata_t>(), 136);
assert_eq!(size_of::<ZSTD_fseCTablesMetadata_t>(), 156);
assert_eq!(size_of::<ZSTD_entropyCTablesMetadata_t>(), 292);
assert_eq!(size_of::<ZSTD_entropyCTables_t>(), 4584);
assert_eq!(size_of::<ZSTD_compressedBlockState_t>(), 4596);
}
}
#[test]
fn repcode_updates_match_the_c_sum_type_rules() {
let mut reps = [1, 4, 8];
update_rep(&mut reps, 10, false);
assert_eq!(reps, [7, 1, 4]);
update_rep(&mut reps, 1, true);
assert_eq!(reps, [1, 7, 4]);
update_rep(&mut reps, 2, false);
assert_eq!(reps, [7, 1, 4]);
update_rep(&mut reps, 3, false);
assert_eq!(reps, [4, 7, 1]);
}
#[test]
fn seq_to_codes_matches_the_reference_tables() {
let mut store = TestSeqStore::new(
vec![
SeqDef {
offBase: 4,
litLength: 0,
mlBase: 0,
},
SeqDef {
offBase: 1,
litLength: 63,
mlBase: 127,
},
SeqDef {
offBase: 1 << 20,
litLength: 64,
mlBase: 128,
},
],
vec![0; 127],
);
let seq_store = store.seq_store();
let long_offsets = unsafe { ZSTD_seqToCodes(&seq_store) };
assert_eq!(long_offsets, 0);
assert_eq!(store.ll_codes, [0, 24, 25]);
assert_eq!(store.ml_codes, [0, 42, 43]);
assert_eq!(store.of_codes, [2, 0, 20]);
}
#[test]
fn rle_sequences_produce_one_byte_table_headers() {
// Five identical sequences select set_rle for all three symbol kinds;
// each table header is then exactly the one RLE symbol byte.
let mut store = TestSeqStore::new(
vec![
SeqDef {
offBase: 8,
litLength: 2,
mlBase: 3,
};
5
],
vec![b'x'; 10],
);
let seq_store = store.seq_store();
let prev = empty_fse_tables(FSE_REPEAT_NONE);
let mut next = empty_fse_tables(FSE_REPEAT_NONE);
let mut metadata =
unsafe { std::mem::MaybeUninit::<ZSTD_fseCTablesMetadata_t>::zeroed().assume_init() };
let mut workspace = vec![0u64; ENTROPY_WORKSPACE_SIZE / size_of::<u64>()];
let size = unsafe {
build_block_entropy_stats_sequences(
&seq_store,
&prev,
&mut next,
1,
&mut metadata,
workspace.as_mut_ptr().cast::<c_void>(),
ENTROPY_WORKSPACE_SIZE,
)
};
assert_eq!(size, 3);
assert_eq!(metadata.llType, SET_RLE);
assert_eq!(metadata.ofType, SET_RLE);
assert_eq!(metadata.mlType, SET_RLE);
assert_eq!(metadata.lastCountSize, 0);
assert_eq!(&metadata.fseTablesBuffer[..3], &[2, 3, 3]);
assert_eq!(next.litlength_repeatMode, FSE_REPEAT_NONE);
assert_eq!(next.offcode_repeatMode, FSE_REPEAT_NONE);
assert_eq!(next.matchlength_repeatMode, FSE_REPEAT_NONE);
}
#[test]
fn empty_seq_store_emits_raw_literals_and_repeats_tables() {
let mut store = TestSeqStore::new(Vec::new(), b"hello".to_vec());
let seq_store = store.seq_store();
let prev = ZSTD_entropyCTables_t {
huf: empty_huf_tables(HUF_REPEAT_NONE),
fse: empty_fse_tables(2),
};
let mut next = ZSTD_entropyCTables_t {
huf: empty_huf_tables(HUF_REPEAT_NONE),
fse: empty_fse_tables(FSE_REPEAT_NONE),
};
let mut workspace = vec![0u64; ENTROPY_WORKSPACE_SIZE / size_of::<u64>()];
let mut output = [0xA5u8; 32];
let size = unsafe {
ZSTD_rust_entropyCompressSeqStore_internal(
output.as_mut_ptr().cast::<c_void>(),
output.len(),
seq_store.litStart.cast::<c_void>(),
5,
&seq_store,
&prev,
&mut next,
1,
0,
workspace.as_mut_ptr().cast::<c_void>(),
ENTROPY_WORKSPACE_SIZE,
0,
)
};
// Raw literals header (5 << 3), five literal bytes, nbSeq == 0.
assert_eq!(size, 7);
assert_eq!(&output[..7], &[0x28, b'h', b'e', b'l', b'l', b'o', 0x00]);
// With no sequences, the previous FSE tables are copied unchanged.
assert_eq!(next.fse.offcode_repeatMode, 2);
assert_eq!(next.fse.matchlength_repeatMode, 2);
assert_eq!(next.fse.litlength_repeatMode, 2);
}
#[test]
fn literal_stats_pick_rle_and_compressed_types() {
let mut workspace = vec![0u64; ENTROPY_WORKSPACE_SIZE / size_of::<u64>()];
let prev = empty_huf_tables(HUF_REPEAT_NONE);
let mut next = empty_huf_tables(HUF_REPEAT_NONE);
let mut metadata =
unsafe { std::mem::MaybeUninit::<ZSTD_hufCTablesMetadata_t>::zeroed().assume_init() };
let rle = [7u8; 128];
let size = unsafe {
build_block_entropy_stats_literals(
rle.as_ptr().cast::<c_void>(),
rle.len(),
&prev,
&mut next,
&mut metadata,
0,
workspace.as_mut_ptr().cast::<c_void>(),
ENTROPY_WORKSPACE_SIZE,
0,
)
};
assert_eq!(size, 0);
assert_eq!(metadata.hType, SET_RLE);
let mut compressible = [0u8; 1024];
for (index, byte) in compressible.iter_mut().enumerate() {
*byte = (index % 4) as u8;
}
let size = unsafe {
build_block_entropy_stats_literals(
compressible.as_ptr().cast::<c_void>(),
compressible.len(),
&prev,
&mut next,
&mut metadata,
0,
workspace.as_mut_ptr().cast::<c_void>(),
ENTROPY_WORKSPACE_SIZE,
0,
)
};
assert!(!ERR_isError(size));
assert!(size > 0);
assert_eq!(metadata.hType, SET_COMPRESSED);
assert_eq!(metadata.hufDesSize, 0); // caller stores the returned size
assert_eq!(next.repeatMode, HUF_REPEAT_CHECK);
}
#[test]
fn copied_sequences_resolve_repcodes_and_append_the_delimiter() {
let mut store = TestSeqStore::new(
vec![
SeqDef {
offBase: 3 + 100, // raw offset 100
litLength: 4,
mlBase: 5,
},
SeqDef {
offBase: 1, // repcode 1 with literals: rep[0] == 100
litLength: 2,
mlBase: 7,
},
SeqDef {
offBase: 3, // repcode 3 without literals: rep[0] - 1 == 99
litLength: 0,
mlBase: 9,
},
],
vec![0; 10],
);
let seq_store = store.seq_store();
let mut out = [ZSTD_Sequence {
offset: 0xAA,
litLength: 0xAA,
matchLength: 0xAA,
rep: 0xAA,
}; 8];
let mut collector = SeqCollector {
collectSequences: 1,
seqStart: out.as_mut_ptr(),
seqIndex: 1,
maxSequences: 8,
};
let reps = [1u32, 4, 8];
let result =
unsafe { ZSTD_rust_copyBlockSequences(&mut collector, &seq_store, reps.as_ptr()) };
assert_eq!(result, 0);
assert_eq!(collector.seqIndex, 5);
let expect = [
(100, 4, 5 + 3, 0),
(100, 2, 7 + 3, 1),
(99, 0, 9 + 3, 3),
(0, 10 - 6, 0, 0xAA), // delimiter: last literals; rep untouched
];
for (index, &(offset, lit, matched, rep)) in expect.iter().enumerate() {
assert_eq!(out[1 + index].offset, offset, "offset {index}");
assert_eq!(out[1 + index].litLength, lit, "litLength {index}");
assert_eq!(out[1 + index].matchLength, matched, "matchLength {index}");
assert_eq!(out[1 + index].rep, rep, "rep {index}");
}
collector.maxSequences = 5;
let result =
unsafe { ZSTD_rust_copyBlockSequences(&mut collector, &seq_store, reps.as_ptr()) };
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
}
}
+191 -49
View File
@@ -6,13 +6,13 @@
//! This is the Rust implementation of `zstd_compress_superblock.c`. Its C
//! entry-point shim only extracts fields from the opaque `ZSTD_CCtx`; all
//! sequence partitioning, literal and sequence section writing, entropy
//! fallback, and repcode repair remain here. The C-shaped leaf layouts are
//! shared with `zstd_compress_stats`, which also owns the block entropy
//! statistics this module consumes.
//! fallback, and repcode repair remain here. The leaf layouts below are
//! intentionally kept C-shaped and checked for both supported pointer widths.
use crate::common::{
LL_BITS, LL_DEFAULT_NORM, LL_DEFAULT_NORM_LOG, MAX_LL, MAX_ML, MAX_OFF, MINMATCH, ML_BITS,
ML_DEFAULT_NORM, ML_DEFAULT_NORM_LOG, OF_DEFAULT_NORM, OF_DEFAULT_NORM_LOG,
ZSTD_MAX_FSE_HEADERS_SIZE, ZSTD_MAX_HUF_HEADER_SIZE,
};
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use crate::hist::{HIST_countFast_wksp, HIST_count_wksp};
@@ -23,17 +23,9 @@ use crate::mem::{MEM_32bits, MEM_writeLE16, MEM_writeLE24, MEM_writeLE32};
use crate::zstd_compress_literals::{
ZSTD_compressRleLiteralsBlock, ZSTD_hufCTables_t, ZSTD_noCompressLiterals,
};
use crate::zstd_compress_sequences::{
SeqDef, ZSTD_crossEntropyCost, ZSTD_encodeSequences, ZSTD_fseBitCost,
};
use crate::zstd_compress_stats::{
build_block_entropy_stats, update_rep, SeqStore_t, ZSTD_compressedBlockState_t,
ZSTD_entropyCTablesMetadata_t, ZSTD_entropyCTables_t, ZSTD_fseCTablesMetadata_t,
ZSTD_fseCTables_t, ZSTD_hufCTablesMetadata_t,
};
use std::ffi::c_void;
use std::mem::MaybeUninit;
use std::os::raw::c_int;
use std::os::raw::{c_int, c_short, c_uint};
use std::ptr;
const SET_BASIC: c_int = 0;
@@ -50,6 +42,121 @@ const LONG_NB_SEQ: usize = 0x7f00;
const DEFAULT_MAX_OFF: u32 = 28;
const BYTE_SCALE: usize = 256;
const OFF_CTABLE_SIZE: usize = 1 + (1 << 7) + ((MAX_OFF + 1) * 2);
const ML_CTABLE_SIZE: usize = 1 + (1 << 8) + ((MAX_ML + 1) * 2);
const LL_CTABLE_SIZE: usize = 1 + (1 << 8) + ((MAX_LL + 1) * 2);
/// ABI-compatible `SeqDef` from `zstd_compress_internal.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct SeqDef {
offBase: u32,
litLength: u16,
mlBase: u16,
}
/// ABI-compatible `SeqStore_t` leaf layout.
///
/// The C context itself remains opaque. The C shim passes its `seqStore`
/// member directly, so this small, stable hot-path structure is the only
/// sequence storage representation crossing into Rust.
#[repr(C)]
struct SeqStore_t {
sequencesStart: *mut SeqDef,
sequences: *mut SeqDef,
litStart: *mut u8,
lit: *mut u8,
llCode: *mut u8,
mlCode: *mut u8,
ofCode: *mut u8,
maxNbSeq: usize,
maxNbLit: usize,
longLengthType: c_int,
longLengthPos: u32,
}
/// C's `ZSTD_fseCTables_t`. The table element type is `FSE_CTable`, an
/// `unsigned`, and the table lengths are the header macros expanded above.
#[repr(C)]
struct ZSTD_fseCTables_t {
offcodeCTable: [u32; OFF_CTABLE_SIZE],
matchlengthCTable: [u32; ML_CTABLE_SIZE],
litlengthCTable: [u32; LL_CTABLE_SIZE],
offcode_repeatMode: c_int,
matchlength_repeatMode: c_int,
litlength_repeatMode: c_int,
}
#[repr(C)]
struct ZSTD_entropyCTables_t {
huf: ZSTD_hufCTables_t,
fse: ZSTD_fseCTables_t,
}
#[repr(C)]
struct ZSTD_compressedBlockState_t {
entropy: ZSTD_entropyCTables_t,
rep: [u32; 3],
}
#[repr(C)]
struct ZSTD_hufCTablesMetadata_t {
hType: c_int,
hufDesBuffer: [u8; ZSTD_MAX_HUF_HEADER_SIZE],
hufDesSize: usize,
}
#[repr(C)]
struct ZSTD_fseCTablesMetadata_t {
llType: c_int,
ofType: c_int,
mlType: c_int,
fseTablesBuffer: [u8; ZSTD_MAX_FSE_HEADERS_SIZE],
fseTablesSize: usize,
lastCountSize: usize,
}
#[repr(C)]
struct ZSTD_entropyCTablesMetadata_t {
hufMetadata: ZSTD_hufCTablesMetadata_t,
fseMetadata: ZSTD_fseCTablesMetadata_t,
}
unsafe extern "C" {
fn ZSTD_buildBlockEntropyStats(
seq_store: *const SeqStore_t,
prev_entropy: *const ZSTD_entropyCTables_t,
next_entropy: *mut ZSTD_entropyCTables_t,
cctx_params: *const c_void,
entropy_metadata: *mut ZSTD_entropyCTablesMetadata_t,
workspace: *mut c_void,
wksp_size: usize,
) -> usize;
fn ZSTD_encodeSequences(
dst: *mut c_void,
dst_capacity: usize,
ctable_match_length: *const u32,
ml_code_table: *const u8,
ctable_offset_bits: *const u32,
of_code_table: *const u8,
ctable_lit_length: *const u32,
ll_code_table: *const u8,
sequences: *const SeqDef,
nb_seq: usize,
long_offsets: c_int,
bmi2: c_int,
) -> usize;
fn ZSTD_fseBitCost(ctable: *const u32, count: *const u32, max: c_uint) -> usize;
fn ZSTD_crossEntropyCost(
norm: *const c_short,
accuracy_log: c_uint,
count: *const c_uint,
max: c_uint,
) -> usize;
}
#[inline]
fn remaining_capacity(capacity: usize, written: usize) -> Result<usize, usize> {
capacity
@@ -731,6 +838,28 @@ unsafe fn size_block_sequences(
nb_seq
}
#[inline]
fn update_rep(reps: &mut [u32; 3], off_base: u32, literal_length_is_zero: bool) {
if off_base > 3 {
reps[2] = reps[1];
reps[1] = reps[0];
reps[0] = off_base - 3;
return;
}
let rep_code = off_base - 1 + u32::from(literal_length_is_zero);
if rep_code == 0 {
return;
}
let current_offset = if rep_code == 3 {
reps[0].wrapping_sub(1)
} else {
reps[rep_code as usize]
};
reps[2] = if rep_code >= 2 { reps[1] } else { reps[2] };
reps[1] = reps[0];
reps[0] = current_offset;
}
#[allow(clippy::too_many_arguments, clippy::manual_checked_ops)]
unsafe fn compress_subblock_multi(
seq_store: *const SeqStore_t,
@@ -969,18 +1098,16 @@ unsafe fn compress_subblock_multi(
/// C ABI implementation called by the declaration-only C superblock shim.
///
/// `ZSTD_CCtx_params` deliberately remains opaque: the C shim extracts the
/// two scalar fields this path reads (the strategy and the
/// literals-compression switch) and the crate-internal entropy statistics
/// builder receives them directly.
/// `cctx_params` deliberately remains opaque: only its two required scalar
/// fields are read by the shim, while the existing C entropy builder receives
/// the original pointer unchanged.
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_compressSuperBlock(
seq_store: *const c_void,
prev_cblock: *const c_void,
next_cblock: *mut c_void,
strategy: c_int,
disable_literal_compression: c_int,
cctx_params: *const c_void,
workspace: *mut c_void,
wksp_size: usize,
bmi2: c_int,
@@ -995,17 +1122,16 @@ pub unsafe extern "C" fn ZSTD_rust_compressSuperBlock(
let seq_store = seq_store.cast::<SeqStore_t>();
let prev_cblock = prev_cblock.cast::<ZSTD_compressedBlockState_t>();
let next_cblock = next_cblock.cast::<ZSTD_compressedBlockState_t>();
// The statistics builder only writes the used prefixes of the two
// metadata byte buffers. Start with initialized storage so treating the
// completed struct as a Rust value never exposes uninitialized elements.
// C only writes the used prefixes of its two metadata byte buffers. Start
// with initialized storage so treating the completed C struct as a Rust
// value never exposes uninitialized array elements.
let mut entropy_metadata = MaybeUninit::<ZSTD_entropyCTablesMetadata_t>::zeroed();
let entropy_result = unsafe {
build_block_entropy_stats(
ZSTD_buildBlockEntropyStats(
seq_store,
ptr::addr_of!((*prev_cblock).entropy),
ptr::addr_of_mut!((*next_cblock).entropy),
strategy,
disable_literal_compression,
cctx_params,
entropy_metadata.as_mut_ptr(),
workspace,
wksp_size,
@@ -1037,34 +1163,50 @@ pub unsafe extern "C" fn ZSTD_rust_compressSuperBlock(
#[cfg(test)]
mod tests {
// The shared C leaf layouts and the repcode update rules are asserted in
// `zstd_compress_stats`, which owns those definitions.
use super::*;
use std::mem::{align_of, offset_of, size_of};
#[test]
fn subblock_sizing_respects_the_budget() {
let sequences = [SeqDef {
offBase: 4,
litLength: 10,
mlBase: 10,
}; 8];
fn c_leaf_layouts_match_supported_abis() {
assert_eq!(size_of::<SeqDef>(), 8);
assert_eq!(align_of::<SeqDef>(), align_of::<u32>());
assert_eq!(offset_of!(SeqStore_t, sequencesStart), 0);
assert_eq!(
offset_of!(SeqStore_t, longLengthPos),
9 * size_of::<usize>() + 4
);
assert_eq!(size_of::<SeqStore_t>(), 9 * size_of::<usize>() + 8);
assert_eq!(size_of::<ZSTD_fseCTables_t>(), 3552);
assert_eq!(offset_of!(ZSTD_compressedBlockState_t, entropy), 0);
assert_eq!(
offset_of!(ZSTD_compressedBlockState_t, rep),
size_of::<ZSTD_entropyCTables_t>()
);
if size_of::<usize>() == 8 {
assert_eq!(size_of::<ZSTD_hufCTablesMetadata_t>(), 144);
assert_eq!(size_of::<ZSTD_fseCTablesMetadata_t>(), 168);
assert_eq!(size_of::<ZSTD_entropyCTablesMetadata_t>(), 312);
assert_eq!(size_of::<ZSTD_entropyCTables_t>(), 5616);
assert_eq!(size_of::<ZSTD_compressedBlockState_t>(), 5632);
} else {
assert_eq!(size_of::<ZSTD_hufCTablesMetadata_t>(), 136);
assert_eq!(size_of::<ZSTD_fseCTablesMetadata_t>(), 156);
assert_eq!(size_of::<ZSTD_entropyCTablesMetadata_t>(), 292);
assert_eq!(size_of::<ZSTD_entropyCTables_t>(), 4584);
assert_eq!(size_of::<ZSTD_compressedBlockState_t>(), 4596);
}
}
// A generous budget keeps every sequence in one sub-block.
let all = unsafe {
size_block_sequences(
sequences.as_ptr(),
sequences.len(),
usize::MAX,
256,
256,
true,
)
};
assert_eq!(all, sequences.len());
// The first sequence always survives, even over budget.
let first =
unsafe { size_block_sequences(sequences.as_ptr(), sequences.len(), 0, 256, 256, true) };
assert_eq!(first, 1);
#[test]
fn repcode_updates_match_the_c_sum_type_rules() {
let mut reps = [1, 4, 8];
update_rep(&mut reps, 10, false);
assert_eq!(reps, [7, 1, 4]);
update_rep(&mut reps, 1, true);
assert_eq!(reps, [1, 7, 4]);
update_rep(&mut reps, 2, false);
assert_eq!(reps, [7, 1, 4]);
update_rep(&mut reps, 3, false);
assert_eq!(reps, [4, 7, 1]);
}
}
+12 -3
View File
@@ -65,16 +65,24 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_BUILD_CONFIG := default
RUST_HUF_MODE := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_BUILD_CONFIG := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
endif
# Test binaries compile every lib/legacy/*.c file regardless of the dispatch
# level (ZSTDLEGACY_FILES is a plain wildcard below), so the Rust archive must
# always carry all ported legacy decoders too. The build configuration still
# encodes the level because the flat C objects bake -DZSTD_LEGACY_SUPPORT into
# the dispatch code and must never outlive a level change.
RUST_LEGACY_FEATURES := legacy-v01,legacy-v02,legacy-v03,legacy-v04,legacy-v05,legacy-v06,legacy-v07
RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG)
RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a
RUST_TARGET_32 ?= i686-unknown-linux-gnu
@@ -84,6 +92,7 @@ RUST_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
ifneq ($(RUST_HUF_FEATURE),)
RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif
RUST_CARGO_FLAGS += --features $(RUST_LEGACY_FEATURES)
$(RUST_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS)