Compare commits

..
Author SHA1 Message Date
ddidderr 4f6a8f0185 feat(rust): port divsufsort
Move the dictionary builder's suffix-array construction from
lib/dictBuilder/divsufsort.c to rust/src/divsufsort.rs, the first
dictBuilder module to migrate.  It rides on the dict-builder cargo
feature dimension introduced by the previous commit.

divsufsort() is a self-contained algorithm (two-stage sort of type-B*
substrings via sssort, rank refinement via trsort, then induced sorting
of the full array), so its context-free signature allows a direct symbol
takeover: the Rust #[no_mangle] export provides the existing `divsufsort`
symbol and the C file becomes a declaration-only shim that just keeps the
header's prototypes in the build.  Only divsufsort() moved; divbwt() has
no callers anywhere in zstd, so it is now declaration-only, keeping the
Rust export surface minimal.  The unused openMP parameter is retained for
signature compatibility (zstd never defines LIBBSC_OPENMP).

The port is a mechanical translation of the exact configuration zstd
compiles: ALPHABET_SIZE=256, SS_INSERTIONSORT_THRESHOLD=8,
SS_BLOCKSIZE=1024, SS_MISORT_STACKSIZE=16, SS_SMERGE_STACKSIZE=32,
TR_STACKSIZE=64.  Every C `int*` cursor into the SA buffer becomes an
`isize` index into a single `&mut [i32]` slice, preserving the pointer
arithmetic (including transient one-before-the-range cursors and the
bitwise-complement rank marking) while staying bounds-checked; all value
arithmetic keeps C int semantics.  The C -1/-2 error results are
preserved, with Vec::try_reserve_exact standing in for the bucket-array
malloc failure path.  Behavior is bit-identical by construction and by
measurement (see test plan); runtime on an 11 MB training buffer is
within ~5% of the C build end-to-end.

Users see no behavioral change: dictionaries trained through
ZDICT_trainFromBuffer_legacy() are byte-identical to the C build.  The
only external difference is that the never-called `divbwt` symbol is no
longer defined in the library.

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  -- -D warnings && cargo test --all-targets && cargo build --release
  (125 tests pass; new unit tests cover empty/one/two-byte inputs,
  all-equal bytes, an exact hand-computed "abracadabra" SA, and
  fixed-seed LCG buffers at 256-, 4-, and 2-symbol alphabets verified
  against a naive reference sort plus permutation/sorted invariants)
- Feature matrix: cargo build --release --no-default-features
  --features compression,decompression (and decompression-only,
  compression-only, compression,dict-builder); `divsufsort` is exported
  only when dict-builder is enabled
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests (includes
  ZDICT training tests): pass
- make -C tests test-rust-lib-smoke: pass
- make -C tests test-invalidDictionaries: pass
- make -C programs zstd zstd-dictBuilder zstd-small zstd-compress
  zstd-decompress: build; compress/decompress round-trip verified
- Byte-identity vs pristine C build (commit 959e4852): a harness calling
  ZDICT_trainFromBuffer_legacy() (the only zstd path reaching
  divsufsort) and divsufsort() directly, linked against both libzstd.a
  builds, produces byte-identical dictionaries (80,288 B and full
  112,640 B capacity) and byte-identical suffix arrays on a 1 MB source
  set and an 11 MB binary/repetitive set; a differential driver over 148
  random and structured buffers (sizes 3..6000, alphabets 1..256,
  Fibonacci word, sawtooth, 6 KB near-constant) shows zero mismatches.
  The CLI --train path could not be exercised because the Rust CLI
  frontend rejects --train in both the pristine and ported builds (a
  pre-existing migration gap unrelated to this change).
2026-07-11 14:21:09 +02:00
ddidderr eecd2e168a build(rust): add dict-builder cargo feature dimension
The dictionary-builder sources (lib/dictBuilder) are about to start moving
to Rust, beginning with divsufsort. The Rust crate previously only modeled
the compression/decompression module split plus the forced-HUF decoder
modes, so no build could express "this C configuration includes (or
excludes) dictBuilder" to Cargo. Without that, a Rust archive could carry
dictBuilder modules into a build whose C side disabled them, or worse,
omit a migrated implementation from a build whose C shims require it.

Add a `dict-builder` cargo feature and thread it through every build that
consumes the Rust static archive, mirroring exactly how each build system
already gates the dictBuilder C sources:

- rust/Cargo.toml: new `dict-builder` feature, included in the default
  set because the C library builds dictBuilder by default
  (ZSTD_LIB_DICTBUILDER ?= 1). The feature is empty until the first
  dictBuilder module lands.
- lib/Makefile: RUST_CARGO_FEATURES gains dict-builder when
  ZSTD_LIB_DICTBUILDER is enabled, following the existing
  ZSTD_LIB_COMPRESSION/ZSTD_LIB_DECOMPRESSION pattern. The archive
  directory naming grows a matching `b<0|1>` dimension
  (c1-d1-b1-default etc.) so differently configured archives never
  collide; the repeated config prefix is factored into
  RUST_MODULE_CONFIG.
- programs/Makefile: the full-featured archives now request
  compression,decompression,dict-builder (equal to the default set, so
  the target directory stays shared with tests). The partial-library
  variants gain the `b0` name dimension, and zstd-dictBuilder gets its
  own lib-c1-d0-b1 archive because it compiles the dictBuilder C sources
  without decompression; it previously shared the compression-only
  archive, which will lack the migrated dictBuilder symbols.
- tests/Makefile: no flag change needed since tests use the crate default
  feature set; a comment now records that dict-builder arrives that way.
- build/cmake/lib/CMakeLists.txt: ZSTD_BUILD_DICTBUILDER now adds the
  dict-builder feature and a `b<0|1>` component in the Rust build-config
  directory name, in lockstep with the DictBuilderSources gating.
- build/meson/lib/meson.build: meson compiles the dictBuilder sources
  unconditionally, so the feature list and config name gain dict-builder
  unconditionally (c1-d1-b1-<huf-mode>).

The `dict-builder` feature deliberately does not imply `compression`.
lib/Makefile forces ZSTD_LIB_DICTBUILDER=0 when compression is disabled,
but CMake does not couple the two options, so encoding the C-side
constraint in Cargo would make the Rust archive diverge from the C source
list in that (already unsupported) CMake configuration.

Test plan:
- cd rust && cargo build --release
- cargo build --release --no-default-features \
    --features compression,decompression
- cargo build --release --no-default-features \
    --features compression,dict-builder
- Full validation (fuzzer, smoke tests, dictionary byte-identity) runs
  with the follow-up commit that ports divsufsort onto this scaffolding.
2026-07-11 09:38:04 +02:00
23 changed files with 2815 additions and 4815 deletions
-1
View File
@@ -34,7 +34,6 @@ install/
# Build artefacts
/rust/target/
/rust/cli/target/
contrib/linux-kernel/linux/
projects/
bin/
+1 -13
View File
@@ -169,18 +169,6 @@ 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)
@@ -207,7 +195,7 @@ if(_zstd_rust_features)
endif()
set(_zstd_rust_build_config
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-b${_zstd_rust_dictbuilder}-${_zstd_rust_huf_mode}-legacy${_zstd_rust_legacy}")
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-b${_zstd_rust_dictbuilder}-${_zstd_rust_huf_mode}")
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}")
+1 -11
View File
@@ -88,16 +88,6 @@ 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 \
@@ -108,7 +98,7 @@ if rust_target == ''
endif
endif
rust_build_config = 'c1-d1-b1-' + rust_huf_mode + '-legacy@0@'.format(legacy_level)
rust_build_config = 'c1-d1-b1-' + rust_huf_mode
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
@@ -104,18 +104,6 @@ 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 ?=
+271 -106
View File
@@ -38,62 +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);
#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
*****************************************************************/
@@ -332,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) {
@@ -472,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;
@@ -1409,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() :
@@ -1418,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() :
@@ -1442,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
@@ -1486,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
@@ -1543,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(
@@ -5303,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)
@@ -7449,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)
{
@@ -7513,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.
@@ -7520,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() :
@@ -7544,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() :
+2108 -4
View File
@@ -17,7 +17,2111 @@
#include "../common/compiler.h"
#include "../common/error_private.h"
/* 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. */
/******************************************
* 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;
}
}
+13 -34
View File
@@ -32,8 +32,7 @@ RUST_CLI_MANIFEST := $(RUST_CLI_DIR)/Cargo.toml
RUST_SOURCES := $(RUST_MANIFEST) $(RUST_DIR)/Cargo.lock \
$(shell find $(RUST_DIR)/src -type f -name '*.rs' -print)
RUST_CLI_SOURCES := $(RUST_CLI_MANIFEST) $(RUST_CLI_DIR)/Cargo.lock \
$(RUST_CLI_DIR)/src/lib.rs $(RUST_DIR)/src/zstd_cli.rs \
$(RUST_DIR)/src/timefn.rs $(RUST_DIR)/src/benchfn.rs
$(RUST_CLI_DIR)/src/lib.rs $(RUST_DIR)/src/zstd_cli.rs
# Keep Rust's HUF implementation in lockstep with libzstd.mk's C selection.
# Forced modes may arrive as libzstd.mk variables or as direct -D flags in
@@ -56,33 +55,16 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_HUF_MODE := default
RUST_BUILD_CONFIG := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
RUST_BUILD_CONFIG := 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
@@ -93,9 +75,6 @@ RUST_CARGO_FLAGS += --features compression,decompression,dict-builder
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)
@@ -103,13 +82,13 @@ $(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_HUF_MODE)
RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_BUILD_CONFIG)
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
RUST_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
--target-dir $(RUST_CLI_TARGET_DIR) \
--no-default-features --features cli,compression,decompression
--no-default-features --features compression,decompression
$(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_CLI_CARGO_FLAGS)
@@ -117,7 +96,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-b0-$(RUST_HUF_MODE)
RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-b0-$(RUST_BUILD_CONFIG)
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 \
@@ -130,17 +109,17 @@ endif
$(RUST_DECOMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CARGO_FLAGS)
RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_HUF_MODE)
RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_BUILD_CONFIG)
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 \
--target-dir $(RUST_DECOMPRESS_CLI_TARGET_DIR) \
--no-default-features --features cli,decompression
--no-default-features --features decompression
$(RUST_DECOMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CLI_CARGO_FLAGS)
RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-b0-$(RUST_HUF_MODE)
RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-b0-$(RUST_BUILD_CONFIG)
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 \
@@ -150,7 +129,7 @@ RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
$(RUST_COMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_COMPRESS_CARGO_FLAGS)
RUST_DICTBUILDER_BUILD_CONFIG := lib-c1-d0-b1-$(RUST_HUF_MODE)
RUST_DICTBUILDER_BUILD_CONFIG := lib-c1-d0-b1-$(RUST_BUILD_CONFIG)
RUST_DICTBUILDER_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DICTBUILDER_BUILD_CONFIG)
RUST_DICTBUILDER_STATICLIB := $(RUST_DICTBUILDER_TARGET_DIR)/release/libzstd_rs.a
RUST_DICTBUILDER_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
@@ -161,12 +140,12 @@ RUST_DICTBUILDER_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
$(RUST_DICTBUILDER_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_DICTBUILDER_CARGO_FLAGS)
RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_HUF_MODE)
RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_BUILD_CONFIG)
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 \
--target-dir $(RUST_COMPRESS_CLI_TARGET_DIR) \
--no-default-features --features cli,compression
--no-default-features --features compression
$(RUST_COMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_COMPRESS_CLI_CARGO_FLAGS)
+243 -17
View File
@@ -8,23 +8,249 @@
* You may select, at your option, one of the above-listed licenses.
*/
/* The implementation lives in rust/src/benchfn.rs, built into the Rust CLI
* static archive. This translation unit stays in the original source lists so
* build configuration keeps working while the implementation is in Rust. */
#include <stddef.h> /* size_t, offsetof */
/* *************************************
* Includes
***************************************/
#include <stdlib.h> /* malloc, free */
#include <string.h> /* memset */
#include <assert.h> /* assert */
#include "timefn.h" /* UTIL_time_t, UTIL_getTime */
#include "benchfn.h"
/* BMK_runTime_t and BMK_runOutcome_t are returned by value across the C/Rust
* boundary, and BMK_benchParams_t is passed by value. The Rust #[repr(C)]
* definitions mirror the offsets pinned here. */
typedef char BMK_staticAssert_runTimeSumOffset[
(offsetof(BMK_runTime_t, sumOfReturn) == sizeof(double)) ? 1 : -1];
typedef char BMK_staticAssert_outcomeResultOffset[
(offsetof(BMK_runOutcome_t, error_result_never_ever_use_directly)
== sizeof(BMK_runTime_t)) ? 1 : -1];
typedef char BMK_staticAssert_outcomeTagOffset[
(offsetof(BMK_runOutcome_t, error_tag_never_ever_use_directly)
== sizeof(BMK_runTime_t) + sizeof(size_t)) ? 1 : -1];
typedef char BMK_staticAssert_shellAlignment[
(sizeof(BMK_timedFnState_shell) == BMK_TIMEDFNSTATE_SIZE) ? 1 : -1];
/* *************************************
* Constants
***************************************/
#define TIMELOOP_MICROSEC SEC_TO_MICRO /* 1 second */
#define TIMELOOP_NANOSEC (1*1000000000ULL) /* 1 second */
#define KB *(1 <<10)
#define MB *(1 <<20)
#define GB *(1U<<30)
/* *************************************
* Debug errors
***************************************/
#if defined(DEBUG) && (DEBUG >= 1)
# include <stdio.h> /* fprintf */
# define DISPLAY(...) fprintf(stderr, __VA_ARGS__)
# define DEBUGOUTPUT(...) { if (DEBUG) DISPLAY(__VA_ARGS__); }
#else
# define DEBUGOUTPUT(...)
#endif
/* error without displaying */
#define RETURN_QUIET_ERROR(retValue, ...) { \
DEBUGOUTPUT("%s: %i: \n", __FILE__, __LINE__); \
DEBUGOUTPUT("Error : "); \
DEBUGOUTPUT(__VA_ARGS__); \
DEBUGOUTPUT(" \n"); \
return retValue; \
}
/* Abort execution if a condition is not met */
#define CONTROL(c) { if (!(c)) { DEBUGOUTPUT("error: %s \n", #c); abort(); } }
/* *************************************
* Benchmarking an arbitrary function
***************************************/
int BMK_isSuccessful_runOutcome(BMK_runOutcome_t outcome)
{
return outcome.error_tag_never_ever_use_directly == 0;
}
/* warning : this function will stop program execution if outcome is invalid !
* check outcome validity first, using BMK_isValid_runResult() */
BMK_runTime_t BMK_extract_runTime(BMK_runOutcome_t outcome)
{
CONTROL(outcome.error_tag_never_ever_use_directly == 0);
return outcome.internal_never_ever_use_directly;
}
size_t BMK_extract_errorResult(BMK_runOutcome_t outcome)
{
CONTROL(outcome.error_tag_never_ever_use_directly != 0);
return outcome.error_result_never_ever_use_directly;
}
static BMK_runOutcome_t BMK_runOutcome_error(size_t errorResult)
{
BMK_runOutcome_t b;
memset(&b, 0, sizeof(b));
b.error_tag_never_ever_use_directly = 1;
b.error_result_never_ever_use_directly = errorResult;
return b;
}
static BMK_runOutcome_t BMK_setValid_runTime(BMK_runTime_t runTime)
{
BMK_runOutcome_t outcome;
outcome.error_tag_never_ever_use_directly = 0;
outcome.internal_never_ever_use_directly = runTime;
return outcome;
}
/* initFn will be measured once, benchFn will be measured `nbLoops` times */
/* initFn is optional, provide NULL if none */
/* benchFn must return a size_t value that errorFn can interpret */
/* takes # of blocks and list of size & stuff for each. */
/* can report result of benchFn for each block into blockResult. */
/* blockResult is optional, provide NULL if this information is not required */
/* note : time per loop can be reported as zero if run time < timer resolution */
BMK_runOutcome_t BMK_benchFunction(BMK_benchParams_t p,
unsigned nbLoops)
{
nbLoops += !nbLoops; /* minimum nbLoops is 1 */
/* init */
{ size_t i;
for(i = 0; i < p.blockCount; i++) {
memset(p.dstBuffers[i], 0xE5, p.dstCapacities[i]); /* warm up and erase result buffer */
} }
/* benchmark */
{ size_t dstSize = 0;
UTIL_time_t const clockStart = UTIL_getTime();
unsigned loopNb, blockNb;
if (p.initFn != NULL) p.initFn(p.initPayload);
for (loopNb = 0; loopNb < nbLoops; loopNb++) {
for (blockNb = 0; blockNb < p.blockCount; blockNb++) {
size_t const res = p.benchFn(p.srcBuffers[blockNb], p.srcSizes[blockNb],
p.dstBuffers[blockNb], p.dstCapacities[blockNb],
p.benchPayload);
if (loopNb == 0) {
if (p.blockResults != NULL) p.blockResults[blockNb] = res;
if ((p.errorFn != NULL) && (p.errorFn(res))) {
RETURN_QUIET_ERROR(BMK_runOutcome_error(res),
"Function benchmark failed on block %u (of size %u) with error %i",
blockNb, (unsigned)p.srcSizes[blockNb], (int)res);
}
dstSize += res;
} }
} /* for (loopNb = 0; loopNb < nbLoops; loopNb++) */
{ PTime const totalTime = UTIL_clockSpanNano(clockStart);
BMK_runTime_t rt;
rt.nanoSecPerRun = (double)totalTime / nbLoops;
rt.sumOfReturn = dstSize;
return BMK_setValid_runTime(rt);
} }
}
/* ==== Benchmarking any function, providing intermediate results ==== */
struct BMK_timedFnState_s {
PTime timeSpent_ns;
PTime timeBudget_ns;
PTime runBudget_ns;
BMK_runTime_t fastestRun;
unsigned nbLoops;
UTIL_time_t coolTime;
}; /* typedef'd to BMK_timedFnState_t within bench.h */
BMK_timedFnState_t* BMK_createTimedFnState(unsigned total_ms, unsigned run_ms)
{
BMK_timedFnState_t* const r = (BMK_timedFnState_t*)malloc(sizeof(*r));
if (r == NULL) return NULL; /* malloc() error */
BMK_resetTimedFnState(r, total_ms, run_ms);
return r;
}
void BMK_freeTimedFnState(BMK_timedFnState_t* state) { free(state); }
BMK_timedFnState_t*
BMK_initStatic_timedFnState(void* buffer, size_t size, unsigned total_ms, unsigned run_ms)
{
typedef char check_size[ 2 * (sizeof(BMK_timedFnState_shell) >= sizeof(struct BMK_timedFnState_s)) - 1]; /* static assert : a compilation failure indicates that BMK_timedFnState_shell is not large enough */
typedef struct { check_size c; BMK_timedFnState_t tfs; } tfs_align; /* force tfs to be aligned at its next best position */
size_t const tfs_alignment = offsetof(tfs_align, tfs); /* provides the minimal alignment restriction for BMK_timedFnState_t */
BMK_timedFnState_t* const r = (BMK_timedFnState_t*)buffer;
if (buffer == NULL) return NULL;
if (size < sizeof(struct BMK_timedFnState_s)) return NULL;
if ((size_t)buffer % tfs_alignment) return NULL; /* buffer must be properly aligned */
BMK_resetTimedFnState(r, total_ms, run_ms);
return r;
}
void BMK_resetTimedFnState(BMK_timedFnState_t* timedFnState, unsigned total_ms, unsigned run_ms)
{
if (!total_ms) total_ms = 1 ;
if (!run_ms) run_ms = 1;
if (run_ms > total_ms) run_ms = total_ms;
timedFnState->timeSpent_ns = 0;
timedFnState->timeBudget_ns = (PTime)total_ms * TIMELOOP_NANOSEC / 1000;
timedFnState->runBudget_ns = (PTime)run_ms * TIMELOOP_NANOSEC / 1000;
timedFnState->fastestRun.nanoSecPerRun = (double)TIMELOOP_NANOSEC * 2000000000; /* hopefully large enough : must be larger than any potential measurement */
timedFnState->fastestRun.sumOfReturn = (size_t)(-1LL);
timedFnState->nbLoops = 1;
timedFnState->coolTime = UTIL_getTime();
}
/* Tells if nb of seconds set in timedFnState for all runs is spent.
* note : this function will return 1 if BMK_benchFunctionTimed() has actually errored. */
int BMK_isCompleted_TimedFn(const BMK_timedFnState_t* timedFnState)
{
return (timedFnState->timeSpent_ns >= timedFnState->timeBudget_ns);
}
#undef MIN
#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
#define MINUSABLETIME (TIMELOOP_NANOSEC / 2) /* 0.5 seconds */
BMK_runOutcome_t BMK_benchTimedFn(BMK_timedFnState_t* cont,
BMK_benchParams_t p)
{
PTime const runBudget_ns = cont->runBudget_ns;
PTime const runTimeMin_ns = runBudget_ns / 2;
int completed = 0;
BMK_runTime_t bestRunTime = cont->fastestRun;
while (!completed) {
BMK_runOutcome_t const runResult = BMK_benchFunction(p, cont->nbLoops);
if(!BMK_isSuccessful_runOutcome(runResult)) { /* error : move out */
return runResult;
}
{ BMK_runTime_t const newRunTime = BMK_extract_runTime(runResult);
double const loopDuration_ns = newRunTime.nanoSecPerRun * cont->nbLoops;
cont->timeSpent_ns += (unsigned long long)loopDuration_ns;
/* estimate nbLoops for next run to last approximately 1 second */
if (loopDuration_ns > ((double)runBudget_ns / 50)) {
double const fastestRun_ns = MIN(bestRunTime.nanoSecPerRun, newRunTime.nanoSecPerRun);
cont->nbLoops = (unsigned)((double)runBudget_ns / fastestRun_ns) + 1;
} else {
/* previous run was too short : blindly increase workload by x multiplier */
const unsigned multiplier = 10;
assert(cont->nbLoops < ((unsigned)-1) / multiplier); /* avoid overflow */
cont->nbLoops *= multiplier;
}
if(loopDuration_ns < (double)runTimeMin_ns) {
/* don't report results for which benchmark run time was too small : increased risks of rounding errors */
assert(completed == 0);
continue;
} else {
if(newRunTime.nanoSecPerRun < bestRunTime.nanoSecPerRun) {
bestRunTime = newRunTime;
}
completed = 1;
}
}
} /* while (!completed) */
return BMK_setValid_runTime(bestRunTime);
}
+155 -8
View File
@@ -8,14 +8,161 @@
* You may select, at your option, one of the above-listed licenses.
*/
/* The implementation lives in rust/src/timefn.rs, built into the Rust CLI
* static archive. This translation unit stays in the original source lists so
* build configuration keeps working while the implementation is in Rust. */
/* === Dependencies === */
#include "timefn.h"
#include "platform.h" /* set _POSIX_C_SOURCE */
#include <time.h> /* CLOCK_MONOTONIC, TIME_UTC */
/* The Rust port mirrors this exact ABI: UTIL_time_t is returned by value and
* must remain a plain 64-bit nanosecond counter. */
typedef char UTIL_staticAssert_ptimeIs64Bit[(sizeof(PTime) == 8) ? 1 : -1];
typedef char UTIL_staticAssert_timeIsPlainCounter[
(sizeof(UTIL_time_t) == sizeof(PTime)) ? 1 : -1];
/*-****************************************
* Time functions
******************************************/
#if defined(_WIN32) /* Windows */
#include <windows.h> /* LARGE_INTEGER */
#include <stdlib.h> /* abort */
#include <stdio.h> /* perror */
UTIL_time_t UTIL_getTime(void)
{
static LARGE_INTEGER ticksPerSecond;
static int init = 0;
if (!init) {
if (!QueryPerformanceFrequency(&ticksPerSecond)) {
perror("timefn::QueryPerformanceFrequency");
abort();
}
init = 1;
}
{ UTIL_time_t r;
LARGE_INTEGER x;
QueryPerformanceCounter(&x);
r.t = (PTime)(x.QuadPart * 1000000000ULL / ticksPerSecond.QuadPart);
return r;
}
}
#elif defined(__APPLE__) && defined(__MACH__)
#include <mach/mach_time.h> /* mach_timebase_info_data_t, mach_timebase_info, mach_absolute_time */
UTIL_time_t UTIL_getTime(void)
{
static mach_timebase_info_data_t rate;
static int init = 0;
if (!init) {
mach_timebase_info(&rate);
init = 1;
}
{ UTIL_time_t r;
r.t = mach_absolute_time() * (PTime)rate.numer / (PTime)rate.denom;
return r;
}
}
/* POSIX.1-2001 (optional) */
#elif defined(CLOCK_MONOTONIC)
#include <stdlib.h> /* abort */
#include <stdio.h> /* perror */
UTIL_time_t UTIL_getTime(void)
{
/* time must be initialized, othersize it may fail msan test.
* No good reason, likely a limitation of timespec_get() for some target */
struct timespec time = { 0, 0 };
if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) {
perror("timefn::clock_gettime(CLOCK_MONOTONIC)");
abort();
}
{ UTIL_time_t r;
r.t = (PTime)time.tv_sec * 1000000000ULL + (PTime)time.tv_nsec;
return r;
}
}
/* C11 requires support of timespec_get().
* However, FreeBSD 11 claims C11 compliance while lacking timespec_get().
* Double confirm timespec_get() support by checking the definition of TIME_UTC.
* However, some versions of Android manage to simultaneously define TIME_UTC
* and lack timespec_get() support... */
#elif (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */) \
&& defined(TIME_UTC) && !defined(__ANDROID__)
#include <stdlib.h> /* abort */
#include <stdio.h> /* perror */
UTIL_time_t UTIL_getTime(void)
{
/* time must be initialized, othersize it may fail msan test.
* No good reason, likely a limitation of timespec_get() for some target */
struct timespec time = { 0, 0 };
if (timespec_get(&time, TIME_UTC) != TIME_UTC) {
perror("timefn::timespec_get(TIME_UTC)");
abort();
}
{ UTIL_time_t r;
r.t = (PTime)time.tv_sec * 1000000000ULL + (PTime)time.tv_nsec;
return r;
}
}
#else /* relies on standard C90 (note : clock_t produces wrong measurements for multi-threaded workloads) */
UTIL_time_t UTIL_getTime(void)
{
UTIL_time_t r;
r.t = (PTime)clock() * 1000000000ULL / CLOCKS_PER_SEC;
return r;
}
#define TIME_MT_MEASUREMENTS_NOT_SUPPORTED
#endif
/* ==== Common functions, valid for all time API ==== */
PTime UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd)
{
return clockEnd.t - clockStart.t;
}
PTime UTIL_getSpanTimeMicro(UTIL_time_t begin, UTIL_time_t end)
{
return UTIL_getSpanTimeNano(begin, end) / 1000ULL;
}
PTime UTIL_clockSpanMicro(UTIL_time_t clockStart )
{
UTIL_time_t const clockEnd = UTIL_getTime();
return UTIL_getSpanTimeMicro(clockStart, clockEnd);
}
PTime UTIL_clockSpanNano(UTIL_time_t clockStart )
{
UTIL_time_t const clockEnd = UTIL_getTime();
return UTIL_getSpanTimeNano(clockStart, clockEnd);
}
void UTIL_waitForNextTick(void)
{
UTIL_time_t const clockStart = UTIL_getTime();
UTIL_time_t clockEnd;
do {
clockEnd = UTIL_getTime();
} while (UTIL_getSpanTimeNano(clockStart, clockEnd) == 0);
}
int UTIL_support_MT_measurements(void)
{
# if defined(TIME_MT_MEASUREMENTS_NOT_SUPPORTED)
return 0;
# else
return 1;
# endif
}
-51
View File
@@ -10,67 +10,16 @@
/* The CLI parser and control flow live in rust/src/zstd_cli.rs. Keep this
* translation unit as the stable C entry point used by program launchers. */
#include <stddef.h> /* size_t */
#define ZSTD_STATIC_LINKING_ONLY /* ZSTD_compressionParameters */
#include "../lib/zstd.h"
#ifndef ZSTD_NOBENCH
# include "benchzstd.h" /* BMK_benchFilesAdvanced, BMK_syntheticTest */
#endif
int ZSTD_rust_cli_main(int argCount, const char* const argv[]);
const char* ZSTD_rust_cli_expected_version(void);
int ZSTD_rust_cli_bench(const char* const* fileNames, unsigned nbFiles,
const char* dictFileName,
int startCLevel, int endCLevel,
const ZSTD_compressionParameters* compressionParams,
int displayLevel, unsigned nbSeconds,
size_t blockSize, int nbWorkers);
const char* ZSTD_rust_cli_expected_version(void)
{
return ZSTD_VERSION_STRING;
}
/* Benchmark bridge for the Rust CLI. Whether benchmarking exists is a C
* preprocessor property (ZSTD_NOBENCH), so the decision stays in this shim:
* the Rust frontend calls in unconditionally, and stripped program variants
* never reference benchmark symbols.
* @return the benchmark result code (>= 0), or -1 when unavailable. */
int ZSTD_rust_cli_bench(const char* const* fileNames, unsigned nbFiles,
const char* dictFileName,
int startCLevel, int endCLevel,
const ZSTD_compressionParameters* compressionParams,
int displayLevel, unsigned nbSeconds,
size_t blockSize, int nbWorkers)
{
#ifndef ZSTD_NOBENCH
BMK_advancedParams_t advancedParams = BMK_initAdvancedParams();
int startLevel = startCLevel;
int endLevel = endCLevel;
advancedParams.nbSeconds = nbSeconds;
advancedParams.blockSize = blockSize;
advancedParams.nbWorkers = nbWorkers;
if (startLevel > ZSTD_maxCLevel()) startLevel = ZSTD_maxCLevel();
if (endLevel > ZSTD_maxCLevel()) endLevel = ZSTD_maxCLevel();
if (endLevel < startLevel) endLevel = startLevel;
if (nbFiles == 0) {
/* No input file: benchmark a synthetic sample (lorem generator). */
return BMK_syntheticTest(-1.0, startLevel, endLevel,
compressionParams, displayLevel,
&advancedParams);
}
return BMK_benchFilesAdvanced(fileNames, nbFiles, dictFileName,
startLevel, endLevel,
compressionParams, displayLevel,
&advancedParams);
#else
(void)fileNames; (void)nbFiles; (void)dictFileName;
(void)startCLevel; (void)endCLevel; (void)compressionParams;
(void)displayLevel; (void)nbSeconds; (void)blockSize; (void)nbWorkers;
return -1;
#endif
}
int main(int argCount, const char* argv[])
{
return ZSTD_rust_cli_main(argCount, argv);
-12
View File
@@ -13,18 +13,6 @@ decompression = []
dict-builder = []
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"
+6 -63
View File
@@ -34,12 +34,6 @@ zstd ABI:
- `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,
@@ -57,10 +51,6 @@ 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.
@@ -73,58 +63,12 @@ zstd ABI:
so library builds do not acquire program-only dependencies. The C
`fileio` backend still owns file opening, safe replacement, sparse writes,
metadata, and streaming I/O.
- `timefn` provides the monotonic nanosecond clock behind `UTIL_time_t`,
and `benchfn` owns the benchmark run/timing loop (`BMK_benchFunction`,
`BMK_benchTimedFn`) used by the CLI benchmark mode and by C test tools.
Both live in the `cli/` package, but C test binaries (fullbench, fuzzer,
zstreamtest, paramgrill, ...) link a helpers-only build of that archive,
produced without the package's `cli` feature, because the parser layer
requires the C `fileio` backend that tests do not compile. Benchmark
orchestration and reporting (`benchzstd.c`) remain C, reached from the
Rust parser through the `ZSTD_NOBENCH`-gated bridge in `zstdcli.c`.
The optimal block matcher, high-level frame compression, dictionary-building
except suffix-array construction, the legacy v0.2-v0.7 decoders, benchmark
orchestration (`benchzstd`), 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.
except suffix-array construction, 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.
## Compatibility boundary
@@ -161,9 +105,8 @@ from `rust/cli` as well:
```sh
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo test --no-default-features --features cli,compression --all-targets
cargo test --no-default-features --features cli,decompression --all-targets
cargo test --no-default-features --all-targets
cargo test --no-default-features --features compression --all-targets
cargo test --no-default-features --features decompression --all-targets
```
Then run original compatibility tests from the repository root, starting with
-9
View File
@@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "zstd-cli-rs"
version = "0.1.0"
dependencies = [
"libc",
]
+1 -8
View File
@@ -7,13 +7,6 @@ edition = "2021"
crate-type = ["staticlib"]
[features]
default = ["cli", "compression", "decompression"]
# The command-line parser and dispatch layer, which requires the C fileio
# backend at link time. Program archives enable it; C test binaries link a
# helpers-only archive (timefn) built without it.
cli = []
default = ["compression", "decompression"]
compression = []
decompression = []
[dependencies]
libc = "0.2"
-5
View File
@@ -1,7 +1,2 @@
#[path = "../../src/benchfn.rs"]
mod benchfn;
#[path = "../../src/timefn.rs"]
mod timefn;
#[cfg(feature = "cli")]
#[path = "../../src/zstd_cli.rs"]
mod zstd_cli;
-575
View File
@@ -1,575 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
//! Benchmark loop for arbitrary functions over a set of blocks.
//!
//! Port of `programs/benchfn.c`. `BMK_benchFunction` measures one batch of
//! runs; `BMK_benchTimedFn` repeats batches, growing the loop count until a
//! run lasts long enough to be reported reliably against `run_ms`, within a
//! `total_ms` budget tracked by `BMK_timedFnState_t`.
//!
//! ABI notes: `BMK_runOutcome_t` and `BMK_runTime_t` are returned by value
//! across the C boundary and `BMK_benchParams_t` is passed by value, so all
//! three are `repr(C)` mirrors of the `benchfn.h` layout, pinned by asserts
//! here and in the C shim. `BMK_timedFnState_t` is opaque to C, but
//! `BMK_initStatic_timedFnState` guarantees it fits the 64-byte
//! `BMK_timedFnState_shell`, and `BMK_createTimedFnState` uses `malloc` so
//! creation and destruction stay interchangeable with C callers.
use std::os::raw::{c_int, c_uint, c_void};
use std::ptr;
use crate::timefn::{PTime, UTIL_clockSpanNano, UTIL_getTime, UTIL_time_t};
const TIMELOOP_NANOSEC: PTime = 1_000_000_000;
/// Valid benchmark result (`BMK_runTime_t` in benchfn.h).
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BMK_runTime_t {
/// Time per iteration, over all blocks.
pub nanoSecPerRun: f64,
/// Sum of the benchmarked function's return values, first loop only.
pub sumOfReturn: usize,
}
/// Outcome variant of a benchmark run (`BMK_runOutcome_t` in benchfn.h):
/// either a valid `BMK_runTime_t` or an error result. C callers treat it as
/// opaque and use the accessor functions below.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BMK_runOutcome_t {
pub internal_never_ever_use_directly: BMK_runTime_t,
pub error_result_never_ever_use_directly: usize,
pub error_tag_never_ever_use_directly: c_int,
}
// These mirror the static asserts in the programs/benchfn.c shim: the structs
// cross the ABI by value, so field offsets must match the C header exactly.
const _: () =
assert!(std::mem::offset_of!(BMK_runTime_t, sumOfReturn) == std::mem::size_of::<f64>());
const _: () = assert!(
std::mem::offset_of!(BMK_runOutcome_t, error_result_never_ever_use_directly)
== std::mem::size_of::<BMK_runTime_t>()
);
const _: () = assert!(
std::mem::offset_of!(BMK_runOutcome_t, error_tag_never_ever_use_directly)
== std::mem::size_of::<BMK_runTime_t>() + std::mem::size_of::<usize>()
);
/// `size_t (*BMK_benchFn_t)(const void*, size_t, void*, size_t, void*)`
pub type BMK_benchFn_t = Option<
unsafe extern "C" fn(
src: *const c_void,
srcSize: usize,
dst: *mut c_void,
dstCapacity: usize,
customPayload: *mut c_void,
) -> usize,
>;
/// `size_t (*BMK_initFn_t)(void*)`
pub type BMK_initFn_t = Option<unsafe extern "C" fn(initPayload: *mut c_void) -> usize>;
/// `unsigned (*BMK_errorFn_t)(size_t)`
pub type BMK_errorFn_t = Option<unsafe extern "C" fn(result: usize) -> c_uint>;
/// Parameters of `BMK_benchFunction`, passed by value (`BMK_benchParams_t`).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct BMK_benchParams_t {
pub benchFn: BMK_benchFn_t,
pub benchPayload: *mut c_void,
pub initFn: BMK_initFn_t,
pub initPayload: *mut c_void,
pub errorFn: BMK_errorFn_t,
pub blockCount: usize,
pub srcBuffers: *const *const c_void,
pub srcSizes: *const usize,
pub dstBuffers: *const *mut c_void,
pub dstCapacities: *const usize,
pub blockResults: *mut usize,
}
/// Aborts, like benchfn.c's `CONTROL`, when an accessor is used on the wrong
/// outcome variant.
fn control(condition: bool) {
if !condition {
std::process::abort();
}
}
fn error_outcome(errorResult: usize) -> BMK_runOutcome_t {
BMK_runOutcome_t {
internal_never_ever_use_directly: BMK_runTime_t {
nanoSecPerRun: 0.0,
sumOfReturn: 0,
},
error_result_never_ever_use_directly: errorResult,
error_tag_never_ever_use_directly: 1,
}
}
fn valid_outcome(runTime: BMK_runTime_t) -> BMK_runOutcome_t {
BMK_runOutcome_t {
internal_never_ever_use_directly: runTime,
error_result_never_ever_use_directly: 0,
error_tag_never_ever_use_directly: 0,
}
}
/// Tells if the outcome carries a valid measurement.
#[no_mangle]
pub extern "C" fn BMK_isSuccessful_runOutcome(outcome: BMK_runOutcome_t) -> c_int {
c_int::from(outcome.error_tag_never_ever_use_directly == 0)
}
/// Extracts the measurement; aborts if the outcome is an error, so validity
/// must be checked first with `BMK_isSuccessful_runOutcome`.
#[no_mangle]
pub extern "C" fn BMK_extract_runTime(outcome: BMK_runOutcome_t) -> BMK_runTime_t {
control(outcome.error_tag_never_ever_use_directly == 0);
outcome.internal_never_ever_use_directly
}
/// Extracts the faulty `benchFn` return value; aborts if the outcome is
/// valid, so failure must be checked first.
#[no_mangle]
pub extern "C" fn BMK_extract_errorResult(outcome: BMK_runOutcome_t) -> usize {
control(outcome.error_tag_never_ever_use_directly != 0);
outcome.error_result_never_ever_use_directly
}
/// Runs `initFn` once, then `benchFn` `nbLoops` times over every block, and
/// reports the mean time per loop. On the first loop, per-block results are
/// stored into `blockResults` (when provided) and checked with `errorFn`
/// (when provided); the first failing block aborts the measurement and
/// produces an error outcome carrying the faulty return value.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchFunction(
p: BMK_benchParams_t,
mut nbLoops: c_uint,
) -> BMK_runOutcome_t {
// Minimum nbLoops is 1.
nbLoops += c_uint::from(nbLoops == 0);
// Warm up and erase the result buffers.
for blockNb in 0..p.blockCount {
unsafe {
let dst = *p.dstBuffers.add(blockNb);
ptr::write_bytes(dst.cast::<u8>(), 0xE5, *p.dstCapacities.add(blockNb));
}
}
let benchFn = p.benchFn.expect("benchFn is mandatory");
let mut dstSize = 0usize;
let clockStart = UTIL_getTime();
if let Some(initFn) = p.initFn {
unsafe { initFn(p.initPayload) };
}
for loopNb in 0..nbLoops {
for blockNb in 0..p.blockCount {
let res = unsafe {
benchFn(
*p.srcBuffers.add(blockNb),
*p.srcSizes.add(blockNb),
*p.dstBuffers.add(blockNb),
*p.dstCapacities.add(blockNb),
p.benchPayload,
)
};
if loopNb == 0 {
if !p.blockResults.is_null() {
unsafe { *p.blockResults.add(blockNb) = res };
}
if let Some(errorFn) = p.errorFn {
if unsafe { errorFn(res) } != 0 {
return error_outcome(res);
}
}
dstSize = dstSize.wrapping_add(res);
}
}
}
let totalTime = UTIL_clockSpanNano(clockStart);
valid_outcome(BMK_runTime_t {
nanoSecPerRun: totalTime as f64 / f64::from(nbLoops),
sumOfReturn: dstSize,
})
}
/// Benchmark session state (`struct BMK_timedFnState_s`), opaque to C.
#[repr(C)]
pub struct BMK_timedFnState_t {
timeSpent_ns: PTime,
timeBudget_ns: PTime,
runBudget_ns: PTime,
fastestRun: BMK_runTime_t,
nbLoops: c_uint,
coolTime: UTIL_time_t,
}
/// `BMK_TIMEDFNSTATE_SIZE` in benchfn.h: capacity of the caller-provided
/// `BMK_timedFnState_shell`, which the state must always fit.
const BMK_TIMEDFNSTATE_SIZE: usize = 64;
const _: () = assert!(std::mem::size_of::<BMK_timedFnState_t>() <= BMK_TIMEDFNSTATE_SIZE);
// The shell aligns via a `long long` member; the state must not need more.
const _: () = assert!(std::mem::align_of::<BMK_timedFnState_t>() <= std::mem::align_of::<u64>());
/// Allocates and initializes a benchmark session lasting a minimum of
/// `total_ms`, paced at intervals of approximately `run_ms`. Uses `malloc`
/// so ownership stays interchangeable with the original C implementation.
#[no_mangle]
pub extern "C" fn BMK_createTimedFnState(
total_ms: c_uint,
run_ms: c_uint,
) -> *mut BMK_timedFnState_t {
let state = unsafe { libc::malloc(std::mem::size_of::<BMK_timedFnState_t>()) }
.cast::<BMK_timedFnState_t>();
if state.is_null() {
return ptr::null_mut();
}
unsafe { BMK_resetTimedFnState(state, total_ms, run_ms) };
state
}
/// Releases a state obtained from `BMK_createTimedFnState`.
#[no_mangle]
pub unsafe extern "C" fn BMK_freeTimedFnState(state: *mut BMK_timedFnState_t) {
unsafe { libc::free(state.cast()) };
}
/// Places the session state into a caller-provided buffer, typically a
/// `BMK_timedFnState_shell`. Returns NULL when the buffer is missing, too
/// small, or misaligned.
#[no_mangle]
pub unsafe extern "C" fn BMK_initStatic_timedFnState(
buffer: *mut c_void,
size: usize,
total_ms: c_uint,
run_ms: c_uint,
) -> *mut BMK_timedFnState_t {
if buffer.is_null() {
return ptr::null_mut();
}
if size < std::mem::size_of::<BMK_timedFnState_t>() {
return ptr::null_mut();
}
if !(buffer as usize).is_multiple_of(std::mem::align_of::<BMK_timedFnState_t>()) {
return ptr::null_mut();
}
let state = buffer.cast::<BMK_timedFnState_t>();
unsafe { BMK_resetTimedFnState(state, total_ms, run_ms) };
state
}
/// Re-arms a session for a new benchmark of `total_ms`, paced at `run_ms`.
#[no_mangle]
pub unsafe extern "C" fn BMK_resetTimedFnState(
timedFnState: *mut BMK_timedFnState_t,
total_ms: c_uint,
run_ms: c_uint,
) {
let total_ms = if total_ms == 0 { 1 } else { total_ms };
let mut run_ms = if run_ms == 0 { 1 } else { run_ms };
if run_ms > total_ms {
run_ms = total_ms;
}
let state = BMK_timedFnState_t {
timeSpent_ns: 0,
timeBudget_ns: PTime::from(total_ms) * TIMELOOP_NANOSEC / 1000,
runBudget_ns: PTime::from(run_ms) * TIMELOOP_NANOSEC / 1000,
fastestRun: BMK_runTime_t {
// Must be larger than any potential measurement.
nanoSecPerRun: TIMELOOP_NANOSEC as f64 * 2_000_000_000.0,
sumOfReturn: usize::MAX,
},
nbLoops: 1,
coolTime: UTIL_getTime(),
};
unsafe { timedFnState.write(state) };
}
/// Tells if the total time budget of the session is spent. Also reports 1
/// after `BMK_benchTimedFn` returned an error.
#[no_mangle]
pub unsafe extern "C" fn BMK_isCompleted_TimedFn(timedFnState: *const BMK_timedFnState_t) -> c_int {
let state = unsafe { &*timedFnState };
c_int::from(state.timeSpent_ns >= state.timeBudget_ns)
}
/// Runs one measurement supposed to last about `run_ms`, automatically
/// scaling `nbLoops`. Runs shorter than half the run budget are re-tried
/// with a larger workload instead of being reported, limiting rounding-error
/// risks; the best (fastest) qualifying run is returned.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchTimedFn(
cont: *mut BMK_timedFnState_t,
p: BMK_benchParams_t,
) -> BMK_runOutcome_t {
let cont = unsafe { &mut *cont };
let runBudget_ns = cont.runBudget_ns;
let runTimeMin_ns = runBudget_ns / 2;
let mut bestRunTime = cont.fastestRun;
loop {
let runResult = unsafe { BMK_benchFunction(p, cont.nbLoops) };
if BMK_isSuccessful_runOutcome(runResult) == 0 {
// Error: move out.
return runResult;
}
let newRunTime = BMK_extract_runTime(runResult);
let loopDuration_ns = newRunTime.nanoSecPerRun * f64::from(cont.nbLoops);
cont.timeSpent_ns = cont.timeSpent_ns.wrapping_add(loopDuration_ns as PTime);
// Estimate nbLoops for the next run to last approximately run_ms.
if loopDuration_ns > runBudget_ns as f64 / 50.0 {
let fastestRun_ns = bestRunTime.nanoSecPerRun.min(newRunTime.nanoSecPerRun);
cont.nbLoops = ((runBudget_ns as f64 / fastestRun_ns) as c_uint).wrapping_add(1);
} else {
// Previous run was too short: blindly increase workload by a
// x10 multiplier.
const MULTIPLIER: c_uint = 10;
debug_assert!(cont.nbLoops < c_uint::MAX / MULTIPLIER); // avoid overflow
cont.nbLoops = cont.nbLoops.wrapping_mul(MULTIPLIER);
}
if loopDuration_ns < runTimeMin_ns as f64 {
// Don't report results when the run time was too small, which
// increases the risk of rounding errors.
continue;
}
if newRunTime.nanoSecPerRun < bestRunTime.nanoSecPerRun {
bestRunTime = newRunTime;
}
return valid_outcome(bestRunTime);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Test payload observed through `benchPayload`/`initPayload` pointers.
#[derive(Default)]
struct CallLog {
bench_calls: usize,
init_calls: usize,
}
/// Counts invocations and reports `srcSize`, like a size-preserving codec.
unsafe extern "C" fn counting_bench_fn(
_src: *const c_void,
srcSize: usize,
_dst: *mut c_void,
_dstCapacity: usize,
payload: *mut c_void,
) -> usize {
let log = unsafe { &mut *payload.cast::<CallLog>() };
log.bench_calls += 1;
srcSize
}
unsafe extern "C" fn counting_init_fn(payload: *mut c_void) -> usize {
let log = unsafe { &mut *payload.cast::<CallLog>() };
log.init_calls += 1;
0
}
/// Flags results of 5 bytes and above as errors.
unsafe extern "C" fn error_on_5(result: usize) -> c_uint {
c_uint::from(result >= 5)
}
struct Fixture {
srcs: Vec<Vec<u8>>,
dsts: Vec<Vec<u8>>,
src_ptrs: Vec<*const c_void>,
src_sizes: Vec<usize>,
dst_ptrs: Vec<*mut c_void>,
dst_capacities: Vec<usize>,
block_results: Vec<usize>,
log: CallLog,
}
impl Fixture {
fn new(block_sizes: &[usize]) -> Box<Self> {
let srcs: Vec<Vec<u8>> = block_sizes.iter().map(|size| vec![0u8; *size]).collect();
let mut dsts: Vec<Vec<u8>> = block_sizes.iter().map(|size| vec![0u8; *size]).collect();
let src_ptrs = srcs.iter().map(|src| src.as_ptr().cast()).collect();
let src_sizes = srcs.iter().map(Vec::len).collect();
let dst_ptrs = dsts.iter_mut().map(|dst| dst.as_mut_ptr().cast()).collect();
let dst_capacities = dsts.iter().map(Vec::len).collect();
let block_results = vec![0usize; block_sizes.len()];
Box::new(Self {
srcs,
dsts,
src_ptrs,
src_sizes,
dst_ptrs,
dst_capacities,
block_results,
log: CallLog::default(),
})
}
fn params(&mut self, errorFn: BMK_errorFn_t) -> BMK_benchParams_t {
let payload: *mut CallLog = &mut self.log;
BMK_benchParams_t {
benchFn: Some(counting_bench_fn),
benchPayload: payload.cast(),
initFn: Some(counting_init_fn),
initPayload: payload.cast(),
errorFn,
blockCount: self.srcs.len(),
srcBuffers: self.src_ptrs.as_ptr(),
srcSizes: self.src_sizes.as_ptr(),
dstBuffers: self.dst_ptrs.as_ptr(),
dstCapacities: self.dst_capacities.as_ptr(),
blockResults: self.block_results.as_mut_ptr(),
}
}
}
#[test]
fn bench_function_accounts_loops_blocks_and_first_loop_results() {
let mut fixture = Fixture::new(&[3, 8]);
let params = fixture.params(None);
let outcome = unsafe { BMK_benchFunction(params, 4) };
assert_eq!(BMK_isSuccessful_runOutcome(outcome), 1);
let run_time = BMK_extract_runTime(outcome);
// benchFn ran nbLoops times over each block; initFn ran once.
assert_eq!(fixture.log.bench_calls, 4 * 2);
assert_eq!(fixture.log.init_calls, 1);
// sumOfReturn and blockResults reflect the first loop only.
assert_eq!(run_time.sumOfReturn, 3 + 8);
assert_eq!(fixture.block_results, vec![3, 8]);
assert!(run_time.nanoSecPerRun >= 0.0);
}
#[test]
fn bench_function_treats_zero_loops_as_one_and_warms_up_buffers() {
let mut fixture = Fixture::new(&[4]);
let params = fixture.params(None);
let outcome = unsafe { BMK_benchFunction(params, 0) };
assert_eq!(BMK_isSuccessful_runOutcome(outcome), 1);
assert_eq!(fixture.log.bench_calls, 1);
// The result buffer was erased with the 0xE5 warm-up pattern.
assert_eq!(fixture.dsts[0], vec![0xE5; 4]);
}
#[test]
fn bench_function_reports_the_first_failing_block() {
let mut fixture = Fixture::new(&[3, 5, 7]);
let params = fixture.params(Some(error_on_5));
let outcome = unsafe { BMK_benchFunction(params, 10) };
assert_eq!(BMK_isSuccessful_runOutcome(outcome), 0);
assert_eq!(BMK_extract_errorResult(outcome), 5);
// Execution stopped at the failing block, before the third one.
assert_eq!(fixture.log.bench_calls, 2);
// blockResults were recorded up to and including the failure.
assert_eq!(fixture.block_results[..2], [3, 5]);
}
#[test]
fn reset_clamps_budgets_and_rearms_the_loop_counter() {
let state = BMK_createTimedFnState(0, 7);
assert!(!state.is_null());
{
let state = unsafe { &*state };
// total_ms 0 becomes 1ms, and run_ms is clamped to total_ms.
assert_eq!(state.timeBudget_ns, 1_000_000);
assert_eq!(state.runBudget_ns, 1_000_000);
assert_eq!(state.nbLoops, 1);
assert_eq!(state.timeSpent_ns, 0);
assert_eq!(state.fastestRun.sumOfReturn, usize::MAX);
}
assert_eq!(unsafe { BMK_isCompleted_TimedFn(state) }, 0);
unsafe { BMK_resetTimedFnState(state, 2_000, 500) };
{
let state = unsafe { &*state };
assert_eq!(state.timeBudget_ns, 2_000_000_000);
assert_eq!(state.runBudget_ns, 500_000_000);
}
unsafe { BMK_freeTimedFnState(state) };
}
#[test]
fn static_state_initialization_validates_its_buffer() {
let mut shell = [0u64; BMK_TIMEDFNSTATE_SIZE / 8];
let buffer: *mut c_void = shell.as_mut_ptr().cast();
// A properly sized and aligned buffer is accepted.
let state = unsafe { BMK_initStatic_timedFnState(buffer, 64, 1_000, 100) };
assert!(!state.is_null());
assert_eq!(unsafe { BMK_isCompleted_TimedFn(state) }, 0);
// NULL, undersized, and misaligned buffers are rejected.
let too_small = std::mem::size_of::<BMK_timedFnState_t>() - 1;
unsafe {
assert!(BMK_initStatic_timedFnState(ptr::null_mut(), 64, 1, 1).is_null());
assert!(BMK_initStatic_timedFnState(buffer, too_small, 1, 1).is_null());
assert!(
BMK_initStatic_timedFnState(buffer.cast::<u8>().add(1).cast(), 63, 1, 1).is_null()
);
}
}
#[test]
fn timed_runs_grow_the_workload_and_spend_the_budget() {
let mut fixture = Fixture::new(&[16]);
let params = fixture.params(None);
let state = BMK_createTimedFnState(4, 2);
assert!(!state.is_null());
let mut rounds = 0usize;
while unsafe { BMK_isCompleted_TimedFn(state) } == 0 {
let outcome = unsafe { BMK_benchTimedFn(state, params) };
assert_eq!(BMK_isSuccessful_runOutcome(outcome), 1);
let run_time = BMK_extract_runTime(outcome);
assert_eq!(run_time.sumOfReturn, 16);
rounds += 1;
assert!(rounds < 1_000, "the time budget must eventually be spent");
}
{
let state = unsafe { &*state };
// A reported run had to last at least runBudget/2, which is only
// reachable for this trivial function with a grown loop counter.
assert!(state.nbLoops > 1);
assert!(state.timeSpent_ns >= state.timeBudget_ns);
}
// Every reported outcome came from a run of >= runBudget/2, and the
// budget accounting matches BMK_isCompleted_TimedFn.
assert!(rounds >= 1);
assert!(fixture.log.bench_calls >= rounds);
unsafe { BMK_freeTimedFnState(state) };
}
#[test]
fn timed_runs_propagate_errors_without_aborting() {
let mut fixture = Fixture::new(&[9]);
let params = fixture.params(Some(error_on_5));
let state = BMK_createTimedFnState(1_000, 100);
let outcome = unsafe { BMK_benchTimedFn(state, params) };
assert_eq!(BMK_isSuccessful_runOutcome(outcome), 0);
assert_eq!(BMK_extract_errorResult(outcome), 9);
unsafe { BMK_freeTimedFnState(state) };
}
}
-40
View File
@@ -1,40 +0,0 @@
//! 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
@@ -1,2354 +0,0 @@
#![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);
}
}
}
-3
View File
@@ -18,7 +18,6 @@ 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;
@@ -31,8 +30,6 @@ 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_superblock;
-198
View File
@@ -1,198 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
//! Precise monotonic time measurement for the command-line programs.
//!
//! Port of `programs/timefn.c`. `UTIL_time_t` is a plain nanosecond counter
//! whose absolute value is meaningless; only spans between two measurements
//! are valid. The struct crosses the C ABI by value, so it stays `repr(C)`
//! with the exact `timefn.h` layout.
//!
//! Platform selection mirrors the C preprocessor structure: Windows uses the
//! performance counter, Apple systems use the Mach absolute clock, and other
//! POSIX systems use `clock_gettime(CLOCK_MONOTONIC)`. The C90 `clock()`
//! fallback is never needed on targets Rust supports, so multi-threaded
//! measurements are always supported.
use std::os::raw::c_int;
/// Precise Time (`PTime` in timefn.h): an unsigned 64-bit nanosecond count.
pub type PTime = u64;
/// Nanosecond time counter with the `timefn.h` `UTIL_time_t` layout.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct UTIL_time_t {
pub t: PTime,
}
const _: () = assert!(std::mem::size_of::<PTime>() == 8);
const _: () = assert!(std::mem::size_of::<UTIL_time_t>() == std::mem::size_of::<PTime>());
#[cfg(windows)]
mod platform {
use super::PTime;
use std::sync::OnceLock;
#[link(name = "kernel32")]
unsafe extern "system" {
/// Takes a `LARGE_INTEGER*`; the union is ABI-identical to `i64*`.
fn QueryPerformanceCounter(count: *mut i64) -> i32;
fn QueryPerformanceFrequency(frequency: *mut i64) -> i32;
}
pub fn monotonic_ns() -> PTime {
static TICKS_PER_SECOND: OnceLock<i64> = OnceLock::new();
let ticks_per_second = *TICKS_PER_SECOND.get_or_init(|| {
let mut frequency = 0i64;
if unsafe { QueryPerformanceFrequency(&mut frequency) } == 0 {
eprintln!(
"timefn::QueryPerformanceFrequency: {}",
std::io::Error::last_os_error()
);
std::process::abort();
}
frequency
});
let mut counter = 0i64;
unsafe { QueryPerformanceCounter(&mut counter) };
(counter as PTime).wrapping_mul(1_000_000_000) / ticks_per_second as PTime
}
}
#[cfg(all(unix, target_vendor = "apple"))]
mod platform {
use super::PTime;
use std::sync::OnceLock;
pub fn monotonic_ns() -> PTime {
static RATE: OnceLock<(PTime, PTime)> = OnceLock::new();
let (numer, denom) = *RATE.get_or_init(|| {
let mut rate = libc::mach_timebase_info { numer: 0, denom: 0 };
unsafe { libc::mach_timebase_info(&mut rate) };
(PTime::from(rate.numer), PTime::from(rate.denom))
});
unsafe { libc::mach_absolute_time() }.wrapping_mul(numer) / denom
}
}
#[cfg(all(unix, not(target_vendor = "apple")))]
mod platform {
use super::PTime;
pub fn monotonic_ns() -> PTime {
// Zero-initialized like the C source, which works around timespec_get
// msan limitations on some targets.
let mut time: libc::timespec = unsafe { std::mem::zeroed() };
if unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time) } != 0 {
eprintln!(
"timefn::clock_gettime(CLOCK_MONOTONIC): {}",
std::io::Error::last_os_error()
);
std::process::abort();
}
(time.tv_sec as PTime)
.wrapping_mul(1_000_000_000)
.wrapping_add(time.tv_nsec as PTime)
}
}
/// Returns the current value of the platform's monotonic nanosecond clock.
#[no_mangle]
pub extern "C" fn UTIL_getTime() -> UTIL_time_t {
UTIL_time_t {
t: platform::monotonic_ns(),
}
}
/// Nanoseconds elapsed between two measurements, with C unsigned wrap-around.
#[no_mangle]
pub extern "C" fn UTIL_getSpanTimeNano(clockStart: UTIL_time_t, clockEnd: UTIL_time_t) -> PTime {
clockEnd.t.wrapping_sub(clockStart.t)
}
/// Microseconds elapsed between two measurements, truncated like C division.
#[no_mangle]
pub extern "C" fn UTIL_getSpanTimeMicro(begin: UTIL_time_t, end: UTIL_time_t) -> PTime {
UTIL_getSpanTimeNano(begin, end) / 1000
}
/// Microseconds elapsed since `clockStart`.
#[no_mangle]
pub extern "C" fn UTIL_clockSpanMicro(clockStart: UTIL_time_t) -> PTime {
UTIL_getSpanTimeMicro(clockStart, UTIL_getTime())
}
/// Nanoseconds elapsed since `clockStart`.
#[no_mangle]
pub extern "C" fn UTIL_clockSpanNano(clockStart: UTIL_time_t) -> PTime {
UTIL_getSpanTimeNano(clockStart, UTIL_getTime())
}
/// Busy-waits until the clock produces a new tick, improving measurement
/// accuracy on platforms with a low timer resolution.
#[no_mangle]
pub extern "C" fn UTIL_waitForNextTick() {
let clockStart = UTIL_getTime();
loop {
let clockEnd = UTIL_getTime();
if UTIL_getSpanTimeNano(clockStart, clockEnd) != 0 {
return;
}
}
}
/// All clock sources used by the Rust port are valid under multi-threaded
/// workloads; only the C90 `clock()` fallback of the C source was not.
#[no_mangle]
pub extern "C" fn UTIL_support_MT_measurements() -> c_int {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nanosecond_spans_subtract_with_unsigned_wrap_around() {
let start = UTIL_time_t { t: 100 };
let end = UTIL_time_t { t: 350 };
assert_eq!(UTIL_getSpanTimeNano(start, end), 250);
assert_eq!(UTIL_getSpanTimeNano(end, start), u64::MAX - 249);
assert_eq!(UTIL_getSpanTimeNano(start, start), 0);
}
#[test]
fn microsecond_spans_truncate_sub_tick_remainders() {
let start = UTIL_time_t { t: 0 };
assert_eq!(UTIL_getSpanTimeMicro(start, UTIL_time_t { t: 999 }), 0);
assert_eq!(UTIL_getSpanTimeMicro(start, UTIL_time_t { t: 1_000 }), 1);
assert_eq!(UTIL_getSpanTimeMicro(start, UTIL_time_t { t: 1_999 }), 1);
assert_eq!(UTIL_getSpanTimeMicro(start, UTIL_time_t { t: 2_000 }), 2);
}
#[test]
fn clock_is_monotonic_across_measurements() {
let first = UTIL_getTime();
let second = UTIL_getTime();
assert!(second.t >= first.t);
assert!(UTIL_clockSpanNano(first) >= UTIL_getSpanTimeNano(first, second));
}
#[test]
fn waiting_for_the_next_tick_advances_the_clock() {
let before = UTIL_getTime();
UTIL_waitForNextTick();
let after = UTIL_getTime();
assert!(UTIL_getSpanTimeNano(before, after) > 0);
}
#[test]
fn multi_threaded_measurements_are_supported() {
assert_eq!(UTIL_support_MT_measurements(), 1);
}
}
+9 -194
View File
@@ -10,15 +10,9 @@
//! writes, dictionary loading, streaming, and metadata preservation remain in
//! `programs/fileio.c` for this first migration step.
//!
//! Benchmark mode (`-b`) parses here and dispatches through the
//! `ZSTD_rust_cli_bench` bridge in `programs/zstdcli.c`: the run/timing loop
//! (benchfn, timefn) is Rust, while orchestration and result formatting
//! (`benchzstd.c`) remain C behind the preprocessor-gated bridge, so builds
//! with `ZSTD_NOBENCH` never reference benchmark symbols.
//!
//! Remaining C-only CLI boundaries are called out in `unsupported()` below:
//! dictionary training, recursive/file-list expansion, tracing,
//! alternate-format selection, and the advanced directory modes.
//! benchmark execution, dictionary training, recursive/file-list expansion,
//! tracing, alternate-format selection, and the advanced directory modes.
use std::env;
use std::ffi::{CStr, CString, OsStr, OsString};
@@ -36,7 +30,6 @@ use std::os::unix::fs::FileTypeExt;
const DEFAULT_CLEVEL: i32 = 3;
#[cfg(feature = "compression")]
const DEFAULT_MAX_CLEVEL: i32 = 19;
const DEFAULT_BENCH_NB_SECONDS: u32 = 3;
const DEFAULT_MEM_LIMIT: u32 = 1 << 27;
const DEFAULT_LONG_WINDOW_LOG: u32 = 27;
const MAX_FAST_ACCELERATION: i32 = 128 << 10;
@@ -180,22 +173,6 @@ unsafe extern "C" {
output: *const c_char,
dict: *const c_char,
) -> c_int;
/// Benchmark bridge implemented by the `programs/zstdcli.c` shim, which
/// owns the `ZSTD_NOBENCH` preprocessor decision. Returns the benchmark
/// result (>= 0), or -1 when benchmarking is compiled out.
fn ZSTD_rust_cli_bench(
file_names: *const *const c_char,
nb_files: c_uint,
dict_file_name: *const c_char,
start_level: c_int,
end_level: c_int,
compression_params: *const ZSTD_compressionParameters,
display_level: c_int,
nb_seconds: c_uint,
block_size: usize,
nb_workers: c_int,
) -> c_int;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -203,7 +180,6 @@ enum Operation {
Compress,
Decompress,
Test,
Bench,
}
#[derive(Debug)]
@@ -234,7 +210,6 @@ struct Cli {
mmap_dict: i32,
progress: i32,
workers: Option<i32>,
single_thread: bool,
block_size: Option<usize>,
mem_limit: Option<u32>,
ldm: bool,
@@ -254,8 +229,6 @@ struct Cli {
row_match_finder: i32,
exclude_compressed: bool,
compression_params: ZSTD_compressionParameters,
bench_end_level: Option<i32>,
bench_nb_seconds: Option<u32>,
unsupported_program: Option<String>,
}
@@ -281,7 +254,6 @@ impl Cli {
mmap_dict: ZSTD_PS_AUTO,
progress: FIO_PS_AUTO,
workers: None,
single_thread: false,
block_size: None,
mem_limit: None,
ldm: false,
@@ -301,8 +273,6 @@ impl Cli {
row_match_finder: ZSTD_PS_AUTO,
exclude_compressed: false,
compression_params: ZSTD_compressionParameters::default(),
bench_end_level: None,
bench_nb_seconds: None,
unsupported_program: None,
};
@@ -362,11 +332,8 @@ unsafe fn default_worker_count() -> i32 {
}
#[cfg(feature = "compression")]
unsafe fn resolved_worker_count(workers: Option<i32>, single_thread: bool) -> i32 {
unsafe fn resolved_worker_count(workers: Option<i32>) -> i32 {
match workers {
/* --single-thread pins zero workers; a bare zero (-T0 or the zstdmt
* program name) auto-detects the core count as in the C CLI. */
Some(0) if single_thread => 0,
Some(0) => unsafe { UTIL_countPhysicalCores() }.max(1),
Some(workers) => workers,
None => unsafe { default_worker_count() },
@@ -418,7 +385,7 @@ fn usage(advanced: bool) {
let _ = writeln!(out, "\nImplemented advanced compression controls:");
let _ = writeln!(
out,
" --fast[=#], --ultra, --long[=#], --threads=#, --single-thread, --block-size=#"
" --fast[=#], --ultra, --long[=#], --threads=#, --block-size=#"
);
let _ = writeln!(
out,
@@ -432,22 +399,9 @@ fn usage(advanced: bool) {
out,
" --adapt[=min=#,max=#], --rsyncable, --[no-]row-match-finder"
);
let _ = writeln!(out, "\nBenchmark options:");
let _ = writeln!(
out,
" -b# Benchmark file(s) at compression level #"
);
let _ = writeln!(
out,
" -e# Test all levels from -b# up to # included"
);
let _ = writeln!(
out,
" -i# Set the minimum evaluation time to # seconds"
);
let _ = writeln!(
out,
"\nNot yet migrated: dictionary training, recursive/file-list expansion,"
"\nNot yet migrated: benchmark, dictionary training, recursive/file-list expansion,"
);
let _ = writeln!(out, "trace, alternate formats, and output-directory modes.");
}
@@ -684,7 +638,6 @@ fn parse_long_option(
| "--no-row-match-finder"
| "--row-match-finder"
| "--rsyncable"
| "--single-thread"
| "--compress-literals"
| "--no-compress-literals"
| "--exclude-compressed"
@@ -856,14 +809,6 @@ fn parse_long_option(
cli.workers = Some(parse_worker_count(&value)?);
Ok(None)
}
"--single-thread" => {
/* As in the C CLI: zero workers plus a latch that suppresses the
* automatic core-count resolution, so fileio runs its
* single-thread streaming mode (slightly different from -T1). */
cli.workers = Some(0);
cli.single_thread = true;
Ok(None)
}
"--memlimit" | "--memory" | "--memlimit-decompress" => {
let value = next_value(attached, args, index, name)?;
cli.mem_limit = Some(parse_u32(&value, "memory limit")?);
@@ -909,6 +854,7 @@ fn parse_long_option(
| "--trace"
| "--format"
| "--priority"
| "--single-thread"
| "--auto-threads"
| "--fake-stdin-is-console"
| "--fake-stdout-is-console"
@@ -946,33 +892,6 @@ fn parse_short_options(
'd' => cli.operation = Operation::Decompress,
'z' => cli.operation = Operation::Compress,
't' => cli.operation = Operation::Test,
'b' => cli.operation = Operation::Bench,
'e' | 'i' => {
// Benchmark range end (-e#) and duration (-i#): like the C
// parser, digits attach directly and default to 0.
let mut digits_end = offset + 1;
while digits_end < bytes.len() && bytes[digits_end].is_ascii_digit() {
digits_end += 1;
}
let digits = &value[offset + 1..digits_end];
if option == 'e' {
cli.bench_end_level = Some(if digits.is_empty() {
0
} else {
parse_i32(digits, "benchmark end level")?
});
} else {
cli.bench_nb_seconds = Some(if digits.is_empty() {
0
} else {
digits
.parse::<u32>()
.map_err(|_| format!("invalid benchmark duration: {digits:?}"))?
});
}
offset = digits_end;
continue;
}
'c' => {
cli.output = Some(cstring(STDOUT_MARK)?);
cli.force_stdout = true;
@@ -1008,7 +927,7 @@ fn parse_short_options(
}
break;
}
'l' | 'p' | 'P' | 'r' | 's' | 'S' => {
'b' | 'e' | 'i' | 'l' | 'p' | 'P' | 'r' | 's' | 'S' => {
unsupported(&format!("-{option}"))?;
}
_ => return Err(format!("unknown option -{option}")),
@@ -1093,7 +1012,7 @@ unsafe fn apply_preferences(cli: &Cli, prefs: *mut FIO_prefs_t, ctx: *mut FIO_ct
}),
);
#[cfg(feature = "compression")]
FIO_setNbWorkers(prefs, resolved_worker_count(cli.workers, cli.single_thread));
FIO_setNbWorkers(prefs, resolved_worker_count(cli.workers));
FIO_setLdmFlag(prefs, u32::from(cli.ldm));
FIO_setAdaptiveMode(prefs, i32::from(cli.adapt));
FIO_setRsyncable(prefs, i32::from(cli.rsyncable));
@@ -1299,52 +1218,16 @@ unsafe fn run_decompress(
dictionary,
)
},
Operation::Compress | Operation::Bench => {
unreachable!("compression and benchmark are dispatched separately")
}
Operation::Compress => unreachable!("compression is dispatched separately"),
}
}
/// Runs benchmark mode through the C bridge. Level clamping against
/// `ZSTD_maxCLevel()` happens on the C side, where the symbol is always
/// available when benchmarking is compiled in. No input file means a
/// synthetic-sample benchmark, matching the C CLI.
fn run_bench(cli: &Cli) -> Result<i32, String> {
let inputs: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
let dictionary = cli
.dictionary
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr());
let result = unsafe {
ZSTD_rust_cli_bench(
inputs.as_ptr(),
inputs.len() as c_uint,
dictionary,
cli.level,
cli.bench_end_level.unwrap_or(cli.level),
&cli.compression_params,
cli.display_level,
cli.bench_nb_seconds.unwrap_or(DEFAULT_BENCH_NB_SECONDS),
cli.block_size.unwrap_or(0),
// The C CLI benchmarks single-threaded unless -T was given.
cli.workers.unwrap_or(1),
)
};
if result < 0 {
return Err("benchmark mode is not available in this build".to_owned());
}
Ok(result)
}
fn run_cli(mut cli: Cli) -> Result<i32, String> {
if let Some(program_name) = &cli.unsupported_program {
return Err(format!(
"{program_name} compatibility mode is not yet implemented by the Rust CLI frontend"
));
}
if cli.operation == Operation::Bench {
return run_bench(&cli);
}
let explicit_input_count = cli.inputs.len();
filter_symlink_inputs(&mut cli);
if explicit_input_count > 0 && cli.inputs.is_empty() {
@@ -1451,7 +1334,6 @@ fn run_cli(mut cli: Cli) -> Result<i32, String> {
#[cfg(not(feature = "decompression"))]
unreachable!("unsupported decompression was rejected above")
}
Operation::Bench => unreachable!("benchmark mode was dispatched earlier"),
}
};
@@ -1620,36 +1502,6 @@ mod tests {
let cli = parse(&["zstdmt", "input"]);
assert_eq!(cli.workers, Some(0));
assert!(!cli.single_thread);
}
#[test]
fn single_thread_pins_zero_workers() {
let cli = parse(&["zstd", "--single-thread", "input"]);
assert_eq!(cli.workers, Some(0));
assert!(cli.single_thread);
}
#[test]
fn a_later_thread_count_overrides_single_thread_workers() {
/* Mirrors the C CLI: -T after --single-thread wins the worker count,
* while the single-thread latch stays set. */
let cli = parse(&["zstd", "--single-thread", "-T2", "input"]);
assert_eq!(cli.workers, Some(2));
assert!(cli.single_thread);
}
#[test]
fn single_thread_rejects_attached_values() {
let error = parse_args(vec![
OsString::from("zstd"),
OsString::from("--single-thread=1"),
])
.expect_err("an attached value must not activate --single-thread");
assert!(error.contains("does not take an argument"));
}
#[test]
@@ -1686,41 +1538,4 @@ mod tests {
assert!(error.contains("not yet implemented"));
}
#[test]
fn bench_mode_parses_level_duration_and_defaults() {
let cli = parse(&["zstd", "-b1", "-i0", "input"]);
assert_eq!(cli.operation, Operation::Bench);
assert_eq!(cli.level, 1);
assert_eq!(cli.bench_nb_seconds, Some(0));
assert_eq!(cli.bench_end_level, None);
assert_eq!(
cli.inputs
.iter()
.map(|input| input.as_bytes())
.collect::<Vec<_>>(),
vec![&b"input"[..]]
);
}
#[test]
fn bench_range_aggregates_within_a_single_argument() {
let cli = parse(&["zstd", "-b5e6i2", "input"]);
assert_eq!(cli.operation, Operation::Bench);
assert_eq!(cli.level, 5);
assert_eq!(cli.bench_end_level, Some(6));
assert_eq!(cli.bench_nb_seconds, Some(2));
}
#[test]
fn bench_duration_without_digits_defaults_to_zero() {
let cli = parse(&["zstd", "-b", "-e", "-i"]);
assert_eq!(cli.operation, Operation::Bench);
assert_eq!(cli.level, DEFAULT_CLEVEL);
assert_eq!(cli.bench_end_level, Some(0));
assert_eq!(cli.bench_nb_seconds, Some(0));
}
}
-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);
}
}
+7 -49
View File
@@ -65,24 +65,16 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_HUF_MODE := default
RUST_BUILD_CONFIG := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
RUST_BUILD_CONFIG := 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
@@ -94,7 +86,6 @@ 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)
@@ -102,28 +93,6 @@ $(RUST_STATICLIB): $(RUST_SOURCES)
$(RUST_STATICLIB_32): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS) --target $(RUST_TARGET_32)
# Program-only helpers that were C sources shared with the tests (timefn,
# benchfn) now live in the Rust CLI package. The tests link a helpers-only
# archive, built without the `cli` feature: the parser/dispatch layer needs
# the C fileio backend, which test binaries do not provide.
RUST_CLI_DIR := $(RUST_DIR)/cli
RUST_CLI_MANIFEST := $(RUST_CLI_DIR)/Cargo.toml
RUST_CLI_HELPER_SOURCES := $(RUST_CLI_MANIFEST) $(RUST_CLI_DIR)/Cargo.lock \
$(RUST_CLI_DIR)/src/lib.rs \
$(RUST_DIR)/src/timefn.rs $(RUST_DIR)/src/benchfn.rs
RUST_CLI_HELPERS_TARGET_DIR := $(RUST_DIR)/target/cli-helpers
RUST_CLI_HELPERS_STATICLIB := $(RUST_CLI_HELPERS_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_CLI_HELPERS_STATICLIB_32 := $(RUST_CLI_HELPERS_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
RUST_CLI_HELPERS_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
--target-dir $(RUST_CLI_HELPERS_TARGET_DIR) \
--no-default-features
$(RUST_CLI_HELPERS_STATICLIB): $(RUST_CLI_HELPER_SOURCES)
$(CARGO) build $(RUST_CLI_HELPERS_CARGO_FLAGS)
$(RUST_CLI_HELPERS_STATICLIB_32): $(RUST_CLI_HELPER_SOURCES)
$(CARGO) build $(RUST_CLI_HELPERS_CARGO_FLAGS) --target $(RUST_TARGET_32)
# These test objects have flat filenames, unlike the configuration-hashed
# program objects. Track the HUF mode separately so a C object set compiled
# for one decoder is never relinked with a Rust archive for another decoder.
@@ -283,8 +252,8 @@ fuzzer32 : $(ZSTD_FILES)
$(LINK.c) $^ -o $@$(EXT)
# note : broken : requires symbols unavailable from dynamic library
fuzzer-dll : $(LIB_SRCDIR)/common/xxhash.c $(PRGDIR)/util.c $(PRGDIR)/timefn.c $(PRGDIR)/datagen.c fuzzer.c $(RUST_CLI_HELPERS_STATICLIB)
$(CC) $(CPPFLAGS) $(CFLAGS) $(filter %.c,$^) $(RUST_CLI_HELPERS_STATICLIB) $(LDFLAGS) -o $@$(EXT)
fuzzer-dll : $(LIB_SRCDIR)/common/xxhash.c $(PRGDIR)/util.c $(PRGDIR)/timefn.c $(PRGDIR)/datagen.c fuzzer.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(filter %.c,$^) $(LDFLAGS) -o $@$(EXT)
CLEAN += zstreamtest zstreamtest32
ZSTREAM_LOCAL_FILES := $(PRGDIR)/datagen.c $(PRGDIR)/util.c $(PRGDIR)/timefn.c seqgen.c zstreamtest.c external_matchfinder.c
@@ -315,8 +284,8 @@ zstreamtest_ubsan : $(ZSTREAMFILES)
# note : broken : requires symbols unavailable from dynamic library
zstreamtest-dll : $(LIB_SRCDIR)/common/xxhash.c # xxh symbols not exposed from dll
zstreamtest-dll : $(ZSTREAM_LOCAL_FILES) $(RUST_CLI_HELPERS_STATICLIB)
$(CC) $(CPPFLAGS) $(CFLAGS) $(filter %.c,$^) $(RUST_CLI_HELPERS_STATICLIB) $(LDFLAGS) -o $@$(EXT)
zstreamtest-dll : $(ZSTREAM_LOCAL_FILES)
$(CC) $(CPPFLAGS) $(CFLAGS) $(filter %.c,$^) $(LDFLAGS) -o $@$(EXT)
CLEAN += paramgrill
paramgrill : DEBUGFLAGS = # turn off debug for speed measurements
@@ -375,17 +344,6 @@ $(RUST_LINK_TARGETS_32): $(RUST_STATICLIB_32)
$(RUST_LINK_TARGETS) $(RUST_LINK_TARGETS_32): $(RUST_HUF_C_MODE_STAMP)
# Tests that compile the timefn/benchfn C shims also link the Rust CLI
# helpers archive, which owns those implementations. The archive is a
# prerequisite so `$^` places it after every C object referencing its symbols.
RUST_CLI_LINK_TARGETS := fullbench fullbench-lib fullbench-dll fuzzer \
zstreamtest zstreamtest_asan zstreamtest_tsan \
zstreamtest_ubsan paramgrill decodecorpus poolTests
$(RUST_CLI_LINK_TARGETS): $(RUST_CLI_HELPERS_STATICLIB)
RUST_CLI_LINK_TARGETS_32 := fullbench32 fuzzer32 zstreamtest32
$(RUST_CLI_LINK_TARGETS_32): $(RUST_CLI_HELPERS_STATICLIB_32)
.PHONY: versionsTest
versionsTest: clean
$(PYTHON) test-zstd-versions.py