Skip to content

verify-baseline: resync the x86 decoder at symbol starts (fixes x64-musl lane on main) - #41549

Merged
dylan-conway merged 2 commits into
mainfrom
claude/verify-baseline-resync-at-symbols
Sep 6, 2026
Merged

dylan-conway merged 2 commits into
mainfrom
claude/verify-baseline-resync-at-symbols

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

What does this PR do?

Fixes the x64-musl verify-baseline failure on main (build 110907): llint_op_wide16 [INVLPGB] (1 insns) — a false positive, not a real post-baseline instruction.

The static scanner decoded all of .text in one linear sweep. JSC's LLInt emits a 4-byte .int <opcode id> in front of every llint_op_* label (LLINT_EMBEDDED_OPCODE_ID, x86_64), so after each of those the sweep decoded a few bytes of garbage before falling back into sync, and what the garbage decoded as depended on the following bytes — here a %rip-relative displacement, which moves with link layout. That is why the PR that last touched this passed and the squash onto a newer main did not, and why allowlist-x64.txt had accumulated three llint_op_* blanket entries "as they trip".

Now the decoder restarts at every symbol start: an instruction that would straddle a symbol boundary is discarded and decoding resumes at the symbol. The three whack-a-mole entries are removed; README/CLAUDE.md describe the new behaviour.

How did you verify your code works?

Ran the old and new scanner on the exact failing artifact (bun-linux-x64-musl-profile/bun-profile from build 110907): old → the CI failure; new → PASS, with the identical set of allowlisted symbols/instruction counts and the identical stale list otherwise (265 fewer garbage decodes overall).

The x86-64 scan was one linear sweep over all of .text. JSC's LLInt puts a
4-byte opcode id in front of every llint_op_* label, so the sweep decoded
garbage after each one, and what that garbage decoded as depended on the
bytes that followed (a %rip-relative displacement, i.e. link layout). On
main's current layout it came out as `tlbsync` inside llint_op_wide16 and
failed the x64-musl lane; the allowlist already carried three llint_op_*
entries added as earlier layouts tripped.

Every symbol start is a real instruction boundary: an instruction that
would straddle one is dropped and decoding restarts at the symbol. Same
allowlisted hits and stale set as before on the failing binary, minus the
false positive; the three llint_op_* blanket entries go.
@robobun

robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 4:05 AM PT - Sep 6th, 2026

@dylan-conway, your commit e084649 is building: #111028

@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: b127e574-db9b-441a-89be-e528fae29f22

📥 Commits

Reviewing files that changed from the base of the PR and between 56c7990 and e084649.

📒 Files selected for processing (1)
  • scripts/verify-baseline-static/CLAUDE.md

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


Walkthrough

The x64 scanner now resynchronizes decoding at symbol boundaries. Documentation describes the behavior and updates related references. The JSC LLInt data-in-text allowlist entries were removed.

Changes

x64 scanner resynchronization

Layer / File(s) Summary
Resynchronize decoding at symbol boundaries
scripts/verify-baseline-static/src/main.rs, scripts/verify-baseline-static/README.md, scripts/verify-baseline-static/CLAUDE.md, scripts/verify-baseline-static/allowlist-x64.txt
scan_x86_64 collects symbol starts and restarts decoding when an instruction crosses a boundary. Documentation describes the behavior and updates line references. The JSC LLInt allowlist block and three symbols were removed.

Suggested reviewers: robobun

Merge Risk: ⚪ Minimal · up to e0846

The decoder behavior documentation and README references are updated consistently. No current merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: resynchronizing the x86 decoder at symbol starts to fix the x64-musl verification failure.
Description check ✅ Passed The description includes both required sections. It explains the failure, the scanner change, the allowlist updates, documentation changes, and verification results with the failing artifact.
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.

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

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 scripts/verify-baseline-static/CLAUDE.md — nit: The README edit added three lines to the data-in-.text section, and this PR updated the README.md:53-62 reference on line 36 accordingly, but the sibling reference README.md:61-74 on line 47 was left unchanged — it now points into the middle of the rewritten paragraph instead of the MSVC jump-table / auto-filter block it describes. Fix: bump this reference by +3 to README.md:64-77 so it matches the shifted content.

    Extended reasoning...

    README.md previously had the MSVC-inlines-jump-tables paragraph starting at line 61; after the +3-line insertion in this PR that paragraph starts at line 64 and the filtered-automatically block ends at 74 (with the "Not filtered" note running to 77). CLAUDE.md line 36 was updated from 53-59 → 53-62 for the same shift, but line 47 was not touched, leaving a stale cross-reference. Purely a doc pointer drift; no runtime effect.

    Verification: nit — doc cross-reference drift only, no runtime effect. The README edit inserted a net +3 lines into the data-in-.text paragraph (base lines 55-59 → HEAD lines 55-62). On base, the MSVC jump-table paragraph started at README.md:61 and the block ran through line 74; on HEAD it starts at line 64 and the equivalent content runs through line 77. CLAUDE.md line 36 was updated in this same diff for…

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — clean structural fix that replaces layout-dependent allowlist entries with decoder resync at symbol boundaries.

What was reviewed:

  • Resync logic in scan_x86_64: starts is filtered to (sec_addr, sec_end), sorted/deduped; next_start cursor is monotone; resync target is always > ip and < sec_end, so set_position can't fail and the loop can't stall.
  • Straddle check runs before the is_invalid() skip, so an INVALID decode that crosses a symbol still resyncs rather than drifting byte-by-byte.
  • Removed llint_op_* allowlist entries are exactly the case the resync now handles (4-byte opcode id immediately preceding each label); README/CLAUDE.md line refs match the reflowed section.
Extended reasoning...

Overview

This PR improves the verify-baseline-static CI tool's x86-64 linear-sweep disassembler by resynchronising the iced decoder at every symbol start. Previously, data embedded in .text (notably JSC LLInt's 4-byte opcode IDs preceding each llint_op_* label) could desync the variable-length x86 decode and produce spurious post-baseline instruction hits whose exact shape shifted with link layout. The fix collects all symbol addresses strictly inside the section, and whenever a decoded instruction would straddle one, discards it and restarts decoding at that boundary. Three brittle llint_op_* allowlist entries (with a long explanatory comment) are removed as no longer needed, and README.md / CLAUDE.md are updated to describe the new bounded-desync behaviour.

Security risks

None. This is an offline CI verification tool that reads a locally-built binary's .text section and symbol table and prints a report. It touches no network, no user input, no runtime code paths, and no auth/crypto. The only new arithmetic is (resync - sec_addr) as usize, which is bounded by the a > sec_addr && a < sec_end filter, so set_position cannot be handed an out-of-range offset (and the .expect is unreachable in practice).

Level of scrutiny

Low-to-moderate. This is internal build tooling, not shipped runtime code. The core invariant — "a symbol start is always a real instruction boundary" — is sound for compiler- and offlineasm-emitted code, and even if it were violated the failure mode is a dropped instruction in a static checker, not a runtime fault. I traced the loop for forward progress: decode_out always advances ≥1 byte, next_start only increments, and any resync jumps strictly forward (resync > ip), so there is no stall or regression path. The straddle check is placed before the is_invalid() continue, which is the right ordering — an invalid decode that happens to span a symbol still triggers resync instead of creeping past it one byte at a time.

Other factors

The change is a textbook "fix the class, not the symptom" improvement per REVIEW.md: instead of accumulating llint_op_* allowlist entries as link layout shifts the desync between siblings, it removes the desync's ability to leak past a symbol boundary at all. Docs are updated consistently; the follow-up commit corrected the README line-range references in CLAUDE.md. No CODEOWNERS entry covers scripts/verify-baseline-static/. No outstanding CHANGES_REQUESTED reviews.

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.

2 participants