Skip to content

jit: record the catching frame's traceback node at bridge handler entries, and widen bridge ref-root/merge-point handling - #893

Merged
youknowone merged 11 commits into
mainfrom
perf-bridge
Jul 31, 2026
Merged

jit: record the catching frame's traceback node at bridge handler entries, and widen bridge ref-root/merge-point handling#893
youknowone merged 11 commits into
mainfrom
perf-bridge

Conversation

@youknowone

@youknowone youknowone commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Eight commits on top of main, all in the bridge-compilation path. The last two
are the substantive ones; the first six were landed earlier on this branch and
are carried across the rebase.

jit(fbw): the catching frame's traceback node at a bridge handler entry

Wrong output in shipped binaries on both backends, not just a missed
optimization.

Seven walker paths enter an except handler. The five in-trace ones record a
traceback node for the catching frame; the two in dispatch_via_miframe — the
exception-edge arm and the carrier-raise-seed arm — called
vstack_enter_exception_handler with no record at all. On the bridge leg the
raising callee runs as real frames, so its nodes are attached by the interpreted
raise machinery, but the catching frame is the compiled one and its node exists
only if the trace records it. A handler reading __traceback__ saw a chain
whose head was the callee frame. pyopcode.py runs
record_application_traceback before lookup_exceptiontable routes to the
handler.

Both arms now record the same triple. The concrete leg of that record mutates
the live exception, and a walk that is later discarded would leave the mutation
behind while the metainterp's own delivery attaches the node again — the
doubled head observed on gc_bug_bridge_flavor_traceback_names. So the attach
is journalled: the arm records (exception, node) in a slot sharing the store
journal's lifecycle, the rollback epilogues restore
exception.w_traceback = node.tb_next while that node is still the head, and
the commit path clears the slot without applying it. Both refs are GC roots
through the existing store-journal root walker.

synth/exception_bridge_traceback_head.py pins the head frame name for two
exception classes raised into one hot try. It reads the traceback inline, so it
does not depend on the callee-inline gates.

jit(fbw): hoist the inline caller-frame catch-marker decline

The decline was decided after the callee frame had been seeded and IR recorded.
Deciding it from the same caller payload before the 'seed block removes the
half-built state; the post-seed arm is now unreachable!.
exception_traceback_frame_lineno: loops_aborted 7 -> 2, loops_compiled 12 -> 13.

Carried commits

  • jit(cranelift): drop the Float-at-Ref-inputarg bail from the bridge ref-root
    scan, and build the ref-root list from the declared inputarg types alone.
  • jit(metainterp): key compile_loop's has_compiled_targets check to the
    closed merge point; scope the end-of-JUMP Ref check to the traces whose
    inputargs the JUMP writes into.
  • jit: let the walker fall through to the merge-point scan when
    compile_trace declines.
  • bench: re-record the .jitstats baselines on both backends.

Verification

Rebased onto main after #886 and #876 landed, LLBC re-extracted, both backends
rebuilt.

  • 17 traceback/exception fixtures x 2 backends x 3 runs, diffed against
    PYRE_JIT=off: 0 diverged, no crashes. Includes
    exception_traceback_frame_lineno (the indicator jit: route the exception-edge bridge on the catch alone #886 names) and
    exc_caught_in_callee_return_loop (a shape jit: route the exception-edge bridge on the catch alone #886 newly routes through the
    exception-edge arm), plus str_search_index_bounds.
  • cargo test on pyre-jit-trace, pyre-jit, majit-metainterp: 0 failed.
  • check.py --snapshot-diff: 73 failed / 270 passed on both backends, the same
    count main produces on this host. The local bench/synth/*.jitstats are
    untracked scratch from a pre-rebase run, so those diffs are not attributable
    on their own. Measured directly instead, by reverting this branch's .rs
    files to main and rebuilding: of the 11 benches reporting a loops_aborted
    regression, 10 read identically on main, and
    exception_try_call_inlined_callee_raise goes 4 -> 1 on this branch. No
    loops_aborted regression originates here.
  • Of the 9 tracked baselines, 8 match exactly; const_arg_call_resume reads
    guard_failures 1805 against a recorded 1804, the build-sensitive +/-1 noted
    in the re-record commit. It is not a badness field and does not gate a plain
    run.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved JIT loop and bridge handling, reducing incorrect type mismatches and allowing declined optimizations to continue through normal compilation.
    • Improved exception traceback restoration when speculative execution is rolled back.
    • Added clearer handling for cancelled or unsuccessful compilation attempts.
  • New Features

    • Added a benchmark covering nested exception handling and traceback reporting.
  • Diagnostics

    • Expanded runtime and benchmark statistics to report descriptor resolution, bridge compilation, guard failures, and aborted loops more consistently.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 175e229d-3f12-4027-89e8-6bf163f97e72

📥 Commits

Reviewing files that changed from the base of the PR and between e1fd1c6 and 302618c.

📒 Files selected for processing (53)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-backend-wasm/src/failguard.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-metainterp/src/jitdriver.rs
  • majit/majit-metainterp/src/lib.rs
  • majit/majit-metainterp/src/optimizeopt/optimizer.rs
  • majit/majit-metainterp/src/optimizeopt/unroll.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • pyre/bench/fannkuch.cranelift.jitstats
  • pyre/bench/fannkuch.dynasm.jitstats
  • pyre/bench/fannkuch.wasm.jitstats
  • pyre/bench/fib_loop.cranelift.jitstats
  • pyre/bench/fib_loop.dynasm.jitstats
  • pyre/bench/fib_loop.wasm.jitstats
  • pyre/bench/fib_recursive.cranelift.jitstats
  • pyre/bench/fib_recursive.dynasm.jitstats
  • pyre/bench/fib_recursive.wasm.jitstats
  • pyre/bench/float_loop.cranelift.jitstats
  • pyre/bench/float_loop.dynasm.jitstats
  • pyre/bench/float_loop.wasm.jitstats
  • pyre/bench/inline_helper.cranelift.jitstats
  • pyre/bench/inline_helper.dynasm.jitstats
  • pyre/bench/inline_helper.wasm.jitstats
  • pyre/bench/int_loop.cranelift.jitstats
  • pyre/bench/int_loop.dynasm.jitstats
  • pyre/bench/int_loop.wasm.jitstats
  • pyre/bench/nbody.cranelift.jitstats
  • pyre/bench/nbody.dynasm.jitstats
  • pyre/bench/nbody.wasm.jitstats
  • pyre/bench/nested_loop.cranelift.jitstats
  • pyre/bench/nested_loop.dynasm.jitstats
  • pyre/bench/nested_loop.wasm.jitstats
  • pyre/bench/raise_catch_loop.cranelift.jitstats
  • pyre/bench/raise_catch_loop.dynasm.jitstats
  • pyre/bench/raise_catch_loop.wasm.jitstats
  • pyre/bench/spectral_norm.cranelift.jitstats
  • pyre/bench/spectral_norm.dynasm.jitstats
  • pyre/bench/spectral_norm.wasm.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats
  • pyre/bench/synth/const_arg_call_resume.cranelift.jitstats
  • pyre/bench/synth/const_arg_call_resume.dynasm.jitstats
  • pyre/bench/synth/const_arg_call_resume.wasm.jitstats
  • pyre/bench/synth/exception_bridge_traceback_head.py
  • pyre/bench/synth/nested_list_comprehension_hot.cranelift.jitstats
  • pyre/bench/synth/nested_list_comprehension_hot.dynasm.jitstats
  • pyre/bench/synth/nested_list_comprehension_hot.wasm.jitstats
  • pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyrex/src/lib.rs

Walkthrough

The PR changes JUMP type handling, bridge compilation outcomes, abort accounting, traceback journaling, diagnostics, and benchmark statistics. It also adds an exception traceback benchmark.

Changes

JIT compilation and bridge control

Layer / File(s) Summary
JUMP type handling and bridge retargeting
majit/majit-backend-cranelift/src/compiler.rs, majit/majit-metainterp/src/optimizeopt/*
Reference-root selection and bridge JUMP type expectations are adjusted. Internal JUMP/LABEL mismatches are logged.
Bridge decline and loop-abort outcomes
majit/majit-metainterp/src/jitdriver.rs, majit/majit-metainterp/src/pyjitpl.rs
Declined bridge compilation falls through to normal loop compilation. Abort reasons are staged and consumed during centralized accounting.
Compilation diagnostics and output labels
majit/majit-backend-wasm/src/*, majit/majit-metainterp/src/lib.rs, pyre/pyre-wasm-runner/src/main.rs, pyre/pyrex/src/lib.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
Diagnostic counters and labels distinguish bridge targets, publication states, cancellations, and compile-trace outcomes.
Benchmark JIT statistics
pyre/bench/**/*.jitstats
Descriptor-set counters are added, and recorded bridge, guard-failure, and aborted-loop values are updated.

Bridge-entry traceback journaling

Layer / File(s) Summary
Traceback journal lifecycle and rooting
pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
Traceback mutations are journaled across reset, commit, rollback, and GC-root walking.
Exception bridge traceback recording
pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
Bridge handler traceback entries record the prior traceback head and journal attached nodes.
Exception traceback benchmark
pyre/bench/synth/exception_bridge_traceback_head.py
A nested-call benchmark raises ValueError and TypeError and reports traceback-head function names.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A rabbit hops through JUMP and LABEL,
While traceback leaves stay on the table.
Bridges decline, then loops proceed,
Counters record each branching deed.
“Type-safe hops!” the rabbit sings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main bridge traceback and ref-root/merge-point changes without being misleading.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-bridge

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 30, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 302618c).
Updated: 2026-07-31T05:12:23.996Z

Files in the reviewed diff
majit/majit-backend-cranelift/src/compiler.rs
majit/majit-backend-wasm/src/failguard.rs
majit/majit-backend-wasm/src/lib.rs
majit/majit-charon-reader/src/ullbc.rs
majit/majit-ir/src/descr.rs
majit/majit-metainterp/src/jitdriver.rs
majit/majit-metainterp/src/lib.rs
majit/majit-metainterp/src/optimizeopt/optimizer.rs
majit/majit-metainterp/src/optimizeopt/unroll.rs
majit/majit-metainterp/src/pyjitpl.rs
majit/majit-translate/src/annotator/unaryop.rs
majit/majit-translate/src/codewriter/annotation_state.rs
majit/majit-translate/src/codewriter/assembler.rs
majit/majit-translate/src/codewriter/call.rs
majit/majit-translate/src/codewriter/jtransform.rs
majit/majit-translate/src/codewriter/type_state.rs
majit/majit-translate/src/front/bool_then.rs
majit/majit-translate/src/front/iter_next.rs
majit/majit-translate/src/front/mir.rs
majit/majit-translate/src/front/mod.rs
majit/majit-translate/src/front/slice_first.rs
majit/majit-translate/src/lib.rs
majit/majit-translate/src/model.rs
majit/majit-translate/src/translator/rtyper/cutover.rs
majit/majit-translate/src/translator/rtyper/flowspace_adapter.rs
majit/majit-translate/src/translator/rtyper/llinterp.rs
majit/majit-translate/src/translator/rtyper/lltypesystem/lltype.rs
majit/majit-translate/src/translator/rtyper/lltypesystem/rstr.rs
majit/majit-translate/src/translator/rtyper/pairtype.rs
majit/majit-translate/src/translator/rtyper/rbuiltin.rs
majit/majit-translate/src/translator/rtyper/rclass.rs
majit/majit-translate/src/translator/rtyper/rint.rs
majit/majit-translate/src/translator/rtyper/rstr.rs
pyre/bench/fannkuch.cranelift.jitstats
pyre/bench/fannkuch.dynasm.jitstats
pyre/bench/fannkuch.wasm.jitstats
pyre/bench/fib_loop.cranelift.jitstats
pyre/bench/fib_loop.dynasm.jitstats
pyre/bench/fib_loop.wasm.jitstats
pyre/bench/fib_recursive.cranelift.jitstats
pyre/bench/fib_recursive.dynasm.jitstats
pyre/bench/fib_recursive.wasm.jitstats
pyre/bench/float_loop.cranelift.jitstats
pyre/bench/float_loop.dynasm.jitstats
pyre/bench/float_loop.wasm.jitstats
pyre/bench/inline_helper.cranelift.jitstats
pyre/bench/inline_helper.dynasm.jitstats
pyre/bench/inline_helper.wasm.jitstats
pyre/bench/int_loop.cranelift.jitstats
pyre/bench/int_loop.dynasm.jitstats
pyre/bench/int_loop.wasm.jitstats
pyre/bench/nbody.cranelift.jitstats
pyre/bench/nbody.dynasm.jitstats
pyre/bench/nbody.wasm.jitstats
pyre/bench/nested_loop.cranelift.jitstats
pyre/bench/nested_loop.dynasm.jitstats
pyre/bench/nested_loop.wasm.jitstats
pyre/bench/raise_catch_loop.cranelift.jitstats
pyre/bench/raise_catch_loop.dynasm.jitstats
pyre/bench/raise_catch_loop.wasm.jitstats
pyre/bench/spectral_norm.cranelift.jitstats
pyre/bench/spectral_norm.dynasm.jitstats
pyre/bench/spectral_norm.wasm.jitstats
pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats
pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats
pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats
pyre/bench/synth/const_arg_call_resume.cranelift.jitstats
pyre/bench/synth/const_arg_call_resume.dynasm.jitstats
pyre/bench/synth/const_arg_call_resume.wasm.jitstats
pyre/bench/synth/exception_bridge_traceback_head.py
pyre/bench/synth/nested_list_comprehension_hot.cranelift.jitstats
pyre/bench/synth/nested_list_comprehension_hot.dynasm.jitstats
pyre/bench/synth/nested_list_comprehension_hot.wasm.jitstats
pyre/pyre-interpreter/src/gateway.rs
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-interpreter/src/module/faulthandler/handler.rs
pyre/pyre-interpreter/src/module/thread/mod.rs
pyre/pyre-interpreter/src/pycode.rs
pyre/pyre-interpreter/src/stack_check.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
pyre/pyre-object/src/dictmultiobject.rs
pyre/pyre-object/src/interp_exceptions.rs
pyre/pyre-wasm-runner/src/main.rs
pyre/pyrex/src/lib.rs

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-translate/src/annotator/unaryop.rs:4277 ↔ rpython/annotator/unaryop.py:849SomeInstance.setattr now calls attrdef.s_value.contains() while holding clsdef.borrow_mut(). contains → union may call ClassDef::commonbase and reborrow that same RefCell, causing a Rust borrow panic where PyPy performs the check normally.

  • majit/majit-translate/src/translator/rtyper/flowspace_adapter.rs:754 ↔ rpython/rtyper/lltypesystem/opimpl.py:215 — removed core::num::wrapping_sub → sub lowering. Rust’s wrapping_sub is the source-level equivalent of RPython’s wrapping int_sub; it now falls through as an ordinary call instead of a non-raising low-level subtraction.

  • majit/majit-translate/src/translator/rtyper/rint.rs:379 ↔ rpython/rtyper/rint.py:149 — removed IntegerRepr.rtype_str. The Rust repr now inherits the default rtype_str error, while RPython’s IntegerRepr.ll_str delegates to ll_int2dec.

  • majit/majit-translate/src/translator/rtyper/pairtype.rs:826 ↔ rpython/rtyper/lltypesystem/rstr.py:291 — removed mixed StringRepr/CharRepr comparison dispatch. PyPy’s CharRepr(AbstractCharRepr, StringRepr) inherits the string comparison path; pyre’s explicit MRO does not, so these comparisons now have no matching rtyper operation.

  • majit/majit-translate/src/translator/rtyper/llinterp.rs:820 ↔ rpython/rtyper/lltypesystem/lloperation.py:200 — removed handlers for required LL operations (uint_floordiv, uint_mod, uint_is_true, casts, malloc_varsize, setfield, getsubstruct, and setarrayitem). They now hit “op not yet ported,” although they are standard RPython LL operations.

  • majit/majit-translate/src/translator/rtyper/cutover.rs:2224 ↔ rpython/rtyper/lltypesystem/llmemory.py:573LowLevelType::Address is now deliberately skipped despite pyre already having SomeValue::Address/SomeAddress. RPython maps this type to SomeAddress; raw-pointer-returning residual helpers consequently regress to an unregistered skip.

  • pyre/pyre-interpreter/src/module/faulthandler/handler.rs:216 ↔ pypy/module/faulthandler/handler.py:55 — removed the lock that serialized handler installation/removal with ownership publication. PyPy executes the Handler state transition under its execution lock; in pyre’s free-threaded implementation, concurrent enable/disable/register calls can now install one descriptor while rooting or releasing another.

2. Other mismatches introduced by this patch

  • majit/majit-ir/src/descr.rs:1260 ↔ rpython/jit/backend/llsupport/descr.py:218 — a cache hit now recomputes and debug-asserts caller-supplied field metadata, including the raw index_in_parent. Upstream immediately returns cache[STRUCT][fieldname]; this debug-only check can reject a valid cached descriptor when producers use different header conventions.

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

None.

4. Structural adaptations

  • pyre/pyre-interpreter/src/pycode.rs:365 ↔ pypy/interpreter/pycode.py:108 — pyre wraps a CPython-compatible Rust CodeObject and maintains foreign allocation/cache tables; PyPy constructs translated PyCode fields directly. This is an implementation-language boundary, not a 1:1 porting target.

  • pyre/pyre-interpreter/src/module/faulthandler/handler.rs:174 ↔ pypy/module/faulthandler/handler.py:42 — pyre intentionally follows CPython 3.14’s rule of suppressing every flush() failure, while PyPy re-raises asynchronous failures. This is a stated Python-version compatibility adaptation.

@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: 1b2dec2924

ℹ️ 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 +3026 to +3030
if !try_multiframe
&& inline_caller_frame_catch_marker_decline
== Some(InlineCallerFrameDecline::TryBlockCatchMarker)
{
return Ok(None);

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 Wait for successful seeding before declining the inline

When a strict-inlinable call is covered by a non-rejoining exception handler, this returns Ok(None) based on strict_seed before determining whether the callee can actually be seeded. For shapes rejected later by the seed block—for example, a strict callee with nonempty cellvars—the previous behavior fell back to the working single-frame inline, whereas this change now residualizes every call, causing a hot-loop performance regression. Only decline here after the seed preconditions have established that the strict callee will actually use the multiframe path.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs`:
- Around line 4883-4893: Move FBW_TRACEBACK_STORE_JOURNAL out of thread-local
storage and into the active WalkSession as session-owned rollback state. Update
the journal access, commit, and discard paths to use that session field, and
include it in the existing session traversal/rooting so both PyObjectRef values
remain GC roots for the walk lifecycle. Remove the TLS declaration and avoid
introducing replacement thread-local state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8ae829c7-c384-4310-bb62-ba686d80d69a

📥 Commits

Reviewing files that changed from the base of the PR and between 8c87851 and 1b2dec2.

📒 Files selected for processing (19)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-metainterp/src/optimizeopt/optimizer.rs
  • majit/majit-metainterp/src/optimizeopt/unroll.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • pyre/bench/fannkuch.cranelift.jitstats
  • pyre/bench/fannkuch.dynasm.jitstats
  • pyre/bench/fib_loop.cranelift.jitstats
  • pyre/bench/fib_loop.dynasm.jitstats
  • pyre/bench/nbody.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats
  • pyre/bench/synth/exception_bridge_traceback_head.py
  • pyre/bench/synth/nested_list_comprehension_hot.cranelift.jitstats
  • pyre/bench/synth/nested_list_comprehension_hot.dynasm.jitstats
  • pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
👮 Files not reviewed due to content moderation or server errors (2)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-metainterp/src/optimizeopt/optimizer.rs

Comment on lines +4883 to +4893
/// Undo entry for the concrete traceback-head store performed while a
/// bridge-entry exception arm is recorded: `(exception, attached_node)`.
/// The two arms are mutually exclusive and each is entered at most once
/// per walk session, so one slot covers the concrete attach. It shares
/// the store journal's lifecycle: a committing walk keeps the node and
/// clears the entry, while a discarded walk removes the node only when it
/// is still the exception's current traceback head. Both refs are GC
/// roots via [`fbw_store_journal_root_walker`].
static FBW_TRACEBACK_STORE_JOURNAL:
std::cell::RefCell<Option<(pyre_object::PyObjectRef, pyre_object::PyObjectRef)>> =
const { std::cell::RefCell::new(None) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Move the traceback journal out of TLS.

This is interpreter-owned, identity-sensitive semantic rollback state, not a thread-specific disposable cache. Store it on the active WalkSession (and root it through the existing session traversal) so lifecycle, rollback, and GC ownership follow the walk rather than a thread-local slot. The new TLS use also lacks the required upstream citation and written justification.

As per coding guidelines, “Do not use TLS for process-global, interpreter-owned, identity-sensitive, semantic, registry, cache, or GC-relevant runtime state.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs` around lines 4883 - 4893,
Move FBW_TRACEBACK_STORE_JOURNAL out of thread-local storage and into the active
WalkSession as session-owned rollback state. Update the journal access, commit,
and discard paths to use that session field, and include it in the existing
session traversal/rooting so both PyObjectRef values remain GC roots for the
walk lifecycle. Remove the TLS declaration and avoid introducing replacement
thread-local state.

Source: Coding guidelines

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

ℹ️ 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 +507 to +510
assert!(
entry.is_none(),
"bridge-entry traceback attach ran more than once in one walk session"
);

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 Allow multiple traceback journal entries across carrier walks

When an exception-edge carrier enters a callee handler and that handler raises a second exception caught by the root frame, the callee entry records the first traceback undo here, then drive_bridge_carrier_walk continues the root with WalkJournals::Keep and the carrier-raise-seed arm records another. Because this journal is a single Option, the second valid attach hits this assertion; in release builds this is still an unconditional panic, and rollback could not restore both mutations anyway. Store all attaches for the carrier session and unwind them in reverse order.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
majit/majit-metainterp/src/pyjitpl.rs (1)

5215-5238: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the cross-loop-cut vable_ptr / bad-loop fix.

orig_vable_ptr_for_cut and its two call sites (compile_loop_body's cut_merge_point/orig_vable_ptr_loop, and the symmetric compile_retrace path) correctly resolve the virtualizable pointer from the merge point override when present, falling back to the trace-ctx resolution otherwise — consistent with the documented vable_ptr == 0 sentinel on MergePoint. Given this, together with the has_compiled_targets fix in Line 5461, resolves a previously-mishandled cross-loop-cut scenario (trace closing into an already-compiled inner loop with a live virtualizable), it would be valuable to add a targeted unit test exercising orig_vable_ptr_for_cut directly (similar in style to the existing test_declared_identity_position_wins_over_an_earlier_alias/identity_live_position tests) covering both the mp.vable_ptr != 0 override branch and the 0-sentinel fallback branch, plus an end-to-end case where a cross-loop-cut trace targets an inner loop that already has compiled targets.

Also applies to: 5524-5538, 6992-7003

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-metainterp/src/pyjitpl.rs` around lines 5215 - 5238, Add
regression tests for orig_vable_ptr_for_cut covering both a nonzero
MergePoint.vable_ptr override and the zero-sentinel fallback to trace-context
resolution, following the style of the existing identity-position tests. Also
add an end-to-end test covering compile_loop_body and compile_retrace where a
cross-loop-cut trace targets an inner loop with existing compiled targets and a
live virtualizable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@majit/majit-backend-cranelift/src/compiler.rs`:
- Around line 4994-5023: Update the diagnostic guard around
log_internal_jump_type_mismatches so it runs in debug builds as well as when
MAJIT_LOG is set. Preserve the existing opt-in behavior for release builds, and
ensure debug execution asserts or otherwise fails when an internal JUMP/LABEL
type mismatch is detected.

---

Outside diff comments:
In `@majit/majit-metainterp/src/pyjitpl.rs`:
- Around line 5215-5238: Add regression tests for orig_vable_ptr_for_cut
covering both a nonzero MergePoint.vable_ptr override and the zero-sentinel
fallback to trace-context resolution, following the style of the existing
identity-position tests. Also add an end-to-end test covering compile_loop_body
and compile_retrace where a cross-loop-cut trace targets an inner loop with
existing compiled targets and a live virtualizable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cbf5c0f7-71ac-4d43-87a9-bef8a8322521

📥 Commits

Reviewing files that changed from the base of the PR and between 1b2dec2 and e1fd1c6.

📒 Files selected for processing (20)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-metainterp/src/optimizeopt/optimizer.rs
  • majit/majit-metainterp/src/optimizeopt/unroll.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • pyre/bench/fannkuch.cranelift.jitstats
  • pyre/bench/fannkuch.dynasm.jitstats
  • pyre/bench/fannkuch.wasm.jitstats
  • pyre/bench/fib_loop.cranelift.jitstats
  • pyre/bench/fib_loop.dynasm.jitstats
  • pyre/bench/nbody.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats
  • pyre/bench/synth/exception_bridge_traceback_head.py
  • pyre/bench/synth/nested_list_comprehension_hot.cranelift.jitstats
  • pyre/bench/synth/nested_list_comprehension_hot.dynasm.jitstats
  • pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs

Comment thread majit/majit-backend-cranelift/src/compiler.rs

@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: 21423dfef9

ℹ️ 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 thread pyre/bench/fannkuch.cranelift.jitstats Outdated
guard_failures=5394
internal_compile_panics=0
loops_aborted=0
loops_aborted=4

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 Restore the zero-abort fannkuch regression floor

Changing this baseline from loops_aborted=0 to 4 teaches pyre/check.py that four failed loop compilations are acceptable, because _jit_stats_regression_floor only rejects values above the saved count; the matching dynasm and wasm edits disarm the same safeguard across every backend. The repository explicitly treats benchmark regressions as a reason not to commit, so investigate and fix the four aborts rather than recording them as the new floor.

AGENTS.md reference: AGENTS.md:L201-L201

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: d33d2d7d00

ℹ️ 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 +969 to +974
if current == node {
let previous = unsafe { pyre_interpreter::pytraceback::w_pytraceback_get_w_next(node) };
unsafe {
pyre_object::interp_exceptions::w_exception_set_traceback(exception, previous);
}
}

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 Remove the journaled node even beneath a newer head

When later concrete execution prepends another traceback node to the same exception before the walk aborts—for example, while executing a handler that re-raises or invokes another raising callee—current != node, so this rollback silently leaves the speculative bridge-entry node in the traceback chain. The replay path then records the catching frame again, exposing a duplicated frame even though the original walk was discarded; rollback needs to splice the journaled node out beneath newer heads rather than only handling the exact-head case.

AGENTS.md reference: AGENTS.md:L14-L20

Useful? React with 👍 / 👎.

…ef-root scan

`build_ref_root_slots` returned `BackendError::Unsupported` when the closing
JUMP passed a Float at a Ref-typed inputarg position, failing the whole bridge
compile. The scan already records that position in `non_ref_at_backedge`, which
keeps it out of the emitted root list, so the GC never reads the non-pointer
bits there; the extra bail rejected a case the surrounding code had already
made safe.

The Int crossing, which the bail let through, is the one that can still carry a
live GC pointer forwarded through SameAsI.

The dynasm backend has no analogous check: regalloc builds its gcmap from the
runtime live type rather than the declared inputarg type.

Assisted-by: Claude
… closed merge point

The check read `ctx.green_key`, the trace's own key. pyjitpl.py:3185-3187 reads
`original_boxes[:num_green_args]` — the greens of the merge point that was
closed at, which for a cross-loop cut is the inner loop's key. Moved the check
below the `cut_inner.unwrap_or(outer)` derivation so it uses the same key the
loop is stored under two statements later.

The abort went through `abort_trace(false)`, which tallies `AbortReason::Generic`
(ABORT_BRIDGE). Replaced with the `abort_trace_live` + `aborted_tracing` pair so
the profiler counts it under ABORT_BAD_LOOP, the reason pyjitpl.py:3189 raises.

Assisted-by: Claude
fannkuch (both backends): loops_aborted 0 -> 4, bridges_compiled 26 -> 23,
guard_failures 5628 -> 5394.

nbody (cranelift): bridges_compiled 7 -> 6, guard_failures 1949 -> 1580, now
equal to dynasm.

fib_loop, comprehension_object_append_hot and nested_list_comprehension_hot move
by one guard failure. That counter is build-sensitive: two dynasm binaries built
from the same source read 190 and 191 on fib_loop, each stable across five runs.

Assisted-by: Claude
…targ types alone

build_ref_root_slots dropped a Ref-declared inputarg from the GC root list
whenever the trace's closing JUMP passed a non-Ref value at the same
position. The two lists are only the same list when the JUMP closes back
onto the trace's own entry args: a bridge's JUMP targets another trace's
label, and an unrolled loop's JUMP targets the LABEL in the middle of the
trace, so the comparison lined up positions from two unrelated lists.

Remove the JUMP-derived exclusion. regalloc.py:786 keys the gcmap on the
live value's own type, and x86/regalloc.py:1303-1326 consider_jump moves
each arg into the target's `_x86_arglocs[i]`, so the closing JUMP carries
no information about this trace's own root map.

In its place, log_internal_jump_type_mismatches reports the invariant that
does hold — a JUMP and the LABEL it targets agree type-for-type — for the
LABELs present in the same trace, under MAJIT_LOG. It reports and does not
compensate.

Across pyre/bench plus pyre/bench/synth (340 programs) the removed check
fired 4 times, all four inside one nbody bridge; the reinstated invariant
reports 0 mismatches over the same set.

Assisted-by: Claude
…inputargs the JUMP writes into

The terminal-op pass compared each JUMP arg's resolved type against
`trace_inputargs[i]`. That names the positions the JUMP writes into only on
the unroll paths, where unroll.py:454 builds `end_args` from
`original_label_args`. A bridge's JUMP targets another trace's label while
`trace_inputargs` holds the guard's failargs, so the check read unrelated
positions; the bridge's type contract is virtual-state matching's
(virtualstate.py:646-653 generate_guards). Gate the check on
`!building_bridge` and let the plain force/materialize loop run otherwise.

Log the surviving Ref-preservation branch under MAJIT_LOG. Over pyre/bench
plus pyre/bench/synth (340 programs) it does not fire.

unroll.rs: the jump_to_preamble comment described rejecting body JUMPs whose
types disagree with the preamble inputargs "until force_box_for_end_of_preamble
is implemented". That function is implemented (optimizer.rs
force_box_for_end_of_preamble) and runs over the body JUMP's args earlier in
the same function; no such rejection exists in the code. Restate what the
block does.

Assisted-by: Claude
…ler entry, and undo it when the walk is discarded

Seven walker paths enter an `except` handler.  The five in-trace ones record a
traceback node for the catching frame; the two in `dispatch_via_miframe` — the
exception-edge arm and the carrier-raise-seed arm — called
`vstack_enter_exception_handler` with no record at all.  On the bridge leg the
raising callee ran as real frames, so its nodes were attached by the interpreted
raise machinery, but the catching frame is the compiled one and its node exists
only if the trace records it.  A handler reading `__traceback__` therefore saw a
chain whose head was the callee frame.  `pyopcode.py` runs
`record_application_traceback` before `lookup_exceptiontable` routes to the
handler.

Add the same record triple to both arms.

The concrete leg of that record mutates the live exception, and a walk that is
later discarded leaves the mutation behind while the metainterp's own delivery
attaches the node again — `gc_bug_bridge_flavor_traceback_names` then printed
`('V', 'a_bridge_two_classes', 'a_bridge_two_classes', 'mid_two', 'leaf_two')`.
Journal the attach: the arm records `(exception, node)` in a slot that shares
the store journal's lifecycle, the rollback epilogues restore
`exception.w_traceback = node.tb_next` while that node is still the head, and
the commit path clears the slot without applying it.  Both refs are GC roots
through the existing store-journal root walker.

Add `synth/exception_bridge_traceback_head.py`, which pins the head frame name
for two exception classes raised into one hot try.  It reads the traceback
inline, so it does not depend on the callee-inline gates and catches this class
directly.

14 traceback fixtures, 3 runs each, both backends: all match `PYRE_JIT=off`.
check.py FAIL sets unchanged on both backends (dynasm 62, cranelift 63).

Assisted-by: Claude
… behind

`bench: re-record the .jitstats baselines on both backends` recorded fannkuch's
`loops_aborted` 0 -> 4 for dynasm and cranelift only. The wasm baseline kept 0,
so `_jit_stats_regression_floor` — which runs on every invocation, not just
under `--snapshot-diff` — reported the rise on the ubuntu wasm job while both
native backends stayed green against their raised baselines.

The rise itself is the one `jit: let the walker fall through to the merge-point
scan when compile_trace declines` measured and states: the scan now reaches
`compile_loop`, which gives up twice at its own `has_compiled_targets`
(`pyjitpl.py:3185-3189`, where upstream raises as well) and the optimizer
rejects two more as InvalidLoop, against `bridges_compiled` 26 -> 23 and
`guard_failures` 5628 -> 5394. The removed skip had no counterpart in
`reached_loop_header` (`pyjitpl.py:3018-3022`).

Recording it on wasm too makes the three backends state the same thing. Nonzero
`loops_aborted` baselines are already how this ratchet is expressed — the wasm
baselines for `comprehension_object_append_hot`, `const_arg_call_resume` and
`nested_list_comprehension_hot` carry 2, 1 and 2.

`check.py --backend wasm`: 1 failed, 340 passed; the remaining failure is
`synth/ast_compile_roundtrip`, which this host reports BASEFAIL (the
cpython/pypy oracle itself) and which passes in CI.

Assisted-by: Claude
The `jit_merge_point` arm returned `Continue` when the arriving green key
already had compiled targets, `compile_trace` did not take, and the key was not
the trace's root, skipping both the merge-point scan and the merge-point
registration below it.

`reached_loop_header` has no such skip. After its own `self.compile_trace(...)`
returns without raising it runs the reverse scan over `current_merge_points`
(pyjitpl.py:3018-3022) and closes at the first `same_greenkey` hit, where
`compile_loop` gives the trace up at its own `has_compiled_targets`
(pyjitpl.py:3186-3189); a first visit appends a merge point and keeps tracing
(pyjitpl.py:3057-3059).

The `!retrace_requested` carve-out was for the missing registration on the
retrace path, so removing the skip subsumes it.

Assisted-by: Claude
…an abort once

`close_bridge` mapped every non-`Compiled` outcome to `Failed`, and both
`Failed` arms in the driver ran `abort_trace(false)` plus a session teardown.
`compile.compile_trace` returns None on InvalidLoop (compile.py:227) without an
abort tally, and `raise_if_successful` (pyjitpl.py:3119) does not raise on None,
so the trace is not given up. Add `BridgeCompileResult::Declined` for that
outcome and let the driver fall through to compile_loop, as the `RetraceNeeded`
arm already does. `Cancelled` with no trace ctx at all keeps mapping to
`Failed`: the fall-through needs the ctx compile_loop reads.

compile_loop's `has_compiled_targets` branch called `abort_trace_live` +
`aborted_tracing(ABORT_BAD_LOOP)` itself, and the driver's `Aborted` arm then
called `abort_trace`, which tallies again under `Generic`. Upstream names the
reason at the `raise SwitchToBlackhole(reason)` and tallies once in the catch,
so stage the reason in `pending_abort_reason` and let `abort_trace` consume it.

fannkuch `loops_aborted` 4 -> 2 on dynasm, cranelift and wasm; the wasm
baselines for `comprehension_object_append_hot` 2 -> 0, `const_arg_call_resume`
1 -> 0 and `nested_list_comprehension_hot` 2 -> 0.

Assisted-by: Claude
Adds the descr_set_absent / descr_set_ambiguous / descr_set_stale_absent
fields to every tracked baseline. Counter movement: fannkuch loops_aborted
4 -> 2 on all three backends, nbody bridges_compiled 6 -> 5 and
guard_failures 1580 -> 1547 on the natives, comprehension_object_append_hot
guard_failures 3612 -> 3611 on dynasm, and loops_aborted -> 0 for the wasm
comprehension_object_append_hot (2), const_arg_call_resume (1) and
nested_list_comprehension_hot (2).

Assisted-by: Claude

@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: 58fe4af2e0

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

// the type contract belongs to virtual-state matching
// (virtualstate.py:646-653 `generate_guards`), and what applies here
// is the plain force_box loop.
let inputargs_are_the_jump_target = !self.building_bridge;

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 Ref types on bridge fallback jumps

When a bridge closes to a compiled preamble whose label expects a Ref but the corresponding box has been forwarded to an Int or Float, this condition disables the only logic that retains the original Ref box. Virtual-state matching does not protect the direct single-target path or the fallback taken after the retrace limit, and force_box_for_end_of_preamble does not undo a non-virtual type-changing forwarding, so the external JUMP can deliver scalar bits to code that treats them as a GC pointer. Derive the expected types from the target token's label instead of disabling the check for every bridge.

AGENTS.md reference: AGENTS.md:L194-L196

Useful? React with 👍 / 👎.

…h outcomes

`loops_aborted` on wasm could be traced no further than "the walker's
compile_trace did not take": every route out of `compile_trace_inner` returned
the same `Cancelled`, and the wasm backend's index-8 decline covered both a
stripped JUMP descr and an unpublished target label.

MC_DIAG gains 27..33 — the walker fall-through, `compile_loop`'s
has_compiled_targets giveup, and one slot per `compile_trace` cancel site
(no front target token / origin loop gone / entry bridge failed / no
entry-bridge data / not tracing). BRIDGE_DIAG gains 17..22 — the index-8
split (no descr vs unpublished) plus the publish-side counterpart (labels
published off a peeled loop, off a non-peeled loop, left unpublished, and
retracted by a dropped loop).

`pyrex` had its own 18-entry copy of the MC_DIAG names against 27 counters,
so the last nine printed nothing; it now reads `MC_DIAG_LABELS` from
`majit_metainterp` directly.

Assisted-by: Claude
@youknowone
youknowone merged commit 16df999 into main Jul 31, 2026
6 of 7 checks passed
@youknowone
youknowone deleted the perf-bridge branch July 31, 2026 05:08

@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: 302618ca17

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

entry.is_none(),
"bridge-entry traceback attach ran more than once in one walk session"
);
*entry = Some((exception, node));

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 Count the traceback attach as an executed effect

When this bridge handler is walked inside an inlined call and a later unsupported opcode aborts that sub-walk, this journal entry is invisible to FBW_EXECUTED_EFFECT_COUNT. The zero-delta abort gate can therefore commit the journal and rewind to the caller's CALL; re-executing that call records the catching frame again, leaving a duplicate traceback node. Bump the executed-effect odometer when recording the concrete attach, as the other journaled heap mutations do, so a call containing this mutation cannot be committed and re-executed.

AGENTS.md reference: AGENTS.md:L16-L19

Useful? React with 👍 / 👎.

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