diff --git a/build/cmake/lib/CMakeLists.txt b/build/cmake/lib/CMakeLists.txt index 66ba88091..d754333cc 100644 --- a/build/cmake/lib/CMakeLists.txt +++ b/build/cmake/lib/CMakeLists.txt @@ -169,6 +169,18 @@ elseif(_zstd_huf_force_x2) endif() endif() +# This CMake build compiles every lib/legacy/zstd_v0N.c whenever legacy +# support is enabled (ZSTD_LEGACY_LEVEL only selects the C dispatch), so the +# Rust archive enables every per-version legacy feature to match. The build +# configuration encodes the switch so archives never mix. +set(_zstd_rust_legacy 0) +if(ZSTD_LEGACY_SUPPORT) + set(_zstd_rust_legacy 1) + list(APPEND _zstd_rust_features + legacy-v01 legacy-v02 legacy-v03 legacy-v04 + legacy-v05 legacy-v06 legacy-v07) +endif() + set(_zstd_rust_target "${ZSTD_RUST_TARGET}") if(NOT _zstd_rust_target AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SIZEOF_VOID_P EQUAL 4) @@ -195,7 +207,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}") + "c${_zstd_rust_compression}-d${_zstd_rust_decompression}-b${_zstd_rust_dictbuilder}-${_zstd_rust_huf_mode}-legacy${_zstd_rust_legacy}") set(ZSTD_RUST_MANIFEST "${ZSTD_SOURCE_DIR}/rust/Cargo.toml") set(ZSTD_RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust-target/${_zstd_rust_build_config}") diff --git a/build/meson/lib/meson.build b/build/meson/lib/meson.build index 9ef1168da..0c629cb32 100644 --- a/build/meson/lib/meson.build +++ b/build/meson/lib/meson.build @@ -88,6 +88,16 @@ elif rust_huf_force_x2 rust_huf_c_args += '-DHUF_FORCE_DECOMPRESS_X2' endif +# Mirror the legacy source selection below: legacy_level N compiles +# lib/legacy/zstd_v0N.c .. zstd_v07.c, so the Rust archive enables the +# matching per-version features. The build configuration encodes the level +# so archives built for different legacy levels never mix. +foreach i : [1, 2, 3, 4, 5, 6, 7] + if legacy_level != 0 and legacy_level <= i + rust_features += 'legacy-v0@0@'.format(i) + endif +endforeach + rust_target = get_option('zstd_rust_target') if rust_target == '' if host_machine_os == os_linux and \ @@ -98,7 +108,7 @@ if rust_target == '' endif endif -rust_build_config = 'c1-d1-b1-' + rust_huf_mode +rust_build_config = 'c1-d1-b1-' + rust_huf_mode + '-legacy@0@'.format(legacy_level) rust_target_dir = join_paths(meson.current_build_dir(), 'rust-target', rust_build_config) is_msvc = cc_id == compiler_msvc or cc_id == 'clang-cl' rust_staticlib_name = is_msvc ? 'zstd_rs.lib' : 'libzstd_rs.a' diff --git a/lib/Makefile b/lib/Makefile index 1bbdce5cb..42ae4f09a 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -104,6 +104,18 @@ ifneq ($(ZSTD_LIB_DECOMPRESSION),0) RUST_CARGO_FEATURES += $(RUST_HUF_FEATURE) endif endif + +# Legacy decoders follow the ZSTD_LEGACY_FILES selection exactly: level N +# enables versions v0.N .. v0.7 (0 disables legacy). The build directory +# also encodes the level, so archives for different legacy levels never mix. +RUST_LEGACY_FEATURES := +ifneq ($(ZSTD_LEGACY_SUPPORT), 0) +ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0) +RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7)) +endif +endif +RUST_CARGO_FEATURES += $(RUST_LEGACY_FEATURES) +RUST_BUILD_CONFIG := $(RUST_BUILD_CONFIG)-legacy$(ZSTD_LEGACY_SUPPORT) RUST_CARGO_FEATURES := $(subst $(space),$(comma),$(strip $(RUST_CARGO_FEATURES))) RUST_TARGET ?= diff --git a/programs/Makefile b/programs/Makefile index 689e53893..36bf6c0db 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -56,16 +56,33 @@ endif endif RUST_HUF_FEATURE := -RUST_BUILD_CONFIG := default +RUST_HUF_MODE := default ifneq ($(RUST_HUF_FORCE_X1),0) RUST_HUF_FEATURE := huf-force-decompress-x1 -RUST_BUILD_CONFIG := huf-force-decompress-x1 +RUST_HUF_MODE := huf-force-decompress-x1 endif ifneq ($(RUST_HUF_FORCE_X2),0) RUST_HUF_FEATURE := huf-force-decompress-x2 -RUST_BUILD_CONFIG := huf-force-decompress-x2 +RUST_HUF_MODE := huf-force-decompress-x2 endif +# The full-featured zstd program mirrors libzstd.mk's legacy file selection: +# ZSTD_LEGACY_SUPPORT=N compiles v0.N .. v0.7, so the Rust archive enables the +# matching per-version features. The archive directory encodes the level so +# builds for different legacy levels never share cached Rust outputs. The +# compress-only, decompress-only, and CLI archives further below are used +# solely by ZSTD_LEGACY_SUPPORT=0 program variants and stay legacy-free. +empty := +space := $(empty) $(empty) +comma := , +RUST_LEGACY_FEATURES := +ifneq ($(ZSTD_LEGACY_SUPPORT), 0) +ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0) +RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7)) +endif +endif +RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT) + RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG) RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a RUST_TARGET_32 ?= i686-unknown-linux-gnu @@ -76,6 +93,9 @@ 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) @@ -83,7 +103,7 @@ $(RUST_STATICLIB): $(RUST_SOURCES) $(RUST_STATICLIB_32): $(RUST_SOURCES) $(CARGO) build $(RUST_CARGO_FLAGS) --target $(RUST_TARGET_32) -RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_BUILD_CONFIG) +RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_HUF_MODE) RUST_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_CLI_BUILD_CONFIG) RUST_CLI_STATICLIB := $(RUST_CLI_TARGET_DIR)/release/libzstd_cli_rs.a RUST_CLI_STATICLIB_32 := $(RUST_CLI_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a @@ -97,7 +117,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_BUILD_CONFIG) +RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-b0-$(RUST_HUF_MODE) RUST_DECOMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_BUILD_CONFIG) RUST_DECOMPRESS_STATICLIB := $(RUST_DECOMPRESS_TARGET_DIR)/release/libzstd_rs.a RUST_DECOMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \ @@ -110,7 +130,7 @@ endif $(RUST_DECOMPRESS_STATICLIB): $(RUST_SOURCES) $(CARGO) build $(RUST_DECOMPRESS_CARGO_FLAGS) -RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_BUILD_CONFIG) +RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_HUF_MODE) RUST_DECOMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_CLI_BUILD_CONFIG) RUST_DECOMPRESS_CLI_STATICLIB := $(RUST_DECOMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \ @@ -120,7 +140,7 @@ RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --releas $(RUST_DECOMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES) $(CARGO) build $(RUST_DECOMPRESS_CLI_CARGO_FLAGS) -RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-b0-$(RUST_BUILD_CONFIG) +RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-b0-$(RUST_HUF_MODE) RUST_COMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_BUILD_CONFIG) RUST_COMPRESS_STATICLIB := $(RUST_COMPRESS_TARGET_DIR)/release/libzstd_rs.a RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \ @@ -130,7 +150,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_BUILD_CONFIG) +RUST_DICTBUILDER_BUILD_CONFIG := lib-c1-d0-b1-$(RUST_HUF_MODE) 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 \ @@ -141,7 +161,7 @@ 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_BUILD_CONFIG) +RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_HUF_MODE) RUST_COMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_CLI_BUILD_CONFIG) RUST_COMPRESS_CLI_STATICLIB := $(RUST_COMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a RUST_COMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \ diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 387e0497e..ec33fc8cf 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -13,6 +13,18 @@ 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" diff --git a/rust/README.md b/rust/README.md index 07653f653..f712f49f2 100644 --- a/rust/README.md +++ b/rust/README.md @@ -57,6 +57,9 @@ 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 (v0.1 through + v0.7). No version has been ported yet; all decoders are still C. - Block decompression - `zstd_decompress_block` decodes literal and sequence sections, maintains FSE/Huffman repeat state, and executes compressed-block sequences. @@ -80,11 +83,45 @@ zstd ABI: 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, legacy decoding callbacks, 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. +except suffix-array construction, the legacy v0.1-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. ## Compatibility boundary diff --git a/rust/src/legacy/mod.rs b/rust/src/legacy/mod.rs new file mode 100644 index 000000000..df2d785b0 --- /dev/null +++ b/rust/src/legacy/mod.rs @@ -0,0 +1,38 @@ +//! 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; +//! ``` + diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4452cb6c3..4176ff9ae 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -18,6 +18,7 @@ pub mod hist; pub mod huf_compress; #[cfg(feature = "decompression")] pub mod huf_decompress; +pub mod legacy; pub mod mem; pub mod pool; pub mod threading; diff --git a/tests/Makefile b/tests/Makefile index f709b0417..52e589a69 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -65,16 +65,24 @@ endif endif RUST_HUF_FEATURE := -RUST_BUILD_CONFIG := default +RUST_HUF_MODE := default ifneq ($(RUST_HUF_FORCE_X1),0) RUST_HUF_FEATURE := huf-force-decompress-x1 -RUST_BUILD_CONFIG := huf-force-decompress-x1 +RUST_HUF_MODE := huf-force-decompress-x1 endif ifneq ($(RUST_HUF_FORCE_X2),0) RUST_HUF_FEATURE := huf-force-decompress-x2 -RUST_BUILD_CONFIG := huf-force-decompress-x2 +RUST_HUF_MODE := huf-force-decompress-x2 endif +# Test binaries compile every lib/legacy/*.c file regardless of the dispatch +# level (ZSTDLEGACY_FILES is a plain wildcard below), so the Rust archive must +# always carry all ported legacy decoders too. The build configuration still +# encodes the level because the flat C objects bake -DZSTD_LEGACY_SUPPORT into +# the dispatch code and must never outlive a level change. +RUST_LEGACY_FEATURES := legacy-v01,legacy-v02,legacy-v03,legacy-v04,legacy-v05,legacy-v06,legacy-v07 +RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT) + RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG) RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a RUST_TARGET_32 ?= i686-unknown-linux-gnu @@ -86,6 +94,7 @@ RUST_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \ ifneq ($(RUST_HUF_FEATURE),) RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE) endif +RUST_CARGO_FLAGS += --features $(RUST_LEGACY_FEATURES) $(RUST_STATICLIB): $(RUST_SOURCES) $(CARGO) build $(RUST_CARGO_FLAGS)