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