Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,19 @@ unset LD_PRELOAD

deflate/inflate and related functions
- deflateInit, deflateInit2, deflateSetDictionary, deflateParams, deflateCopy, deflate, deflateEnd, deflateReset, deflateResetKeep
- inflateInit, inflateInit2, inflateSetDictionary, inflateCopy, inflate, inflateEnd, inflateReset, inflateReset2, inflateResetKeep
- inflateInit, inflateInit2, inflateSetDictionary, inflateCopy, inflate, inflateEnd, inflateReset, inflateReset2, inflateResetKeep, inflateSync

For deflate, offload is supported for Z_FINISH flush option. Support for additional options will be added in later releases.
For deflateSetDictionary/inflateSetDictionary, zlib-accel simply sets the execution path to zlib, as dictionary compression is currently not supported for accelerators.
A dictionary a decompressor was never told about is detected from the FDICT bit in the zlib header, which `inflate` reads before choosing an engine. The bit is in the second header byte, so a first call carrying only one byte of a zlib-format stream is also pinned to zlib: an accelerator handed that byte consumes it into its own header buffer, and by the time it reports the dictionary the bytes zlib would need to parse the header itself are no longer in the caller's buffer. The cost is that such a stream stays on zlib even when it turns out to carry no dictionary.
deflateParams is intercepted only to keep the recorded compression level current, so that a level set after initialization is still seen by path selection, and to give up an IGZIP stream that was built for a level the call supersedes; the call itself is always forwarded to zlib.
inflateReset2 is intercepted because it is the only zlib entry point that changes `windowBits` on a live stream: the recorded window size and format have to be refreshed, or path selection keeps deciding on the format the stream was initialized with, and an IGZIP decompression state built for the old format is discarded rather than reset (ISA-L's reset deliberately preserves its window and format settings). It also clears the terminal state described below, since a stream it restarts is ready to decode again. As with `deflateParams`, the call is forwarded to zlib first and the recorded state is only updated when zlib accepts the new `windowBits`.
Once a stream has returned `Z_STREAM_END`, `deflate`/`inflate` answer every later call from that terminal state instead of dispatching it to an engine. What matches zlib is the return code, the `msg` string, and the fact that no input is consumed and no output is written — for `deflate`, `Z_STREAM_END` for `Z_FINISH` with no remaining input, `Z_BUF_ERROR` when input remains or there is no output room, and `Z_STREAM_ERROR` for any other flush or for a null `next_out`/`next_in`; for `inflate`, `Z_STREAM_END` for every flush, and `Z_STREAM_ERROR` for a null `next_out` or for a null `next_in` with input pending. `msg` follows zlib's own rule of writing it only where zlib rejects through its internal `ERR_RETURN` macro, so an out-of-range flush on `deflate` — and every `inflate` rejection, which zlib returns without setting `msg` — leaves the field as the caller left it. `data_type` is *not* written, for the reason given above: no offloaded call can compute it, and this gate is no better placed to guess. These calls are counted separately as `deflate_stream_end_count`/`inflate_stream_end_count` when built with `ENABLE_STATISTICS`, so the per-engine counters still add up to `deflate_count`/`inflate_count`. This matters because an offloaded stream never feeds zlib's own deflate/inflate state, so without it a call after `Z_STREAM_END` would be dispatched from scratch and could append a second stream to a finished one. `deflateReset`, `inflateReset`, `inflateReset2`, `deflateResetKeep`, and `inflateResetKeep` clear the terminal state, and `deflateCopy`/`inflateCopy` carry it to the copy, so a copy of a finished stream refuses input exactly as its source does.
The gate covers `deflate`/`inflate` only. Two other entry points called on a finished stream still report a different return code than zlib would, in either direction, without moving any bytes and without changing how the stream answers a later `Z_FINISH`. `deflateParams` returns `Z_OK` where zlib returns `Z_STREAM_ERROR`: zlib reaches that error through an internal `deflate(strm, Z_BLOCK)` that it only performs once its own encoder has flushed something, and an offloaded stream never advanced that state. `deflateSetDictionary` returns `Z_STREAM_ERROR` where zlib returns `Z_OK`: zlib accepts a dictionary on a finished stream, having cleared the wrapper flag that its "before compression begins" check tests when it wrote the trailer, whereas the mid-stream rejection above also catches a stream that is merely finished. Applications that inspect these return codes on a finished stream should disable offload for those streams.
`deflateResetKeep`/`inflateResetKeep` (declared in zlib.h among the functions zlib does not document) are intercepted because they restart a stream without going through `deflateReset`/`inflateReset`, so a terminal state they left set would wedge the stream at `Z_STREAM_END`. `inflateResetKeep` additionally pins the stream to zlib: keeping the window is the only reason to call it rather than `inflateReset`, so the next stream may reference the previous stream's bytes, which no accelerator can see — the same treatment `inflateSetDictionary` gets. A later `inflateReset` lifts the pin, being the reset that discards the history. `deflateResetKeep` needs no pin: what it keeps only affects how zlib would encode the next stream, and an offloaded stream emits a self-contained one instead. Both are forwarded to zlib first, and the recorded state is only updated when zlib reports success.
The pin decides which engine decodes the next stream; it cannot supply the history. If the previous stream was offloaded, zlib's window never received it, so a next stream that does reference those bytes fails with `Z_DATA_ERROR` where unaccelerated zlib would have decoded it. This is a limitation of offloading rather than of the pin: the bytes exist only in the output the accelerator already returned to the caller, and whether a later stream will reference them is unknowable while the previous one is still being decoded. The pin is what makes that case fail as a zlib data error on the stream that needs the history, instead of an accelerator decoding the lookback against an unrelated window and reporting success. Applications that carry decode history across streams this way should disable offload for those streams. Used as a plain restart — the history-independent case — `inflateResetKeep` decodes correctly on every path, but the pin still applies: the stream stays on zlib until an `inflateReset` lifts it, so an application that restarts per message with `inflateResetKeep` gets no acceleration for the life of that `z_stream`. Any IGZIP decompression state the stream held is released at the pin rather than kept for a path that can no longer be selected, and a later `inflateReset` builds a new one.
When a decode fails part-way through a stream, `inflate` reports `Z_DATA_ERROR` after handing back the bytes the engine had already decoded in that call, and every later call on the stream reports `Z_DATA_ERROR` too — the same way zlib latches a data error. The failed stream is *not* handed to zlib to finish: zlib's own inflate state never saw the earlier chunks, so the input left in `next_in` begins inside a deflate block it would read as the start of a new stream, which on a raw deflate stream means junk counted as output and, at some input alignments, a `Z_STREAM_END` on a short decode. A failure on the *first* call of a stream is different and still goes to zlib, which can take the stream from its start. Only a stateful engine can fail mid-stream, so this applies to IGZIP, including where QAT or IAA reached it through `igzip_fallback`. The latched calls are counted as `inflate_failed_stream_count` when built with `ENABLE_STATISTICS`, for the same reason `inflate_stream_end_count` exists: no engine executes them, and the per-engine counters still have to add up to `inflate_count`.
`inflateSync` is intercepted because it is the one call that legitimately resumes a stream somewhere other than where the shim left it: it searches the input for a full-flush point and discards the decode state, so decoding continues there with no history. A stream that has already had input consumed is pinned to zlib at that point, and any latched data error is cleared. zlib performed the search, so zlib's state is the one left at the flush point; an accelerator still holds the bits it read ahead of the failure, and whether it resumes correctly would depend on how far it happened to have read. As with the `inflateResetKeep` pin, the stream stays on zlib for the rest of its life, and any IGZIP decompression state it held is released at the next `inflate`. A stream that has consumed nothing keeps its engine — it is still at its own start.
deflateCopy/inflateCopy are intercepted so that the copy gets its own per-stream state: zlib duplicates the stream it owns, but zlib-accel keys its own state on the `z_stream` pointer, so without this the copy would be unknown to the shim and silently run on zlib. The copy inherits the settings and execution path of the source, and for inflate it also gets an independent copy of the IGZIP decompression state, so either stream can be used, reset, or ended without affecting the other. `inflateCopy` is supported on every path; `deflateCopy` has one restriction, described under IGZIP above.

utility functions
Expand Down
31 changes: 21 additions & 10 deletions statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,27 @@
using namespace config;

const std::array<const char*, STATS_COUNT> stat_names{
{"deflate_count", "deflate_error_count",
"deflate_qat_count", "deflate_qat_error_count",
"deflate_iaa_count", "deflate_iaa_error_count",
"deflate_igzip_count", "deflate_igzip_error_count",
"deflate_zlib_count", "deflate_stream_end_count",
"inflate_count", "inflate_error_count",
"inflate_qat_count", "inflate_qat_error_count",
"inflate_iaa_count", "inflate_iaa_error_count",
"inflate_igzip_count", "inflate_igzip_error_count",
"inflate_zlib_count", "inflate_stream_end_count"}};
{"deflate_count",
"deflate_error_count",
"deflate_qat_count",
"deflate_qat_error_count",
"deflate_iaa_count",
"deflate_iaa_error_count",
"deflate_igzip_count",
"deflate_igzip_error_count",
"deflate_zlib_count",
"deflate_stream_end_count",
"inflate_count",
"inflate_error_count",
"inflate_qat_count",
"inflate_qat_error_count",
"inflate_iaa_count",
"inflate_iaa_error_count",
"inflate_igzip_count",
"inflate_igzip_error_count",
"inflate_zlib_count",
"inflate_stream_end_count",
"inflate_failed_stream_count"}};

thread_local std::array<uint64_t, STATS_COUNT> stats{};

Expand Down
8 changes: 5 additions & 3 deletions statistics.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
#define VISIBLE_FOR_TESTING __attribute__((visibility("default")))

// *_STREAM_END_COUNT counts the calls answered from the terminal state a stream
// reached earlier, which no engine executes. Without it DEFLATE_COUNT and
// INFLATE_COUNT would no longer be the sum of their per-engine counters. Keep
// this enum and stat_names in statistics.cpp index-parallel.
// reached earlier, and INFLATE_FAILED_STREAM_COUNT the calls answered from a
// latched decode failure; no engine executes either. Without them DEFLATE_COUNT
// and INFLATE_COUNT would no longer be the sum of their per-engine counters.
// Keep this enum and stat_names in statistics.cpp index-parallel.
enum class Statistic : size_t {
DEFLATE_COUNT = 0,
DEFLATE_ERROR_COUNT,
Expand All @@ -35,6 +36,7 @@ enum class Statistic : size_t {
INFLATE_IGZIP_ERROR_COUNT,
INFLATE_ZLIB_COUNT,
INFLATE_STREAM_END_COUNT,
INFLATE_FAILED_STREAM_COUNT,
STATS_COUNT
};

Expand Down
Loading