Skip to content

jit: branch-arm hazard scoping to invalid mirror + inlined-callee abort_permanent decline (gh#467) - #471

Merged
youknowone merged 13 commits into
mainfrom
fib_recursive
Jul 12, 2026
Merged

youknowone merged 13 commits into
mainfrom
fib_recursive

Conversation

@youknowone

@youknowone youknowone commented Jul 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

Three JIT tracing fixes, census-driven:

  1. Scope the branch-arm unrestorable-Ref hazard to the invalid walk mirror
    (jitcode_dispatch.rs). Hazard (1) of the branch-guard resume decline
    (reads_null_ref) ran the branch_arm_reads_unrestorable_ref register scan
    regardless of walk-mirror validity, while Hazards (2)/(3) were already scoped
    to !vstack_valid. A valid Eliminate pc_map and per-PC labels by switching the tracer to interpret JitCode bytecode #73 walk mirror sources kept operand-stack slots
    from vstack_boxes and kept locals from the vable shadow per-slot on resume,
    so the register scan only proves a hazard for the invalid mirror.
    capture_resumedata (pyjitpl.py:2610-2624) appends virtualizable_boxes and
    the framestack to every guard's resume data, so these reads are always
    restorable upstream. Soundness was force-verified: with the decline force-
    disabled, the two affected guards re-executed their not-taken arms across
    594+404 real guard failures with output byte-identical to the interpreter.
    exec_defined_global_read and loops_comprehension each gain one compiled
    loop.

  2. Decline the walk when an inlined callee body carries abort_permanent
    (trace.rs, gh#467 stopgap). loop_body_has_abort_permanent scans only the
    top-level jitcode, so an abort_permanent inside an inlined callee slipped
    past it: a non-journaled concrete heap store executed during the walk, the
    callee sub-walk aborted, and the epilogue replayed from loop entry —
    re-executing the store (d[0] = d[0] + 1 over-counted 300001 vs 300000,
    deterministic). The new loop_inlines_abort_permanent_callee resolves callee
    candidates concretely before the walk (module globals via co_names,
    root-frame fastlocals, closure cells), scans their jitcode bodies
    transitively, and declines up front so the loop re-interprets (correct, at
    interpreter speed). This is a documented over-declining adaptation: upstream
    has no up-front scan — an unsupported op raises SwitchToBlackhole and
    run_blackhole_interp_to_cancel_tracing (pyjitpl.py:2949) continues forward
    in the blackhole interpreter, so nothing ever replays. Callees unresolvable
    before the walk (attributes, container elements, call results) remain the
    residual tracked in gh#467; the convergence is the fib_recursive: recursive CALL_ASSEMBLER lands (now faster than CPython); residual ~4-5x-vs-pypy floor needs flat/virtualized jitframe #126/fib_recursive major performance blockers #215 forward-resume
    work.

  3. Correct stale TO_BOOL references in the vstack classifier docs
    (jitcode_dispatch.rs, doc-only). TO_BOOL emits no JitCode
    (Instruction::ToBool => {} in the codewriter) and never reaches
    classify_vstack_opcode.

Validation

  • check.py dynasm 160/160 + cranelift 160/160.
  • cargo test -p pyre-jit-trace and -p pyre-jit (dynasm): all green.
  • gh#467 repros (global helper, extend variant, arg-passed helper) flip to
    interpreter-identical output; a clean-helper control loop still JIT-compiles
    (~7.6×).
  • Over-decline census for the stopgap: 0 of 201 corpus programs newly
    declined.

Codex parity review

Sections 1/2: the two findings are the stopgap's own over-declining/static
nature — accepted as a deliberate, documented adaptation (the in-code comment
cites run_blackhole_interp_to_cancel_tracing as the orthodox mechanism and
gh#467/#126/#215 as the convergence). Sections 3/4: pre-existing #125-family
and #215 P2 items, unchanged here; this diff narrows the
jitcode_dispatch.rs kept-stack decline mismatch rather than widening it.

Closes nothing; tracks gh#467.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved AArch64 function calls with more reliable handling of mixed integer and floating-point arguments, including overflow arguments passed on the stack.
    • Improved JIT recovery when optimized calls are interrupted, helping prevent duplicated side effects and ensuring interpreter state is restored safely.
    • Added safeguards to avoid tracing hot loops that contain unsupported permanent-abort paths.
    • Refined handling of object state, frame restoration, and garbage collection during JIT fallback scenarios.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 31 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: fe06487a-e4e2-4e75-a306-eb63a352509f

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd8976 and 286c1ee.

📒 Files selected for processing (8)
  • majit/majit-backend-dynasm/src/aarch64/assembler.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/state.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-jit/src/jit/codewriter.rs

Walkthrough

Changes

AArch64 Call ABI

Layer / File(s) Summary
Independent ABI banks and stack spills
majit/majit-backend-dynasm/src/aarch64/assembler.rs
Call arguments now use separate core and floating-point register banks, spill overflow arguments to aligned outgoing stack slots, and restore sp after the call.

JIT Forward-Flush Recovery

Layer / File(s) Summary
Effect tracking and abort resume carrier
pyre/pyre-jit-trace/src/jitcode_dispatch.rs
Executed effects and abort-call resume state are tracked, reset, exposed through internal helpers, and rooted during abort unwinding.
Inline abort decision and dispatch updates
pyre/pyre-jit-trace/src/jitcode_dispatch.rs
Inline callee handling snapshots effect state, conditionally captures outer-call resume data, and updates dispatch gates and hazard logic.
Outer-call frame restoration and abort epilogue
pyre/pyre-jit-trace/src/state.rs, pyre/pyre-jit-trace/src/trace.rs
Outer-frame state is validated and restored at CALL boundaries, with legacy replay retained when flushing fails.
Transitive abort-permanent loop scan
pyre/pyre-jit-trace/src/trace.rs
Loop tracing scans eligible transitively inlined callees for abort_permanent and declines affected traces before walking.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InlineDispatch
  participant AbortUnwind
  participant WalkTrace
  participant OuterFrame
  InlineDispatch->>InlineDispatch: record executed-effect state
  InlineDispatch->>AbortUnwind: latch CALL pc and operand stack
  AbortUnwind->>WalkTrace: root saved PyObjectRef values
  WalkTrace->>OuterFrame: flush_walk_end_state_at_outer_call
  OuterFrame-->>WalkTrace: restore frame or reject flush
  WalkTrace->>OuterFrame: re-execute CALL or use legacy replay
Loading

Possibly related issues

Possibly related PRs

  • youknowone/pyre#440 — Overlaps the inline dispatch CALL-boundary resumption and outer-frame snapshot logic.

Poem

I’m a rabbit with registers, hopping through the stack,
Core and float lanes keep every argument on track.
When JIT paths stumble, I save the CALL in flight,
Root each little object till the frame is right.
No doubled hops, no replayed bite—
Forward flush brings code back bright!

🚥 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 accurately captures two major PR themes and is specific enough to identify the change, even though it omits several additional fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fib_recursive

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: 9da2dea0e9

ℹ️ 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 +1781 to +1784
if cand.is_null() || (*cand).ob_type as *const () as usize != function_type_addr {
return false;
}
let callee_w_code = pyre_interpreter::function_get_code(cand);

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 Reject BuiltinCode-backed functions before scanning callees

When a hot loop references an imported/module builtin created with make_builtin_function* (for example from sys import getdefaultencoding), the object has FUNCTION_TYPE but its code is a BuiltinCode, not a PyCode. This gate admits it, then function_get_code feeds that BuiltinCode to sub_jitcode_body_for_code/jitcode_for, which treats the pointer as a PyCode via w_code_get_ptr and can panic or dereference bogus CodeObject memory during the upfront scan. Please also reject is_builtin_code(callee_w_code as PyObjectRef) before trying to build/scan a sub-jitcode.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 0b607d1. make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object via function_new_with_fixed_code, so an imported builtin in module globals passed the gate and its BuiltinCode reached sub_jitcode_body_for_code/w_code_get_ptr as if it were a PyCode. The scan now rejects is_builtin_code right after the null/visited check. Regression repros (builtin import called in a hot loop with a heap store, alone and combined with a hidden callee) run without crash and match the interpreter; check.py 161/161 on both backends.

— commented by Claude

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

🤖 Codex parity review

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

1. Regressions to PyPy parity introduced by this patch

  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs:8499 ↔ rpython/jit/metainterp/pyjitpl.py:177 — frame identity remains bypassable: callee_vable_ref_at accepts a slot when fold_frame_reg == frame_reg even if entry.frame_reg != frame_reg. The new test explicitly endorses this foreign-frame read at jitcode_dispatch.rs:31804. PyPy keeps separate MIFrame.registers_* per inline frame; a same-numbered local in another frame cannot satisfy this read.

2. Other mismatches introduced by this patch

  • pyre/pyre-jit-trace/src/trace.rs:216 ↔ rpython/jit/metainterp/blackhole.py:1711 — mid-body recovery constructs the callee with only &[x_arg], although the captured state supports arbitrary live_locals and the inline path supports nparams parameters. _copy_data_from_miframe restores all three register banks of the resumed frame; this reconstruction either declines valid multi-argument calls during binding or begins from a differently bound frame.

  • pyre/pyre-jit-trace/src/trace.rs:2294 ↔ rpython/jit/metainterp/pyjitpl.py:2949 — the new transitive co_names/locals scan blacklists a trace before execution if any statically discoverable callee contains abort_permanent. Upstream does no such whole-program scan; it converts the actual live frame stack to blackhole frames after SwitchToBlackhole. This patch’s own comment admits it can reject uncalled and rarely taken callees.

  • pyre/pyre-jit-trace/src/trace.rs:1900 ↔ rpython/jit/metainterp/pyjitpl.py:2949 — InlineAbortCarrier::Entry flushes the caller to the CALL and re-executes the callee, rather than preserving and converting the inlined callee frame. This is a semantic shortcut, not PyPy’s per-frame forward-resume model; it remains dependent on effect heuristics instead of exact frame reconstruction.

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

  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs:13976 ↔ rpython/jit/metainterp/pyjitpl.py:177 — fallback “single-frame collapse” resumes at the caller CALL boundary and re-executes an inlined callee. PyPy retains one MIFrame per inline call and snapshots each frame’s own registers; this pre-existing path loses callee frame identity.

  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs:357 ↔ rpython/jit/metainterp/pyjitpl.py:177 — CalleeLocalsShadow uses slot-indexed HashMaps as a side table for local SSA/concrete values. PyPy stores inline-frame state in the frame’s registers_i/r/f; the side table predates this patch, though this patch extends it with frame_reg.

4. Structural adaptations

  • pyre/pyre-jit-trace/src/trace.rs:186 ↔ rpython/jit/metainterp/blackhole.py:1679 — CPython 3.11+ exception-table lookup and PyError propagation replace PyPy’s ExitFrameWithExceptionRef. This is a Python-version/runtime representation adaptation, not a direct port.

  • pyre/pyre-interpreter/src/pyframe.rs:391 ↔ pypy/interpreter/pyframe.py:259 — explicit FrameLocalsRoot rooting around generator/frame allocation is required by Rust’s moving-GC integration; PyPy’s GC-managed frame object does not expose this RAII root operation.

  • majit/majit-backend-dynasm/src/aarch64/assembler.rs:5303 ↔ rpython/jit/backend/aarch64/callbuilder.py:21 — Rust/Dynasm manually builds ABI register and stack argument plans instead of using PyPy’s Location/remap_frame_layout objects. The changed logic preserves the source’s separate core/float register banks and aligned overflow stack area; this is an implementation-language adaptation, not a detected behavioral mismatch.

youknowone added a commit that referenced this pull request Jul 10, 2026
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

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: 0b607d13b0

ℹ️ 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 +13877 to +13880
if matches!(e, DispatchError::AbortPermanentMarkerReached { .. })
&& is_top_inline
&& fbw_concrete_heap_write_count() == concrete_writes_before
{

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 Keep replay when a pre-call residual did not execute

When the caller has already hit a residual that try_execute_residual_call_via_executor declined, fbw_mark_unjournaled_effect() means that side effect has not run and depends on the legacy replay from loop entry. This gate only checks that the odometer did not move during the callee, so a pre-CALL unjournaled effect is included in concrete_writes_before and still allows the CALL-forward flush; run_perfn_walk then resumes the frame at the CALL and skips the earlier unexecuted residual entirely. Require that no unjournaled effect was present at the CALL before latching this forward resume.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 9609b6e (slice 2 of the CALL-forward flush, originally pushed as 9d08d3c before the rebase). Exactly as suggested: try_walker_inline_user_call now snapshots the unjournaled-effect flag at sub-walk entry (unjournaled_before_subwalk), and the latch requires the flag to have been clear at the CALL — a pre-CALL declined residual therefore forbids the forward flush and the walk stays on legacy replay from loop entry. The odometer was also split so it counts only executed effects (FBW_EXECUTED_EFFECT_COUNT); a mark raised inside the discarded zero-executed-effect callee no longer poisons the latch, while an outside mark still does. Covered by the m3_hidden_* battery; check.py 161/161 on both backends.

— commented by Claude

youknowone added a commit that referenced this pull request Jul 10, 2026
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

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

https://github.com/youknowone/pyre/blob/9d08d3c54cdf12590395b3b7291b5c7d464589f3/pyre-jit-trace/src/trace.rs#L1840-L1843
P2 Badge Skip callees that the walker cannot inline

This upfront decline treats every plain FUNCTION_TYPE value as a possible inlined callee, but the actual inline path rejects several shapes before walking the body (for example closures and zero-arg functions in try_walker_inline_user_call). When such a non-inlineable helper is merely referenced/called from the loop and its body contains an abort_permanent, this scan still disables the whole full-body walk even though the callee would have stayed a residual interpreter call and could not trigger the inlined-callee replay bug. Please mirror the inline eligibility gates here (at least reject closure/zero-param bodies via resolve_inlinable_callee) before scanning for abort_permanent.

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

youknowone added a commit that referenced this pull request Jul 10, 2026
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

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: 9609b6e1d2

ℹ️ 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/pyre-jit-trace/src/trace.rs Outdated
Comment on lines +1932 to +1935
if let Some(body) = crate::state::sub_jitcode_body_for_code(callee_w_code) {
for op in crate::jitcode_runtime::decoded_ops(body.code) {
if op.opname == "abort_permanent" {
return true;

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 Mirror inline eligibility before scanning abort callees

At this point the pre-scan treats any FUNCTION_TYPE whose body contains abort_permanent as an inline-abort hazard, but the actual try_walker_inline_user_call path only sub-walks exact positional, closure-free callees and returns Ok(None) for shapes such as closures (and other non-inlineable arities). In a hot loop that references or calls a closure helper with an unsupported opcode, this new scan will decline full-body walking even though that callee would have gone through the residual interpreter and could not trigger the inline-callee abort replay bug; please apply the same inline-eligibility gates before returning true here.

Useful? React with 👍 / 👎.

@youknowone

Copy link
Copy Markdown
Collaborator Author

Re: the P2 "Skip callees that the walker cannot inline" review — confirmed and fixed in f971f09. consider_candidate now reuses resolve_inlinable_callee and mirrors the inline path's static eligibility gates (closure-carrying, zero-parameter, missing sub-jitcode body, nparams > num_regs_r) before the abort_permanent scan and before transitive enqueueing; call-site-dependent gates (passed-arg count, recursion depth) are statically unresolvable and keep over-declining. New closure/zero-param repros previously declined by the scan now compile and match the interpreter; check.py 161/161 on both backends.

One honest consequence: the precision fix removes an accidental mask in one adversarial scratchpad repro (a zero-parameter factory referenced by the loop used to decline the whole walk), re-exposing the documented executed-effect callee-abort residual that is deferred to the #126/#215 forward-resume convergence (tracked in gh#467).

— commented 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: f971f09740

ℹ️ 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 +6073 to +6076
if !provably_side_effect_free
&& (writes_live_heap
|| heap_write_odometer_before
.is_some_and(|before| pyre_interpreter::call::frame_entry_count() != before))

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 FOR_ITER consumes before CALL-forward flush

When a top-level inlined callee advances an already-live iterator via FOR_ITER and then hits a supported abort/nested LoopBearingCalleeInlineUnsupported before any other counted effect, this gate does not bump the executed-effect odometer because ForIterNext is included in provably_side_effect_free above. The new CALL-forward latch only checks this odometer, so it can commit the outer frame, clear the in-flight FOR_ITER item, and re-execute the CALL, advancing the same iterator a second time; please either count ForIterNext consumes for this odometer or block the forward flush while an in-flight item exists.

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: 6bd8976abb

ℹ️ 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 +2013 to +2015
let Some(cand) =
pyre_object::dictmultiobject::w_dict_getitem_str(globals, name.as_str())
else {

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 Check builtins when scanning aborting callees

When a referenced call target is resolved through the frame's builtins fallback rather than the module globals (for example builtins.h = helper with no global h, or a transitive LOAD_GLOBAL miss inside a callee), this scan skips it because it only probes w_globals. The interpreter/JIT LOAD_GLOBAL path falls back to the frame's picked w_builtin on a globals miss, so such a helper can still be inlined; if it executes any counted effect before hitting abort_permanent, the CALL-forward flush is refused and the legacy replay can double-apply the caller's pre-CALL heap write. Please mirror the builtins lookup here before deciding no aborting callee is reachable.

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/state.rs`:
- Around line 3906-3915: Guard the raw code pointer before liveness analysis: in
the code block producing raw_code, check whether the pointer is null and
decline/return through the existing failure path before calling
crate::liveness::liveness_for. Match the null-check pattern used by the other
w_code_get_ptr call sites.
🪄 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

Run ID: a8c41730-d87f-4cd1-8f83-b414511ffbf2

📥 Commits

Reviewing files that changed from the base of the PR and between b8c0c7f and 6bd8976.

📒 Files selected for processing (4)
  • majit/majit-backend-dynasm/src/aarch64/assembler.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/state.rs
  • pyre/pyre-jit-trace/src/trace.rs

Comment thread pyre/pyre-jit-trace/src/state.rs Resolved

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

ℹ️ 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 +1948 to +1949
if nparams > body.num_regs_r || !visited.insert(callee_w_code) {
return false;

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 Track scanned callees by both code and globals

When two distinct function objects share the same CodeObject but have different __globals__ (for example via types.FunctionType(code, globals_a) and types.FunctionType(code, globals_b)), this code-only visited entry causes the second function's globals never to be scanned transitively. If the first globals bind h to a clean helper and the loop actually calls the second function whose LOAD_GLOBAL h reaches an abort_permanent helper, the upfront decline is missed and the original inline-callee abort/double-apply path remains reachable; the scan should remember at least (callee_w_code, callee_globals) for transitive global resolution.

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: 0e02d5f6d6

ℹ️ 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 +103 to +104
| pyre_interpreter::Instruction::BinaryOp { .. }
| pyre_interpreter::Instruction::CompareOp { .. }

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 Reject user-dispatch opcodes before mid-body replay

In the mid-body abort path, try_commit_midbody_abort runs execute_frame on the rebuilt callee before deciding whether the outer frame can be flushed; if this post-marker suffix contains BINARY_OP/COMPARE_OP on user objects, the interpreter can enter __add__/__eq__, mutate heap, and then raise. Because an Err here falls back to the legacy replay, that mutation remains and the loop/call is executed again, reintroducing the double-apply that gh#467 is trying to avoid. Only opcodes that cannot dispatch to user code or commit observable effects should pass this check.

Useful? React with 👍 / 👎.

youknowone added a commit that referenced this pull request Jul 11, 2026
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

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: 855414fe56

ℹ️ 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/pyre-jit-trace/src/trace.rs Outdated
Comment on lines +222 to +224
let Ok(mut retval) = frame.execute_frame(None, None) else {
return false;
};

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 Do not replay after rebuilt callee raises

When this mid-body path is selected, the inlined callee has already moved the executed-effect counter before the abort marker; if an allowed post-marker opcode can raise without mutating (for example LOAD_FAST_CHECK on an unbound local), execute_frame returns Err here. Returning false leaves the walk uncommitted, so the epilogue falls back to the legacy replay from loop entry and re-executes the already-committed prefix effect that this path was meant to avoid. Please reject suffixes that can raise until forward exception delivery exists, or commit the outer frame/exception instead of replaying after the rebuilt callee has run.

Useful? React with 👍 / 👎.

youknowone added a commit that referenced this pull request Jul 11, 2026
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

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: 331a77cc06

ℹ️ 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/pyre-jit/src/eval.rs
Comment on lines +4610 to +4611
if let Some(err) = propagated_exception {
return Some(LoopResult::Done(Err(err)));

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 Clear tracing depth before returning propagated exceptions

When the new mid-body abort path sets WALK_END_PROPAGATED_EXCEPTION at a merge-point trace site, this early return bypasses the post-trace cleanup just below that clears meta.tracing_call_depth and the starting trace bookkeeping. If the exception is caught and execution continues, eval_loop_jit keeps using the stale depth in its merge-point gating, so subsequent execution can skip or mis-route trace feeding until some unrelated path clears it. Please run the existing cleanup before returning the propagated exception.

Useful? React with 👍 / 👎.

TO_BOOL never reaches classify_vstack_opcode: the codewriter lowers
Instruction::ToBool to zero JitCode instructions (codewriter.rs
`Instruction::ToBool => {}`), so the py-pc boundary mapping never
yields its pc. Remove TO_BOOL from the ResultToTos doc list and note
in the Unmodeled arm why it cannot appear there.

Assisted-by: Claude
Hazard (1) of the branch-guard resume decline (reads_null_ref) ran the
branch_arm_reads_unrestorable_ref register scan regardless of walk-mirror
validity, while Hazards (2)/(3) were already scoped to !vstack_valid. A
valid #73 walk mirror sources kept operand-stack slots from vstack_boxes
and kept locals from the vable shadow per-slot on resume, so the register
scan only proves a hazard for the invalid mirror. Gate Hazard (1)
identically.

capture_resumedata (pyjitpl.py:2610-2624) appends virtualizable_boxes and
the framestack to every guard's resume data, so the not-taken arm's vable
and kept-slot reads are always restorable upstream.

exec_defined_global_read and loops_comprehension gain one compiled loop
each with output identical to the interpreter; check.py 160/160 on both
dynasm and cranelift; pyre-jit-trace and pyre-jit test suites pass.

Assisted-by: Claude
…nent

loop_body_has_abort_permanent scans only the top-level per-CodeObject
jitcode, so an abort_permanent inside an inlined callee slipped past it:
a non-journaled concrete heap store in the loop body executed during the
walk, the callee sub-walk aborted, and the epilogue replayed from loop
entry, re-executing the store (d[0]=d[0]+1 over-counted 300001 vs
300000).

Add loop_inlines_abort_permanent_callee as a sibling up-front decline:
resolve candidate callees concretely before the walk (module globals via
co_names, root-frame fastlocals and closure cells), gate to plain user
functions, scan their jitcode bodies transitively for abort_permanent,
and decline the walk so the loop re-interprets. Over-declining stopgap
until the forward-resume convergence (#126/#215); callees reached via
attributes, containers, call results, or deeper frames remain
unresolvable up front and are documented as the residual.

Corpus over-decline census: 0 of 201 programs newly declined. Repros
(global helper, extend variant, arg-passed helper) flip to
interpreter-identical output; a clean-helper control still compiles.
check.py 160/160 dynasm; pyre-jit-trace tests pass.

Assisted-by: Claude
… abort (gh#467)

When an abort_permanent marker fires inside a top-level inline sub-walk
whose callee committed no heap effect, flush the outer frame at the CALL
that entered the callee and resume the interpreter re-executing that
CALL, instead of the legacy replay from loop entry that re-applied a
non-journaled pre-CALL store (the gh#467 double-apply).

try_walker_inline_user_call latches (outer CALL py_pc, concrete
[callable, null_or_self, args...]) only when a new concrete-heap-write
counter is unmoved across the sub-walk; the carrier is GC-rooted through
the store-journal root walker. flush_walk_end_state_at_outer_call
flushes locals from the vable shadow, rebuilds the operand stack from
the concrete call operands, cross-checks the height against the forward
analysis depth_at_py_pc, and declines all-or-nothing on any mismatch
(the caller then keeps the legacy replay). Journals commit on this path.
Frame-level analogue of run_blackhole_interp_to_cancel_tracing
(pyjitpl.py:2949) without the inner-frame rebuild (#126/#215).

The up-front callee scan stays: it covers named effectful callees the
forward-flush gate declines; hidden effect-free callees (factory-made,
container-held, call-returned) are covered by this path. Hidden
EFFECTFUL callees remain on the legacy path, unchanged.

Repros: hidden dict/return/attr callee shapes flip 300001 to 300000;
named-callee and journaled-store shapes unchanged; effectful-callee gate
declines; clean-helper control still compiles. check.py 161/161 dynasm
and 161/161 cranelift; pyre-jit-trace and pyre-jit tests pass; GC stress
on the hidden repros passes.

Assisted-by: Claude
…t scan

make_builtin_function* wraps a BuiltinCode in a FUNCTION_TYPE object
(function_new_with_fixed_code), so a builtin imported into module
globals (from sys import getsizeof) passed the exact-FUNCTION_TYPE gate
in loop_inlines_abort_permanent_callee and its BuiltinCode reached
sub_jitcode_body_for_code / w_code_get_ptr, which cast it as a PyCode
CodeObject and dereferenced garbage during the up-front scan. Reject
is_builtin_code before scanning; a builtin carries no traceable body and
never inlines.

Found by Codex review on PR #471. Repros: builtin import called in a
hot loop with a heap store, alone and combined with a hidden callee —
no crash, output identical to the interpreter.

Assisted-by: Claude
…llee aborts (gh#467)

The forward-flush latch armed only on AbortPermanentMarkerReached, but
the hidden effectful-callee repro never reaches that marker: its store
residual is declined by the concrete executor and the sub-walk exits
earlier with LoopBearingCalleeInlineUnsupported. A declined residual did
not execute, so the whole callee attempt is discardable and re-executing
the CALL in the interpreter runs the callee exactly once —
observationally the same as never having inlined the call (tracing
aborts and switch_to_blackhole re-runs it).

Rename FBW_CONCRETE_HEAP_WRITE_COUNT to FBW_EXECUTED_EFFECT_COUNT and
make it count only effects actually applied (successful effectful
executor calls, user-frame entry, journal pushes);
fbw_mark_unjournaled_effect no longer bumps it. Snapshot the unjournaled
flag at sub-walk entry so a mark raised inside the discarded callee
cannot poison the latched flush while a pre-existing outside mark still
forbids it. Arm the latch and the trace.rs consumer for both abort
classes; ResidualCallArgUnbound stays ineligible. A non-latched
nested-decline abort keeps legacy replay.

m3_hidden_gstore flips to interpreter-identical (300000, 300000); a new
adversary whose hidden callee executes a store before aborting stays
gated on the legacy path with its callee store applied exactly once.
check.py 161/161 dynasm and 161/161 cranelift; pyre-jit-trace and
pyre-jit tests pass; GC-stress runs match the interpreter.

Assisted-by: Claude
…rker (gh#467)

The FBW walker executes residuals concretely; when a top-level inlined
callee runs a concrete effect and then reaches an abort_permanent marker
(e.g. MATCH_SEQUENCE) inside its sub-walk, the marker pc is a callee
coordinate with no outer meaning, so every landed flush declined and the
walk replayed from loop entry, re-applying the outer non-journaled store
(m3_hidden_executed_then_abort: (300001,300000) vs (300000,300000)).

Add a depth-1 inner-frame carrier: capture the callee w_code, exact
callee pc (via the prereq-1 exact first-jit anchor
first_jit_pc_by_py_pc[py] == abort_jit_pc), live stack refs from the
callee register bank, and live locals from the callee-local shadow into
a GC-rooted MidBodyPayload before the sub-walk guards drop; consume it in
one trace.rs leg that rebuilds the callee FrameBox, runs it forward in
the plain interpreter from the marker pc, delivers the return into the
outer post-CALL slot, and resumes the outer frame past the CALL. This is
the depth-1 specialization of run_blackhole_interp_to_cancel_tracing /
_copy_data_from_miframe / _setup_return_value_r.

All-or-nothing gates decline to legacy (never worse) on: nested inline
(depth>1), generator/coroutine, raw cellvars/freevars, missing exact
first-jit anchor, unsourceable live stack/local slot, non-statement-call
outer shape, lastblock/capacity mismatch, and — validated BEFORE running
the callee — a post-marker body that is not effect-free. The effect-free
whitelist is fail-closed: only opcodes proven free of heap/global/cell
mutation and user calls (load/store-fast, match/unpack, binary/compare,
jumps, return) may reach execute_frame, so an Err (implicit raise) cannot
have committed an effect the subsequent legacy fallback would double.

Landed zero-executed-effect Entry (Rung-B) semantics are unchanged;
LocalsRoot is exposed as FrameLocalsRoot for the trace-side rebuild.

Deferred (decline-to-legacy = status quo): forward exception delivery for
a raising callee (including a post-marker user-dunder that mutates then
raises), subexpression-position calls, and depth>1 (#126/#215).

check.py 161/161 dynasm and cranelift; 39/39 scratchpad battery matches
the interpreter; GC stress clean.

Assisted-by: Claude
…bling the structural fold (gh#467)

When the mid-body abort carrier rebuilds an inlined callee and runs it forward,
an exception raised by that callee is now delivered to the outer frame at the
CALL instead of declining to legacy replay (which double-applies the outer
non-journaled store). Parity: _resume_mainloop / _exit_frame_with_exception
(blackhole.py:1612-1682); sys.exc_info save/restore stays an interpreter-level
concern (PUSH_EXC_INFO/POP_EXCEPT).

- try_commit_midbody_abort: on execute_frame Err at a portal trace site, restore
  the outer pre-CALL sys_exc_value, position the outer frame at the CALL pc with
  the empty statement-position operand stack, and run handle_exception. A matching
  handler resumes at the handler pc through the existing WALK_END_FLUSH_COMMITTED
  seam; no handler stores the exception in the new WALK_END_PROPAGATED_EXCEPTION
  channel, which both portal tracing entry sites consume as LoopResult::Done(Err).
  The store journal commits either way (the outer store is real, not doubled).
- Remove the effect-free / empty-exception-table pre-execution gate for portal
  traces, where Err now has a forward channel. Bridge trace sites have no PyError
  propagation channel yet and retain the former gate (fail-closed).
- E-G2: accept only an exactly reconstructible depth-0 handler stack; any handler
  preserving an operand below the call stays on legacy replay.
- Extend the carrier to LoopBearingCalleeInlineUnsupported
  (MidBodyAbortKind::Structural) when the failing opcode applied no observable
  effect (FBW_STRUCTURAL_ABORT_OPCODE_EFFECTS zero-delta) and inverts to an exact
  first-jit anchor; the callee resumes from the enclosing opcode boundary. All
  existing generator/cell/freevar/liveness/GC-root gates are kept.
- Factor write_back_outer_locals out of flush_walk_end_state_after_outer_call and
  reuse it on the exception-delivery path.

refute_double_store flips to interpreter parity (300000, 300000, 300000). The
outer-CALL try-region shape declines the enclosing CALL before the carrier forms
and remains a documented residual.

Assisted-by: Claude
…er inline decline (gh#467)

try_walker_inline_user_call declined a multiframe callee whose CALL sits
inside a try block before entering the callee sub-walk:
compute_inline_caller_frame returns no paused parent frame when the call
carries an after-residual catch marker, and the resulting
LoopBearingCalleeInlineUnsupported error carried no abort carrier, so
the walk fell back to legacy replay from the loop entry and re-applied
non-journaled stores that preceded the CALL.

Distinguish that decline reason
(InlineCallerFrameDecline::TryBlockCatchMarker) from the other
unavailable-frame reasons, hoist the existing Entry-latch inputs above
the paused-parent-frame gate, and latch the CALL-forward Entry carrier
(fbw_set_abort_call_resume) before returning the unchanged error when
every existing Entry predicate holds. The callee was never entered at
this decline, so re-running the whole CALL forward is observationally
identical to never inlining it (pyjitpl.py:2949). Other decline reasons
keep the bare error and legacy replay.

Assisted-by: Claude
…llees (gh#467)

The always-portal flip (9e1dc2e) gives an inlined callee body the
portal frame-vable prologue, which broke the executed-effect MidBody
carrier in two places; the walk declined to legacy replay and
re-applied non-journaled outer stores.

First, serialize_op's first-wins rule hands the marker's python-pc
anchor slot to the adjacent same-pc setfield_vable_i(last_instr) write,
so the exact_first_jit_anchor equality never holds in portal shape. Add
portal_marker_first_jit_anchor: a Marker abort also anchors when every
op between the first jit pc and the marker is that same-pc portal
last_instr bookkeeping write. Structural aborts keep the exact anchor.

Second, the live operand-stack recovery reads pcdep-colored concrete
registers, but a portal-shaped callee keeps those values in the vable
shadow. Tag each concrete callee-shadow entry with its writer frame
register and let callee_vable_ref_at accept either the strict-fold
witness or an innermost-scope entry whose recorded register matches the
callee metadata's portal_frame_reg. Recovery still declines on
Int/Float/null/missing and foreign-frame entries.

m3_hidden_executed_then_abort now matches the interpreter; the
PYRE_ALWAYS_PORTAL=0 path keeps its pre-flip commit unchanged.
check.py 161/161 dynasm and cranelift, three runs each; cargo tests
green on both backends; GC stress green.

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: 286c1ee408

ℹ️ 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 +120 to +121
| pyre_interpreter::Instruction::JumpBackward { .. }
| pyre_interpreter::Instruction::JumpBackwardNoInterrupt { .. }

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 Reject backward jumps during mid-body replay

When the bridge mid-body replay path (allow_propagate_out = false) resumes a callee at an abort marker inside a loop, allowing JumpBackward/JumpBackwardNoInterrupt here means execute_frame can leave the vetted post-marker suffix and run earlier bytecode that was not checked by midbody_post_marker_is_effect_free (for example a prior heap mutation in the loop body). If that replay later raises and returns false, the caller falls back to legacy replay from the loop entry, so the jumped-to mutation is left committed and then executed again; reject backward targets before start_pc or include their target range in the safety check.

Useful? React with 👍 / 👎.

@youknowone
youknowone merged commit 8a40839 into main Jul 12, 2026
29 of 30 checks passed
@youknowone
youknowone deleted the fib_recursive branch July 12, 2026 12:27
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