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
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, andcpuimplement byte-order, bitstream, and target-feature operations used by the codecs.errors,debug,xxhash, andzstd_commonprovide common exported ABI functions and state.commoncontains shared frame constants and internal data types.
- Entropy coding
entropy_commonreads FSE normalized counts and Huffman statistics.fse_decompressbuilds FSE decoding tables and decodes FSE streams.fse_compressnormalizes counts, writes FSE headers, builds compression tables, and encodes FSE streams.huf_compressbuilds Huffman compression tables, writes table headers, and encodes one- and four-stream Huffman payloads.huf_decompressbuilds Huffman decoding tables and decodes X1 and X2 Huffman streams.
- Compression primitives
histcounts byte frequencies for FSE and Huffman compression.zstd_presplitchooses split points for full compression blocks.zstd_compress_literalsemits raw, RLE, and Huffman literal sections while preserving the compressor's Huffman-table repeat state.zstd_compress_frameserializes frame headers, skippable frames, and the last empty block; it takes scalar frame parameters so the C-ownedZSTD_CCtx_paramslayout never crosses the language boundary.zstd_compress_paramsowns the compression-level tables (formerlyclevels.h), parameter bounds, clamping, validation, table selection, source/dictionary adjustment, and match-state/CDict size estimation. The C integration layer keeps the publicZSTD_*symbols and feeds the leaves configuration-owned scalars: the excluded-block-compressor strategy cascade, struct sizes, and sanitizer redzone policy.zstd_fastandzstd_double_fastimplement the single- and two-table fast block match finders, including attached and external dictionary paths.zstd_lazyimplements greedy, lazy, lazy2, and binary-tree matching, including row-based and dictionary search variants.zstd_opt_treemaintains the binary-tree index used by optimal matching; the dynamic-programming optimal parser itself remains in C for now.zstd_ldmimplements long-distance-match parameter selection, table maintenance, sequence generation, and sequence consumption.
- Dictionary building
divsufsortconstructs the suffix array that drives the legacyZDICTtrainer (ZDICT_trainFromBuffer_legacy). The sample analysis and dictionary assembly inzdict.c,cover.c, andfastcover.cremain C.
- Runtime support
threadingprovides platform pthread wrappers required by zstd headers.poolimplements the bounded worker pool used by multithreaded compression.
- Dictionary support
zstd_ddictowns, loads, copies, and references decode dictionaries.
- Legacy decoding
legacyhosts one frozen module per historical format;legacy::zstd_v01ports the self-contained v0.1 decoder. Versions v0.2 through v0.7 are still C.
- Block decompression
zstd_decompress_blockdecodes literal and sequence sections, maintains FSE/Huffman repeat state, and executes compressed-block sequences.zstd_decompressowns 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_cliowns the Rust parser, safety policy, and dispatch. It is built by the separatecli/static-library package only for program archives, so library builds do not acquire program-only dependencies. The Cfileiobackend still owns file opening, safe replacement, sparse writes, metadata, and streaming I/O.timefnprovides the monotonic nanosecond clock behindUTIL_time_t, andbenchfnowns the benchmark run/timing loop (BMK_benchFunction,BMK_benchTimedFn) used by the CLI benchmark mode and by C test tools. Both live in thecli/package, but C test binaries (fullbench, fuzzer, zstreamtest, paramgrill, ...) link a helpers-only build of that archive, produced without the package'sclifeature, because the parser layer requires the Cfileiobackend that tests do not compile. Benchmark orchestration and reporting (benchzstd.c) remain C, reached from the Rust parser through theZSTD_NOBENCH-gated bridge inzstdcli.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/Makefileandprograms/MakefilemapZSTD_LEGACY_SUPPORT=Nto the features for versions >= N (0 disables legacy), matching theZSTD_LEGACY_FILESselection inlib/libzstd.mk.tests/Makefilealways enables all seven features because the test objects compile everylib/legacy/*.cfile regardless of dispatch level.build/mesonmapslegacy_levellike the makefiles;build/cmakeenables all seven wheneverZSTD_LEGACY_SUPPORTis 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.