Repository navigation
Release Notes
v1.0.0 is a major release, and the stability release: the format is frozen, the C API is settled, and the compressed bytes do not change.
Its codename is Hydrogen, element 1. Each later major or minor releases will take their codenames from the next elements of Mendeleev's periodic table. Patch releases keep the codename of their series.
Format v9 is frozen. Archives written by v0.15.0 decode as they are, and v1.0.0 writes byte-identical archives at levels 1 to 7. Any later format change is additive: newer readers keep reading older archives.
What does change is the surface around those bytes. Settings move from option structs to parameters set on a context, which lets the API grow without breaking the ABI. The CLI now keeps its input files by default, and the bindings verify checksums by default when decoding.
⚠️ Breaking change (API, ABI, CLI and bindings). No archive migration from v0.15.x. The soname moves tolibzxc.so.6, so programs linked againstlibzxc.so.5must be rebuilt, and C callers must update their source. Go users must update their imports: the module moves togithub.com/hellobertrand/zxc/bindings/go. These behaviours change silently or at run time:
zxc_decompress()no longer verifies checksums, and neither dozxc_decompress_inplace(),zxc_dstream_create(NULL)andzxc_stream_decompress(fin, fout, NULL). To keep the check, decode through azxc_dctxwithZXC_D_CHECKSUMset.- An out-of-range level is refused, no longer clamped.
zxc_compress(..., 99)returnsZXC_ERROR_BAD_LEVEL.zxckeeps its input files. Scripts that relied on the old removal must add--rm.zxcno longer ignores file operands.zxc -z a b cwithout-mnow fails instead of overwritingb.- The bindings change their checksum defaults. Go, Python, Node.js and WASM now verify checksums when decoding, and Rust no longer writes them by default.
Coming from v0.14.x or older, archives still need the one-time transcode, as format v9 only reads v9:
zxc-old -dc old.zxc | zxc-new -z -c > new.zxcFull guide, with a call-by-call table for the C API: docs/MIGRATION.md.
The format freeze
Format v9 keeps the wire layout of v0.15.0. From this release on, docs/FORMAT.md, the conformance vectors and the golden archives are the reference.
- The bytes did not move. The 13 golden archives and the 53 conformance files are identical to those of v0.15.0. Only their names and location changed (#538, #548).
- Stricter decoders (#547). File, block and
.zxdheaders with non-zero reserved bits or bytes are rejected as corrupt, closing a channel for smuggled metadata. v0.15.0 writes zeros there, so its archives and dictionaries are unaffected. - Block types get descriptive names (#538). GLO becomes SPL (split streams) and GHI becomes PAK (packed sequences), in the code, the documentation and the test vectors. The on-disk bytes are unchanged.
A C API built to last (#545, #546)
Option structs were part of the ABI, so adding a field changed their layout. Parameters are appendable identifiers, so new settings no longer break it.
zxc_cctx* c = zxc_create_cctx();
zxc_cctx_set_param(c, ZXC_C_LEVEL, 5);
zxc_cctx_set_param(c, ZXC_C_CHECKSUM, 1);
zxc_compress_cctx(c, src, n, dst, cap);
zxc_free_cctx(c);- Parameters.
zxc_cctx_set_param()/zxc_dctx_set_param()and their getters, with bounds fromzxc_cparam_bounds()/zxc_dparam_bounds().0restores the default, and a newZXC_ERROR_BAD_PARAMreports an invalid value or identifier. - One-shot calls.
zxc_compress(src, n, dst, cap, level)andzxc_decompress(src, n, dst, cap)take nothing else. Checksums and dictionaries need a context. - Dictionaries are copied on attach.
zxc_cctx_set_dict()/zxc_dctx_set_dict()leave the caller free to release its buffers as soon as they return. - One dictionary prefix.
zxc_train_dict->zxc_dict_train_content,zxc_train_dict_huf->zxc_dict_train_huf,zxc_dict_id->zxc_dict_compute_id,zxc_dict_get_id->zxc_dict_read_id. - Bound functions return
size_t.zxc_compress_bound(),zxc_compress_block_bound(),zxc_decompress_block_bound()andzxc_estimate_cctx_size()dropuint64_t. An input too large for the platform returns 0 instead of wrapping. - Run-time version.
zxc_version_number()andzxc_version_codename()report the loaded library (#545, #560). - Streams take a context.
zxc_stream_compress(),zxc_stream_decompress(),zxc_cstream_create()andzxc_dstream_create()read their parameters from aconstcontext.
Performance
Single-threaded speed is unchanged from v0.15.0: within 2 % on Neoverse-V2, Zen 5 and Zen 3 for both compression and decompression. The compressed sizes are identical.
Decompression lead over the codec with the nearest ratio, on Silesia (202 MB), single thread, lzbench 2.4.1. ZXC is also smaller in every pairing.
| Machine | -1 vs lz4 --fast |
-3 vs lz4 |
-6 vs lz4hc -9 |
-7 vs zstd -1 |
|---|---|---|---|---|
| Apple M3 | 2.41x | 2.14x | 1.61x | 2.95x |
| Neoverse-V2 | 1.92x | 1.54x | 1.28x | 2.16x |
| EPYC Zen 5 | 2.20x | 1.64x | 1.22x | 2.47x |
| EPYC Zen 3 | 1.82x | 1.35x | 1.15x | 2.35x |
In absolute terms, level 1 decodes at 14.8 GB/s on Apple M3 and 11.7 GB/s on Zen 5, and level 3 at 11.0 GB/s and 8.3 GB/s. Full tables: README and WHITEPAPER.
The reference client moves from Apple M2 to Apple M3, so the M3 figures cannot be compared with those published for v0.15.0. The benchmarks also move to lzav 5.20.
Seekable ranges (#562)
Single-threaded ranges decode directly into the caller's buffer when it is safe, as the one-shot API does. Multi-threaded workers claim batches of consecutive blocks by size, as the FILE* engine does. Silesia on Apple M3, 8 threads, against the previous code:
| Block size | Before | After | Gain |
|---|---|---|---|
| 4 KB | 15.72 GB/s | 28.80 GB/s | +83.2 % |
| 64 KB | 36.02 GB/s | 43.02 GB/s | +19.4 % |
| 512 KB | 37.84 GB/s | 46.17 GB/s | +22.0 % |
Single-threaded ranges gain 7 to 18 %.
Thread counts (#561)
-T N and the thread parameters now always mean N encode or decode workers, with the reader and writer threads on top. Auto-detection uses the online processors minus one, unless the workers do their own reads.
CLI
- Input files are kept by default (#558).
--rmremoves the input once its output is written. It is ignored with an explicit output, standard output,-tand-l, and-ktakes precedence. - File metadata is preserved (#539). Output files get the input's permission bits, access and modification times, group, and owner when run as root.
--no-preserveturns this off. - Failed or interrupted runs no longer lose data (#578).
-freplaces the output only once the new one is complete, where a failed run used to destroy it. A fatal signal removes the partial output, except on Windows. A read error on the input fails instead of passing for the end of the input. - No file operand is dropped (#578). More than two operands need
-m, and-otakes one input.-cand-ttreat every operand as an input, sozxc -dc a.zxc b.zxcwrites both andzxc -t a.zxc missing b.zxcfails.-t -jover several files prints one JSON array. - An output that is the input under another name is refused (#555).
zxc -f -o DATA.TXT data.txton a case-insensitive filesystem could destroy the input and still exit 0. The guard now compares file identity as well as paths, which also covers short names and links. Reported and fixed by @Vladexy88x (#550). --rmon a symbolic link removes the link, not its target (#557).-rpicks the right files (#558). It skips.zxcfiles when compressing and selects only them when decompressing or testing. Links are ignored, including a link given as the starting path.-Vnames the release and the build (#535, #559, #560):ZXC CLI v1.0.0 "Hydrogen" (build arm64 64-bit LE).- On Windows, long options that take no argument now reject one, as they already did on Linux and macOS (#558).
--benchlogs its errors (#546).
Bindings
wrappers/becomesbindings/(#581). The Go module moves togithub.com/hellobertrand/zxc/bindings/go, taggedbindings/go/vX.Y.Z, and versions up to v0.15.0 stay published underwrappers/go. It now carries the C sources, sogo getneeds cgo and a C compiler but no separate build of the library.- Checksums are verified by default on decode (#580). Go, Python, Node.js and WASM now verify the checksums an archive carries, and an archive without checksums decodes either way. Opt out with
zxc.WithChecksum(false),checksum=Falseorchecksum: false. The C one-shotzxc_decompress()stays the fast path and does not verify. - Rust writes no checksums by default (#580), like the other bindings. Ask with
with_checksum();without_checksum()is deprecated. Decoding already verified by default. - Hardening (#580): Go and Rust refuse an output that is the input in their file functions.
zxc.WithMaxOutputSize(n)in Go andmaxOutputSizein Node.js and WASM refuse a size declared by an untrusted footer before allocating it. - Node.js (#580): the package now needs Node.js
^20.17.0or>=22.9.0. - Rust, Go, Python, Node.js and WASM moved to context parameters (#546), with the dictionary pinning dropped now that the library copies dictionaries. An out-of-range level is now an error in Go, Python, Node.js and WASM.
- Rust (#545):
compress_bound(),compress_block_bound()anddecompress_block_bound()returnusizeinstead ofu64, andzxc-compress-sysfollows the C API. - Go (#533): a handle dropped without
Closeno longer leaks native memory or aFILE*, andOpenBytesno longer crashes the process once the garbage collector finds it still pinned.Closeremains required. Thanks to @Vladexy88x. - Go and Rust (#546): a failed file operation leaves an existing output file intact.
- Release codename (#560): exposed in Go, Python, Rust and WASM.
Portability & Build
- Windows kernel-mode library (#513):
contrib/windows-kernelbuilds the buffer API aszxc_km.libfrom the unmodified core sources, with a self-test driver and a CI job. It is the counterpart ofcontrib/linux-kernel. Contributed by @jeanga. - Wider CI coverage (#543, #556): the Debian release architectures are required (armhf, ppc64le, s390x, riscv64, loong64), with ports such as hppa, m68k and sh4 in a non-required tier. New targets include Arch Linux, AlmaLinux 9, Amazon Linux 2023 on ARM64, FreeBSD 15.1, x86_64 APX and Clang 23.
- Scalar fuzzing (#537): fuzz targets also cover the scalar paths, for platforms without SIMD.
- CMake defaults to
Release(#529) for top-level projects with no build type or optimization flag. Meson warns when built as an unoptimized subproject. - Ubuntu 26.04 and static analysis (#576): CI follows the new
ubuntu-latest, and the CLI keepserrnoand removes the created file whenfdopenfails. - Shared library file name: it now starts with the soname number, as in
libzxc.so.6.0.0, instead of carrying the release number. On macOS, the current version therefore no longer falls below the compatibility version.pkg-configstill reports1.0.0. - Third-party notices (#587): the rapidhash MIT notice moves from
LICENSEtoTHIRD_PARTY_NOTICES, soLICENSEis BSD-3-Clause alone. The packages ship both.
Reliability
- Decompression tail pad (#564):
ZXC_DECOMPRESS_TAIL_PADis recomputed from the real bounds of the decoder, 2084 bytes instead of 2112.zxc_decompress_block_bound()follows. FILE*engine (#578): a read error on the input now fails instead of ending the stream. After a corrupt block, the output is exactly the blocks before it, whatever order the threads finished in.
Security
- Hardened dictionary training against size overflow, >4 GiB offset truncation, NULL samples and a resulting divide-by-zero. Compression and decompression are not affected. ([GHSA-6r4j-7r6c-xf94], #590). Reported by @ccsimplyspolit.
Documentation
- libzxc.org (#512): a new website with documentation, benchmark comparisons per processor and the API reference, built from the published release.
- Throughput over a link (#530) replaces the effective throughput metric. It accounts for transfer and decoding overlapping, and shows where each codec leads as bandwidth grows.
- WHITEPAPER and README rewritten and condensed (#540).
- MIGRATION: the C API guide, with a call-by-call table and every behaviour change.
Acknowledgements
Thanks to @Vladexy88x for finding and fixing the CLI data-loss bug (#550, #555) and the Go handle leaks (#533), to @jeanga for the Windows kernel-mode library (#513) and continued testing, and to @GeekQAQ for the context report (#532).
Thanks to @chmike, author of zxc-go, a native Go encoder and decoder that needs no cgo, and whose suggestion started the seekable in-place work (#562).
A 1.0 is also the moment to thank those it stands on: @MarcinZukowski for PivCo-Huffman, @rygorous for the breakdown of its merge operations, @inikep (lzbench) and @powturbo (TurboBench) for the independent benchmarks, and @nuberchardzer1 for the Python binding.
Changelog
- api: Honor -T as worker count (#561)
- api: Add zxc_version_codename() (#560)
- api: Move settings from option structs to context parameters (#546)
- api: Cleans up public API, soversion 6 (#545)
- api: Reject non-zero reserved header fields (#547)
- fix: Reject wrapped corpus sizes in dictionary training (#590)
- fix: Bindings - verify checksums by default on decode (#580)
- fix: Wrapper Go - release native resources of handles dropped without Close (#533)
- fix: Correct decompression tail pad to 2084 (#564)
- perf: Decodes seekable ranges in place and batches multi-threaded jobs by size (#562)
- cli: Keeps input files by default, adds --rm to remove them (#558)
- cli: Refuse an output that is the input under another name (#555)
- cli: Preserves file metadata and refines output handling (#539)
- cli: Stop losing data on failed or interrupted runs (#578)
- cli: Reorder -V banner, version before arch (#559)
- cli: Remove a symlink input, not its target (#557)
- cli: Include byte order and architecture in version output (#535)
- build: Exclude language-specific wrapper tests from Snyk analysis (#542)
- build: Fix CI failures on ubuntu-latest (26.04) (#576)
- build: Split third-party notices from LICENSE (#587)
- build: Rename wrappers to bindings (#581)
- build: Bump pytest from 9.1.1 to 9.2.0 in /wrappers/python (#571)
- build: Bump cibuildwheel from 4.3.1 to 4.3.5 in /wrappers/python (#573)
- build: Bump vitest from 5.0.3 to 5.0.5 in /wrappers/nodejs (#570)
- build: Bump cc from 1.7.0 to 1.10.0 in /wrappers/rust (#569)
- build: Bump libc from 0.2.190 to 0.2.191 in /wrappers/rust (#567)
- build: Bump filelock from 3.29.0 to 4.2.4 in /wrappers/python/requirements (#552)
- build: Bump setuptools from 84.0.0 to 84.1.0 in /wrappers/python/requirements (#551)
- build: Bump certifi in /wrappers/python/requirements (#544)
- build: Expand and reorganize platform coverage (#543)
- build: Use standard arm64 runners for pull request benchmarks (#536)
- build: Default to Release build type for top-level CMake projects (#529)
- build: Bump cibuildwheel from 4.2.1 to 4.3.1 in /wrappers/python (#519)
- build: Bump scikit-build-core from 1.1.0 to 1.1.1 in /wrappers/python (#522)
- build: Bump cc from 1.5.1 to 1.7.0 in /wrappers/rust (#518)
- build: Bump libc from 0.2.189 to 0.2.190 in /wrappers/rust (#517)
- build: Bump vitest from 5.0.2 to 5.0.3 in /wrappers/nodejs (#520)
- build: Update lzbench source to original upstream repository
- build: Add x86_64 APX tier and Clang 23 support (#556)
- portability: Windows kernel-mode library for drivers (zxc_km.lib) (#513)
- doc: Add AI tool usage policy (#596)
- doc: Add 1.0.0 upgrade section to MIGRATION
- doc: Condense and refine README and API documentation (#540)
- doc: Replace effective throughput metric with link throughput chart (#530)
- doc: Refresh benchmarks
- doc: Swap Go binding to zxc-go
- doc: Add hyperlinks to contributors in README
- doc: Rewrite and condense WHITEPAPER.md for clarity
- doc: Adds website with automated Cloudflare deployment (#512)
- misc: Raise go directive to 1.22, test 1.22 and stable (#594)
- misc: Adds release/X.Y maintenance branch workflow (#589)
- misc: Reorganize tests into subdirectories (#548)
- misc: Standardizes unsigned integer literal suffixes (#531)
- misc: Add scalar fallback fuzzing targets (#537)
- misc: Add GitHub star button, fix doc wording (#588)
- misc: Bump codecov/codecov-action from 7.0.0 to 7.1.1 (#586)
- misc: Bump github/codeql-action/upload-sarif (#585)
- misc: Bump msys2/setup-msys2 from 2.32.0 to 2.33.0 (#584)
- misc: Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 (#583)
- misc: Bump actions/upload-artifact from 7.0.1 to 7.0.2 (#582)
- misc: Disable native arch for scan-build analysis (#579)
- misc: Bump github/codeql-action/analyze from 4.38.3 to 4.39.1 (#574)
- misc: Bump cross-platform-actions/action from 1.5.0 to 1.7.0 (#572)
- misc: Bump actions/attest-build-provenance from 4.1.1 to 4.2.2 (#568)
- misc: Bump actions/setup-python from 7.0.0 to 7.1.0 (#566)
- misc: Bump rust-lang/crates-io-auth-action from 1.0.5 to 1.0.6 (#565)
- misc: Bump marked from 18.1.0 to 18.1.2 in /site (#575)
- misc: Parallelize valgrind per test (#577)
- misc: Use BUILD_ONLY list (#563)
- misc: Run benchmarks on draft PRs
- misc: Replaces magic numbers with named constants (#554)
- misc: Rename GLO and GHI block types to SPL and PAK (#538)
- misc: Remove .github from export-ignore in .gitattributes
- misc: Restrict local preview server to localhost and rename cache key (#528)
- misc: Bump github/codeql-action/init from 4.38.2 to 4.38.3 (#524)
- misc: Bump oss-fuzz-base/base-builder in /.clusterfuzzlite (#516)
- misc: Bump marked from 18.0.14 to 18.1.0 in /site (#521)
- misc: Bump github/codeql-action/upload-sarif (#523)
- misc: Bump actions/download-artifact from 8.0.1 to 8.0.2 (#515)
- misc: Bump actions/setup-node from 6.4.0 to 7.1.0 (#514)
- misc: Bump ossf/scorecard-action from 2.4.3 to 2.5.0 (#525)
Full Changelog: v0.15.0...v1.0.0