Skip to main content

structured_zstd/
lib.rs

1//! Pure-Rust Zstandard codec with a production-grade decoder, dictionary
2//! handle reuse, and an actively-improved encoder.
3//!
4//! The crate ships:
5//!
6//! * [`decoding`] — [RFC 8878] decoder ([`decoding::StreamingDecoder`],
7//!   [`decoding::FrameDecoder`], dictionary-backed paths via
8//!   [`decoding::DictionaryHandle`]).
9//! * [`encoding`] — frame compressor, streaming encoder, named and numeric
10//!   compression levels ([`encoding::CompressionLevel`]).
11//! * [`dictionary`] (feature `dict-builder`) — COVER / FastCOVER training
12//!   plus raw-to-finalized dictionary helpers.
13//! * `decoding::workspace` (feature `caller-workspace`): streaming decoder
14//!   that runs entirely in caller-owned memory, never allocating, with
15//!   caller-set limits on window, output and work.
16//! * v0.5, v0.6 and v0.7 frames (feature `legacy`, on by default): the
17//!   formats every libzstd 1.5.7 build reads by default decode through the
18//!   same entry points, the frame queries and the streaming decoder
19//!   included; `decoding::LegacyVersion` names them. Without the feature
20//!   they are refused as an unknown format.
21//!
22//! No FFI, no cmake, no system zstd. `no_std` builds are supported by
23//! disabling the default `std` feature.
24//!
25//! # CPU kernel features
26//!
27//! Both the decode and the encode hot paths ship per-CPU-tier SIMD kernels.
28//! On x86 and aarch64 with `std` the tier is chosen at runtime (CPU-feature
29//! detection, cached on first use); on `no_std` it is chosen at compile time
30//! from `cfg(target_feature)`. WebAssembly is compile-time only either way:
31//! its kernels additionally require `target_feature = "simd128"`, so a wasm
32//! build without `-C target-feature=+simd128` stays scalar.
33//!
34//! Each tier is gated by a cargo feature: `kernel-scalar`, `kernel-sse`,
35//! `kernel-bmi2`, `kernel-avx2`, `kernel-vbmi2` (x86) and `kernel-neon`,
36//! `kernel-sve` (aarch64). All are on by default except `kernel-vbmi2`, which
37//! is opt-in because the AVX-512 decode tier measures slower than AVX2 on the
38//! bursty decode path, so the default build is a universal binary that picks
39//! the best available tier per the above. `kernel-scalar` gates no code: the
40//! scalar path is the mandatory fallback and is always compiled, so the flag
41//! exists only to name that tier explicitly in a feature set. `kernel-vbmi2`
42//! and `kernel-sve` are decoder-only; the encoder has no AVX-512 or SVE tier.
43//! The chain mirrors the ISA
44//! dependency (`kernel-avx2` implies `kernel-bmi2` implies `kernel-sse`;
45//! `kernel-sve` implies `kernel-neon`). `kernel-sse` covers two x86 tiers:
46//! SSE4.2 where the CPU has it, and a plain-SSE2 tier otherwise, so a
47//! pre-SSE4.2 CPU still gets vector match compares. Any subset is valid, and a
48//! flag is inert on architectures it doesn't apply to. Constrained targets can
49//! shrink the binary by trimming
50//! tiers: `--no-default-features --features kernel-scalar` compiles out every
51//! per-tier dispatch, the BMI2/AVX2/VBMI2/NEON trampolines, and the explicit
52//! SSE2/NEON intrinsics in both the copy primitives and the encoder
53//! match-finder. The `kernel_*` features control the crate's own explicit
54//! SIMD; they do not constrain the compiler's autovectorizer, which may still
55//! emit vector instructions from ordinary scalar code regardless of the
56//! enabled tiers.
57//!
58//! The packaged README is included below for the docs.rs landing page; the
59//! API anchors above link straight into the per-module documentation.
60//!
61//! [RFC 8878]: https://www.rfc-editor.org/rfc/rfc8878
62// Keep crate docs aligned with the packaged README via the crate-local symlink in `zstd/README.md`.
63#![doc = include_str!("../README.md")]
64#![no_std]
65#![deny(trivial_casts, trivial_numeric_casts, rust_2018_idioms)]
66#![cfg_attr(docsrs, feature(doc_cfg))]
67
68#[cfg(feature = "std")]
69extern crate std;
70
71#[cfg(not(feature = "rustc-dep-of-std"))]
72extern crate alloc;
73
74#[cfg(feature = "std")]
75pub(crate) const VERBOSE: bool = false;
76
77macro_rules! vprintln {
78    ($($x:expr),*) => {
79        #[cfg(feature = "std")]
80        if crate::VERBOSE {
81            std::println!($($x),*);
82        }
83    }
84}
85
86mod bit_io;
87mod common;
88/// Largest block the format allows (RFC 8878 3.1.1.2.3, 128 KiB): the single
89/// source of truth for every surface that has to name it, the C ABI included.
90/// A frame's own maximum is the smaller of this and its window.
91pub use common::MAX_BLOCK_SIZE;
92/// Smallest accepted maximum block size (the `ZSTD_BLOCKSIZE_MAX_MIN` bound),
93/// shared by `set_max_block_size` and the C ABI parameter surface.
94pub use common::MIN_MAX_BLOCK_SIZE;
95/// Smallest accepted block-size target (the `ZSTD_TARGETCBLOCKSIZE_MIN`
96/// bound): the single source of truth shared by the Rust setters
97/// (`set_target_block_size`) and the C ABI parameter surface.
98pub use common::MIN_TARGET_BLOCK_SIZE;
99mod cpu_kernel;
100pub mod decoding;
101#[cfg(feature = "dict-builder")]
102#[cfg_attr(docsrs, doc(cfg(feature = "dict-builder")))]
103pub mod dictionary;
104pub mod encoding;
105mod histogram;
106
107pub mod skippable;
108
109pub(crate) mod blocks;
110
111#[cfg(feature = "fuzz-exports")]
112pub mod fse;
113#[cfg(feature = "fuzz-exports")]
114pub mod huff0;
115
116// `pub fn init_state<K: CpuKernel>` and friends inside the
117// fuzz-exports-public `huff0` module name `crate::cpu_kernel::CpuKernel`
118// in their signatures. Without a publicly-reachable path to `CpuKernel`
119// the bound triggers `private_bounds` / `private_interfaces`. Re-export
120// under the same feature gate so the fuzz harness build is clean.
121#[cfg(feature = "fuzz-exports")]
122pub use crate::cpu_kernel::{CpuKernel, ScalarKernel};
123
124/// Name of the active CPU kernel tier (entropy / sequence hot paths) for this
125/// process — for diagnostics and benchmark/dashboard reporting. See
126/// [`cpu_kernel::active_cpu_kernel_name`].
127pub use crate::cpu_kernel::active_cpu_kernel_name;
128/// The ceiling on the instruction sets the kernels may use: see
129/// [`set_cpu_ceiling`].
130pub use crate::cpu_kernel::{
131    CpuCeilingError, CpuLevel, UnknownCpuLevel, cpu_ceiling, set_cpu_ceiling,
132};
133/// Name of the match-finder kernel tier for this process. See
134/// [`encoding::active_match_kernel_name`].
135pub use crate::encoding::active_match_kernel_name;
136
137#[cfg(not(feature = "fuzz-exports"))]
138pub(crate) mod fse;
139#[cfg(not(feature = "fuzz-exports"))]
140pub(crate) mod huff0;
141
142#[cfg(feature = "std")]
143pub mod io_std;
144
145#[cfg(feature = "std")]
146pub use io_std as io;
147
148#[cfg(not(feature = "std"))]
149pub mod io_nostd;
150
151#[cfg(not(feature = "std"))]
152pub use io_nostd as io;
153
154#[cfg(test)]
155mod tests;
156
157/// Re-exports of internal types used by benchmarks.
158///
159/// Gated behind the `bench-internals` feature so normal builds do not
160/// widen the public API surface. Not part of the stable API; items may
161/// change or disappear without notice.
162#[cfg(feature = "bench-internals")]
163#[doc(hidden)]
164pub mod testing {
165    /// Compression parameters selected for `(level, srcSize, dictSize)` →
166    /// `(windowLog, chainLog, hashLog, searchLog, minMatch, targetLength,
167    /// strategy)`. Facade for the `ffi-bench` parity test that diffs the
168    /// selection against the reference `ZSTD_getCParams`.
169    pub fn compression_params(
170        level: i32,
171        src: u64,
172        dict: usize,
173    ) -> (u32, u32, u32, u32, u32, u32, u32) {
174        // `ZSTD_getCParams` spells an unknown source size as 0.
175        let cp =
176            crate::encoding::LevelParameters::for_level(level, (src != 0).then_some(src), dict);
177        (
178            cp.window_log,
179            cp.chain_log,
180            cp.hash_log,
181            cp.search_log,
182            cp.min_match,
183            cp.target_length,
184            cp.strategy.ordinal(),
185        )
186    }
187
188    /// Force every HUF table build onto the cheap single-build path (skip the
189    /// #167 table-log search) so a bench harness can A/B the search across
190    /// levels. Measurement-only.
191    pub fn set_force_cheap_huf(on: bool) {
192        crate::huff0::huff0_encoder::set_force_cheap_huf(on);
193    }
194
195    pub use crate::bit_io::BitReaderReversed;
196    // `BitReaderReversed` is generic over `K: CpuKernel = ScalarKernel`,
197    // so both the trait bound and the default need a `pub` path to
198    // match the re-exported type's visibility. Without this the
199    // bench-build trips `private_bounds` / `private_interfaces`.
200    pub use crate::cpu_kernel::{CpuKernel, ScalarKernel};
201
202    /// Bench-only facade for the decoder wildcopy implementation.
203    ///
204    /// # Safety
205    /// Caller must satisfy the same safety contract as
206    /// `decoding::copy_bytes_overshooting_for_bench`.
207    #[inline(always)]
208    pub unsafe fn copy_bytes_overshooting_for_bench(
209        src: (*const u8, usize),
210        dst: (*mut u8, usize),
211        copy_at_least: usize,
212    ) {
213        // Keep decoder internals crate-private and expose only this bench shim.
214        unsafe { crate::decoding::copy_bytes_overshooting_for_bench(src, dst, copy_at_least) };
215    }
216
217    /// Maximum block size per RFC 8878 §3.1.1.2.3 (128 KiB).
218    /// Exposed for parity tests that feed exactly-one-block chunks
219    /// into the block-splitter comparator.
220    pub const MAX_BLOCK_SIZE: u32 = crate::common::MAX_BLOCK_SIZE;
221
222    /// Run our block splitter on a 128 KB chunk.
223    ///
224    /// `split_level` mirrors upstream zstd `ZSTD_splitBlock(level)`: `0` selects
225    /// the borders heuristic (`ZSTD_splitBlock_fromBorders`), `1..=4`
226    /// select `ZSTD_splitBlock_byChunks` at the corresponding sampling
227    /// level. Returns the split position (or `block.len()` if no split).
228    ///
229    /// Crate-internal facade for the block-splitter parity comparator test —
230    /// the underlying functions stay `fn` so they don't widen the
231    /// stable API surface.
232    pub fn block_splitter_decision(block: &[u8], split_level: usize) -> usize {
233        crate::encoding::frame_compressor::block_splitter_decision_for_bench(block, split_level)
234    }
235
236    /// White-box capture of our Huffman weight description for `data`:
237    /// `(description, weights)` where `description` is the length-prefixed
238    /// FSE payload and `weights` the raw per-symbol weights. Facade for the
239    /// `ffi-bench` conformance test that feeds it through the C `HUF_readStats`.
240    pub fn huf_weight_description(data: &[u8]) -> (alloc::vec::Vec<u8>, alloc::vec::Vec<u8>) {
241        crate::huff0::huff0_encoder::huf_weight_description_for_test(data)
242    }
243
244    /// White-box capture of our 4-stream Huffman payload for `data`. Facade for
245    /// the `ffi-bench` conformance test that decodes it through the C HUF reader.
246    pub fn huf_encode4x(data: &[u8]) -> alloc::vec::Vec<u8> {
247        crate::huff0::huff0_encoder::huf_encode4x_for_test(data)
248    }
249
250    /// White-box capture of the level-22 sequence stream (literal-length,
251    /// offset, match-length triples) our match generator emits for `data`.
252    /// Facade for the sequence-conformance test in `ffi-bench`, which
253    /// compares this stream against the C reference's `ZSTD_generateSequences`
254    /// output. Pure Rust; the C side stays out of this crate.
255    pub fn collect_level22_sequences(data: &[u8]) -> alloc::vec::Vec<(usize, usize, usize)> {
256        crate::encoding::match_generator::collect_level22_sequences(data)
257    }
258
259    /// FastCOVER dictionary roundtrip fixture: `(finalized_dictionary,
260    /// compressed_frame, original_payload)`. Facade for the `ffi-bench`
261    /// conformance test that decodes `compressed_frame` against the dictionary
262    /// through the C decoder and compares to `original_payload`.
263    #[cfg(feature = "dict-builder")]
264    pub fn dict_roundtrip_fixture() -> (
265        alloc::vec::Vec<u8>,
266        alloc::vec::Vec<u8>,
267        alloc::vec::Vec<u8>,
268    ) {
269        crate::dictionary::dict_roundtrip_fixture()
270    }
271
272    /// The content the legacy trainer selects, before any header: facade for
273    /// the `ffi-bench` test that compares it with the tail of the reference's
274    /// `ZDICT_trainFromBuffer_legacy` dictionary. `None` when the trainer
275    /// yields nothing.
276    #[cfg(feature = "dict-builder")]
277    pub fn legacy_dict_content(
278        samples: &[u8],
279        sample_sizes: &[usize],
280        dict_size: usize,
281        selectivity: u32,
282    ) -> Option<alloc::vec::Vec<u8>> {
283        crate::dictionary::legacy_dict_content(samples, sample_sizes, dict_size, selectivity)
284    }
285
286    pub use crate::blocks::block::BlockType;
287
288    /// First block's type (raw / rle / compressed) in a frame. Facade over the
289    /// internal block decoder for the FFI parity tests in `ffi-bench`.
290    pub fn first_block_type(frame: &[u8]) -> BlockType {
291        let (_, header_size) = crate::decoding::frame::read_frame_header_with_format(frame, false)
292            .expect("frame header should parse");
293        let mut decoder = crate::decoding::block_decoder::new();
294        let (header, _) = decoder
295            .read_block_header(&frame[header_size as usize..])
296            .expect("block header should parse");
297        header.block_type
298    }
299
300    /// `(single_segment_flag, frame_content_size, fcs_field_size_bytes)` parsed
301    /// from a frame header. Facade for the FFI parity tests in `ffi-bench` so
302    /// they need not reach into the internal `FrameHeader` type.
303    pub fn frame_header_info(frame: &[u8]) -> (bool, u64, u8) {
304        let (h, _) = crate::decoding::frame::read_frame_header_with_format(frame, false)
305            .expect("frame header should parse");
306        (
307            h.descriptor.single_segment_flag(),
308            h.frame_content_size(),
309            h.descriptor.frame_content_size_bytes().unwrap_or(0),
310        )
311    }
312}
313
314/// SIMD wildcopy overshoot slack carried by every decoder backend
315/// (currently **32 bytes**). Sized so the AVX2 chunked kernel in
316/// `simd_copy::copy_bytes_overshooting` (32-byte stride on x86-64) can
317/// fire on tail copies near the end of a fixed-capacity output buffer.
318/// Upstream zstd's `WILDCOPY_OVERLENGTH` is also 32 bytes today; this
319/// matches that contract.
320///
321/// Public so callers sizing an output slice for
322/// [`crate::decoding::FrameDecoder::decode_all`] can size
323/// `frame_content_size + WILDCOPY_OVERLENGTH` symbolically without
324/// duplicating the value. Use the const reference rather than a
325/// hardcoded literal — `simd_copy::copy_bytes_overshooting` already
326/// ships an AVX-512 64-byte chunked kernel, and the slack may grow
327/// further to reliably enable that wider kernel at buffer tails
328/// (mirroring how the bump from 16 → 32 enabled the AVX2 32-byte
329/// kernel at the tail).
330pub const WILDCOPY_OVERLENGTH: usize = crate::decoding::buffer_backend::WILDCOPY_OVERLENGTH;