Skip to content

Align assembly encoders with Go#47

Merged
klauspost merged 2 commits into
minio:mainfrom
klauspost:align-asm
Jul 1, 2026
Merged

Align assembly encoders with Go#47
klauspost merged 2 commits into
minio:mainfrom
klauspost:align-asm

Conversation

@klauspost

@klauspost klauspost commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Final results (enwik9, -cpu=1)

level original asm now Go (noasm) speed
1 Fastest 46.97% 44.91% (+375 B) 44.91% 673 MB/s (Go 563)
2 Balanced 38.30% 37.80% (−875 B) 37.81% 390 MB/s (Go 373)

Both asm encoders now match-or-beat the Go reference and stay faster. Every bit of the gap traced to a concrete bug — nothing diffuse.

All three bugs (all quality-only → invisible to roundtrip/fuzz)

  1. L1 hash1 stored s instead of s+1 → dead s+1 probe (46.97→45.29).
  2. L1 post-match JA→JG → unsigned underflow killed near matches in each block's first ~2 MB (45.29→44.91).
  3. L2 sparse index stored index1 instead of index2 → half the match interior filed under a stale position (38.30→37.80).

Plus: L1 store-order set to Go's (your call — OoO-friendly), L2 repeat-interior indexing + bail aligned.

Summary by CodeRabbit

  • Bug Fixes
    • Improved match/copy decision behavior to reduce missed matches in corner cases.
    • Fixed edge handling for early-block and high-offset scenarios so valid matches aren’t incorrectly rejected.
    • Corrected repeat-match and interior indexing behavior to preserve search coverage after encoded matches.
  • Performance
    • Refined encoding search/table update logic to avoid incorrect table updates and improve decision efficiency.

Final results (enwik9, -cpu=1)

| level | original asm | now | Go (noasm) | speed |
|---|---|---|---|---|
| `-1` Fastest | 46.97% | **44.91%** (+375 B) | 44.91% | 673 MB/s (Go 563) |
| `-2` Balanced | 38.30% | **37.80%** (−875 B) | 37.81% | 390 MB/s (Go 373) |

Both asm encoders now match-or-beat the Go reference and stay faster. Every bit of the gap traced to a concrete bug — nothing diffuse.

All three bugs (all quality-only → invisible to roundtrip/fuzz)

  1. L1 hash1 stored s instead of s+1 → dead s+1 probe (46.97→45.29).
  2. L1 post-match JA→JG → unsigned underflow killed near matches in each block's first ~2 MB (45.29→44.91).
  3. L2 sparse index stored index1 instead of index2 → half the match interior filed under a stale position
  (38.30→37.80).

Plus: L1 store-order set to Go's (your call — OoO-friendly), L2 repeat-interior indexing + bail aligned.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fixes in _generate/gen.go adjust assembly-generation behavior for hash-table updates, match selection, offset checks, repeat-match indexing, and a far-copy3 bailout condition.

Changes

Assembly Generator Correctness Fixes

Layer / File(s) Summary
genEncodeBlockAsm: hash ordering, match8 path, offset compare, length
_generate/gen.go
Reorders candidate slot loads before hash saves; broadens the fast-path guard for match8; switches the offset-max check from unsigned JA to signed JG; fixes the match_nolit2 length increment from 4 to 8.
genEncodeBetterBlockAsm: minPos gate and repeat indexing
_generate/gen.go
Gates minPos allocation on matchOffsetCMOV; adds a guarded interior-repeat indexing loop after repeat emission; fixes lTab.SaveIdx to use index2 instead of index1.
genEncodeBetterBlockAsm: far copy3 bail heuristic
_generate/gen.go
Narrows the far copy3 bailout to the minimal 4-byte non-repeat match at far offsets.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through hashes, neat and sly,
Signed checks kept near matches alive.
Eight bytes now leap where four once ran,
And repeat trails index as planned.
Far-copy bails got trimmed just so,
The rabbit approves this byte-wise glow.

🚥 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 change: aligning the assembly encoders with the Go implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
_generate/gen.go (1)

1397-1416: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not leave the non-CMOV offset path with a nil minPos.

Line 1397 only allocates minPos when matchOffsetCMOV is true, but the false branch still dereferences minPos at Line 1415. Flipping this generator knob will panic during generation; allocate minPos for both branches or remove the dead branch.

Proposed fix
-		if o.maxOffset > maxOffset && matchOffsetCMOV {
+		if o.maxOffset > maxOffset {
 			minPos = GP64()
 			LEAL(Mem{Base: s, Disp: -maxOffset + 2}, minPos.As32())
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@_generate/gen.go` around lines 1397 - 1416, The non-CMOV offset path in the
generator still uses minPos even when it was only initialized inside the
matchOffsetCMOV branch, which can cause a nil dereference when the else path is
selected. Update the offset-check setup around checkCandidate in gen.go so
minPos is allocated and initialized whenever either branch can reference it, or
restructure the logic so the non-CMOV path no longer depends on minPos. Keep the
fix localized to the offset handling around checkCandidate, matchOffsetCMOV, and
minPos.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@_generate/gen.go`:
- Around line 1566-1603: The repeat interior indexing loop in
encodeBlockBetterGo needs the same sLimitL safety check used by the match-emit
path before reading src[index1] and src[index1+1]. Add the guard around the
repeat_index_loop setup so the code only enters this indexing block when s is
within the safe range, preventing out-of-bounds reads while still indexing the
repeat interior into sTab and lTab.

---

Outside diff comments:
In `@_generate/gen.go`:
- Around line 1397-1416: The non-CMOV offset path in the generator still uses
minPos even when it was only initialized inside the matchOffsetCMOV branch,
which can cause a nil dereference when the else path is selected. Update the
offset-check setup around checkCandidate in gen.go so minPos is allocated and
initialized whenever either branch can reference it, or restructure the logic so
the non-CMOV path no longer depends on minPos. Keep the fix localized to the
offset handling around checkCandidate, matchOffsetCMOV, and minPos.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd784de2-ea28-440a-9478-fa61c776ee3d

📥 Commits

Reviewing files that changed from the base of the PR and between cc484d6 and 3efac72.

📒 Files selected for processing (2)
  • _generate/gen.go
  • asm_amd64.s

Comment thread _generate/gen.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
_generate/gen.go (1)

1395-1419: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

minPos allocation condition no longer matches its use in the non-CMOV branch.

Adding && matchOffsetCMOV means minPos is only allocated on the CMOV path, but the else branch in checkCandidate (Lines 1415) still dereferences minPos.As32(). If matchOffsetCMOV is ever set false while o.maxOffset > maxOffset, minPos is nil and the generator panics. It's currently masked only because matchOffsetCMOV is a const true, so the branch is dead. Gate the else branch on the same condition or keep the allocation aligned with both consumers.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9ee562fa-934c-4611-a3eb-072df56afee7

📥 Commits

Reviewing files that changed from the base of the PR and between 3efac72 and ac9233f.

📒 Files selected for processing (2)
  • _generate/gen.go
  • asm_amd64.s
💤 Files with no reviewable changes (1)
  • asm_amd64.s

@klauspost klauspost merged commit c8038d8 into minio:main Jul 1, 2026
59 checks passed
@klauspost klauspost deleted the align-asm branch July 1, 2026 07:56
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