Skip to content

jit(wasm): CA callee jitframes as nursery GC objects + live-Box virtualizable identity#501

Merged
youknowone merged 4 commits into
mainfrom
wasm-jit
Jul 12, 2026
Merged

jit(wasm): CA callee jitframes as nursery GC objects + live-Box virtualizable identity#501
youknowone merged 4 commits into
mainfrom
wasm-jit

Conversation

@youknowone

@youknowone youknowone commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Epic slice A1: wasm CALL_ASSEMBLER callee jitframes become real GC objects, replacing the malloc+LIFO pool, with the two invariants upstream requires for that to be sound.

  • 878e6fc8409 majit: encode the virtualizable identity as a live Box in resume snapshots — removes state_field_identity_const and the identity_const parameter chain. The virtualizable identity in a resume snapshot is now always SnapshotTagged::Box, i.e. a live failarg decoded from the failing frame's slot at deopt time (pyjitpl.py:3319 appends virtualizable_boxes[-1] as a live box; resume.py:1566-1578 decodes it via get_ref_value). The previous TAGCONST rewrite baked a trace-time object address into resume data — a latent unsoundness once frames can move.
  • a1d16c1626a wasm: allocate CA callee jitframes in the nursery and reload the frame pointer after collecting callswasm_jit_ca_alloc_frame switches to a collecting nursery allocation (rewrite.py gen_malloc_nursery_varsize_frame), retiring the CA_FRAMES pool. Since a minor collection can now move the running frame and the collector cannot rewrite wasm locals, trace bodies reload local 0 from the jitframe shadow-stack top before every post-collection home reload — the _reload_frame_if_necessary port (x86 assembler.py:1369) — and the CA arm reloads the caller frame via the new jf_under_top_ptr() accessor.

Verification

  • fib32 via CALL_ASSEMBLER: 2178309, exit 0; gc_majors 262 (oldgen variant) → 4.
  • Synthetic suite 151/151 on wasm, dynasm, cranelift (cranelift green on rerun; single known-flaky failure on first run).
  • Two previously failing wasm tests on this branch (calls_closures wrong-output, generator_tree_recursion vable panic) were this bug class and now pass.

Remaining

  • Trampoline fallback paths still document the non-moving-frame assumption.
  • Perf A/B vs the pooled-malloc baseline measured separately.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved JIT reliability during garbage collection, recursive calls, residual calls, and allocation-heavy code.
    • Prevented stale or incorrect frame and reference handling by reloading caller/callee frame state as needed.
    • Corrected snapshot/resume behavior for virtualizable identities so guard failures and blackhole resumes keep identities consistent.
  • Stability
    • Improved wasm backend support for managed call frames and movement-safe frame pointer handling.
    • Updated wasm call trampolines to support a more flexible “call area” layout for residual calls.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR parameterizes wasm frame layouts, makes CALL_ASSEMBLER frames nursery-GC-managed, and adds frame/Ref reload handling across collecting operations. It also replaces virtualizable identity constants with live boxes and supports optional host-stack identity overrides during blackhole resume.

Changes

GC-managed CALL_ASSEMBLER frames

Layer / File(s) Summary
Frame geometry and reload-aware codegen
majit/majit-backend-wasm/src/codegen.rs
Code generation uses frozen frame geometry for call areas, dispatch keys, and Ref homes, with reloads around recursive CA calls, residual calls, and allocation paths.
CA frame lifecycle and bridge wiring
majit/majit-backend-wasm/src/lib.rs, majit/majit-backend-wasm/src/failguard.rs, majit/majit-gc/src/shadow_stack.rs
CA frames use nursery allocation, shadow-stack gc maps, reload helpers, and frozen geometry validation across loops and bridges.
Call-area trampoline protocol
majit/majit-backend-wasm/js/jit_glue.js, pyre/pyre-wasm-runner/src/*, majit/majit-backend-wasm/tests/codegen_test.rs
Residual-call trampolines accept fixed or compact call-area offsets, and matching wasm imports and test configuration are wired.

Virtualizable resume identity

Layer / File(s) Summary
Snapshot identity contract
majit/majit-metainterp/src/jit_state.rs, majit/majit-metainterp/src/pyjitpl/dispatch.rs, majit/majit-metainterp/src/trace_ctx.rs, majit/majit-macros/src/jit_interp/codegen_state.rs
Identity-constant parameters are removed, virtualizable identities are encoded as typed boxes, and generated state-field implementations expose an optional identity hook.
Blackhole identity override flow
majit/majit-metainterp/src/jitdriver.rs, majit/majit-metainterp/src/resume.rs, majit/majit-metainterp/src/blackhole.rs, pyre/pyre-jit/src/call_jit.rs
JIT drivers compute optional identity overrides, resume readers preserve tape alignment, and blackhole/deoptimization call sites pass the new argument.

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

Possibly related issues

Possibly related PRs

  • youknowone/pyre#257 — Overlaps with the Ref-home, GC-root, write-barrier, and reload plumbing modified here.
  • youknowone/pyre#312 — Shares the CALL_ASSEMBLER, bridge codegen, frame layout, and callee-frame machinery changed here.
  • youknowone/pyre#347 — Shares the CA bridge-chaining, frame allocation, and trampoline infrastructure updated here.

Poem

I’m a rabbit hopping through frames in the hay,
GC moves the pointers, but none lose their way.
CA calls recurse, then reload with care,
Live Ref boxes dance through the air.
At deopt, I bring the right identity bright—
Every shadow-stack trail ends just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: nursery-GC CA callee jitframes and live-Box virtualizable identity handling.
✨ 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 wasm-jit

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

Copy link
Copy Markdown

🤖 Codex parity review

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

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-backend-wasm/src/codegen.rs:679 ↔ rpython/jit/backend/x86/assembler.py:1369: the new _reload_frame_if_necessary analogue unconditionally replaces wasm local-0 with jf_top_ptr() whenever direct-call types exist. But the legacy entry path still executes on a Vec<i64> and never pushes that frame onto the jitframe shadow stack (majit/majit-backend-wasm/src/lib.rs:2232); after a direct residual/slow allocation call, local-0 becomes null or an unrelated outer frame. PyPy reloads only its always-shadow-stack-rooted JitFrame. This regresses the default non-CA wasm path.

2. Other mismatches introduced by this patch

None.

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

  • majit/majit-backend-wasm/src/lib.rs:547 ↔ rpython/jit/backend/llsupport/rewrite.py:674: build_callee_gcmap marks every CALL_ASSEMBLER input slot, ignoring input_types; PyPy selects a frame descriptor from arg.type for each slot. Thus an integer/float slot can be traced as a GC reference. This predates the patch (the old function likewise accepted only an input count), although the new comment calls them “real Ref inputs.”

4. Structural adaptations

  • majit/majit-metainterp/src/resume.rs:6860 ↔ rpython/jit/metainterp/resume.py:1399: the Rust state-field host may supply identity_override because its virtualizable is a host-stack &state, whereas PyPy always obtains the virtualizable from resume data. The patch correctly retains and consumes the encoded TAGBOX first, preserving resume-stream alignment.

  • majit/majit-backend-wasm/src/codegen.rs:97 ↔ rpython/jit/backend/llsupport/rewrite.py:613: per-token compact wasm frame geometry replaces PyPy’s native JitFrame/frame-info layout. This is a wasm storage/ABI adaptation, while preserving the required per-frame allocation sizing.

  • majit/majit-backend-wasm/js/jit_glue.js:72 ↔ rpython/jit/backend/x86/assembler.py:1369: jit_call_compact(frame, call_area_ofs) is a wasm host-import adaptation for dynamically positioned call scratch space; PyPy uses native machine-code frame offsets instead.

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

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

op.opcode,
OpCode::New | OpCode::NewWithVtable | OpCode::NewArray | OpCode::NewArrayClear
)
(op.opcode.is_call()

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 Root Ref call arguments before collecting direct calls

When residual calls are treated as collecting here, RefHomes::collect still only homes refs that are live after the op via live_across_any; a Ref operand whose last use is this call is deliberately excluded. The direct residual-call path passes those args as raw wasm values, so for a call like f(obj) where obj dies at the call, a minor collection inside f cannot see or update obj and the callee can observe a moved/freed pointer. Please home Ref operands for collecting residual calls as the CA arm does, or keep these direct residual calls on a no-collect path.

Useful? React with 👍 / 👎.

…shots

Remove state_field_identity_const and the identity_const parameter chain:
build_vable_snapshot_boxes now always emits the identity as
SnapshotTagged::Box. The identity is a live input box; rewriting it to
TAGCONST excluded it from liveboxes/failarg numbering, baking a
trace-time object address into resume data. next_ref() now decodes the
identity from the failing frame's failarg slot (resume.py:1566-1578).

Assisted-by: Claude
…e pointer after collecting calls

wasm_jit_ca_alloc_frame switches from the malloc+LIFO pool to a
collecting nursery allocation (rewrite.py gen_malloc_nursery_varsize_frame),
retiring the CA_FRAMES pool. Because a minor collection can now move the
running frame and the collector cannot update wasm locals, trace bodies
reload local 0 from the jitframe shadow-stack top before every
post-collection home reload (_reload_frame_if_necessary, assembler.py:1369),
and the CA arm reloads the caller frame via the new jf_under_top_ptr()
accessor after its callee push. Trampoline fallback paths keep the
documented non-moving-frame assumption.

fib32 via CA: gc_majors 262 (oldgen variant) -> 4. Synthetic suite
151/151 on wasm, dynasm, cranelift.

Assisted-by: Claude
Replace the fixed MIN_FRAME_BYTES frame layout for CA-allocated callee
frames with a per-token FrameGeometry frozen at the token's first
compile: value region sized from the token's actual slot needs, then
call area, dispatch key, and Ref homes at per-token offsets
(jitframe.py sizes frames by the regalloc frame_depth). Arena
execute_token frames keep the fixed floor. compile_bridge declines a
chained bridge whose slot needs exceed the frozen geometry. Host-side
frame readers and per-bridge gcmap building take the token geometry
instead of the global constants.

fib32 via CA: gc_minors 5034 -> 1243, wall 1.20s -> 0.66-0.69s
(malloc-pool baseline 0.83s). Synthetic suite 151/151 on wasm, dynasm,
cranelift.

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

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

resumereader.handling_async_forcing();
// resume.py:1350
resumereader.consume_vref_and_vable(vrefinfo, vinfo, ginfo);
resumereader.consume_vref_and_vable(vrefinfo, vinfo, ginfo, 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 Preserve virtualizable identity while forcing

On the async-forcing path (handle_async_forcing -> force_from_resumedata), state-field virtualizables have the same host-stack identity issue that the new blackhole paths handle with vable_identity_override. This call still passes None, so after the snapshot writer stopped encoding the &state identity as a const, a GUARD_NOT_FORCED force can decode the folded-out TAGBOX/null in consume_vable_info before force_all_virtuals, misidentifying the virtualizable. Please pass an equivalent current-call identity here or keep const encoding for this force path.

Useful? React with 👍 / 👎.

Comment on lines +680 to +683
sink.i32_const(ca_reload_fn_ptr as i32);
sink.call_indirect(0, base);
sink.i32_wrap_i64();
sink.local_set(0);

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 Avoid reloading Vec frames from the jitframe stack

When execute_token takes the legacy Vec-backed frame path (wasm_jitframe_tid() == 0), no jitframe is pushed on the jf shadow stack. Direct helper/alloc paths now emit this reload after collecting calls, so wasm_jit_ca_reload_frame reads an empty jf stack and returns FIRST_ITEM_OFFSET; local 0 is then overwritten with that small offset instead of the Vec frame pointer, and subsequent frame/home accesses go to the wrong address. Please gate this reload to GC-managed JitFrame execution, or keep direct collecting helpers disabled for the legacy frame 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: 3

🤖 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-wasm/src/codegen.rs`:
- Around line 76-117: Update the trampoline-call fallback using
FrameGeometry::compact and jit_call so calls with more than 16 arguments are
rejected before writing call_args entries. Enforce the limit against
FrameGeometry::CALL_AREA_SLOTS’ 16 argument capacity, preserving existing
behavior for valid calls and preventing writes into dispatch_key_ofs or the
Ref-home region.

In `@majit/majit-metainterp/src/jitdriver.rs`:
- Around line 2799-2807: Extract the duplicated virtualizable identity
derivation into a shared `compute_vable_identity_override` method on the
surrounding type, accepting the state and compiled metadata. Replace the inline
`self.meta.virtualizable_info().and_then(...)` blocks in `back_edge_internal`
and `run_back_edge_generic` with calls to this helper, preserving the existing
`Option<i64>` behavior and arguments.

In `@majit/majit-metainterp/src/resume.rs`:
- Around line 6872-6880: Update the identity handling in the resume-data path
around `encoded_identity` and `virtualizable` so `identity_override` consumes
the raw resume item without calling `next_ref()` or decoding it; call
`next_ref()` only when no override is provided, preserving the existing decoded
identity behavior for the non-override path.
🪄 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: b998267b-8ea3-4b2d-a207-a5641dd56509

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce495a and e80d0bd.

📒 Files selected for processing (16)
  • majit/majit-backend-wasm/js/jit_glue.js
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/failguard.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-backend-wasm/tests/codegen_test.rs
  • majit/majit-gc/src/shadow_stack.rs
  • majit/majit-macros/src/jit_interp/codegen_state.rs
  • majit/majit-metainterp/src/blackhole.rs
  • majit/majit-metainterp/src/jit_state.rs
  • majit/majit-metainterp/src/jitdriver.rs
  • majit/majit-metainterp/src/pyjitpl/dispatch.rs
  • majit/majit-metainterp/src/resume.rs
  • majit/majit-metainterp/src/trace_ctx.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm-runner/src/wasmi_host.rs

Comment on lines +76 to +117
const CALL_AREA_SLOTS: usize = 3 + 16; // result, function, nargs, args

/// Historical fixed geometry, used by direct codegen tests and by callers
/// that deliberately need the arena-compatible layout.
pub const fn fixed() -> Self {
Self {
value_slots: MIN_FRAME_BYTES / 8,
call_result_ofs: CALL_RESULT_OFS,
call_func_ofs: CALL_FUNC_OFS,
call_nargs_ofs: CALL_NARGS_OFS,
call_args_ofs: CALL_ARGS_OFS,
dispatch_key_ofs: DISPATCH_KEY_OFS,
home_slot_base: HOME_SLOT_BASE,
home_slots: 0,
frame_bytes: (MIN_FRAME_BYTES + SLOT_SIZE as usize) as u32,
}
}

/// Compact geometry for one token. `value_slots` includes frame[0], so
/// the call area begins immediately after the greatest positional input or
/// fail-arg slot used by that token.
pub fn compact(value_slots: usize, home_slots: usize) -> Self {
let value_slots = value_slots.max(1);
let call_result_ofs = (value_slots as u64) * SLOT_SIZE;
let call_func_ofs = call_result_ofs + SLOT_SIZE;
let call_nargs_ofs = call_func_ofs + SLOT_SIZE;
let call_args_ofs = call_nargs_ofs + SLOT_SIZE;
let dispatch_key_ofs = call_result_ofs + Self::CALL_AREA_SLOTS as u64 * SLOT_SIZE;
let home_slot_base = dispatch_key_ofs + SLOT_SIZE;
let frame_bytes = home_slot_base + home_slots as u64 * SLOT_SIZE;
Self {
value_slots,
call_result_ofs,
call_func_ofs,
call_nargs_ofs,
call_args_ofs,
dispatch_key_ofs,
home_slot_base,
home_slots,
frame_bytes: frame_bytes as u32,
}
}

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any arity/args bound check gating call-area residual calls.
rg -nP --type=rust 'call_args_ofs|CALL_ARGS_OFS|CALL_AREA_SLOTS' majit/majit-backend-wasm/src/codegen.rs
# Look for declines related to call arity / too many args.
rg -nP --type=rust 'arity|nargs|too many|args\.len\(\)\s*[<>]=?' majit/majit-backend-wasm/src -C2
# Confirm max_value_slots / home_slots are the only frozen-layout bounds enforced.
rg -nP --type=rust 'exceed frozen frame layout' majit/majit-backend-wasm/src/codegen.rs -C3

Repository: youknowone/pyre

Length of output: 30959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the direct residual-call and trampoline emission paths.
sed -n '2820,2925p' majit/majit-backend-wasm/src/codegen.rs
printf '\n----\n'
sed -n '2925,3090p' majit/majit-backend-wasm/src/codegen.rs

# Look for any explicit caps on call argument counts in codegen.
printf '\n==== ARITY CHECKS ====\n'
rg -nP --type=rust 'saturating_sub\(1\)|call_args\.len\(\)|nargs|arity|too many|Unsupported\(format!\(' majit/majit-backend-wasm/src/codegen.rs -C2

Repository: youknowone/pyre

Length of output: 25475


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any global cap or validation on call argument counts.
rg -nP --type=rust 'MAX_.*ARG|max.*arg|call.*limit|too many.*arg|arg count|getarglist\(\)\.len\(\)|saturating_sub\(1\)' majit -C2

# Find any tests or docs describing the maximum supported residual-call arity.
rg -nP --type=rust '16 arg|16 args|CALL_AREA_SLOTS|dispatch_key_ofs|call_args_ofs' majit -C2

# Look for any compile-time or runtime checks in the backend that reject high-arity calls.
rg -nP --type=rust 'Unsupported\(format!\(".*arity|Unsupported\(format!\(".*args|return Err\(BackendError::Unsupported' majit/majit-backend-wasm/src -C3

Repository: youknowone/pyre

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the frame-layout commentary and the actual layout checks in build_wasm_module.
sed -n '35,55p' majit/majit-backend-wasm/src/codegen.rs
printf '\n----\n'
sed -n '1120,1165p' majit/majit-backend-wasm/src/codegen.rs

Repository: youknowone/pyre

Length of output: 3404


Reject trampoline calls with >16 args. FrameGeometry::compact() reserves only 16 call-argument slots, but the jit_call fallback still writes call_args.len() entries with no cap. A larger fallback call will overwrite dispatch_key_ofs and the Ref-home region.

🤖 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-backend-wasm/src/codegen.rs` around lines 76 - 117, Update the
trampoline-call fallback using FrameGeometry::compact and jit_call so calls with
more than 16 arguments are rejected before writing call_args entries. Enforce
the limit against FrameGeometry::CALL_AREA_SLOTS’ 16 argument capacity,
preserving existing behavior for valid calls and preventing writes into
dispatch_key_ofs or the Ref-home region.

Comment on lines +2799 to +2807
// The state-field macro's `&state` is host-stack storage, so
// its identity may be folded out of the failing frame. Ask
// only an explicit host opt-in for the current call's address;
// heap virtualizables retain the live resume TAGBOX path.
let vable_identity_override = self.meta.virtualizable_info().and_then(|info| {
state
.blackhole_virtualizable_identity(&compiled_meta, &info.name, info)
.map(|ptr| ptr as i64)
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate vable_identity_override computation across two call sites.

The same override-derivation block (self.meta.virtualizable_info().and_then(|info| state.blackhole_virtualizable_identity(...).map(|ptr| ptr as i64))) is duplicated verbatim in back_edge_internal and run_back_edge_generic. A future change to this logic risks drifting between the two.

♻️ Proposed helper extraction
+    fn compute_vable_identity_override(&self, state: &S, meta: &S::Meta) -> Option<i64> {
+        self.meta.virtualizable_info().and_then(|info| {
+            state
+                .blackhole_virtualizable_identity(meta, &info.name, info)
+                .map(|ptr| ptr as i64)
+        })
+    }

Then replace both inline blocks with self.compute_vable_identity_override(state, &compiled_meta) / self.compute_vable_identity_override(state, &meta).

Also applies to: 4896-4902

🤖 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/jitdriver.rs` around lines 2799 - 2807, Extract
the duplicated virtualizable identity derivation into a shared
`compute_vable_identity_override` method on the surrounding type, accepting the
state and compiled metadata. Replace the inline
`self.meta.virtualizable_info().and_then(...)` blocks in `back_edge_internal`
and `run_back_edge_generic` with calls to this helper, preserving the existing
`Option<i64>` behavior and arguments.

Comment on lines +6872 to +6880
// Consume the encoded identity even when a host supplies an override:
// it occupies one resume-data item and keeps the reader aligned for
// the field payload. Heap virtualizables (PyFrame) use this live
// TAGBOX exactly as RPython does. The state-field macro JIT opts in
// to `identity_override` because its host-stack `&state` is folded out
// of backend failargs; at deopt it must use the current call's address,
// never a trace-time pointer or an unrelated deadframe slot.
let encoded_identity = self.next_ref();
let virtualizable = identity_override.unwrap_or(encoded_identity);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not decode the encoded identity when an override is present.

Line 6879 calls next_ref() before applying identity_override. For state-field JITs, the host-stack identity is omitted from backend failargs, so decoding the TAGBOX can index a missing or unrelated deadframe slot. Consume the raw resume item when overridden and decode only when no override is provided.

Proposed fix
-        let encoded_identity = self.next_ref();
-        let virtualizable = identity_override.unwrap_or(encoded_identity);
+        let virtualizable = match identity_override {
+            Some(identity) => {
+                let _ = self.resumecodereader.next_item();
+                identity
+            }
+            None => self.next_ref(),
+        };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Consume the encoded identity even when a host supplies an override:
// it occupies one resume-data item and keeps the reader aligned for
// the field payload. Heap virtualizables (PyFrame) use this live
// TAGBOX exactly as RPython does. The state-field macro JIT opts in
// to `identity_override` because its host-stack `&state` is folded out
// of backend failargs; at deopt it must use the current call's address,
// never a trace-time pointer or an unrelated deadframe slot.
let encoded_identity = self.next_ref();
let virtualizable = identity_override.unwrap_or(encoded_identity);
// Consume the encoded identity even when a host supplies an override:
// it occupies one resume-data item and keeps the reader aligned for
// the field payload. Heap virtualizables (PyFrame) use this live
// TAGBOX exactly as RPython does. The state-field macro JIT opts in
// to `identity_override` because its host-stack `&state` is folded out
// of backend failargs; at deopt it must use the current call's address,
// never a trace-time pointer or an unrelated deadframe slot.
let virtualizable = match identity_override {
Some(identity) => {
let _ = self.resumecodereader.next_item();
identity
}
None => self.next_ref(),
};
🤖 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/resume.rs` around lines 6872 - 6880, Update the
identity handling in the resume-data path around `encoded_identity` and
`virtualizable` so `identity_override` consumes the raw resume item without
calling `next_ref()` or decoding it; call `next_ref()` only when no override is
provided, preserving the existing decoded identity behavior for the non-override
path.

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