Skip to content

fix(encoding): bound the dms-table hash-log floor by the matcher hash_log#459

Merged
polaz merged 3 commits into
mainfrom
fix/dict-bt-prime-min-max
Jun 30, 2026
Merged

fix(encoding): bound the dms-table hash-log floor by the matcher hash_log#459
polaz merged 3 commits into
mainfrom
fix/dict-bt-prime-min-max

Conversation

@polaz

@polaz polaz commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a panic when compressing with a dictionary on a binary-tree strategy
(level ≥ 13) and a small source: prime_dms_bt (the dictionary match
binary-tree, a ZSTD_dictMatchState analog) sized its hash table with

ceil_log2(region).clamp(10, hash_log)

A tiny source makes ZSTD_adjustCParams legitimately shrink the matcher's
hash_log below 10, so the clamp floor (10) exceeds the ceiling (hash_log),
and std's clamp panics:

panicked at storage.rs: min > max. min = 10, max = 7

Non-BT strategies (Fast / Dfast / Lazy, levels 1–12) never call prime_dms_bt,
so dictionary compression there was unaffected; only the BT dict path hit it.

Root cause

The 10 floor was a spurious minimum with no upstream-C counterpart: C sizes
the dms table from the dictionary's own (ZSTD_createCDict) cParams hashLog,
which is itself adjusted down for small dictionaries — there is no fixed 10-bit
floor. A hash_log below 10 is therefore a valid adjusted value, not an
invariant violation; the bug is that the floor could exceed the ceiling.

Fix

Lower the floor to min(10, hash_log):

  • hash_log ≥ 10 → the floor stays 10 (unchanged; byte-identical for every
    existing fixture and the whole bench corpus).
  • hash_log < 10 → the floor collapses to hash_log, so the clamp stays valid
    and the dms table simply matches the (small) live-table width — exactly what
    the dictionary's cParams hashLog yields in upstream for the same small dict.

The sizing was extracted into storage::dms_hash_log so the bound is covered by
a direct unit test (no need to coax the full compressor into a hash_log < 10
configuration, which the current window-log floor otherwise masks).

Testing (test-first)

  • storage::dms_hash_log_tests — calls dms_hash_log with hash_log < 10;
    this failed with the exact min > max panic before the fix and asserts a
    sane table width after, plus pins the unchanged hash_log ≥ 10 floor/cap.
  • frame_compressor::tests::dict_compress_bt_level_tiny_source_round_trips_through_prime_dms_bt
    — end-to-end BtUltra2 + raw-content dictionary + tiny source round-trip
    through the BT dict-prime + dict-match path.
  • cargo nextest run -p structured-zstd --features hash,std,dict_builder — 841 pass
  • cargo nextest run -p ffi-bench --features bench_internals,dict_builder — 59 pass
  • clippy (default + --tests, --no-default-features --features kernel_scalar,hash),
    cargo fmt --check — clean

Note

The 10 floor for hash_log ≥ 10 is pre-existing and intentionally preserved
here for byte-identity. Dropping it entirely (fully matching upstream, which has
no floor) would change the dms table width for tiny dictionaries at
hash_log ≥ 10 and thus the emitted bytes — a separate behaviour-changing
optimisation, out of scope for this fix.

Summary by CodeRabbit

  • Bug Fixes
    • Improved dictionary-based compression reliability for very small inputs.
    • Prevented a rare crash when using dictionaries with lower compression settings.
    • Added coverage to ensure compressed data still round-trips correctly with matching dictionaries.

polaz added 2 commits June 30, 2026 12:54
Extract the dms-table hash-log sizing into `storage::dms_hash_log` and cover it:
a tiny source adjusts a BT matcher's `hash_log` below 10, and the
`ceil_log2(region).clamp(10, hash_log)` sizing then panics 'min > max'
(min = 10, max = 7) when priming the dictionary match binary-tree (level >= 13
with a dictionary). The unit test calls `dms_hash_log` with hash_log < 10 and
FAILS on this commit; a BtUltra2 dict round-trip exercises the path end to end.

Fix follows separately.
…_log

prime_dms_bt sized the dictionary match binary-tree with
ceil_log2(region).clamp(10, hash_log). When a tiny source adjusts the BT
matcher's hash_log below 10, the clamp floor (10) exceeds the ceiling
(hash_log), so std clamp panics 'min > max'. Lower the floor to
min(10, hash_log): at hash_log >= 10 it is the unchanged 10-bit minimum, and
below 10 it collapses to hash_log so the dms table just matches the (small)
live-table width. Byte-identical for every hash_log >= 10 configuration (all
existing fixtures); only the previously-panicking small-BT-dict path changes.

Closes the level >= 13 + dictionary regression from 0.0.46.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f253927c-7e8b-42d9-b94a-de92bd3632f0

📥 Commits

Reviewing files that changed from the base of the PR and between e20b0b3 and 784402f.

📒 Files selected for processing (3)
  • zstd/src/encoding/frame_compressor/tests.rs
  • zstd/src/encoding/match_table/storage.rs
  • zstd/src/encoding/match_table/storage/dms_hash_log_tests.rs

📝 Walkthrough

Walkthrough

Extracts a new dms_hash_log helper in match_table/storage.rs that safely clamps dictionary match-state hash-log computation for small hash_log values (below 10), preventing a panic. The build_dms! macro is updated to call this helper. Unit tests and an integration test for BT-strategy dictionary compression at level 19 are added.

DMS hash-log fix and tests

Layer / File(s) Summary
dms_hash_log helper and build_dms! update
zstd/src/encoding/match_table/storage.rs
Adds pub(crate) fn dms_hash_log(region, hash_log) that computes ceil_log2(region) clamped to [min(10, hash_log), hash_log], replacing the prior inline clamp in build_dms! that panicked when hash_log < 10.
Unit tests for dms_hash_log
zstd/src/encoding/match_table/storage/dms_hash_log_tests.rs
Regression test covering the previously-panicking hash_log < 10 case (asserting output in [1, hash_log]) and a behavior test for the 10-bit floor and cap-at-hash_log semantics.
BT dictionary round-trip integration test
zstd/src/encoding/frame_compressor/tests.rs
New #[test] builds a raw dictionary, compresses "hello world" at level 19, and asserts byte-exact round-trip via FrameDecoder primed with the same dictionary.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

A rabbit found a clamp gone wrong,
When hash_log dipped below ten all along,
A helper was born, bounds properly set,
No panic, no crash — no cause to fret!
🐇 Round-trips now pass, the dictionary's pleased~

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: bounding the DMS hash-log floor by the matcher hash_log to prevent invalid clamp behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dict-bt-prime-min-max

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes dictionary compression for tiny BT-strategy inputs. The main changes are:

  • Extracted DMS table sizing into dms_hash_log.
  • Bounded the 10-bit floor by the live matcher hash log.
  • Added direct unit coverage for small hash logs.
  • Added an end-to-end BT dictionary round-trip test.

Confidence Score: 5/5

The change is narrowly scoped to DMS hash-log sizing and is covered by both direct sizing tests and a dictionary compression round-trip.

The patch preserves existing behavior for larger hash logs while handling the small-input case that previously panicked, with focused tests covering both paths.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex ran the requested verification for the pull request, but its local artifact references were not uploaded.

T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/dict-bt-pri..." | Re-trigger Greptile

@polaz polaz merged commit e9a85a7 into main Jun 30, 2026
8 checks passed
@polaz polaz deleted the fix/dict-bt-prime-min-max branch June 30, 2026 10:25
@sw-release-bot sw-release-bot Bot mentioned this pull request Jun 30, 2026
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.

1 participant