Skip to content

jit: name the handler's live-in in every -live- an exception table covers - #993

Merged
youknowone merged 1 commit into
mainfrom
perf-loop
Aug 3, 2026
Merged

youknowone merged 1 commit into
mainfrom
perf-loop

Conversation

@youknowone

Copy link
Copy Markdown
Collaborator

What

bench/synth/list_length_hint_validate.py regressed loops_aborted 14 -> 34
against its tracked jit-stats baseline. The +20 is exactly 20 x
RegisterReadUnbound { pc: 456, reg: 3, bank: "r" } in classify_extend.

Why

compute_liveness unions a handler's alive set into the markers before its
catch_exception — the op carries TLabel(L), so the backward pass folds the
label's set in (liveness.py:19-23). The coordinate an after-residual-call
guard records sits PAST that catch_exception, so its own marker describes the
no-exception arm alone. Measured from the runtime liveness map of that jitcode:

jit_pc what it is live.ref_
359 the call's trailing -live-, directly before catch_exception [3, 9, 10, 11]
362 catch_exception/L 420 —
373 the coordinate the after-residual-call guard resumes at [9, 10]
420 the catch target route_exc_edge re-points the walk to [3, 9, 11]

route_exc_edge re-points the walk 373 -> 420 with a bank seeded only from
373's set, so the handler's first frame write-back reads the local r
(color 3) unbound and aborts the walk.

Upstream has no equivalent gap: finishframe_exception moves the pc of the
SAME MIFrame, so the handler reads the bank the raise left behind. A pyre
bridge rebuilds its bank from one coordinate's resume data, so that coordinate
has to name what the handler reads.

How

Union the handler PC's marker Ref colors into the marker of every py_pc
lookup_exceptiontable covers. Widening a -live- set is the conservative
direction — the snapshot carries boxes the no-exception arm does not consume,
which costs a slot and never mis-restores.

Keyed on the exception table rather than on the SSA catch_exception: the
runtime's recorded resume py is not semantic_fallthrough_pc(call_pc), so
keying it that way augments a different marker and measures as a no-op.

Rejected alternative

Re-keying pos_for_pc[fallthrough] to the call's trailing marker fixes the same
fixture exactly, but moves the walk START before the catch_exception; a bridge
resuming with the exception already active then walks onto it.
exception_reraise_tb_depth_jitstress goes 900/0/7/1798 to 893/0/30/0 with
CatchExceptionWithActiveException. Widening the marker's live set is safe;
moving the coordinate is not.

Verification

Measured on base c0891858087:

backend result remaining failures
dynasm 368 / 369 exception_args_virtual
cranelift 368 / 369 exception_args_virtual
wasm 363 / 365 exception_args_virtual, pickle_terminal_raise_resume
cargo test --all --no-default-features --features dynasm 481 / 482 registered_paths_sharing_an_address_are_alias_spellings

Each remaining failure reproduces unchanged with this commit reverted. Of the
354 tracked jit-stats baselines per backend only list_length_hint_validate
moves, back to its recorded 2/4/14/828.

Re-verification against the current base (6fbef5f7c7c, #982) is running and
this section will be updated with those numbers.

— authored by Claude

…vers

`compute_liveness` unions a handler's alive set into the markers before its
`catch_exception`: the op carries `TLabel(L)`, so the backward pass folds the
label's set in (liveness.py:19-23).  The coordinate an after-residual-call
guard records sits PAST that `catch_exception`, so its own marker describes the
no-exception arm alone.  In `bench/synth/list_length_hint_validate.py`'s
`classify_extend` the call's trailing marker names ref `[3, 9, 10, 11]`, the
recorded resume marker names `[9, 10]`, and the catch target needs `[3, 9, 11]`.

`route_exc_edge` re-points the walk from that coordinate into the handler with a
bank seeded from the recorded set, so the handler's first frame write-back read
the local `r` (color 3) unbound: 20 x `RegisterReadUnbound`, `loops_aborted
14 -> 34` against the tracked baseline.

Upstream has no equivalent gap.  `finishframe_exception` moves the pc of the
SAME MIFrame, so the handler reads the bank the raise left behind; a pyre bridge
rebuilds its bank from one coordinate's resume data, so that coordinate has to
name what the handler reads.  Union the handler PC's marker Ref colors into the
marker of every py_pc `lookup_exceptiontable` covers.  Keyed on the exception
table rather than on the SSA `catch_exception`: the runtime's recorded resume py
is not `semantic_fallthrough_pc(call_pc)`, so keying it that way augments a
different marker and measures as a no-op.

Re-keying `pos_for_pc[fallthrough]` to the call's trailing marker instead fixes
the same fixture but moves the walk START before the `catch_exception`, and a
bridge resuming with the exception already active then walks onto it:
`exception_reraise_tb_depth_jitstress` goes 900/0/7/1798 to 893/0/30/0 with
`CatchExceptionWithActiveException`.

check.py: dynasm 368/369, cranelift 368/369, wasm 363/365.  Every remaining
failure reproduces unchanged with this commit reverted (`exception_args_virtual`
on all three, `pickle_terminal_raise_resume` on wasm), as does the one
`cargo test --all --no-default-features --features dynasm` failure
(`jit_fnaddr::tests::registered_paths_sharing_an_address_are_alias_spellings`).
Of the 354 tracked jit-stats baselines per backend only `list_length_hint_validate`
moves, back to its recorded 2/4/14/828.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 2, 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: 25 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 Plus

Run ID: 14dc82d2-08d8-4f83-b5d8-ff9dd29d3253

📥 Commits

Reviewing files that changed from the base of the PR and between 6fbef5f and 0d0dbaa.

📒 Files selected for processing (1)
  • pyre/pyre-jit/src/jit/codewriter.rs

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d0dbaa9da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +4383 to +4385
// `handler py -> live-in`, memoized: a try body of N PCs shares one handler.
let mut handler_refs: std::collections::HashMap<usize, std::collections::BTreeSet<u16>> =
std::collections::HashMap::new();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace the handler cache with dense storage

This introduces a HashMap for handler_py, even though the key is a dense instruction index bounded by live_markers.len() and the surrounding comment explicitly says there is no upstream equivalent whose dict shape is being preserved. Use indexed storage such as Vec<Option<BTreeSet<u16>>> instead; the repository requires proving the corresponding RPython/PyPy container before adding a map and specifically prefers a Vec for small or dense key spaces.

AGENTS.md reference: AGENTS.md:L115-L124

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 0d0dbaa).
Updated: 2026-08-02T23:51:03.814Z

Files in the reviewed diff
pyre/pyre-jit/src/jit/codewriter.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-jit/src/jit/codewriter.rs:4403 ↔ rpython/jit/codewriter/liveness.py:44-52: the new helper widens a protected PC’s marker with the handler marker’s already-allocated Ref color. Upstream follows the exception edge during fixed-point liveness and preserves the source-side values flowing over that edge; a handler’s register color is not necessarily a color/value valid at the protected PC. The added color has no corresponding source-PC pcdep_color_slots mapping, so trace materialization skips it (semantic_ref_slot_for_reg_color returns None) and a resumed handler can still read an unbound or stale register.

  • pyre/pyre-jit/src/jit/codewriter.rs:4403,4547,4880 ↔ rpython/jit/metainterp/pyjitpl.py:194-199: the correction is attached only to live_markers[py], not to after_call_post_merge[py]. The latter is the liveness marker selected for after_residual_call; when it is distinct from the per-PC marker, the exception-handler live-ins are not widened at the actual guard-resume coordinate. This leaves precisely the post-call exception bridge case unprotected.

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

None.

4. Structural adaptations

  • pyre/pyre-jit/src/jit/codewriter.rs:4387-4393 ↔ rpython/jit/codewriter/flatten.py:220-226: consulting CPython 3.11+ exceptiontable entries is a necessary compiler/runtime adaptation for Pyre’s CPython-compatible bytecode. PyPy represents the same control-flow relationship explicitly as catch_exception TLabel(...) in JitCode.

  • pyre/pyre-jit/src/jit/codewriter.rs:4361-4385 ↔ rpython/jit/codewriter/liveness.py:19-23: the BTreeMap/HashMap cache is a Rust implementation adaptation of upstream’s fixed-point label2alive dictionary. It is acceptable only if it preserves the same edge-value semantics; the two section-2 findings show that the current cache’s target-color approximation does not.

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