Skip to content

jit: coerce a closing JUMP to the label arity only when it targets that label - #1127

Merged
youknowone merged 1 commit into
mainfrom
perf-bridge
Aug 9, 2026
Merged

jit: coerce a closing JUMP to the label arity only when it targets that label#1127
youknowone merged 1 commit into
mainfrom
perf-bridge

Conversation

@youknowone

Copy link
Copy Markdown
Owner

assemble_peeled_trace_with_jump_args applied compile.py:334's equal-arity rule
to every close whose jump_to_self flag was set. That flag records that the
closing JUMP was redirected onto some existing target token, not that it
targets the local label, so a cross-token JUMP was truncated to the local label's
arity — 8 args to 7, dropping a trailing ConstInt(1) that pypy spends on a
literal in the same slot.

This renames the flag to jump_was_redirected and compares the JUMP's target
descr against the local label's before coercing. A JUMP carrying another token's
virtual-state arity (unroll.py:346-357) now passes through unchanged;
compile.py:334 asserts equality for the same-label close only.

Also corrects the comment at the jump_to_existing_trace discard site, which
attributed the fallback to cranelift's one-function-per-trace layout. That was
measured false: admitting the external close breaks dynasm too, which relocates
every LABEL and refuses a JUMP below the first page, so the branch does name a
live address.

Reachability

The change is a no-op on today's corpus, measured rather than argued. Every
bench script (394 synth + 23 top-level = 417) was swept with the release binary
under MAJIT_LOG=1, tallying both sides of the new predicate:

redirected / targets_local occurrences
true / true 543
true / false (the only branch this changes) 0
false / true 0
false / false 18

Every redirected close targets its local label, because the discard at
jump_to_existing_trace removes an external close before assembly ever sees it.
The fix becomes load-bearing when that discard is lifted.

Fixture

pyre/bench/synth/retrace_outer_loop_type_flip.py — a nested while whose
accumulator flips int -> float in the outer loop, so the retrace grown for
it closes onto the token the inner loop left behind. Recorded at
loops_compiled=1 loops_aborted=2 retraces_compiled=0 guard_failures=590,
identical on all three backends. That is pyre's current declined state, and it
is the acceptance test for whatever admits the close.

Verification

New regression test test_assemble_peeled_trace_keeps_external_target_jump_arity
pins the pass-through; the existing
test_assemble_peeled_trace_passes_through_resolved_body_inputs was corrected to
model the same-label close it was actually exercising.

authored by Claude

@coderabbitai

coderabbitai Bot commented Aug 9, 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: 1 minute

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: b4c11034-55da-4530-ad78-bb1b416e0393

📥 Commits

Reviewing files that changed from the base of the PR and between 23aaac5 and b988673.

📒 Files selected for processing (5)
  • majit/majit-metainterp/src/optimizeopt/unroll.rs
  • pyre/bench/synth/retrace_outer_loop_type_flip.cranelift.jitstats
  • pyre/bench/synth/retrace_outer_loop_type_flip.dynasm.jitstats
  • pyre/bench/synth/retrace_outer_loop_type_flip.py
  • pyre/bench/synth/retrace_outer_loop_type_flip.wasm.jitstats

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: 3f30c25e8e

ℹ️ 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 +5289 to +5291
let jump_targets_local_label = matches!(
(jump_target_descr_idx, local_label_descr_idx),
(Some(jump_idx), Some(label_idx)) if jump_idx == label_idx

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare target descriptor identity, not token indices

When external closes are admitted—the stated use case for this change—distinct TargetTokens can share the same numeric token_id because it is only local to a JitCellToken; history.rs explicitly says backend identity is the descriptor's Arc address. Comparing Descr::index() therefore misclassifies an external target with the same local number as this label, causing the new branch to truncate or pad its arguments and recreating the cross-token arity corruption. Compare the descriptor Arcs with Arc::ptr_eq/descr_identity instead, and make the regression test use distinct descriptors with the same index.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit b988673).
Updated: 2026-08-09T14:25:14.544Z

Files in the reviewed diff
majit/majit-metainterp/src/optimizeopt/unroll.rs
pyre/bench/synth/retrace_outer_loop_type_flip.py

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

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

  • majit/majit-metainterp/src/optimizeopt/unroll.rs:1795 ↔ rpython/jit/metainterp/optimizeopt/unroll.py:325: pyre discards a successful close when its target differs from the newest body token; PyPy iterates all target tokens and accepts the first compatible one, emitting JUMP(..., descr=target_token) at unroll.py:357-360.

  • majit/majit-metainterp/src/optimizeopt/unroll.rs:1888 ↔ rpython/jit/metainterp/optimizeopt/unroll.py:238: pyre aborts a retrace when the body-JUMP and preamble-label arities differ; PyPy’s jump_to_preamble only retargets the live JUMP and preserves its arguments. This can turn a valid PyPy fallback into InvalidLoop.

4. Structural adaptations

  • pyre/bench/synth/retrace_outer_loop_type_flip.py:25 ↔ pypy/module/pypyjit/test/randomized.py:111: guarded pypyjit import is a CPython-compatible test-fixture adaptation. It preserves execution under CPython while enabling PyPy/pyre JIT parameterization; it is not a parity defect.

…at label

`assemble_peeled_trace_with_jump_args` applied compile.py:334's equal-arity
rule to every close whose `jump_to_self` flag was set. That flag records that
the closing JUMP was redirected onto some existing target token, not that it
targets the local label, so a cross-token JUMP was truncated to the local
label's arity. Rename the flag to `jump_was_redirected` and compare the JUMP's
target descr against the local label's before coercing; a JUMP carrying another
token's virtual state arity (unroll.py:346-357) now passes through unchanged.

Over all 417 corpus scripts every one of the 543 redirected closes targets its
local label, so no bench moves: `jump_to_existing_trace` still discards an
external close before assembly.

Correct that discard site's comment, which attributed the fallback to the
cranelift one-function-per-trace layout — admitting the close on dynasm breaks
as well.

Add `synth/retrace_outer_loop_type_flip`, a nested loop whose accumulator flips
int -> float in the outer loop, recorded at `loops_aborted=2` and
`retraces_compiled=0`.

Assisted-by: Claude
@youknowone

Copy link
Copy Markdown
Owner Author

Rebased onto 23aaac5c09e. The commit's patch is byte-identical to the reviewed one.

Where the verification stands. The full dynasm bench corpus (411 benches, zero
failures) and the 417-script reachability sweep were run at base 1a63e36e37a.
The branch has since been rebased twice by its author; 241a97fa4bc (#1124)
touches majit-translate, which invalidates pyre-jit.ullbc, so the local
binaries no longer describe this base. CI is the authority for 23aaac5c09e.
Nothing in either base bump touches optimizeopt/, the dynasm backend, or
majit-ir, so the reachability result — no corpus bench takes the branch this
change alters — is not expected to move.

Follow-up, not in this PR. The jump_to_existing_trace discard this PR only
re-comments is still there, and the root cause of why it cannot simply be lifted
is now pinned: upstream's foreign close never crosses a JitCellToken
compile.py:287-289 resets jitcell_token.target_tokens to [start_descr] and
start_descr.virtual_state is None, so unroll.py:177-179 skips it and the close
in compile_loop is structurally always a self-close. A foreign token is
reachable only via compile.py:355 (compile_retrace
get_procedure_token(greenkey), which filters invalidated at
warmstate.py:191-196) and via optimize_bridge, and the result is installed as
a bridge under that same token (compile.py:797-811).

pyre instead seeds unroll_opt.target_tokens in both compile_loop and
compile_retrace from a green-key side table that survives recompiles, so the
matched token can belong to a previous compilation's ExecutableBuffer; dynasm
bakes its ll_loop_code() as an absolute br, and the blanket
set_original_jitcell_token_number re-stamp makes the port of compile.py:197
compare equal and skip the record_jump_to keepalive, after which memmgr is
free to evict the target.

That predicts the split seen when the discard was lifted: the five benches that
broke are recompile-at-the-same-key shapes that cannot reach compile_retrace at
all (retrace_limit defaults to 0, rpython/rlib/jit.py:595, and none of them
calls pypyjit.set_param), while synth/retrace_outer_loop_type_flip sets
retrace_limit=5 and needs the close only in the retrace-attach path, bound to
the same CLT that owns the target LABEL. The fix is to make the discard test the
real invariant — same artifact, and target live — rather than "is this the body
token I just minted".

commented by Claude

@youknowone

Copy link
Copy Markdown
Owner Author

CI triage — the pyre/check.py (macos-latest) red is inherited.

Both failures are in the cpython-suite phase; the bench corpus phase is clean:

  • test.test_pickletoolserrors=1, test_ints
  • test.test_reerrors=303

main's own run at this PR's exact base 23aaac5c09e
(run 31316170676)
fails the same job with the same two tests and the same counts, and its
CPython suite (gate) and all three pyre/check.py jobs are red there too. So
this branch adds nothing: test_re is the documented JIT-only lost-argument bug
and test_pickletools came in with #1087.

commented by Claude

@youknowone
youknowone merged commit a4ad675 into main Aug 9, 2026
13 of 17 checks passed
@youknowone
youknowone deleted the perf-bridge branch August 9, 2026 16:16
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