Skip to content

Add encoder selector to LZ4 plugin 32004 via cd_values[1]#267

Open
ajelenak wants to merge 3 commits into
masterfrom
lz4-encoder-param
Open

Add encoder selector to LZ4 plugin 32004 via cd_values[1]#267
ajelenak wants to merge 3 commits into
masterfrom
lz4-encoder-param

Conversation

@ajelenak

@ajelenak ajelenak commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

A new signed cd_values[1] slot selects LZ4HC level (positive), fast-encoder acceleration (negative), or the legacy LZ4_compress_default (zero/absent), with HC level clamped to [LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX]. The on-disk chunk format is unchanged, so existing archives and the reverse filter continue to work without modification.

Closes #244.

ajelenak and others added 2 commits June 26, 2026 19:23
A new signed `cd_values[1]` slot selects LZ4HC level (positive), fast-encoder
acceleration (negative), or the legacy LZ4_compress_default (zero/absent),
with HC level clamped to [`LZ4HC_CLEVEL_MIN`, `LZ4HC_CLEVEL_MAX`]. The on-disk
chunk format is unchanged, so existing archives and the reverse filter
continue to work without modification.
Comment thread LZ4/src/H5Zlz4.c Outdated
static int
lz4_encode(const char *src, char *dst, int srcSz, int dstCap, int encoderParam)
{
if (encoderParam > 0) {

@nhz2 nhz2 Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (encoderParam > 0) {
if (encodeParam < LZ4_CLEVEL_MIN) encodeParam = LZ4_CLEVEL_MIN;
if (encodeParam > LZ4_CLEVEL_MAX) encodeParam = LZ4_CLEVEL_MAX;
if (encoderParam >= LZ4HC_CLEVEL_MIN) {

long isn't always wider than int so I think it is safer to clamp encoderParam to its usable range at the start before doing any math.

Using >= LZ4HC_CLEVEL_MIN matches the behavior in https://github.com/lz4/lz4/blob/6cf42afbea04c9ea6a704523aead273715001330/lib/lz4frame.c#L956

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LZ4_CLEVEL_MAX is LZ4HC_CLEVEL_MAX and LZ4_CLEVEL_MIN is -(LZ4_ACCELERATION_MAX - 1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — long is 32-bit on LLP64, so the widen-before-negate didn't actually protect INT_MIN. Now clamped to the usable range before any arithmetic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LZ4_CLEVEL_MAX is LZ4HC_CLEVEL_MAX and LZ4_CLEVEL_MIN is -(LZ4_ACCELERATION_MAX - 1)

Those aren't exposed as macros in liblz4, so I used LZ4HC_CLEVEL_MAX for the upper bound and a locally-mirrored, #ifndef-guarded LZ4_ACCELERATION_MAX for the lower bound.

@nhz2 nhz2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but I think we should try to match how compression levels are used in the lz4frame library.

Here is my understanding of how it works there.

-65536 to -1 are mapped to fast mode with acceleration 65537 to 2

0 and 1 are default fast mode.

2 to 12 are LZ4HC levels.

Everything outside -65536 to 12 is clamped to that range.

Comment thread LZ4/src/H5Zlz4.c Outdated
}
else if (encoderParam < 0) {
/* Widen before negating so encoderParam == INT_MIN cannot overflow. */
long accel = -(long)encoderParam;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int const acceleration = (level < 0) ? -level + 1 : 1;

Is the standard conversion from lz4frame: https://github.com/lz4/lz4/blob/6cf42afbea04c9ea6a704523aead273715001330/lib/lz4frame.c#L913

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, done.

Comment thread LZ4/src/H5Zlz4.c Outdated
level = LZ4HC_CLEVEL_MAX;
return LZ4_compress_HC(src, dst, srcSz, dstCap, level);
}
else if (encoderParam < 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else if (encoderParam < 0) {
else {

Comment thread LZ4/src/H5Zlz4.c Outdated
* LZ4_ACCELERATION_MAX (currently 65537; see lz4.c). */
return LZ4_compress_fast(src, dst, srcSz, dstCap, (int)accel);
}
return LZ4_compress_default(src, dst, srcSz, dstCap);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return LZ4_compress_default(src, dst, srcSz, dstCap);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both done — the fast branch now handles 0/1 at acceleration 1, which is exactly LZ4_compress_default.

@ajelenak ajelenak requested a review from nhz2 July 7, 2026 04:41

@nhz2 nhz2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Added a few minor comments and a suggestion for improved error handling.

Comment thread LZ4/src/H5Zlz4.c
blockSize = nbytes;
}
nBlocks = (nbytes - 1) / blockSize + 1;
maxDestSize = nBlocks * LZ4_compressBound(blockSize) + 4 + 8 + nBlocks * 4;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before the call to malloc we should check that LZ4_compressBound(blockSize) hasn't overflowed.
This also protects the (int)blockSize casts later on.
Something like:

Suggested change
if (blockSize > (size_t)LZ4_MAX_INPUT_SIZE) {
goto error;
}
maxDestSize = nBlocks * LZ4_compressBound(blockSize) + 4 + 8 + nBlocks * 4;

Comment thread LZ4/LZ4_HDF5_format.md
Comment on lines +46 to +55

## Encoder choice

The on-disk format above is independent of the encoder the writer used.
The plugin's `cd_values[1]` selects between `LZ4_compress_default`
(value `0`, the default), `LZ4_compress_HC` (positive value, HC level),
and accelerated `LZ4_compress_fast` (negative value, acceleration =
`-cd_values[1]`). All three produce LZ4 Block payloads that the same
`LZ4_decompress_safe` path reads, so files written with any encoder
choice are bitstream-compatible with readers that only know the default.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the information in the RegisteredFilterPlugins.md so can be removed to avoid it getting outdated. This document is to describe the on disk format, so the compression level details are not need here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add LZ4HC and fast-encoder acceleration parameter to LZ4 plugin 32004

3 participants