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

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

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

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

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

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

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  --features legacy-v01 -- -D warnings && cargo test --all-targets
  --features legacy-v01 (127 tests, 9 for v0.1)
- cargo clippy/test --no-default-features --features
  decompression,legacy-v01 (module builds standalone)
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests, also with
  ZSTD_LEGACY_SUPPORT=1 (mixed Rust v0.1 + C v0.2-0.7 link)
- make -C tests test-rust-lib-smoke && make -C tests test-legacy
- make -C programs zstd (default and ZSTD_LEGACY_SUPPORT=1); nm shows
  the nine ZSTDv01_* symbols provided by Rust at level 1
- make -C lib libzstd.a ZSTD_LEGACY_SUPPORT=0 (no legacy symbols) and
  meson -Dlegacy_level=1 shared library exporting all nine
2026-07-11 23:08:19 +02:00
..
2026-07-11 23:08:19 +02:00
2026-07-11 23:08:19 +02:00

Rust rewrite

This directory contains the in-progress Rust replacement for the zstd library and command-line program. During the migration, the crate is built as a static library and linked into the original C test programs. Production C translation units become declaration-only shims as their implementations move to Rust; the original C tests remain unchanged and provide compatibility coverage.

Component map

The crate is organized from low-level representation helpers toward the public zstd ABI:

  • Common primitives
    • mem, bits, bitstream, and cpu implement byte-order, bitstream, and target-feature operations used by the codecs.
    • errors, debug, xxhash, and zstd_common provide common exported ABI functions and state.
    • common contains shared frame constants and internal data types.
  • Entropy coding
    • entropy_common reads FSE normalized counts and Huffman statistics.
    • fse_decompress builds FSE decoding tables and decodes FSE streams.
    • fse_compress normalizes counts, writes FSE headers, builds compression tables, and encodes FSE streams.
    • huf_compress builds Huffman compression tables, writes table headers, and encodes one- and four-stream Huffman payloads.
    • huf_decompress builds Huffman decoding tables and decodes X1 and X2 Huffman streams.
  • Compression primitives
    • hist counts byte frequencies for FSE and Huffman compression.
    • zstd_presplit chooses split points for full compression blocks.
    • zstd_compress_literals emits raw, RLE, and Huffman literal sections while preserving the compressor's Huffman-table repeat state.
    • zstd_compress_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, including row-based and dictionary search variants.
    • zstd_opt_tree maintains the binary-tree index used by optimal matching; the dynamic-programming optimal parser itself remains in C for now.
    • zstd_ldm implements long-distance-match parameter selection, table maintenance, sequence generation, and sequence consumption.
  • Dictionary building
    • divsufsort constructs the suffix array that drives the legacy ZDICT trainer (ZDICT_trainFromBuffer_legacy). The sample analysis and dictionary assembly in zdict.c, cover.c, and fastcover.c remain C.
  • Runtime support
    • threading provides platform pthread wrappers required by zstd headers.
    • 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.
    • zstd_decompress owns the public decompression context, one-shot, dictionary, parameter, and streaming state machines. Its C shim retains configuration-dependent context allocation plus legacy and trace leaves.
  • Command-line frontend
    • zstd_cli owns the Rust parser, safety policy, and dispatch. It is built by the separate cli/ static-library package only for program archives, 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.

Compatibility boundary

The public ABI continues to come from the existing headers under lib/. Exported Rust functions therefore use C layout and calling conventions. A C source file whose implementation has moved to Rust remains in the original makefile source list as a small shim so header configuration and platform preprocessor behavior stay available during the transition.

The library, test, and program makefiles select an archive directory for the active C configuration: enabled compression/decompression/dictionary-builder modules, default or forced HUF X1/X2, and the matching Rust target for 32-bit C binaries. The native static archive flattens Rust object members rather than nesting a Rust archive, while the native shared library retains all migrated Rust exports. When the HUF mode changes, the test and program paths also rebuild cached C outputs before linking. This prevents original C tests from using a stale or configuration-incompatible implementation.

Validation

Run focused Rust checks from this directory:

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo build --release

The program-only Rust archive has its own feature matrix and should be checked from rust/cli as well:

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

Then run original compatibility tests from the repository root, starting with the narrow target for the component being migrated. For example:

make -C tests fuzzer
./tests/fuzzer -i1 --no-big-tests
make -C tests test-rust-lib-smoke

Broader tests/Makefile targets remain the authoritative integration gates as more of the library and CLI are rewritten.