Skip to content

jit: narrow the wasm bridge decline to exception-resume (GuardException)#416

Merged
youknowone merged 1 commit into
mainfrom
wasm-narrow-exception-decline
Jul 8, 2026
Merged

jit: narrow the wasm bridge decline to exception-resume (GuardException)#416
youknowone merged 1 commit into
mainfrom
wasm-narrow-exception-decline

Conversation

@youknowone

Copy link
Copy Markdown
Owner

Summary

Follow-up to #407. #407 declined every CallMayForce bridge on wasm, which also dropped the non-raising loop-closing bridges wasm relies on for speed — bool_arithmetic ~6× slower, ~+11% on the synthetic suite. This narrows the decline to only the bridges that are actually broken.

Root cause (pinned via in-guest instrumentation)

Only exception-resume bridges are broken. A bridge containing GuardException resumes into the exception handler by re-entering the interpreter at the raising bytecode, reading the pre-call operand stack. Constant stack entries (e.g. a float dividend 1.5) live only in the guard's rd_consts resume data, not in a spilled frame slot. The compiled bridge reconstructs its state from inputargs (spilled slots) alone and cannot see rd_consts, so the constant materialises as NULL and the re-entered interpreter runs truediv(NULL, int) → spurious unsupported operand type(s) for /.

Dumping every CallMayForce bridge gave a crisp discriminator:

bench guard tail status
float_div_zero_caught_loop GuardException Finish (interp re-entry) broken
bool_arithmetic / is_op / string_ops GuardNoException Jump (loop-closing) fast, correct

Fix

Decline only bridges containing OpCode::GuardException (was is_call_may_force()). The broken shape blackholes — rematerialising constants from rd_consts, the native path — while GuardNoException loop-closing bridges keep their compiled fast path.

Verification (on current main base)

#407 declined every CallMayForce bridge on wasm, which also dropped the
non-raising loop-closing bridges wasm relies on for speed (bool_arithmetic
~6x slower, ~+11% on the synthetic suite).

Only exception-resume bridges are actually broken: a bridge containing
`GuardException` resumes into the exception handler by re-entering the
interpreter at the raising bytecode, reading the pre-call operand stack.
Constant stack entries (e.g. a float dividend) live only in the guard's
`rd_consts` resume data, not in a spilled frame slot, and the compiled
bridge — reconstructing state from inputargs alone — cannot see
`rd_consts`, so the constant materialises as NULL and the re-entered
interpreter runs e.g. `truediv(NULL, int)`.

Narrow the decline from `is_call_may_force()` to `GuardException`: the
broken shape blackholes (rematerialising constants from `rd_consts`, the
native path) while `GuardNoException` loop-closing bridges keep their
compiled fast path. wasm synthetic suite 173/173 (float_div correct), no
CallMayForce-bridge regression, and faster overall than both #407 and the
#404 workaround.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@youknowone, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 166bc43a-1e03-491d-ad64-044a7c24acef

📥 Commits

Reviewing files that changed from the base of the PR and between b75e424 and 2d29ca5.

📒 Files selected for processing (1)
  • majit/majit-backend-wasm/src/lib.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasm-narrow-exception-decline

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.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 0e5ab08).

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-backend-wasm/src/lib.rs:1397 ↔ rpython/jit/backend/test/runner_test.py:5081: wasm now rejects any bridge containing GuardException (any(|op| op.opcode == ... GuardException)), but PyPy explicitly compiles bridges starting with px = guard_exception(...) and then calls self.cpu.compile_bridge(...) at runner_test.py:5105. This is a parity regression versus upstream/main for GuardException bridges that do not also contain CallMayForce.

  • majit/majit-backend-wasm/src/lib.rs:1401 ↔ rpython/jit/metainterp/compile.py:886: wasm returns Unsupported("exception-resume bridge..."), but PyPy defines class ResumeGuardExcDescr(ResumeGuardDescr): pass, so exception guards use the normal resumable/bridgeable guard path, not a special blackhole-only path.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

None.

4. Structural adaptations

  • majit/majit-backend-wasm/src/lib.rs:915 ↔ rpython/jit/backend/test/runner_test.py:3791: wasm still declines most CALL_ASSEMBLER ops unless the special self-recursive wasm CA shape is enabled, while PyPy backend tests exercise ordinary call_assembler_i(...). The local comment cites wasm’s per-trace module/table limitation, so this is a wasm backend structural adaptation rather than a new parity regression from this patch.

@youknowone youknowone merged commit 01d81fa into main Jul 8, 2026
29 of 30 checks passed
@youknowone youknowone deleted the wasm-narrow-exception-decline branch July 8, 2026 08:59
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