Skip to content

jit: drop the WITH_EXCEPT_START gate + with-frame exception-correctness fixes (#389) - #532

Merged
youknowone merged 6 commits into
mainfrom
no-token
Jul 15, 2026
Merged

jit: drop the WITH_EXCEPT_START gate + with-frame exception-correctness fixes (#389)#532
youknowone merged 6 commits into
mainfrom
no-token

Conversation

@youknowone

@youknowone youknowone commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Removes the WITH_EXCEPT_START JIT gate so hot loops inside a with-block are admitted for tracing, and lands the exception-correctness and resume fixes that the gate previously masked.

Commits (rebased onto current main)

  • 0c527d51591 jit: read the FBW sys_exc journal through the captured root area — the walker read FBW_SYS_EXC_JOURNAL via TLS on the store-journal path; on abort rollback this could diverge from the captured root area. Now threads the journal cell through the captured root area so the walker and the rollback see the same store.
  • 5e03d05dfc7 jit: preserve exception disposition on an uncaught exception-guard bridge (#389c root Optimize info transfer in replace_op for AbstractInfo handling #6) — a loop-free with-callee trace reaching the no-replay portal exit swallowed an exception-guard bridge's raise, so e.g. re.compile('\\') intermittently did not raise. The bridge resolver now returns the exception continuation for an uncaught exception-guard bridge instead of the captured concrete return.
  • 4f84553d51b jit: admit with-block frames for tracing (drop the WITH_EXCEPT_START gate) — deletes the emit_abort_permanent! at the WithExceptStart codewriter arm and the StructuralRegion decline in unsupported_jit_shape. with-frames now trace as plain residual regions.
  • 948c40dca36 jit: skip the executed-nonpure abort-flush when the entry carrier owns the same outer resume (test_fnmatch) — a double abort-flush at the same outer resume pc corrupted a bound-method local in a nested FOR_ITER. The executed-nonpure arm now declines when the Entry-carrier arm already owns that resume.
  • 15ea5baec63 jit: seed a bridge-named operand in a reserved red color regardless of tagged-int support (test_strftime) — the bridge red-color seeding was gated on CAN_BE_TAGGED, leaving a stale ConstPtr(ec) callable on no-token and a SIGSEGV. The seeding now runs unconditionally.

Status

Every baseline-gated regression the gate drop exposed is fixed and green:

  • test_fnmatch — wrong result at test_range (baseline-gated) — 948c40dca36
  • test_strftime — SIGSEGV (baseline-gated) — 15ea5baec63
  • test_strtod — rust panic at pyframe.rs:2098 (baseline-gated) — no longer reproduces after the rebase onto current main; the super-instruction resume defect it hit is subsumed by the upstream resume-frame rework (jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape #536 / majit/wasm: GC-lifetime fixes + dict/set O(N²) and module-scope JIT resume defects #543). The dedicated resume commit that previously fixed it was dropped as redundant — against the new resume-frame shape it miscompiled the gh#498 branch-resume guard (synth/closure_freevar_branch_list_cell).
  • test_re — ~20 JIT-only errors: an expected exception (re.PatternError/OverflowError) escapes a with assertRaises(...) block instead of being suppressed (non-gated; test_re baseline is already FAIL, no CI impact) — #389c root Align exception table to upstream #4-alt, tracked as a follow-up. RCA is still open: the escape route traces as CompareOp -> PopJumpIfFalse -> RaiseVarargs (not the with-cleanup opcodes); the WITH_EXCEPT_START result-slot hypothesis was investigated and refuted.

check.py --backend dynasm: 181/181 (rebased onto main at 928139e6d95).

All exposed miscompiles reproduce only with the JIT on (PYRE_NO_JIT=1 is clean), confirming they are trace-path bugs, not interpreter regressions.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 13, 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: 34 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: f9d88b51-0b8e-4428-9646-20dc2bea8e5d

📥 Commits

Reviewing files that changed from the base of the PR and between 7103208 and dc7ba20.

📒 Files selected for processing (4)
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-jit/src/jit/codewriter.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch no-token

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.

@youknowone
youknowone marked this pull request as ready for review July 14, 2026 00:17
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Codex parity review

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

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

1. Regressions to PyPy parity introduced by this patch

  • pyre/pyre-jit/src/jit/codewriter.rs:9089 ↔ pypy/interpreter/pyopcode.py:1350: removing emit_abort_permanent! now lets tracing continue after WITH_EXCEPT_START, but the new code only pushes a fresh symbolic value. PyPy obtains __exit__, calls it with (exc_type, exc, traceback), and pushes that call’s result (pyopcode.py:1351-1363). The patch therefore omits an observable call, exception behavior, and its boolean result.

  • pyre/pyre-jit/src/eval.rs:4111 ↔ pypy/interpreter/pyopcode.py:462: removing the WithExceptStart structural-region rejection makes the incomplete lowering above reachable. Main conservatively kept such frames interpreted; the patch admits them despite codewriter.rs:9089 not implementing the opcode’s required semantics.

2. Other mismatches introduced by this patch

None.

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

None.

4. Structural adaptations

  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs:430 ↔ rpython/jit/metainterp/blackhole.py:661: Rust stores separate typed temporary registers plus a Ref concrete-value shadow in WalkSession. This is a representation adaptation for Rust’s typed OpRef banks and concrete-shadow machinery; it matches Blackhole’s typed tmpreg_i/r/f behavior. The tracing implementation’s single pushed_box (rpython/jit/metainterp/pyjitpl.py:479) is structurally different, but generated renamings are type-local, so this is not itself a semantic mismatch.

  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs:8440 ↔ rpython/memory/gc/base.py:396: capturing the thread-local journal’s sys_exc storage in a Rust root-area object is a GC/runtime adaptation. It ensures the root walker forwards the correct thread’s journal rather than re-entering the current thread-local lookup; this has no direct 1:1 RPython object layout.

@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: bf740e31d8

ℹ️ 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 9086 to 9088
push_fresh_ref(&mut current_state, &mut graph);
current_depth += 1;
emit_vsd!(current_depth, py_pc);

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 Preserve WITH_EXCEPT_START semantics before admitting frames

With this gate removed, frames containing WITH_EXCEPT_START can now be traced, but the only lowering for that opcode is still this shadow-stack update: repo-wide search shows no residual/helper call for with_except_start. When a hot path actually takes an exceptional with exit, such as a context manager whose __exit__ swallows the exception or mutates state, the trace can pass through this opcode without invoking __exit__, so the compiled path observes the wrong boolean and misses required side effects. Please keep the structural decline until this arm emits the real helper semantics.

Useful? React with 👍 / 👎.

@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: 15ea5baec6

ℹ️ 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 +2029 to +2030
if entry_carrier_call_py_pc == Some(resume_py_pc) {
crate::jitcode_dispatch::fbw_abort_outer_stack_overrides_clear();

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 Skip fallback only after the entry flush commits

When the Entry carrier's flush_walk_end_state_at_outer_call declines, this comparison still treats the same resume PC as already handled because entry_carrier_call_py_pc was set before the commit result was known. For a LoopBearingCalleeInlineUnsupported path that also latched FBW_ABORT_OUTER_RESUME_PY_PC, this clears the rooted stack overrides and skips the flush_walk_end_state_to_frame_with_stack_overrides fallback, so an executed non-pure residual falls back to legacy replay instead of being resumed forward. Gate this skip on the Entry flush actually setting WALK_END_FLUSH_COMMITTED.

Useful? React with 👍 / 👎.

@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: f0e14b8462

ℹ️ 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".

len,
bank: "i",
})?;
*slot = val;

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 Preserve Int concrete shadow through tmpreg moves

When insert_renamings emits an integer cycle, this pop only restores registers_i[dst] and leaves concrete_registers_i[dst] holding whatever value the destination had before the swap. On paths where the concrete Int shadow is populated and a later goto_if_not/iL, switch/id, or int_return/i reads it, the walker can fold or return using stale concrete data even though the symbolic OpRef was moved correctly. Mirror the ref tmpreg path by carrying a tmpreg_i_concrete value from int_push/i and using write_int_reg on pop.

Useful? React with 👍 / 👎.

The per-mutator root walker fbw_store_journal_root_walker_area read the
sys_exc journal from the current thread's TLS instead of the captured
area, unlike every other journal it walks. Under a stop-the-world
collection walking a paused mutator's area, this forwarded the collector
thread's journal rather than the paused thread's, leaving a displaced
sys_exc_value unrooted. Add a sys_exc field to FbwStoreJournalRootArea
and walk it through area.

Assisted-by: Claude
…gate)

unsupported_jit_shape declined any frame containing WITH_EXCEPT_START as
StructuralRegion, and the codewriter emitted emit_abort_permanent! for the op,
keeping with-block loops and their callees out of the JIT. With the bridge
exception-disposition fix in place, WITH_EXCEPT_START lowers as a plain residual
(push the exit-function result, +1 stack effect) and the frame is admitted. The
StructuralRegion variant stays for the census/JitSuppressionGuard machinery but
is no longer produced. with_loop.py now compiles (loops_compiled=2) with the
correct result.

Assisted-by: Claude
…s the same outer resume

The gh#467/#495 inline-abort flush ran two arms on a single
LoopBearingCalleeInlineUnsupported abort: the Entry-carrier arm
(depth-validated flush_walk_end_state_at_outer_call) and the
executed-nonpure arm (flush_walk_end_state_to_frame_with_stack_overrides,
reading FBW_ABORT_OUTER_RESUME_PY_PC). When both described the same outer
resume pc, the second arm committed a forward-flush that the first had
already handled (by commit or by depth-mismatch decline), overwriting a
caller operand-stack slot — a bound method stored in a local read back as
the receiver, so a `local(args)` call dispatched through the receiver's
__call__.

Track the Entry carrier's call_py_pc; when the executed-nonpure arm's
resume pc matches it, clear the override stash and skip the second flush.
The arm still runs for outer-resume stashes with no matching Entry carrier.

Assisted-by: Claude
…f tagged-int support

A kept-stack branch-guard bridge resumed through `bridge_registers_r`
skipped seeding a reserved red color unless `tagged_int::CAN_BE_TAGGED`
was set. With that flag false, a real operand living in a non-frame
reserved red color (the free-register allocator reuses the portal EC
color for a live operand at PCs with no live EC read) was left holding
the stale `ConstPtr(ec)` seed. The residual CALL then read that stale
pointer as its callable, deterministically routing a wrong value into
`re/_parser` on the `re.match` path (surfacing as a SIGSEGV dereferencing
a bogus ob_type, or a wrong-type value reaching _compile/_parse).

The seeding is independent of tagged-int support: whenever the bridge
names a genuine operand (an opref other than the pre-seeded ec_box/
frame_box) in a reserved red color, seed the real operand. Only the
frame color stays protected, since overwriting its standard
virtualizable identity forces the nonstandard vable-finish leg.

Assisted-by: Claude
The FBW walker declined `ref_push`/`ref_pop`/`int_push`/`int_pop`/
`float_push`/`float_pop` at the catch-all `UnsupportedOpname`. These are
the blackhole `tmpreg_{r,i,f}` scratch ops (blackhole.py:661-679) that
`insert_renamings` (flatten.py:154) emits to break a cyclic parallel move
(a register swap `r_a <-> r_b` lowers to `push r_b; copy r_b<-r_a;
pop r_a`).

Add `tmpreg_r`/`tmpreg_i`/`tmpreg_f` (plus the Ref-bank concrete shadow)
to `WalkSession` and handle the six ops, mirroring `ref_copy/r>r`: push
reads a source register into the tmpreg, pop writes it back into a dst
register in lock-step with its concrete shadow. No IR op recorded (pure
SSA-level scratch move).

Assisted-by: Claude
`try_walker_specialize_load_attr` elided `guard_value(self.map, C_map)`
whenever `box_value(map_op) == Some(Int(map))`. Every traced getfield op
carries its live concrete value (`opimpl_getfield_gc_i` →
`set_opref_concrete`), so that equality holds on the trace's very first
map read and the guard was dropped entirely. `guard_class` pins only
`ob_type == INSTANCE_TYPE`, shared by all boxed instances, not the map
layout, so a trace compiled for one map re-entered by a same-class
instance with a different map read `storage[storageindex]` at the wrong
slot.

Emit the map guard unless `map_op` is already a compile-time constant,
then `replace_box(map_op, map_const)` so a later fold on the same
receiver in-trace elides — matching the trait fold path
(`implement_guard_value`).

Fixes test_shlex `read_token` (IndexError on empty deque / SIGSEGV),
where shlex instances across the suite carry multiple maps.

Assisted-by: Claude
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