Skip to content

perf(encoding): early incompressible fast-path + benchmark parity#99

Merged
polaz merged 31 commits intomainfrom
perf/#97-early-incompressible-fastpath
Apr 11, 2026
Merged

perf(encoding): early incompressible fast-path + benchmark parity#99
polaz merged 31 commits intomainfrom
perf/#97-early-incompressible-fastpath

Conversation

@polaz
Copy link
Copy Markdown
Member

@polaz polaz commented Apr 9, 2026

Summary

  • align donor-style hinted small-frame parity in Rust framing: single_segment=true for hinted one-shot payloads up to 16 KiB across levels
  • keep C-FFI correctness on tiny compressed frames via temporary compat guard: payloads below 512 stay on non-single-segment framing
  • make dictionary priming dense explicitly for Row backend (skip_matching_with_hint(Some(false)))
  • seed remaining Dfast hashable tail starts before trailing literals in both parse loops
  • harden Fastest-level early-exit tests by asserting returned BlockType from compress_block_encoded()

Current behavior (HEAD)

  • hinted one-shot frames (source_size_hint <= 16 KiB) now use single-segment framing across levels when:
    • no dictionary state is active
    • total payload is in [512 ..= 16 KiB]
  • sub-512 hinted compressed-path payloads remain non-single-segment for C-FFI compatibility
  • Row dictionary priming path is explicitly dense
  • Dfast general/fast loops now backfill remaining hashable tail starts before emitting trailing literals

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --features hash,std,dict_builder -- -D warnings
  • cargo nextest run --workspace --features hash,std,dict_builder
  • STRUCTURED_ZSTD_EMIT_REPORT=1 cargo bench -p structured-zstd --bench compare_ffi --features hash,std,dict_builder -- 'compress/fastest/small-4k-log-lines/matrix'
    • parity for the user-reported case:
      • REPORT_HDR scenario=small-4k-log-lines level=fastest encoder=rust ... single_segment=true
      • REPORT_HDR scenario=small-4k-log-lines level=fastest encoder=ffi ... single_segment=true

Notes

  • this PR remains performance-first on incompressible/early-exit paths while preserving C-FFI decode correctness
  • known follow-up: remove sub-512 compat guard once 1-byte-FCS single-segment compressed-path compatibility is fully aligned

Closes #97

Summary by CodeRabbit

  • Improvements

    • Better fast-path for incompressible/raw blocks with stricter detection and level/window gating.
    • Frame header logic refined: single-segment headers are now conditional for small pledged payloads and respect dictionary presence and window constraints.
    • Dictionary-state only enabled when an attached dictionary is present.
  • Tests

    • Expanded/refactored tests for raw-block behavior, single-segment framing, hinted sizes, level/window edge cases, and clearer decode failure messages.
    • Relaxed one cross-validation size-ratio assertion.
  • Bench

    • New benchmark helper and enhanced frame-header reporting.
  • Chore

    • Added AGENTS.md to .gitignore.

- add heuristic early no-compress path for fastest/default/level<=3

- add dfast lazy-skipping in skip_matching for incompressible blocks

- preserve boundary seeding for cross-block matching and add coverage

- keep roundtrip + cross-validation + benchmark compatibility
Copilot AI review requested due to automatic review settings April 9, 2026 23:05
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e902dc33-73eb-4003-b167-a89a1cc62c71

📥 Commits

Reviewing files that changed from the base of the PR and between 73361d5 and 882dfd7.

📒 Files selected for processing (3)
  • .gitignore
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/streaming_encoder.rs

📝 Walkthrough

Walkthrough

Compute a small-source hint; add a sampling-based incompressibility classifier and raw fast-path; thread Option incompressible hints through matchers; pass CompressionLevel into block encoding; gate dictionary priming on an attached dictionary; emit single-segment headers only when small-frame + no-dictionary conditions hold.

Changes

Cohort / File(s) Summary
Frame & encoder call sites
zstd/src/encoding/frame_compressor.rs, zstd/src/encoding/streaming_encoder.rs
Derive small_source_hint from source_size_hint; require self.dictionary.is_some() to enable dictionary-state/priming; forward compression_level into compress_block_encoded; compute single_segment conditionally and omit window_size when true.
Raw fast-path & classifier
zstd/src/encoding/incompressible.rs, zstd/src/encoding/mod.rs
Add incompressible module with sampling-based incompressibility classifier and constants; expose compression_level_allows_raw_fast_path, block_looks_incompressible/_strict; add BETTER_WINDOW_LOG and default Matcher::skip_matching_with_hint.
Fastest block emitter
zstd/src/encoding/levels/fastest.rs, zstd/src/encoding/levels/mod.rs
Change compress_block_encoded visibility to crate-only and signature to accept CompressionLevel and return BlockType; implement RLE hint semantics, raw fast-path (commit raw bytes + write Raw header) and stricter incompressibility gating for Best.
Match generator / seeding
zstd/src/encoding/match_generator.rs
Thread Option<bool> incompressible hints through driver/backends; add skip_matching_with_hint and skip_matching_for_dictionary_priming; implement sparse-prefix + dense-tail seeding, Dfast fast-loop split, and related helper refactors.
Tests & benches
zstd/tests/..., zstd/benches/compare_ffi.rs
Refactor/expand tests with deterministic payload helpers and first-block parsing; assert single-segment behavior at small sizes and add raw-fast-path regression tests; add ffi_encode_all_aligned and frame-header reporting in benches; tighten contextual panics.
Visibility & minor fixes
zstd/src/encoding/mod.rs, zstd/src/encoding/levels/mod.rs, .gitignore
Declare incompressible module; fix doc typos; add shim skip_matching_with_hint default on Matcher; change compress_block_encoded re-export to crate visibility; ignore AGENTS.md.

Sequence Diagram(s)

sequenceDiagram
  participant Stream as rgba(20,120,200,0.5) StreamingEncoder
  participant Frame as rgba(100,200,120,0.5) FrameCompressor
  participant Class as rgba(200,120,100,0.5) IncompressibleModule
  participant Matcher as rgba(160,100,220,0.5) MatchGenerator
  participant Block as rgba(220,180,80,0.5) compress_block_encoded

  Stream->>Frame: encode_block(data, source_size_hint, compression_level)
  Frame->>Class: sample -> block_looks_incompressible(block)
  alt allowed_by_level_and_window AND looks_incompressible
    Frame->>Matcher: skip_matching_with_hint(Some(true))
    Frame->>Block: compress_block_encoded(..., compression_level) -> BlockType::Raw + raw bytes
  else
    Frame->>Matcher: skip_matching_with_hint(None)
    Frame->>Block: compress_block_encoded(..., compression_level) -> BlockType::Compressed / RLE
  end
  Block-->>Frame: encoded block bytes + BlockType
  Frame->>Frame: compute single_segment (depends on small hint, dict-state, totals) and set window_size accordingly
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐇 I nibble bytes where patterns hide and play,
I probe the head and tail and hop the gray,
I skip and seed with sparse tails left behind,
I sprint the raw-fast path when entropy's unkind,
Tiny frames snug — thump-thump, compression hooray!

🚥 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 'perf(encoding): early incompressible fast-path + benchmark parity' accurately summarizes the main changes, focusing on the key performance optimization (early incompressible fast-path) and benchmark alignment improvements.
Linked Issues check ✅ Passed All primary objectives from issue #97 are addressed: early incompressible fast-path implemented across target levels, matcher seeding correctness hardened, dictionary priming routed correctly, benchmark parity normalized, and C-FFI interoperability preserved.
Out of Scope Changes check ✅ Passed All changes are within scope of issue #97: incompressible module, matcher seeding hardening, frame header optimization, compression level gating, benchmark parity fixes, and supporting test additions align with stated objectives.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 perf/#97-early-incompressible-fastpath

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 96.48986% with 45 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
zstd/src/encoding/match_generator.rs 96.65% 21 Missing ⚠️
zstd/src/encoding/frame_compressor.rs 96.24% 10 Missing ⚠️
zstd/src/encoding/levels/fastest.rs 92.03% 9 Missing ⚠️
zstd/src/encoding/mod.rs 0.00% 3 Missing ⚠️
zstd/src/encoding/incompressible.rs 99.16% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a conservative “incompressible input” fast-path to the Rust encoder so high-entropy blocks can bypass expensive match-finding / entropy setup and emit Raw blocks quickly, improving throughput for Fastest/Default and low numeric levels.

Changes:

  • Thread CompressionLevel into the shared compressed-block path (frame + streaming) and add a raw fast-path heuristic for likely-incompressible blocks.
  • Add dfast matcher “lazy skipping” during skip_matching() for blocks classified as incompressible, while still densely seeding the tail for cross-block matching.
  • Add regression tests covering raw fast-path on random payloads and ensuring compressible log-like inputs don’t incorrectly fall back to Raw.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
zstd/src/encoding/streaming_encoder.rs Passes the active CompressionLevel into the shared block compression routine for streaming mode.
zstd/src/encoding/match_generator.rs Adds incompressible detection + sparse hash insertion in dfast skip_matching(), with dense tail seeding.
zstd/src/encoding/levels/fastest.rs Implements the early Raw emission heuristic for eligible levels and wires it into compress_block_encoded.
zstd/src/encoding/frame_compressor.rs Wires the level into the shared block path for one-shot/frame mode and adds targeted regression tests.

Comment thread zstd/src/encoding/levels/fastest.rs Outdated
Comment thread zstd/src/encoding/levels/fastest.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zstd/src/encoding/match_generator.rs (1)

1643-1665: ⚠️ Potential issue | 🟠 Major

Keep dictionary priming on the dense seeding path.

skip_matching() is also used from MatchGeneratorDriver::prime_with_dictionary() on Lines 637-638. With this change, any ≥1 KiB dictionary chunk that looks high-entropy is only indexed every 8 bytes plus the tail, so first-block matches into most dictionary positions disappear. That regresses dictionary-backed compression even though no raw block was emitted. Please bypass the incompressible sparse path when seeding primed dictionary history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/match_generator.rs` around lines 1643 - 1665, The current
skip_matching() uses sparse insertion for blocks that look incompressible, which
breaks priming from MatchGeneratorDriver::prime_with_dictionary(); modify
skip_matching() (or add a new call path) so that when invoked for dictionary
priming it always uses the dense seeding path: detect the priming call (e.g. add
a boolean flag on MatchGenerator or a new method skip_matching_for_priming())
and bypass the insert_positions_with_step(..., INCOMPRESSIBLE_SKIP_STEP) branch,
calling insert_positions(...) for the whole range and still performing the dense
tail insertion (keep dense_tail/tail_start logic intact) so dictionary history
is fully indexed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 657-679: This test only exercises CompressionLevel::Fastest; add a
second assertion exercising CompressionLevel::Default (and optionally one of the
Level(0..=3) variants) to ensure compressible input doesn't fall back to
BlockType::Raw for the default path: call
crate::encoding::compress_to_vec(data.as_slice(),
super::CompressionLevel::Default) (and/or super::CompressionLevel::Level(3)),
assert_ne!(first_block_type(&compressed_default), BlockType::Raw), assert
compressed_default.len() < data.len(), and round-trip decode via
zstd::stream::copy_decode to verify output equals data, mirroring the existing
Fastest checks.

In `@zstd/src/encoding/levels/fastest.rs`:
- Around line 86-146: The current should_emit_raw_fast_path only inspects the
block prefix (sample = &block[..sample_len]), causing misclassification for
mixed-entropy inputs; change the sampling to use a head+tail or strided probe
(e.g., take up to sample_len/2 from the start and sample_len/2 from the end, or
evenly spaced strides through the whole block) and feed those bytes into the
same histogram and 4-byte-quad repeat logic (update counts, sampled_quads,
repeats and distinct computation to reflect the combined sample), or perform a
quick second probe on a tail sample before returning true; keep existing guards
(max_symbol_guard, repeat_guard) but compute them from the effective sampled
length so the decision in should_emit_raw_fast_path reflects the whole-block
entropy rather than only the prefix.

---

Outside diff comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 1643-1665: The current skip_matching() uses sparse insertion for
blocks that look incompressible, which breaks priming from
MatchGeneratorDriver::prime_with_dictionary(); modify skip_matching() (or add a
new call path) so that when invoked for dictionary priming it always uses the
dense seeding path: detect the priming call (e.g. add a boolean flag on
MatchGenerator or a new method skip_matching_for_priming()) and bypass the
insert_positions_with_step(..., INCOMPRESSIBLE_SKIP_STEP) branch, calling
insert_positions(...) for the whole range and still performing the dense tail
insertion (keep dense_tail/tail_start logic intact) so dictionary history is
fully indexed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 840a716f-439b-4858-afe8-2ab9170aae67

📥 Commits

Reviewing files that changed from the base of the PR and between 40b6e89 and 1fd3f10.

📒 Files selected for processing (4)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/streaming_encoder.rs

Comment thread zstd/src/encoding/frame_compressor.rs
Comment thread zstd/src/encoding/levels/fastest.rs Outdated
- factor incompressible heuristic into shared helper + constants

- switch heuristic sampling to head+tail to avoid prefix-only bias

- keep dictionary priming on dense dfast seeding path

- extend compressible negative-path tests for default and level(3)
@polaz polaz requested a review from Copilot April 9, 2026 23:17
@polaz
Copy link
Copy Markdown
Member Author

polaz commented Apr 9, 2026

@coderabbitai Addressed the outside-diff item on dictionary priming: MatchGeneratorDriver::prime_with_dictionary now uses a dedicated dense seeding path for dfast (skip_matching_for_dictionary_priming -> skip_matching_dense), so incompressible sparse stepping is not used for primed dictionary history.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

Tip

For best results, initiate chat on the files or code changes.

@polaz: Thanks for the update! That's a clean separation — routing dictionary priming through a dedicated skip_matching_dense path guarantees the incompressible heuristic and sparse step insertion never interfere with history seeding for primed dictionaries. The cross-block matching invariants for dictionary-compressed streams are preserved correctly.


✏️ Learnings added
Learnt from: polaz
URL: https://github.com/structured-world/structured-zstd/pull/99

Timestamp: 2026-04-09T23:17:57.736Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `MatchGeneratorDriver::prime_with_dictionary` uses a dedicated dense seeding path for the `DfastMatchGenerator` backend: `skip_matching_for_dictionary_priming` routes to `skip_matching_dense`, bypassing the incompressible heuristic and sparse step insertion (`insert_positions_with_step`). This ensures primed dictionary history is always densely seeded and cross-block matching invariants are preserved for dictionary-compressed streams. Do not flag `prime_with_dictionary` as potentially using sparse/incompressible stepping.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: polaz
Repo: structured-world/structured-zstd PR: 48
File: zstd/src/encoding/match_generator.rs:336-339
Timestamp: 2026-04-02T12:03:03.856Z
Learning: In `zstd/src/encoding/match_generator.rs`, `min_primed_tail = 4` is correct for both `MatcherBackend::Dfast` and `MatcherBackend::HashChain` because `HcMatchGenerator::insert_position` unconditionally requires 4 bytes of hash lookahead (`if idx + 4 > concat.len() { return; }`). `backfill_boundary_positions` only re-inserts positions already fully within extended history; it cannot make a <4-byte dictionary tail fragment hashable. Do not suggest lowering `min_primed_tail` below 4 for the `HashChain` backend.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-01T06:49:40.012Z
Learning: In the structured-zstd codebase (`zstd/src/encoding/match_generator.rs`), `CompressionLevel::Default` uses the `DfastMatchGenerator` backend (with `pick_lazy_match` for lazy evaluation), while `CompressionLevel::Better` uses the `HcMatchGenerator` (hash chain backend) which inserts positions before lazy evaluation inside its own `start_matching` loop. These are distinct backends with separate lazy-seeding paths.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-01T06:49:34.233Z
Learning: In the `structured-zstd` codebase (`zstd/src/encoding/match_generator.rs`), `CompressionLevel::Default` uses the `DfastMatchGenerator` backend (with `pick_lazy_match` lazy evaluation), while `CompressionLevel::Better` uses the `HcMatchGenerator` (hash chain backend) which inserts positions before lazy evaluation inside its own `start_matching` loop. These two backends are independent; review comments about Dfast internals (e.g. position seeding order in `pick_lazy_match`) do not apply to the Better level's implementation path.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-05T08:17:53.856Z
Learning: In `zstd/src/encoding/frame_compressor.rs` (structured-world/structured-zstd), `set_source_size_hint` applies only to the payload (uncompressed source) bytes, not to dictionary priming. Dictionary priming is separate and does not inflate the advertised frame window size. Do not suggest including dictionary bytes in the size hint passed to `set_source_size_hint`.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/tests/roundtrip_integrity.rs:498-509
Timestamp: 2026-04-02T22:26:07.979Z
Learning: In `structured-zstd` (`zstd/src/tests/roundtrip_integrity.rs`), `best_level_does_not_regress_vs_better` uses a `<=` (not strict `<`) assertion because the `repeat_offset_fixture(b"HelloWorld", ...)` input is simple enough that HC saturates at both Better (16 candidates) and Best (32 candidates) search depths, producing identical compressed sizes (~30243 bytes). Strict `<` would be a false positive on this fixture. The strict `Best < Better` quality assertion lives in `cross_validation::best_level_beats_better_on_corpus_proxy` on the decodecorpus sample. Do not re-flag the `<=` as a weakened guard.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-06T01:40:24.378Z
Learning: In `zstd/benches/compare_ffi.rs` (structured-world/structured-zstd), Rust FastCOVER trains with the post-finalization content budget in both the `REPORT_DICT_TRAIN` emission path (around lines 208-225) and the Criterion benchmark path (around lines 266-280). Both paths were aligned in commit 8622344. Do not flag these ranges as using inconsistent budget values.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-08T16:00:30.438Z
Learning: In `zstd/src/encoding/match_generator.rs`, the `RowMatchGenerator` (used for `CompressionLevel::Level(4)` / `MatcherBackend::Row`) uses a stable 4-byte hash key, consistent with the 4-byte lookahead constraint shared by `HcMatchGenerator`. Previous-block tail positions are backfilled into the row tables before matching/skip begins (analogous to `backfill_boundary_positions` for the HC backend), and a regression test covers cross-boundary tail reuse. Do not flag missing backfill or key-width issues for the Row backend.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/tests/roundtrip_integrity.rs:479-523
Timestamp: 2026-04-02T19:19:44.750Z
Learning: In `structured-zstd` (`zstd/src/tests/roundtrip_integrity.rs`), the per-level roundtrip test suites (Better, Best) share a common `level_roundtrip_suite!` macro that expands the same 7-test matrix (compressible, random, multi-block, streaming, edge cases, repeat offsets, large literals) for each level via module-scoped macro invocations. Do not suggest re-inlining these as separate flat test functions — the macro was added intentionally to eliminate cross-level drift.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/encoding/frame_compressor.rs:280-287
Timestamp: 2026-04-02T16:31:58.167Z
Learning: In `structured-zstd`, `CompressionLevel::Best` intentionally uses `MatcherBackend::HashChain` (not a binary-tree/DUBT backend) due to memory constraints: a BT matcher for a 16 MiB window requires ~128–256 MB of tables (C zstd level 11 uses chainLog=25 → 256 MB), which is impractical. Best is distinguished from Better by deeper HC search (32 vs 16 candidates), larger hash/chain tables (2M/1M vs 1M/512K), and higher target match length (128 vs 48), keeping memory under 20 MB. It achieves a proportional compression gain (~0.27% better than Better on corpus) comparable to C zstd's L7→L11 delta (~0.14%). A proper BT backend is deferred as a future optimization. Do not flag Best's use of HashChain as missing a BT implementation.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-05T07:19:26.176Z
Learning: In `cli/src/main.rs` (structured-world/structured-zstd), path-based compression calls `set_source_size_hint(source_size as u64)` (NOT `set_pledged_content_size`) for the filesystem metadata size. This is intentional: `metadata().len()` is unreliable for pseudo-files (`/proc/*`), FIFOs, and growing inputs, so the hint must remain advisory (only scaling matcher/window parameters) and must not impose a hard byte contract on the encoder. Do not flag the absence of `set_pledged_content_size` in path-based compression as a missing feature.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 48
File: zstd/src/encoding/match_generator.rs:1554-1559
Timestamp: 2026-04-02T12:03:04.619Z
Learning: In `zstd/src/encoding/match_generator.rs`, `HcMatchGenerator::insert_position` has an early-return guard at `abs_pos >= u32::MAX` that disables all further HC insertions for streams >4 GiB, degrading `CompressionLevel::Better` to repcodes-only. This is a known limitation tracked in issue `#51`; the proper fix (table rebasing) is deferred. A TODO comment and a limitation note on `CompressionLevel::Better` doc were added in PR `#48`. Do not re-flag this as a new bug.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-08T21:51:51.926Z
Learning: In `zstd/src/decoding/ringbuffer.rs` (structured-world/structured-zstd), the test suite for `copy_bytes_overshooting` includes a misaligned extra-capacity case that exercises the rounded-up wildcopy path (where `copy_multiple > copy_at_least`) and validates prefix correctness for that scenario. Do not flag the absence of a misaligned/overshooting extra-capacity sub-case as missing test coverage.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread zstd/src/encoding/incompressible.rs
Comment thread zstd/src/encoding/incompressible.rs Outdated
Comment thread zstd/src/encoding/match_generator.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/levels/fastest.rs`:
- Around line 49-58: The matcher is redundantly recomputing the "incompressible"
verdict inside state.matcher.skip_matching(); modify the call sites and matcher
API so the precomputed result from should_emit_raw_fast_path(compression_level,
&uncompressed_data) is threaded into the skip path (e.g., add a parameter or
setter to Matcher such as skip_matching(incompressible: bool) or
set_incompressible_then_skip()), then call state.matcher.commit_space(...)
followed by state.matcher.skip_matching(true) (or the equivalent setter + skip)
so skip_matching reuses the already-known incompressible verdict instead of
calling block_looks_incompressible(...) again. Ensure you update the Matcher
methods (skip_matching signature and any internal use of
block_looks_incompressible) and all callers accordingly (references:
should_emit_raw_fast_path, state.matcher.commit_space,
state.matcher.skip_matching).

In `@zstd/src/encoding/match_generator.rs`:
- Around line 1659-1675: The extra dense-tail reseed should only run when the
sparse insertion path was taken; change the logic so the tail re-insertion
(using dense_tail, tail_start, insert_positions) is executed only when
block_looks_incompressible(...) is true (i.e., the branch that called
insert_positions_with_step), leaving the full dense insert branch
(insert_positions(current_abs_start, current_abs_end)) untouched to avoid
redundant work and keep skip_matching() lean. Locate the block containing
block_looks_incompressible(current_abs_start, current_abs_end),
insert_positions_with_step, insert_positions, and the tail calculation
(dense_tail, tail_start) and move or guard the tail insert so it runs only for
the sparse branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5886a03d-eb16-47b6-a3be-579337a33129

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd3f10 and c5b41c2.

📒 Files selected for processing (5)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/incompressible.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/mod.rs

Comment thread zstd/src/encoding/levels/fastest.rs Outdated
Comment thread zstd/src/encoding/match_generator.rs Outdated
- avoid repeat sentinel collisions via explicit slot occupancy tracking

- thread precomputed incompressible hints through matcher skip API

- run dense tail reseed only for sparse dfast insertion path

- add regression test for sparse incompressible cross-block tail matching
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 471-481: Tests contain a duplicate generate_data implementation
inside source_size_hint_levels_remain_ffi_compatible_small_inputs_matrix; remove
that inner helper and call the module-level fn generate_data(seed: u64, len:
usize) -> Vec<u8> instead so all tests share the same generator and signature.
Locate the inner function in
source_size_hint_levels_remain_ffi_compatible_small_inputs_matrix, delete it,
and replace its usages with direct calls to generate_data(seed, len).

In `@zstd/src/encoding/match_generator.rs`:
- Around line 1682-1687: skip_matching_dense currently only seeds hashes for the
newly appended slice (using insert_positions with
current_abs_start..current_abs_end) which misses inserting the last 1–3
positions of the previous slice when a primed dictionary spans slices; update
skip_matching_dense so after computing current_abs_start/current_abs_end it also
computes the previous-slice tail absolute range (using
self.window.back().unwrap() and self.history_abs_start + self.window_size -
prev_len to get prev_abs_end) and calls insert_positions for that tail (e.g.
max(prev_abs_end - 3, prev_abs_start) .. current_abs_start) so the last 1–3
positions of the prior slice become hashable; keep insert_positions, window,
history_abs_start and window_size as the referenced symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3082542c-8b6d-44ec-80dc-17241d715c77

📥 Commits

Reviewing files that changed from the base of the PR and between c5b41c2 and 02f662e.

📒 Files selected for processing (6)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/incompressible.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/mod.rs
  • zstd/src/encoding/streaming_encoder.rs

Comment thread zstd/src/encoding/frame_compressor.rs
Comment thread zstd/src/encoding/match_generator.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread zstd/src/encoding/mod.rs Outdated
Comment thread zstd/src/encoding/mod.rs Outdated
Comment thread zstd/src/encoding/levels/fastest.rs Outdated
@polaz polaz requested a review from Copilot April 9, 2026 23:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread zstd/src/encoding/mod.rs
Comment thread zstd/src/encoding/mod.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 617-643: Add a third test that mirrors the existing
fastest_random_block_uses_raw_fast_path and
default_random_block_uses_raw_fast_path but exercises CompressionLevel::Level(2)
to cover the unique Dfast tuning; call crate::encoding::compress_to_vec with
super::CompressionLevel::Level(2), assert first_block_type(&compressed) ==
BlockType::Raw, and round-trip via zstd::stream::copy_decode to verify decoded
== data, using the same generate_data seed/size pattern as the other tests to
ensure parity with the existing test cases.

In `@zstd/src/encoding/match_generator.rs`:
- Around line 4550-4556: The test is exercising the sparse-incompressible branch
indirectly by calling matcher.skip_matching(None); change this to explicitly
call matcher.skip_matching(Some(true)) so the explicit incompressible-hint path
is covered; locate the block where current_abs_start/current_abs_end are
computed and where matcher.block_looks_incompressible(...) is asserted, and
replace the skip_matching(None) invocation with skip_matching(Some(true)) to pin
the new API contract for matcher.skip_matching.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 34a11fa6-1a29-41ca-9ecd-bea546ffa7b7

📥 Commits

Reviewing files that changed from the base of the PR and between 02f662e and 772e891.

📒 Files selected for processing (2)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/match_generator.rs

Comment thread zstd/src/encoding/frame_compressor.rs
Comment thread zstd/src/encoding/match_generator.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread zstd/src/encoding/incompressible.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 4599-4605: Remove the heuristic precondition assert so the test no
longer depends on classifier tuning: delete the assert!(
matcher.block_looks_incompressible(current_abs_start, current_abs_end), ... )
lines (which reference matcher.block_looks_incompressible,
current_abs_start/current_abs_end computed from matcher.history_abs_start,
matcher.window_size and first.len()), and keep the call to
matcher.skip_matching(Some(true)) so the test directly exercises the explicit
hint without relying on the heuristic.
- Around line 4382-4395: The tests incorrectly assert SuffixStore occupancy by
calling last.suffixes.get(...) (collision-prone); instead, replace these
assertions with observable-match checks: use the matcher to attempt a match for
the slices (e.g., the prefix at last.data[0..MIN_MATCH_LEN], the sparse start at
last.data[3..3+MIN_MATCH_LEN], and the tail at
tail_start..tail_start+MIN_MATCH_LEN) and assert the expected match/no-match
behavior and offsets; update assertions to call the public match-finding API on
matcher (or the module's match lookup function) rather than inspecting
last.suffixes.get directly so the test verifies real matching behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d163c8d1-2f35-4fd6-a88f-073fbc7a91e4

📥 Commits

Reviewing files that changed from the base of the PR and between 772e891 and c279a5d.

📒 Files selected for processing (4)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/mod.rs

Comment thread zstd/src/encoding/match_generator.rs Outdated
Comment thread zstd/src/encoding/match_generator.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
zstd/src/encoding/match_generator.rs (2)

4599-4605: ⚠️ Potential issue | 🟡 Minor

Remove the heuristic precondition from this explicit-hint regression.

This test already exercises skip_matching(Some(true)) directly. Keeping block_looks_incompressible(...) in the setup ties it to classifier tuning and can create false failures without any change to the hinted path.

♻️ Minimal follow-up
-    let current_abs_start = matcher.history_abs_start + matcher.window_size - first.len();
-    let current_abs_end = current_abs_start + first.len();
-    assert!(
-        matcher.block_looks_incompressible(current_abs_start, current_abs_end),
-        "test fixture must take the sparse incompressible branch"
-    );
     matcher.skip_matching(Some(true));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/match_generator.rs` around lines 4599 - 4605, Remove the
heuristic precondition that asserts matcher.block_looks_incompressible(...) in
the test setup; the test should directly exercise
matcher.skip_matching(Some(true)) without relying on classifier tuning. Delete
the computations of current_abs_start and current_abs_end (which are only used
for the assertion) and remove the assert!(...) call that references
matcher.block_looks_incompressible(current_abs_start, current_abs_end), leaving
the matcher.skip_matching(Some(true)) invocation and any remaining setup intact
so the test no longer depends on block_looks_incompressible.

4382-4395: ⚠️ Potential issue | 🟡 Minor

Don’t assert sparse indexing through raw SuffixStore::get().

SuffixStore is overwrite-on-collision, so Some(0) / None here does not reliably prove which starts were indexed. This regression can pass or fail based on unrelated bucket collisions instead of observable matcher behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/match_generator.rs` around lines 4382 - 4395, The
assertions directly inspecting SuffixStore via last.suffixes.get(...) are
brittle because SuffixStore is overwrite-on-collision; remove those direct get()
assertions and instead verify observable matcher behavior using the public
matcher API (e.g., call the match generation routine on data that exercises
cross-block tail matches and sparse-prefix starts) to assert expected match
results; specifically, stop asserting on last.suffixes.get(...) and replace with
tests that confirm the matcher (matcher.window.last(), match generator
functions) returns the expected matches for the dense tail case and does not
rely on sparse-start indexing assumptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 1675-1701: In skip_matching, after computing
current_abs_start/current_abs_end and determining used_sparse, ensure the
previous-slice tail is backfilled on the normal Dfast path: when used_sparse is
false, compute dense_tail = DFAST_MIN_MATCH_LEN + 3, set tail_start =
current_abs_start.saturating_sub(dense_tail) and if tail_start <
current_abs_start call self.insert_positions(tail_start, current_abs_start)
before calling self.insert_positions(current_abs_start, current_abs_end) (i.e.,
seed the last 1–3 bytes of the prior slice so cross-boundary matches are
hashable for the following block).

---

Duplicate comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 4599-4605: Remove the heuristic precondition that asserts
matcher.block_looks_incompressible(...) in the test setup; the test should
directly exercise matcher.skip_matching(Some(true)) without relying on
classifier tuning. Delete the computations of current_abs_start and
current_abs_end (which are only used for the assertion) and remove the
assert!(...) call that references
matcher.block_looks_incompressible(current_abs_start, current_abs_end), leaving
the matcher.skip_matching(Some(true)) invocation and any remaining setup intact
so the test no longer depends on block_looks_incompressible.
- Around line 4382-4395: The assertions directly inspecting SuffixStore via
last.suffixes.get(...) are brittle because SuffixStore is
overwrite-on-collision; remove those direct get() assertions and instead verify
observable matcher behavior using the public matcher API (e.g., call the match
generation routine on data that exercises cross-block tail matches and
sparse-prefix starts) to assert expected match results; specifically, stop
asserting on last.suffixes.get(...) and replace with tests that confirm the
matcher (matcher.window.last(), match generator functions) returns the expected
matches for the dense tail case and does not rely on sparse-start indexing
assumptions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1cf996c8-c431-476d-a9b3-e6b247561bae

📥 Commits

Reviewing files that changed from the base of the PR and between 772e891 and c279a5d.

📒 Files selected for processing (4)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/mod.rs

Comment thread zstd/src/encoding/match_generator.rs
- enable and gate best-level raw early exits for incompressible data

- preserve large-window match advantage with window-aware guard

- propagate incompressible hints to HC skip path with sparse indexing

- add regression tests for best fast-path and cross-block tail behavior
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 779-805: The test
compressible_logs_do_not_fall_back_to_raw_fast_path currently checks block type
and round-trip decompression but doesn't verify the hinted-header path; update
the test (or assert_not_raw_for_level helper) so that for hinted variants
(super::CompressionLevel::Default and any explicit hinted Level like Level(0) or
Level(3) you want to cover) you also parse the compressed header and assert
single_segment_flag(&compressed) == false (use the existing single_segment_flag
symbol or equivalent header inspection helper), keeping the existing checks for
first_block_type and decompression; this ensures the hinted non-raw header path
is exercised and prevents regressions in the all_blocks_raw guard.

In `@zstd/src/encoding/levels/fastest.rs`:
- Around line 158-220: The tests currently only inspect the serialized header
bits in output, but compress_block_encoded(...) returns a BlockType that the
real codepath (FrameCompressor) consumes; capture the return value of
compress_block_encoded in both tests and assert it equals the expected enum
(e.g., BlockType::Rle for rle_branch_passes_compressible_hint_to_skip_matching
and BlockType::Raw for
raw_fast_path_emits_raw_block_and_passes_incompressible_hint) instead of—or in
addition to—inspecting output bytes, using the returned value from
compress_block_encoded to verify the correct branch was chosen.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a8c5e34b-d992-465c-8ddd-20b5f72f8174

📥 Commits

Reviewing files that changed from the base of the PR and between b0ff651 and 0cafc7d.

📒 Files selected for processing (2)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs

Comment thread zstd/src/encoding/frame_compressor.rs
Comment thread zstd/src/encoding/levels/fastest.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zstd/src/encoding/frame_compressor.rs (1)

647-661: ⚠️ Potential issue | 🟡 Minor

Pin the 511/512 single-segment boundary explicitly.

Line 323 introduces a new lower bound at 512 bytes, but the updated coverage starts at 513. That leaves the compat floor itself untested, so an off-by-one here would still pass. Please add explicit hinted 511-byte and 512-byte cases and assert that single_segment_flag() stays false / flips true at that boundary.

Also applies to: 699-741

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/frame_compressor.rs` around lines 647 - 661, In
source_size_hint_levels_remain_ffi_compatible_small_inputs_matrix add explicit
test cases for the 511 and 512 byte boundaries (i.e. include sizes 511 and 512
in the sizes test set used by that function and the similar block around lines
699-741), then assert that single_segment_flag() is false at 511 and true at 512
for the relevant compression levels; update the array of sizes and the per-size
assertions so the single-segment boundary is explicitly tested rather than
starting at 513.
♻️ Duplicate comments (1)
zstd/src/encoding/match_generator.rs (1)

1907-1918: ⚠️ Potential issue | 🟠 Major

Seed the last 4-byte-hashable Dfast starts here, not just 6-byte starts.

Line 1914 still stops at DFAST_MIN_MATCH_LEN, but insert_position() can already index starts with only 4 bytes of lookahead. That leaves the final two hashable starts of a literals-only block unseeded, so the next block can miss an immediate cross-boundary match that begins 4–5 bytes before the prior block ended. Because this helper is shared, the gap affects both start_matching_general() and start_matching_fast_loop().

♻️ Proposed fix
     fn seed_remaining_hashable_starts(
         &mut self,
         current_abs_start: usize,
         current_len: usize,
         pos: usize,
     ) {
         let mut seed_pos = pos.min(current_len);
-        while seed_pos + DFAST_MIN_MATCH_LEN <= current_len {
+        while seed_pos + 4 <= current_len {
             self.insert_position(current_abs_start + seed_pos);
             seed_pos += 1;
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/match_generator.rs` around lines 1907 - 1918, The loop in
seed_remaining_hashable_starts uses DFAST_MIN_MATCH_LEN which only preserves
6-byte starts; change the loop to seed the last 4-byte-hashable starts by
replacing the termination check with the 4-byte threshold (use the existing
4-byte constant if present or literal 4) so the while becomes while seed_pos + 4
<= current_len { self.insert_position(current_abs_start + seed_pos); seed_pos +=
1; } — this ensures insert_position(...) seeds the final two hashable starts for
current_abs_start/current_len/pos and fixes cross-block matches used by
start_matching_general() and start_matching_fast_loop().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 845-882: The test
hinted_small_compressible_frames_use_single_segment_across_levels only checks
header parity and roundtrip; update it to also assert the compressed frame does
not use raw blocks and still achieves size reduction: after compressing with
FrameCompressor (constructed via FrameCompressor::new and using
set_source_size_hint, set_source, set_drain, compress) and before decoding,
parse the frame (read_frame_header and any block iteration utilities) and add an
assertion that no BlockType::Raw blocks are present, and also assert that
compressed.len() < data.len() to ensure compression actually reduced size; keep
the existing single_segment_flag and roundtrip checks.

---

Outside diff comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 647-661: In
source_size_hint_levels_remain_ffi_compatible_small_inputs_matrix add explicit
test cases for the 511 and 512 byte boundaries (i.e. include sizes 511 and 512
in the sizes test set used by that function and the similar block around lines
699-741), then assert that single_segment_flag() is false at 511 and true at 512
for the relevant compression levels; update the array of sizes and the per-size
assertions so the single-segment boundary is explicitly tested rather than
starting at 513.

---

Duplicate comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 1907-1918: The loop in seed_remaining_hashable_starts uses
DFAST_MIN_MATCH_LEN which only preserves 6-byte starts; change the loop to seed
the last 4-byte-hashable starts by replacing the termination check with the
4-byte threshold (use the existing 4-byte constant if present or literal 4) so
the while becomes while seed_pos + 4 <= current_len {
self.insert_position(current_abs_start + seed_pos); seed_pos += 1; } — this
ensures insert_position(...) seeds the final two hashable starts for
current_abs_start/current_len/pos and fixes cross-block matches used by
start_matching_general() and start_matching_fast_loop().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25f1af24-9b00-4cbd-89d8-444e8ea3262e

📥 Commits

Reviewing files that changed from the base of the PR and between 28c51b4 and b8dd1ab.

📒 Files selected for processing (4)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/streaming_encoder.rs

Comment thread zstd/src/encoding/frame_compressor.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
zstd/src/encoding/frame_compressor.rs (1)

843-883: ⚠️ Potential issue | 🟡 Minor

Add non-raw and size-reduction assertions to prevent silent regression.

This test verifies single-segment header parity but does not assert that the compressible fixture avoids BlockType::Raw or achieves size reduction. A future change could silently regress compression behavior while this test still passes.

🛡️ Proposed fix to harden the test
             let (frame_header, _) = read_frame_header(compressed.as_slice()).unwrap();
             assert!(
                 frame_header.descriptor.single_segment_flag(),
                 "hinted small compressible frame should use single-segment (level={level:?})"
             );
+            assert_ne!(
+                first_block_type(&compressed),
+                BlockType::Raw,
+                "compressible hinted frame should stay off the raw fast path (level={level:?})"
+            );
+            assert!(
+                compressed.len() < data.len(),
+                "compressible hinted frame should still shrink (level={level:?})"
+            );
             let mut decoded = Vec::new();
             zstd::stream::copy_decode(compressed.as_slice(), &mut decoded)
                 .unwrap_or_else(|e| panic!("ffi decode failed (level={level:?}): {e}"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstd/src/encoding/frame_compressor.rs` around lines 843 - 883, The test
hinted_small_compressible_frames_use_single_segment_across_levels should also
assert that the compressed output actually achieved compression and did not emit
raw blocks: after building compressed (from FrameCompressor with
set_source_size_hint and set_source), parse the frame payload to inspect blocks
(use existing frame/block-parsing helpers or the BlockType enum) and assert no
BlockType::Raw is present for this fixture, and assert compressed.len() <
data.len() to ensure size reduction; keep these assertions inside the same
per-level loop so any regression that preserves single_segment_flag but emits
raw data or grows the output will fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/streaming_encoder.rs`:
- Around line 247-261: The single-segment calculation in StreamingEncoder (the
single_segment variable computed from pledged_content_size and window_size)
intentionally omits the !use_dictionary_state guard present in FrameCompressor
because StreamingEncoder currently has no dictionary support; add a concise
inline comment next to the single_segment computation (or near the FrameHeader
construction) referencing FrameCompressor and noting that StreamingEncoder lacks
set_dictionary/use_dictionary_state and therefore deliberately allows
single-segment framing, and include a TODO that if
set_dictionary/use_dictionary_state or dictionary priming is later added to
StreamingEncoder the single_segment logic must be updated to include the
!use_dictionary_state guard to match FrameCompressor behavior.

---

Duplicate comments:
In `@zstd/src/encoding/frame_compressor.rs`:
- Around line 843-883: The test
hinted_small_compressible_frames_use_single_segment_across_levels should also
assert that the compressed output actually achieved compression and did not emit
raw blocks: after building compressed (from FrameCompressor with
set_source_size_hint and set_source), parse the frame payload to inspect blocks
(use existing frame/block-parsing helpers or the BlockType enum) and assert no
BlockType::Raw is present for this fixture, and assert compressed.len() <
data.len() to ensure size reduction; keep these assertions inside the same
per-level loop so any regression that preserves single_segment_flag but emits
raw data or grows the output will fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ca9f252-caaa-414a-9efa-75954c949ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 28c51b4 and b8dd1ab.

📒 Files selected for processing (4)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/levels/fastest.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/streaming_encoder.rs

Comment thread zstd/src/encoding/streaming_encoder.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread zstd/src/encoding/match_generator.rs
Copy link
Copy Markdown

@sw-release-bot sw-release-bot Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'structured-zstd vs C FFI (x86_64-gnu)'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: b8dd1ab Previous: 40b6e89 Ratio
compress/default/small-4k-log-lines/matrix/pure_rust 0.088 ms 0.067 ms 1.31
compress/level4-row/small-4k-log-lines/matrix/pure_rust 0.066 ms 0.047 ms 1.40
compress/level4-row/low-entropy-1m/matrix/pure_rust 9.058 ms 6.449 ms 1.40
decompress/fastest/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/default/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/better/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/level4-row/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/best/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62

This comment was automatically generated by workflow using github-action-benchmark.

CC: @polaz

@polaz
Copy link
Copy Markdown
Member Author

polaz commented Apr 11, 2026

@coderabbitai Re duplicate at zstd/src/encoding/frame_compressor.rs:843-883: fixed in cba456d. The test now asserts first block is not Raw and verifies compressed.len() < data.len() for each hinted compressible level, in addition to single-segment and roundtrip checks.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check commit cba456d
git show cba456d --stat 2>/dev/null || echo "commit not found"
git show cba456d -- zstd/src/encoding/frame_compressor.rs 2>/dev/null | head -120

# Check current state of lines 830-900 in frame_compressor.rs
sed -n '830,910p' zstd/src/encoding/frame_compressor.rs

# Also look for the test function name nearby
grep -n "hinted_small_compressible\|block_type\|BlockType::Raw\|single_segment\|compressed.len" zstd/src/encoding/frame_compressor.rs | grep -A2 -B2 "84[0-9]\|85[0-9]\|86[0-9]\|87[0-9]\|88[0-9]" | head -40

Length of output: 3336


@polaz: Confirmed on the current branch (commit hash not resolvable directly — likely rebased/squashed, but the current HEAD carries the fix).

assert_not_raw_for_level helper (around Lines 820–833):

  • assert_ne!(first_block_type(&compressed), BlockType::Raw) — first block is not Raw ✅
  • compressed.len() < data.len() — verifies meaningful compression ✅
  • zstd::stream::copy_decode roundtrip + assert_eq!(decoded, data)

Called for Fastest, Default, Level(3), Better, and Best. ✅

hinted_small_compressible_frames_use_single_segment_across_levels (Lines 845–883): asserts single_segment_flag() == true for all hinted levels (Fastest, Default, Better, Best, Level(0), Level(3), Level(4), Level(11)) with FFI roundtrip. ✅

All four properties the review comment requested are now covered.


✏️ Learnings added
Learnt from: polaz
URL: https://github.com/structured-world/structured-zstd/pull/99

Timestamp: 2026-04-11T08:52:47.911Z
Learning: In `zstd/src/encoding/frame_compressor.rs` (structured-world/structured-zstd, PR `#99`), `hinted_small_compressible_frames_use_single_segment_across_levels` asserts `single_segment_flag() == true` for hinted 4 KiB compressible frames across all levels (Fastest, Default, Better, Best, Level(0), Level(3), Level(4), Level(11)). A helper `assert_not_raw_for_level` additionally asserts the first block is not `BlockType::Raw`, that `compressed.len() < data.len()`, and that FFI roundtrip decodes correctly. Do not flag the test as missing size-reduction or non-Raw block assertions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-10T12:52:14.220Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `HcMatchGenerator::skip_matching`'s dense-tail reseed (the `Some(true)` / sparse incompressible path) iterates position-by-position over `tail_start..current_abs_end` and skips positions where `(pos - current_abs_start) % INCOMPRESSIBLE_SKIP_STEP == 0`. This prevents sparse-grid positions already indexed in the sparse pass from being reinserted and creating self-loop chain entries. A regression test `hc_sparse_skip_matching_does_not_reinsert_sparse_tail_positions` (line ~4086) covers this invariant. Do not flag the absence of a bulk `insert_positions(tail_start, current_abs_end)` call in the sparse-hint dense-tail path as a missing optimization.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/match_generator.rs:1675-1701
Timestamp: 2026-04-10T07:20:51.122Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `DfastMatchGenerator::skip_matching` (the dense/non-sparse path, i.e. when `used_sparse` is false) backfills the previous-slice tail (`current_abs_start.saturating_sub(DFAST_MIN_MATCH_LEN + 3) .. current_abs_start`) before calling `insert_positions(current_abs_start, current_abs_end)`. This mirrors the `skip_matching_dense` backfill and `HcMatchGenerator::backfill_boundary_positions` patterns, ensuring positions at block boundaries become hashable as soon as the next slice extends history. A regression test `dfast_dense_skip_matching_backfills_previous_tail_for_next_block` covers immediate cross-boundary matching after a dense skip. Do not flag the absence of this backfill as a missing step.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-11T07:15:56.578Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd, PR `#99`), `RowMatchGenerator::skip_matching_with_hint(Some(true))` performs sparse-prefix insertion via `insert_positions_with_step(current_abs_start, current_abs_end, INCOMPRESSIBLE_SKIP_STEP)` followed by a dense-tail reseed over `ROW_MIN_MATCH_LEN + INCOMPRESSIBLE_SKIP_STEP` positions, skipping sparse-grid positions (`!(pos - current_abs_start).is_multiple_of(INCOMPRESSIBLE_SKIP_STEP)`). The `MatcherBackend::Row` dispatch in `MatchGeneratorDriver::skip_matching_with_hint` now passes the hint through to the Row backend. A regression test `row_skip_matching_with_incompressible_hint_uses_sparse_prefix` covers this. Do not flag Row as ignoring incompressible hints or defaulting to dense-only skip.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/match_generator.rs:1682-1687
Timestamp: 2026-04-09T23:59:08.161Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `DfastMatchGenerator::skip_matching_dense` backfills the previous-slice tail (`max(current_abs_start - 3, history_abs_start) .. current_abs_start`) before seeding the current slice via `insert_positions`. This ensures that positions at internal priming-slice boundaries become hashable as soon as the next slice extends history, keeping cross-slice dictionary priming fully dense. The pattern mirrors `HcMatchGenerator::backfill_boundary_positions`. Do not flag the absence of this backfill as a missing step in `skip_matching_dense`.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/match_generator.rs:1684-1709
Timestamp: 2026-04-10T08:57:45.654Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `DfastMatchGenerator::skip_matching` backfills the previous-slice tail (`current_abs_start.saturating_sub(BOUNDARY_DENSE_TAIL_LEN) .. current_abs_start`) before the sparse/dense branch, using a shared constant `BOUNDARY_DENSE_TAIL_LEN` (= `DFAST_MIN_MATCH_LEN + 3`). This ensures boundary positions become hashable for consecutive sparse-skip scenarios. The dense-only path (`skip_matching_dense`) also uses this constant for its backfill. Do not flag the absence of this backfill in the sparse branch as missing.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/match_generator.rs:457-463
Timestamp: 2026-04-11T07:48:12.353Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd, PR `#99`), `MatchGeneratorDriver::skip_matching_for_dictionary_priming` calls `row_matcher_mut().skip_matching_with_hint(Some(false))` for the `MatcherBackend::Row` case — explicitly requesting dense seeding, consistent with Simple (`skip_matching_with_hint(Some(false))`) and HashChain (`skip_matching(Some(false))`) backends. Do not flag this as using an implicit/unhinted path or suggest reverting to `skip_matching()`.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/match_generator.rs:1713-1716
Timestamp: 2026-04-10T14:32:43.111Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `DfastMatchGenerator::skip_matching`'s sparse tail reseed computes `tail_start` as `current_abs_end.saturating_sub(Self::BOUNDARY_DENSE_TAIL_LEN).max(current_abs_start)`. The `.max(current_abs_start)` clamp is intentional: it prevents short blocks (shorter than `BOUNDARY_DENSE_TAIL_LEN`) from reaching back into the previous block during the dense tail pass, since Lines 1693–1696 already backfill that boundary. Do not flag the clamp as redundant or suggest removing it.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-09T23:17:57.736Z
Learning: In `zstd/src/encoding/match_generator.rs` (structured-world/structured-zstd), `MatchGeneratorDriver::prime_with_dictionary` uses a dedicated dense seeding path for the `DfastMatchGenerator` backend: `skip_matching_for_dictionary_priming` routes to `skip_matching_dense`, bypassing the incompressible heuristic and sparse step insertion (`insert_positions_with_step`). This ensures primed dictionary history is always densely seeded and cross-block matching invariants are preserved for dictionary-compressed streams. Do not flag `prime_with_dictionary` as potentially using sparse/incompressible stepping.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-08T16:00:30.438Z
Learning: In `zstd/src/encoding/match_generator.rs`, the `RowMatchGenerator` (used for `CompressionLevel::Level(4)` / `MatcherBackend::Row`) uses a stable 4-byte hash key, consistent with the 4-byte lookahead constraint shared by `HcMatchGenerator`. Previous-block tail positions are backfilled into the row tables before matching/skip begins (analogous to `backfill_boundary_positions` for the HC backend), and a regression test covers cross-boundary tail reuse. Do not flag missing backfill or key-width issues for the Row backend.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 48
File: zstd/src/encoding/match_generator.rs:1470-1490
Timestamp: 2026-04-02T07:27:15.870Z
Learning: In `zstd/src/encoding/match_generator.rs`, `HcMatchGenerator::pick_lazy_match` intentionally queries `find_best_match(abs_pos + 1, ...)` and `find_best_match(abs_pos + 2, ...)` *before* those positions are inserted into the hash-chain tables. This matches C zstd lazy evaluation semantics: pre-seeding those positions would allow a position to match against itself, producing invalid self-matches. Do not flag the absent pre-seeding as a bug.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 48
File: zstd/src/encoding/match_generator.rs:336-339
Timestamp: 2026-04-02T12:03:03.856Z
Learning: In `zstd/src/encoding/match_generator.rs`, `min_primed_tail = 4` is correct for both `MatcherBackend::Dfast` and `MatcherBackend::HashChain` because `HcMatchGenerator::insert_position` unconditionally requires 4 bytes of hash lookahead (`if idx + 4 > concat.len() { return; }`). `backfill_boundary_positions` only re-inserts positions already fully within extended history; it cannot make a <4-byte dictionary tail fragment hashable. Do not suggest lowering `min_primed_tail` below 4 for the `HashChain` backend.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-01T06:49:34.233Z
Learning: In the `structured-zstd` codebase (`zstd/src/encoding/match_generator.rs`), `CompressionLevel::Default` uses the `DfastMatchGenerator` backend (with `pick_lazy_match` lazy evaluation), while `CompressionLevel::Better` uses the `HcMatchGenerator` (hash chain backend) which inserts positions before lazy evaluation inside its own `start_matching` loop. These two backends are independent; review comments about Dfast internals (e.g. position seeding order in `pick_lazy_match`) do not apply to the Better level's implementation path.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-01T06:49:40.012Z
Learning: In the structured-zstd codebase (`zstd/src/encoding/match_generator.rs`), `CompressionLevel::Default` uses the `DfastMatchGenerator` backend (with `pick_lazy_match` for lazy evaluation), while `CompressionLevel::Better` uses the `HcMatchGenerator` (hash chain backend) which inserts positions before lazy evaluation inside its own `start_matching` loop. These are distinct backends with separate lazy-seeding paths.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/encoding/frame_compressor.rs:280-287
Timestamp: 2026-04-02T16:31:58.167Z
Learning: In `structured-zstd`, `CompressionLevel::Best` intentionally uses `MatcherBackend::HashChain` (not a binary-tree/DUBT backend) due to memory constraints: a BT matcher for a 16 MiB window requires ~128–256 MB of tables (C zstd level 11 uses chainLog=25 → 256 MB), which is impractical. Best is distinguished from Better by deeper HC search (32 vs 16 candidates), larger hash/chain tables (2M/1M vs 1M/512K), and higher target match length (128 vs 48), keeping memory under 20 MB. It achieves a proportional compression gain (~0.27% better than Better on corpus) comparable to C zstd's L7→L11 delta (~0.14%). A proper BT backend is deferred as a future optimization. Do not flag Best's use of HashChain as missing a BT implementation.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/incompressible.rs:73-84
Timestamp: 2026-04-10T19:40:51.187Z
Learning: In `zstd/src/encoding/incompressible.rs` (structured-world/structured-zstd, PR `#99`), `compression_level_allows_raw_fast_path(level, window_size)` intentionally returns `true` for `Fastest`, `Default`, and `Better` (unconditionally), `true` for `Best` and `Level(_)` when `window_size <= BETTER_WINDOW_SIZE_BYTES`, and `false` for `Uncompressed`. The inclusion of `Better` and all numeric `Level(_)` (window-gated) is a deliberate perf-first decision for the incompressible early-exit throughput gains documented in issue `#97`. Do not flag Better as returning false or suggest restricting Level(_) to 0..=3 only.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 61
File: zstd/src/encoding/mod.rs:169-179
Timestamp: 2026-04-04T20:45:28.506Z
Learning: In `zstd/src/encoding/mod.rs`, `Matcher::set_source_size_hint()` has a default no-op implementation intentionally. Only the built-in `MatchGeneratorDriver` overrides it to apply the hint during level resolution. `NoDictionaryMatcher` and `TinyMatcher` are test stubs and are explicitly exempt from implementing this hook. Do not flag the absent override in non-driver matchers as a bug.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/tests/roundtrip_integrity.rs:498-509
Timestamp: 2026-04-02T22:26:07.979Z
Learning: In `structured-zstd` (`zstd/src/tests/roundtrip_integrity.rs`), `best_level_does_not_regress_vs_better` uses a `<=` (not strict `<`) assertion because the `repeat_offset_fixture(b"HelloWorld", ...)` input is simple enough that HC saturates at both Better (16 candidates) and Best (32 candidates) search depths, producing identical compressed sizes (~30243 bytes). Strict `<` would be a false positive on this fixture. The strict `Best < Better` quality assertion lives in `cross_validation::best_level_beats_better_on_corpus_proxy` on the decodecorpus sample. Do not re-flag the `<=` as a weakened guard.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/frame_compressor.rs:779-805
Timestamp: 2026-04-11T07:48:58.281Z
Learning: In `zstd/src/encoding/frame_compressor.rs` (structured-world/structured-zstd, PR `#99`), hinted tiny frames (`source_size_hint <= 2^14` and `total_uncompressed <= 2^14`) are intentionally emitted as single-segment across ALL compression levels, including levels that produce compressed (non-raw) blocks. The `hinted_small_compressible_frames_use_single_segment_across_levels` test asserts `single_segment_flag() == true` for compressible hinted inputs (e.g. 4 KiB repeated log lines). Do not re-suggest asserting `single_segment_flag() == false` for compressible hinted frames — the donor-parity design requires single-segment for all hinted tiny frames.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-06T01:40:24.378Z
Learning: In `zstd/benches/compare_ffi.rs` (structured-world/structured-zstd), Rust FastCOVER trains with the post-finalization content budget in both the `REPORT_DICT_TRAIN` emission path (around lines 208-225) and the Criterion benchmark path (around lines 266-280). Both paths were aligned in commit 8622344. Do not flag these ranges as using inconsistent budget values.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 53
File: zstd/src/tests/roundtrip_integrity.rs:479-523
Timestamp: 2026-04-02T19:19:44.750Z
Learning: In `structured-zstd` (`zstd/src/tests/roundtrip_integrity.rs`), the per-level roundtrip test suites (Better, Best) share a common `level_roundtrip_suite!` macro that expands the same 7-test matrix (compressible, random, multi-block, streaming, edge cases, repeat offsets, large literals) for each level via module-scoped macro invocations. Do not suggest re-inlining these as separate flat test functions — the macro was added intentionally to eliminate cross-level drift.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/frame_compressor.rs:779-805
Timestamp: 2026-04-11T07:48:58.281Z
Learning: In `zstd/src/encoding/frame_compressor.rs` (structured-world/structured-zstd, PR `#99`), a temporary compatibility guard exists for sub-256-byte compressed payloads: the 1-byte-FCS single-segment form is not yet fully C-FFI compatible for payloads below 256 bytes, and a compat guard was added to handle this case while the path is hardened. Do not flag this guard as unnecessary.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-08T21:51:51.926Z
Learning: In `zstd/src/decoding/ringbuffer.rs` (structured-world/structured-zstd), the test suite for `copy_bytes_overshooting` includes a misaligned extra-capacity case that exercises the rounded-up wildcopy path (where `copy_multiple > copy_at_least`) and validates prefix correctness for that scenario. Do not flag the absence of a misaligned/overshooting extra-capacity sub-case as missing test coverage.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 0
File: :0-0
Timestamp: 2026-04-08T21:51:56.626Z
Learning: In `zstd/src/decoding/ringbuffer.rs` (structured-world/structured-zstd), the test `copy_bytes_overshooting_preserves_prefix_for_runtime_chunk_lengths` (or equivalent) includes a misaligned extra-capacity case that uses `add(1)`-offset pointers and a `chunk_plus_one` length to exercise the rounded-up wildcopy overshoot path (where `copy_multiple > copy_at_least`) and validates prefix correctness. Do not flag the absence of a misaligned-pointer case in this test — it is present by design.

Learnt from: polaz
Repo: structured-world/structured-zstd PR: 99
File: zstd/src/encoding/levels/fastest.rs:100-107
Timestamp: 2026-04-10T07:24:31.749Z
Learning: In `zstd/src/encoding/levels/fastest.rs` (structured-world/structured-zstd), the `should_emit_raw_fast_path` Best-level window guard uses the shared constant `BETTER_WINDOW_SIZE_BYTES` from `match_generator` (not a hardcoded `8 * 1024 * 1024` literal), and the level-table Better windows are wired to `BETTER_WINDOW_LOG` so the fast-path cutoff stays synchronized with matcher tuning. Do not re-flag the literal as present or suggest introducing a new constant.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstd/src/encoding/match_generator.rs`:
- Around line 1908-1918: In seed_remaining_hashable_starts change how seed_pos
is initialized so the final hashable starts at the block tail are always seeded
even when adaptive skip jumps to current_len: instead of seed_pos =
pos.min(current_len) compute seed_pos =
pos.min(current_len.saturating_sub(DFAST_SHORT_HASH_LOOKAHEAD)) (so when pos >=
current_len you still start at the last hashable start), then keep the existing
while loop that calls insert_position(current_abs_start + seed_pos) and
increments seed_pos; this ensures the tail hashable 4-byte starts aren't skipped
across block boundaries.

In `@zstd/src/encoding/streaming_encoder.rs`:
- Around line 252-255: The single_segment gate currently checks both
pledged_content_size and the matcher's window_size, causing small pledged frames
to be treated differently depending on matcher internals; change the closure
that computes single_segment to only depend on pledged_content_size (i.e., keep
the 512..=(1<<14) range check and remove the "size <= window_size" condition) so
single_segment is true whenever a known frame size falls in that range
regardless of matcher window; this affects the variable single_segment computed
from pledged_content_size and ensures parity with FrameCompressor and
new_with_matcher behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9cd60c69-7aa8-4cc8-98ca-6f3b5f944012

📥 Commits

Reviewing files that changed from the base of the PR and between b8dd1ab and 73361d5.

📒 Files selected for processing (3)
  • zstd/src/encoding/frame_compressor.rs
  • zstd/src/encoding/match_generator.rs
  • zstd/src/encoding/streaming_encoder.rs

Comment thread zstd/src/encoding/match_generator.rs
Comment thread zstd/src/encoding/streaming_encoder.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@polaz polaz requested a review from Copilot April 11, 2026 09:31
@polaz polaz merged commit 8cef003 into main Apr 11, 2026
19 checks passed
@polaz polaz deleted the perf/#97-early-incompressible-fastpath branch April 11, 2026 09:32
@sw-release-bot sw-release-bot Bot mentioned this pull request Apr 11, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

@sw-release-bot sw-release-bot Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'structured-zstd vs C FFI (x86_64-gnu)'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: 882dfd7 Previous: 40b6e89 Ratio
compress/level4-row/small-4k-log-lines/matrix/pure_rust 0.068 ms 0.047 ms 1.45
compress/level4-row/low-entropy-1m/matrix/pure_rust 8.86 ms 6.449 ms 1.37
decompress/fastest/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/default/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/better/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/level4-row/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62
decompress/best/low-entropy-1m/c_stream/matrix/c_ffi 0.27 ms 0.167 ms 1.62

This comment was automatically generated by workflow using github-action-benchmark.

CC: @polaz

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.

perf(encoding): early incompressible fast-path for fastest/default encode

2 participants