Frame and legacy compression of multiple files each built the name of
the compressed file, with the same buffer management : initial
allocation, growth, strcpy + strcat of the suffix, and stdout special
case. They now share LZ4IO_compressedFileName(), and decompression of
multiple files shares its buffer management, LZ4IO_reserveFileName().
Single and multi-threaded frame compression duplicated the opening of
files, the preparation of preferences, and the end of file processing :
closing files, copying owner, permissions and modification time, --rm,
final status. Legacy compression had none of this end : `lz4 -l` ignored
`--rm`, and the compressed file didn't keep the source's attributes.
The end of file processing is now LZ4IO_finishCompressedFile(), shared by
frame and legacy compression, and LZ4IO_compressFilename_extRess() opens
files and prepares preferences for both frame compressors, which only
compress.
Also, LZ4IO_compressFilename_extRess() now reports a processed size of 0
when a file can't be opened, instead of leaving it uninitialized : with
-m, a missing file added a stale or garbage size to the final speed
summary (found by scan-build).
`--fast=5x` was accepted as `--fast=5`: characters following the
number were silently ignored, unlike `--threads=5x` which is rejected.
`--threadsX 2` was accepted as `--threads 2`, since the long command
was matched as a prefix only.
Both are now rejected with the usage message.
fseek() succeeds beyond end of file, so a skippable frame whose
declared size exceeds the remaining input was silently accepted
when reading from a file (`lz4 -t`, `lz4 -d` and `lz4 --list` all
reported success), while the same input from a pipe correctly failed
with "Error 43 : cannot skip skippable area".
fseek_u32() now seeks to the last byte of the area and reads it,
so truncation is detected the same way for files and pipes.
XXH_isLittleEndian() returned an int,
and each caller cast this function call to XXH_endianness,
which triggers -Wbad-function-cast (6 warnings, gcc and clang).
It now returns an XXH_endianness.
The casts at call sites are kept:
they still support an XXH_CPU_LITTLE_ENDIAN defined externally as an int,
including in C++ builds, where int to enum requires a cast.
Generated code is unchanged (-O3).
-Wbad-function-cast is added to the lib/Makefile warning flags,
so that a regression is caught by existing builds.
Reported, with a first fix, by @brainy in #1711.
Like LZ4_FAST_DEC_LOOP, LZ4_WILDCOPY64 can now be enabled or disabled
at compile time (-DLZ4_WILDCOPY64=0 or 1). Defaults are unchanged.
#1804 proposed enabling both on wasm, measuring +21% (default) and
+25% (HC9) decompression speed on Apple M5 (Chromium 153, wasm32).
But a wasm module runs on any host, and on x86-64 the same change slows
decompression down (AMD Zen 4, Node 24 / V8 13.6, wasm32, Silesia) :
-5.8% / -7.8% on #1804's baseline, -1.5% / -3% on current dev,
with text files losing 10-20% and poorly compressible ones gaining 20-40%.
So it's not enabled by default : a wasm build can opt in with
-DLZ4_FAST_DEC_LOOP=1 -DLZ4_WILDCOPY64=1, which produces the same module
as #1804 applied on dev.
Based on #1804 by @JCash.
Block header, compressed block, block checksum, frame checksum and
skippable frame size each gathered their input bytes their own way,
4 of them with a "get" stage reading directly from src and a "store"
stage buffering it, joined by case labels nested within if statements.
LZ4F_gatherInput() now provides these bytes, directly from src when
possible, otherwise buffered over as many calls as needed: each pair
becomes a single stage, 11 stages instead of 15.
The frame header keeps its 2 stages: LZ4F_getFrameInfo() relies on
dstage_storeFrameHeader, and LZ4F_decodeHeader() may request more bytes.
No behavior change.
`mflimitPlusOne` and `matchlimit` pointed before the input buffer
for inputs shorter than `LZ4_minLength`, which is undefined behavior.
They are only used once the input is long enough.
The 2 compressed block decoding paths (directly into dst, or into tmpOut)
duplicated the dictionary size clamp, LZ4_decompress_safe_usingDict(), its
error check, and the content checksum and remaining size updates: they now
share LZ4F_decodeBlock(). The history move in front of tmpOut, written both
in LZ4F_updateDict() and at the end of LZ4F_decompress(), becomes
LZ4F_moveDictBeforeTmpOut().
Also fix a wrong comment and a stray unary plus.
No behavior change.
C++ forbids including a header within a linkage specification.
With clang C++ modules (-fmodules), including lz4.h or lz4hc.h failed :
<stdint.h> was imported within extern "C".
All public headers (lz4.h, lz4hc.h, lz4frame.h, lz4file.h) now include
their dependencies before opening extern "C".
New test : make -C tests test-cxx-modules (requires clang),
run in CI with clang on Linux, and on macOS.
Fixes#1600. Based on #1815 by @sergioperezcheco.
The 4 near-identical blocks evaluating a candidate (long or short match,
in prefix or extDict) are replaced by LZ4MID_countMatch(). Each search
stage now has a single "match found" exit: 3 goto instead of 5. The only
_lz4mid_dest_overflow use is inlined: 2 labels instead of 3.
Also fix 2 comments, and log found matches in one place.
No output change.
`lz4 -b -d` reserved 255x each frame's compressed size in the result
buffer, but capped that buffer at LZ4_MAX_INPUT_SIZE. With multiple
large files, later frames pointed past the buffer : 4 files of 4 MB
crash. The decode loop also recomputed each capacity with an INT_MAX
cap instead, so a frame decoding to more than LZ4_MAX_INPUT_SIZE wrote
out of bounds.
Decode-only output is not checked, so all frames now decode at the
start of the result buffer, each with its whole capacity. The capacity
is computed once, when the block table is laid out, and reused by the
decode loop.
Bug since v1.9.4. Based on #1770 by @arshsmith1
- actions/checkout v7.0.1, as in the other workflows
- vmactions/freebsd-vm v1.5.9, which ignores `usesh` (scripts always
run with sh), so the input is dropped
- list freebsd.yml in .github/workflows/README.md
The multithreaded legacy decoder updated the decoded-byte counter
in its decoding thread, while the writer thread read it to display progress :
a data race, reported by ThreadSanitizer on any multi-block legacy stream.
The writer thread is now the only one to update and read it.
The final count is read after both threads complete.
Found by @agamal026 in #1890
The kernel's legacy decoder also stops at a zero Block Compressed Size,
and initramfs builders (mkinitcpio) append these four zero bytes
to delimit concatenated archives.
lz4 passed it to LZ4_decompress_safe() as an empty block,
and failed with "Error 64 : Decoding Failed".
A zero block size now ends the legacy frame,
in both legacy decoders and in --list,
and decoding continues with the next frame, if any.
It was never a valid block : a compressed block is at least 1 byte.
The format spec (v1.6.5) documents it as an extension :
encoders should not produce it, since older decoders reject it.
Fixes#1889, based on #1890 by @agamal026
The beginning of a match was inserted at ipIndex, the index of the search
position. But ip moves before that point, when a longer match is found at
ip+1, or when the match is extended backward (catch back): these hash table
entries then pointed to the wrong position, and overwrote useful ones.
Level 2 compresses 0.1-0.5% better, and runs 0.8-1.9% fewer instructions.
BMK_benchMem() sized the compressed buffer with a margin of 1024 bytes
per block, computed as `maxNbBlocks * 1024` in 32-bit unsigned
arithmetic. With 4M blocks or more (e.g. `-B32` on a 128 MiB input),
the product wrapped around, and the buffer could be smaller than the
LZ4_compressBound() of all blocks, making the benchmark write past its end.
LZ4_compressBound() adds exactly 16 bytes per block, so the margin is
now `maxNbBlocks * 16`, computed in size_t.
A ring buffer of LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) is allocated statically,
so its size is compiled into applications.
16d695b1 enlarged its margin from 14 to 64 bytes to fit the fast loop's wide copies,
leaving applications compiled with the former macro exposed to corrupted output.
The decoder now also supports the former margin, without advertising it.
When decoding restarts at the beginning of the ring, the oldest referenced history
lies in the external dictionary, ahead of the write position, at a constant distance.
When this distance is shorter than FASTLOOP_SAFE_DISTANCE, the block is decoded by the safe loop,
which writes at most 16 bytes beyond a copy, like the v1.8.x decoder the 14-byte margin was sized for.
This only concerns rings with less than 64 bytes of margin, hence never the current macro,
for the blocks following such a restart, until 64 KB have been decoded.
Other decoders are unaffected : the test is resolved at compile time outside of usingExtDict.
Applications compiled with older headers decode correctly with the new library, without recompiling.
The static assert now checks what correctness requires, i.e. the safe loop's margin,
which the former macro satisfies : on this point, lz4.c compiles again with an older lz4.h (#1863).
The fuzzer's worst-case ring test now also runs with the former ring size :
it fails without this change, and passes with it.
The other ring condition (decoder ring larger than the encoder's by maxBlockSize + 64 bytes, 2f449bb3)
is unchanged : the decoder can't know the encoder's ring size.
With 3 ascending matches, m2 was already shifted against m1 at _Search3,
so the second LZ4HC_splitOverlap() always reduced to cutting m1 where m2
starts. Remove it, and share this path with the "no better m3" one,
which encodes m1 the same way: m2 then becomes m1, every sequence is
encoded as m1, and _dest_overflow no longer needs m1 = m2.
_Search3 becomes a loop: 3 labels instead of 4, 8 goto instead of 11.
Also add state comments, and fix 2 wrong ones: start0 != ip is common
after the restore test, and when m3 starts after m1, m2 is not removed,
it becomes the fallback m0.
No output change.
The safe loop had its own copy of LZ4_memcpy_using_offset_base(), the
fast loop's helper: the same inc32table/dec64table trick for offsets
below 8, followed by the same 8-byte copies. Use the shared helper.
It now supports offset 1 (first 4 bytes copied one by one), is defined
without LZ4_FAST_DEC_LOOP, and follows the safe loop's structure: the
second 8 bytes are copied directly, LZ4_wildCopy8() only beyond 16 bytes.
No behavior change. Instruction counts move by less than 0.15%.
Speed: +0% to +3.7% on all decoders without LZ4_FAST_DEC_LOOP;
with it, -3% to +4% depending on decoder and code alignment.
LZ4_wildCopy64 (long literals, and matches with offset >= 64) was restricted to aarch64.
It speeds up decompression on x86-64 too.
AMD EPYC Genoa (Zen 4), gcc 11, silesia files :
level 1 : +5.1% (geomean), up to +18.8% (sao) ; level 9 : +2.6%, up to +9.8% (xml).
Worst cases : -1.7% (sao, level 9), -1.0% (reymont, level 9).
Not enabled on 32-bit x86 (same machine, gcc 12 -m32) :
without SSE2 : -0.8% / -1.9% (geomean, levels 1 / 9), down to -10.2% (osdb) ;
with SSE2 : +0.7% / +1.0%, from -3.0% (sao) to +12.1% (x-ray).
Other targets are unchanged.
Proposed for x86 in #1407 by Nicolas De Carli.
Firefox stores its .jsonlz4 / .mozlz4 files (session, bookmark backups,
search engines) as "mozLz40\0", the decompressed size (LE32),
then a single LZ4 block extending to the end of the file
(see MozLZ4 in Firefox's xpcom/ioutils/IOUtils.cpp).
`lz4 -d`, `lz4 -t` and `lz4cat` now recognize this format,
and decode it like Firefox's reader :
- the decompressed size is only used as decoding capacity
- empty content has no block (Firefox doesn't write one)
Allocations are bounded : sizes > LZ4_MAX_INPUT_SIZE are rejected,
as they can't be compressed into a single block,
and input is read up to LZ4_COMPRESSBOUND(size), the largest valid block.
Data starting with the first half of the magic only ("mozL") keeps
the previous behavior : `lz4cat` passes it through unchanged,
including the bytes read to check the second half.
Decoding only : lz4 doesn't produce this non-standard format.
Based on #1259 by @UnitedMarsupials, rewritten for current dev
(no mmap, bounded allocations, full magic check, tests).
The fast loop checked for the safe loop handover twice, once per branch
of the match length token, and nested its 18-byte copy shortcut in the
short match branch. Decode the full match length first, then check the
handover once, then try the shortcut, in the same order as the safe loop.
No behavior change. Without LZ4_FAST_DEC_LOOP the generated code is
identical; with it, instruction counts move by less than 0.2%.