Skip to content

jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape#536

Merged
youknowone merged 15 commits into
mainfrom
rewrite-tracer
Jul 14, 2026
Merged

jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape#536
youknowone merged 15 commits into
mainfrom
rewrite-tracer

Conversation

@youknowone

@youknowone youknowone commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Collapses the JIT resume frame's dual coordinate into RPython's single-pc shape.
Before this stack the pyre resume frame carried both the Python bytecode pc
word and a redundant jitcode_pc twin side-channel (a per-frame 3rd rd_numb
word). RPython's resume.py numbers 2 words/frame (jitcode_index, pc)
where pc is the JitCode byte offset. This stack makes the pc word the JitCode
offset and physically removes the twin.

Phases

  • P1 (encode completion) — every production resume frame now carries a real
    jitcode coordinate or is skeleton; deleted the production-dead portal-bridge
    PyJitCode mode (no undefined-coordinate frame class remains).
  • P3 (losslessness census) — certified back-translation jitcode→py at the
    three python-resume readers; the flip-obstruction rows were all shown benign
    (single-frame guards whose exact resume pc survives in the virtualizable
    last_instr).
  • P4 (the flip) — encode writers store the resolved JitCode offset into the
    pc word; decode py_pc-readers back-translate; single-frame resume_pc sourced
    from vable ni. Landed gated, then flipped default-ON (s3). Full A/B
    green with the gate OFF and ON.
  • P5 (collapse) — removed the gate and audit scaffolding (s0/s1),
    collapsed the decode callbacks and readers onto the pc word (s2/s3a),
    derived the bridge-resume gates and recipe/carrier twin sources from frame.pc
    (s3b/s3c), and finally deleted the rd_numb 3rd word (s5): the frame
    header is now (jitcode_index, pc). Both encoders and all three decoders drop
    the twin word in lockstep; SnapshotFrame.jitcode_pc / RebuiltFrame.jitcode_pc
    and the *_with_jitcode_pc constructors are gone. The encode-side twin
    computation is retained — it still feeds resolve_resume_pc_with_jitcode_pc,
    which bakes the resume coordinate into the surviving pc word.

Verification

  • pyre/check.py --backend dynasm 180/180, --backend cranelift 180/180.
  • cargo test -p pyre-jit-trace 279/0-fail, cargo test -p majit-metainterp 1465/0-fail.
  • cargo fmt --check + git diff --check clean.

Follow-up (not in this PR)

S4 — deleting the pyre-side ReconstructRecipe.jitcode_pc and
BridgeInlineCarrier.root_jitcode_pc twins — is deferred. It is not
byte-identical and has two genuine blockers (carrier root_pc offset consumed
as py_pc; recipe.pc is a py_pc and the twin is the recipe's only offset),
so it needs a real offset field on the recipe first and its own census + A/B.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Simplified resume-state metadata and frame-position handling for more consistent JIT resume and guard-recovery behavior.
    • Improved translation between JIT offsets and interpreter positions during frame reconstruction.
    • Unified stack-depth, liveness, and snapshot handling across resume paths.
  • Bug Fixes

    • Corrected frame-value liveness calculations and encoded resume-data offsets.
    • Improved restoration of guard failures and blackhole execution state.
  • Cleanup

    • Removed obsolete portal-bridge behavior and diagnostic audit logging.

…e-opcode parent frame word carry

Add ResumeFrameState.resume_marker_jit_pc (jitcode-space twin of the frame's
encoded pc word). build_framestack_frames parent frames write the twin into
the jitcode_pc word under PYRE_M369_PFRAME_CARRY (default ON) with a
tframe-parent census tag; the word stays NO_JITCODE_PC when the twin is
absent or the gate is off. The single constructor site
(build_pending_inline_frame) populates None — the interpreter-opcode seam
has no jitcode coordinate in hand.

Assisted-by: Claude
Assisted-by: Codex
…on probes

Probe the three production encode_after_residual_call_pc callers (top-frame
marker_aware_resume_pc, parent-frame marker_aware_parent_resume_pc,
multi-frame callee word) under PYRE_M369_RESUME_PC_AUDIT, printing the marked
py pc and the jitcode twin in hand. Sweep: 0 rows across 165 corpus programs
and the 284 pyre-jit-trace tests — no flag word is encoded anywhere in the
exercised corpus.

Assisted-by: Claude
Assisted-by: Codex
…tCode mode

The portal-bridge mode (non-empty code + !is_drained) had no production
creation path: install_portal_for and portal_bridge_jitcode_for were reached
only from tests. Every production frame resolves through the codewriter drain
to an is_drained payload, so is_portal_bridge() was always false. Its pc word
has no jitcode-space coordinate by construction (the portal body is the shared
dispatch loop), so the mode is the only frame class the #369 pc-word flip
cannot cover — and it is dead.

Delete canonical_bridge.rs, portal_bridge_jitcode_for and its payload builder,
the portal-only decode arms (frame_value_count_at, frame_liveness_*_with_jitcode_pc,
BridgeSemanticMaps::is_portal_bridge), the portal-only encode arms in
trace_opcode, portal_bridge_vable_vsd, and PyJitCode::is_portal_bridge. The
semantic-mirror overlay keeps its production-reachable !has_color_map trigger;
skeleton (!is_populated) shapes and built_as_portal are retained.

Assisted-by: Claude
Assisted-by: Codex
…ython-resume sites

Add pub wrappers (python_pc_for_jitcode_pc_public, resolve_resume_offset_public,
skip_python_trivia_forward_public) and extend the m369-route audit with
[m369-backxlat] rows at the three python-resume readers (build_resumed_frames
per-frame, rd_numb_pc outermost, ReconstructRecipe.pc inline-callee). Each row
resolves the carried word through the production resolver, back-translates the
JitCode offset via python_pc_for_jitcode_pc + skip_python_trivia_forward, and
compares to the stored py pc.

Certificate over 165 programs: eq=true 1726/1734, 0 sentinel. The 8 residual
eq=false rows are 4 resume events (list_ops, list_bound_method_mutation,
short_circuit_falsy_func_entry_resume, subscr_negative_index_deopt) where the
resolved offset lands on a block-head marker that maps many-to-one back to an
earlier py coordinate than the stored semantic resume pc — the kept-stack /
short-circuit family, bounding the genuine flip obstruction to those events.

Assisted-by: Claude
Assisted-by: Codex
…te audit

Thread divergent_vs_ni into every [m369-backxlat] row and add a diagnostic-only
[m369-INVARIANT-VIOLATION] tripwire for eq=false && divergent_vs_ni=true — the
only row class that would make the pc-word flip lossy at a load-bearing resume.
All behind PYRE_M369_ROUTE_AUDIT (production silent).

Certificate over 166 programs: 2080 rows, 0 sentinel, 0 invariant violations.
Every eq=false row (8, the 4 benign single-frame guard events) is
divergent_vs_ni=false, so the flip sources their resume from the vable ni
channel, never the lossy back-translation.

Assisted-by: Claude
Assisted-by: Codex
Add majit_ir::resumedata::m369_pcword_flip_enabled (PYRE_M369_PCWORD_FLIP,
default OFF). When on, the 7 SnapshotFrame pc writers (top/parent trait frames,
main guard, arm path, multi-frame callee/parent, nonstandard-vable) store the
JitCode offset resolve_resume_pc_with_jitcode_pc yields from the frame's own
carried twin, keeping the twin word alongside; an unresolved coordinate retains
the Python pc. The parent writer stores the resolver's raw after-residual catch
offset without re-applying the bit-14 marker. The two history.rs snapshot
pc-range asserts are skipped under the gate (a flipped pc is a raw offset
outside the bit-14 range). The rd_numb serializer is unchanged.

Gate OFF is byte-identical: check.py 164/164 dynasm+cranelift, jit-trace 277,
metainterp 1467. Gate-ON decode is completed in the next slice.

Assisted-by: Claude
Assisted-by: Codex
…e pc-word flip

Add backxlat_py_pc(jitcode_index, pc_word): gate OFF returns
decode_resume_pc(pc).0 (Python-pc word); gate ON back-translates the JitCode
offset via python_pc_for_jitcode_pc + skip_python_trivia_forward, falling back
to the Python-pc decode when the inverse is unavailable. Route the python-resume
readers through it: build_resumed_frames py_pc/rd_numb_pc, the outer frames[0]
rd_numb_pc, jit_state.resume_pc, and reconstruct_inline_recipe (one translation
reused for the stack-depth lookups and both ReconstructRecipe.pc). resume_pc
selection under the gate: single-frame sources vable ni, multi-frame the
translated innermost py_pc. Offset-consumers and metadata helpers are unchanged
(they prefer the carried twin). The route audit compares in translated pc space.

Full A/B green: check.py 164/164 dynasm+cranelift with the gate OFF AND ON;
flipped route-audit census 0 invariant violations over 164 programs;
short_circuit_value_local_kept.py matches the interpreter. jit-trace 277,
metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
Flip m369_pcword_flip_enabled to default on: resume-frame pc words carry the
JitCode byte offset in production; PYRE_M369_PCWORD_FLIP=0 restores the
Python-pc words. The p4-s2 A/B proved both paths equivalent (check.py 164/164
dynasm+cranelift each way).

Battery with the flip default on: check.py 164/164 dynasm+cranelift, gate-off
restore parity 164/164, jit-trace 277, metainterp 1467.

Assisted-by: Claude
The pc-word flip is the sole resume-coordinate path now. Delete
m369_pcword_flip_enabled and collapse every caller to the flip branch: the 7
encode writers store the resolved JitCode offset unconditionally, backxlat_py_pc
always back-translates, resume_pc selection always uses ni/translated-innermost,
and the two bit-14 snapshot pc-range asserts (a flipped pc is a raw offset
outside that range) are deleted. Byte-identical to the default-on production
config.

check.py 164/164 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
Remove the env-gated diagnostic machinery built to certify the pc-word flip:
the M369 route/recover/backxlat/resume-pc audits and their captured state
(M369_INNERMOST_RECOVERY, M369_BACKXLAT_*, m369_invariant_check,
resolve_resume_offset_public), and the M73 census/audit emitters
(m73_sentinel_word_census, m73_*_audit/census gates and every M73_* eprintln
block). The path-selecting m73_*_carry_enabled / m369_pframe_carry_enabled gates
and the resume_marker / after_residual_marker twin producers are preserved —
they choose stored resume values, not diagnostics. python_pc_for_jitcode_pc_public
and skip_python_trivia_forward_public stay (live in backxlat_py_pc).

Byte-identical: check.py 164/164 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
… directly

The two blackhole `resolve_jitcode` closures (call_jit.rs) and the
`frame_value_count_at` liveness-count callback (state.rs) are decode-only: the
rd_numb decoder (`rebuild_from_numbering`) passes each the stored per-frame `pc`
word, which post-flip is already the JitCode byte offset. Resolve the offset
from `pc` directly when it names a valid `-live-` startpoint, else translate the
stored word through `resolve_resume_pc`, instead of preferring the carried
`jitcode_pc` twin. The 3rd rd_numb word is still threaded to the callbacks
(param renamed `_carried_jitcode_pc`) until the field is deleted in a later
slice. The encode writers and the encode-shared helpers
(`frame_liveness_reg_indices_by_bank_at_with_jitcode_pc`,
`resolve_bridge_walk_entry_at`) keep the resolver.

Byte-identical: check.py 164/164 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
…lve from frame.pc

Add thin `_from_pc(jitcode_index, pc)` wrappers over
`frame_liveness_reg_indices_by_bank_at_with_jitcode_pc`, `const_ref_slots_at_pc_at`,
and `bridge_semantic_maps_at_with_jitcode_pc` that pass the frame pc word as its own
jitcode coordinate, and route the four decode call sites (`reconstruct_inline_recipe`,
`setup_bridge_sym`) through them, dropping the carried `frame.jitcode_pc` argument.
Post-flip the stored `pc` word is the JitCode byte offset and equals the expanded twin
offset for every frame, so the wrappers take the same twin-keyed path and are
byte-identical. The gates and struct copies that still read `frame.jitcode_pc` are
later slices.

Byte-identical: check.py 164/164 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
…e.pc

The two setup_bridge_sym gates that chose a code path by testing
`frame0.jitcode_pc != NO_JITCODE_PC` (seed_deferred_to_overlay, bridge_walk_entry_pc,
and the kept-stack-bridge diagnostic) now derive from
`frame_pc_is_resolved_offset_at(jitcode_index, pc)` = `pc >= 0 && can_decode_live_vars(pc)`.
The flip stores a decodable JitCode offset in the frame pc word exactly when the
guard's resume marker resolves, which is exactly when the twin is non-sentinel, so
the predicate reproduces the twin test. `bridge_walk_entry_pc` becomes
`predicate.then_some(frame0.pc)` (the removed resolve_bridge_walk_entry_at returned
`frame0.pc` whenever the twin was non-sentinel). A corpus census certified 0
divergences between the predicate and the twin over 180×2 before the flip.

After this slice no setup_bridge_sym gate reads frame.jitcode_pc; only three struct
copies remain (slice-3c).

Byte-identical: check.py 180/180 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
…e.pc

The three struct copies that populated the pyre-side ReconstructRecipe.jitcode_pc
and BridgeInlineCarrier.root_jitcode_pc now read frame.pc instead of the redundant
frame.jitcode_pc twin. Post-flip frame.pc equals the twin offset for every corpus
frame, so the copied value is unchanged; the recipe/carrier fields and their
trace.rs readers stay. After this slice nothing reads RebuiltFrame.jitcode_pc.

Byte-identical: check.py 180/180 dynasm+cranelift, jit-trace 277, metainterp 1467.

Assisted-by: Claude
Assisted-by: Codex
Remove the per-frame jitcode_pc twin word from the resume numbering. The
rd_numb frame header is now (jitcode_index, pc), matching resume.py's
2-word frame. Both encoders (Snapshot::number, ResumeData::encode, and the
unused ResumeDataLoopMemo::encode_shared) stop appending the third word;
all three decoders (rebuild_from_numbering, decode_layout,
read_jitcode_pos_pc) stop reading it, and read_jitcode_pos_pc now returns
(jitcode_pos, pc).

Delete SnapshotFrame.jitcode_pc (resume + recorder) and
RebuiltFrame.jitcode_pc; collapse the *_with_jitcode_pc snapshot
constructors into single_frame_boxes / multi_frame_boxes; narrow the
SnapshotFramePcs tuple to (i32, i32); and drop the third argument from the
frame_value_count and resolve_jitcode callback families.

The encode-side twin computation is retained: guard_jitcode_pc, arm_word,
nsvable_word, top_word, parent_word, pf_word and callee_jitcode_pc still
feed resolve_resume_pc_with_jitcode_pc, which bakes the resume coordinate
into the surviving pc word.

Assisted-by: Claude
Assisted-by: Codex
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Walkthrough

Changes

Resume coordinate simplification

Layer / File(s) Summary
Resume-data contracts and encoding
majit/majit-ir/..., majit/majit-metainterp/src/{resume,history,recorder,compile}.rs, majit/majit-metainterp/tests/*
Resume frames, callbacks, snapshots, encoded layouts, and tests no longer carry jitcode_pc.
Trace snapshot coordinate construction
pyre/pyre-jit-trace/src/{jitcode_dispatch,trace,trace_opcode}.rs
Snapshot PC words and stack depths now use direct JitCode resolution and concrete frame state.
JitCode coordinate and bridge resolution
pyre/pyre-jit-trace/src/state.rs
Pc-only translation, liveness, semantic-map, constant-slot, and inline-reconstruction helpers replace carried-coordinate paths.
JitCode mode and audit cleanup
pyre/pyre-jit-trace/src/{canonical_bridge,lib,liveness,pyjitcode}.rs
Portal-bridge construction and related audit and diagnostic plumbing are removed or updated.
Runtime resume reconstruction
pyre/pyre-jit/src/{call_jit,eval}.rs
Blackhole and guard-failure paths resolve and back-translate resume coordinates using (jitcode_index, pc).

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

Sequence Diagram(s)

sequenceDiagram
  participant Trace
  participant ResumeData
  participant Runtime
  participant PyJitCode
  Trace->>ResumeData: encode jitcode_index and pc
  ResumeData->>Runtime: decode frame coordinates
  Runtime->>PyJitCode: resolve resume PC and liveness
  PyJitCode-->>Runtime: return resolved frame state
Loading

Possibly related issues

  • youknowone/pyre#369 — Directly covers removal of the carried jitcode_pc side channel across resume-data and runtime paths.
  • youknowone/pyre#368 — Addresses the coupled resume-coordinate and jitcode_pc plumbing changes.

Possibly related PRs

  • youknowone/pyre#365 — Overlaps in removing carried JitCode-PC data from resume-coordinate resolution.
  • youknowone/pyre#455 — Touches the same resume-data encoding and callback interpretation.
  • youknowone/pyre#501 — Shares changes to blackhole and resume-data reconstruction signatures.

Suggested reviewers: lifthrasiir

Poem

A rabbit hops through frames of code,
And drops the carried PC load.
Two numbers guide the trail anew,
Liveness follows, crisp and true.
The bridge sheds audits, light and bright—
Resume paths now leap 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 main change: collapsing resume frames from three words to the RPython two-word shape.
✨ 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 rewrite-tracer

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

Copy link
Copy Markdown

🤖 Codex parity review

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

Files in the reviewed diff
majit/majit-backend-cranelift/src/compiler.rs
majit/majit-ir/src/resumedata.rs
majit/majit-metainterp/src/blackhole.rs
majit/majit-metainterp/src/compile.rs
majit/majit-metainterp/src/history.rs
majit/majit-metainterp/src/jitdriver.rs
majit/majit-metainterp/src/lib.rs
majit/majit-metainterp/src/optimizeopt/mod.rs
majit/majit-metainterp/src/pyjitpl.rs
majit/majit-metainterp/src/pyjitpl/dispatch.rs
majit/majit-metainterp/src/recorder.rs
majit/majit-metainterp/src/resume.rs
majit/majit-metainterp/tests/jit_driver_runtime_parity.rs
majit/majit-metainterp/tests/resume_parity.rs
pyre/pyre-jit-trace/src/canonical_bridge.rs
pyre/pyre-jit-trace/src/jitcode_dispatch.rs
pyre/pyre-jit-trace/src/lib.rs
pyre/pyre-jit-trace/src/liveness.rs
pyre/pyre-jit-trace/src/pyjitcode.rs
pyre/pyre-jit-trace/src/state.rs
pyre/pyre-jit-trace/src/trace.rs
pyre/pyre-jit-trace/src/trace_opcode.rs
pyre/pyre-jit/src/call_jit.rs
pyre/pyre-jit/src/eval.rs

1. Regressions to PyPy parity introduced by this patch

  • pyre/pyre-jit/src/eval.rs:7425 ↔ rpython/jit/metainterp/resume.py:1049: single-frame guard recovery now selects ni and discards the decoded snapshot PC. RPython always resumes each rebuilt frame at the encoded pc (f.setup_resume_at_op(pc)). This regresses main’s behavior, which used the decoded innermost snapshot PC whenever it differed from ni; a stale virtualizable last_instr can now resume a single-frame bridge at the loop header rather than the guard’s recorded position.

2. Other mismatches introduced by this patch

  • pyre/pyre-jit-trace/src/trace_opcode.rs:4131 ↔ rpython/jit/metainterp/resume.py:249, with equivalent new fallbacks at pyre/pyre-jit-trace/src/trace_opcode.rs:4081, pyre/pyre-jit-trace/src/jitcode_dispatch.rs:11130, 11894, 11929, 12483, and 12495: after changing the serialized pc word to mean a JitCode offset, failed translation falls back to serializing the original Python PC (unwrap_or(...py_pc)). RPython’s numbering always stores a valid JitCode PC and the reader passes it directly to setposition/setup_resume_at_op; a Python instruction index is not a valid substitute and can select different liveness or fail JitCode startpoint assertions.

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

  • pyre/pyre-jit/src/eval.rs:8029 ↔ pypy/interpreter/pyframe.py:128: every rebuilt inline section is assigned frame_ptr = vable_frame_ptr, collapsing all resumed frames onto the portal frame. PyPy retains a separate frame whose get_w_globals() comes from that frame; this loses callee frame identity.

  • pyre/pyre-jit/src/eval.rs:8059 ↔ pypy/interpreter/pyframe.py:128: inner resumed frames explicitly use vable_ns (“Inner frames share the chain virtualizable’s namespace”) instead of their own frame globals. Thus an inlined callee’s LOAD_GLOBAL can resolve in the caller namespace.

  • pyre/pyre-jit-trace/src/trace_opcode.rs:4316 ↔ rpython/jit/metainterp/virtualizable.py:86: virtualizable snapshot slots reuse frame-register OpRefs, so numbering deduplicates them. RPython’s read_boxes() creates distinct boxes with wrap() for every virtualizable field/array item.

  • pyre/pyre-jit-trace/src/trace_opcode.rs:4553 ↔ rpython/jit/metainterp/virtualizable.py:94: an unavailable virtualizable array slot is encoded as NULL rather than as the actual wrapped field value. RPython serializes every array element; the later pyre-only refetch is not part of resume-data semantics.

  • pyre/pyre-jit-trace/src/trace_opcode.rs:2510 ↔ pypy/module/pypyjit/interp_jit.py:87: pyre publishes last_instr = orgpc - 1, while PyPy’s JIT driver uses next_instr as the current dispatch coordinate. The source itself documents this as an unresolved semantic mismatch; it was present before this patch.

4. Structural adaptations

  • pyre/pyre-jit-trace/src/trace_opcode.rs:4128 ↔ rpython/jit/metainterp/resume.py:249: converting CPython instruction coordinates to generated JitCode offsets before numbering is a necessary compiler-boundary adaptation. It restores the canonical two-word resume-frame layout (jitcode_index, jitcode_pc) where resolution succeeds.

  • majit/majit-ir/src/resumedata.rs:528 ↔ rpython/jit/metainterp/resume.py:928: removing pyre’s third per-frame jitcode_pc word is structurally aligned with RPython’s read_jitcode_pos_pc(), which reads exactly two items.

@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: 40f83ed432

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

@@ -9059,7 +8988,7 @@ impl JitState for PyreJitState {
} 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.

P1 Badge Translate root pc before building bridge carrier

When resume_data.frames.len() > 1, this root_pc is later consumed as a Python bytecode PC by the bridge-carrier path (e.g. trace_bytecode sets last_instr from it, and drive_outer_continuation_and_map calls result_color_at_pc_at / resume_jitcode_pc_for(root_pc)). After this change frame.pc is the JitCode byte offset, so multi-frame guard failures now root the caller continuation at a JitCode offset whenever that offset differs from the Python PC. Please back-translate this value for root_pc and keep the raw offset only in root_jitcode_pc.

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 764-797: Guard negative inputs before the i32-to-usize conversions
in python_pc_for_jitcode_pc_public and skip_python_trivia_forward_public,
returning None for values below zero. Update backxlat_py_pc to detect a negative
pc_word and return its existing decoded fallback immediately, preserving the
current fallback behavior for lookup failures.
🪄 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: 703afaf9-8cd9-4d7b-8e79-00bf159a67fd

📥 Commits

Reviewing files that changed from the base of the PR and between 51a2c2c and 40f83ed.

📒 Files selected for processing (24)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-ir/src/resumedata.rs
  • majit/majit-metainterp/src/blackhole.rs
  • majit/majit-metainterp/src/compile.rs
  • majit/majit-metainterp/src/history.rs
  • majit/majit-metainterp/src/jitdriver.rs
  • majit/majit-metainterp/src/lib.rs
  • majit/majit-metainterp/src/optimizeopt/mod.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • majit/majit-metainterp/src/pyjitpl/dispatch.rs
  • majit/majit-metainterp/src/recorder.rs
  • majit/majit-metainterp/src/resume.rs
  • majit/majit-metainterp/tests/jit_driver_runtime_parity.rs
  • majit/majit-metainterp/tests/resume_parity.rs
  • pyre/pyre-jit-trace/src/canonical_bridge.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/lib.rs
  • pyre/pyre-jit-trace/src/liveness.rs
  • pyre/pyre-jit-trace/src/pyjitcode.rs
  • pyre/pyre-jit-trace/src/state.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-jit-trace/src/trace_opcode.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-jit/src/eval.rs
💤 Files with no reviewable changes (6)
  • pyre/pyre-jit-trace/src/lib.rs
  • pyre/pyre-jit-trace/src/canonical_bridge.rs
  • majit/majit-metainterp/tests/jit_driver_runtime_parity.rs
  • majit/majit-metainterp/src/recorder.rs
  • majit/majit-metainterp/src/lib.rs
  • majit/majit-metainterp/src/pyjitpl/dispatch.rs

Comment on lines +764 to +797
/// Resolve a stored JitCode offset back to its Python instruction coordinate.
pub fn python_pc_for_jitcode_pc_public(jitcode_index: i32, offset: i32) -> Option<i32> {
let payload = pyjitcode_for_jitcode_index(jitcode_index)?;
Some(
crate::jitcode_dispatch::python_pc_for_jitcode_pc(&payload.metadata, offset as usize)
as i32,
)
}

/// Advance a Python instruction coordinate past resume trivia when code is available.
pub fn skip_python_trivia_forward_public(
jitcode_index: i32,
raw_py_pc: i32,
) -> Option<(i32, bool)> {
let payload = pyjitcode_for_jitcode_index(jitcode_index)?;
if payload.code_ptr.is_null() {
return Some((raw_py_pc, false));
}
let code = unsafe { &*payload.code_ptr };
Some((
crate::jitcode_dispatch::skip_python_trivia_forward(code, raw_py_pc as usize) as i32,
true,
))
}

/// Translate a resume-frame pc word to a Python instruction coordinate.
pub fn backxlat_py_pc(jitcode_index: i32, pc_word: i32) -> i32 {
let fallback = majit_ir::resumedata::decode_resume_pc(pc_word).0;
python_pc_for_jitcode_pc_public(jitcode_index, pc_word)
.and_then(|raw_py_pc| skip_python_trivia_forward_public(jitcode_index, raw_py_pc))
.map(|(py_pc, _)| py_pc)
.unwrap_or(fallback)
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all call sites of the new pc-translation helpers pass a
# non-negative/already-validated pc word, and inspect the callee
# implementations' tolerance for out-of-range offsets.
rg -nP -C4 '\b(backxlat_py_pc|python_pc_for_jitcode_pc_public|skip_python_trivia_forward_public)\s*\(' --type=rust
rg -nP -C6 'fn (python_pc_for_jitcode_pc|skip_python_trivia_forward)\s*\(' --type=rust pyre/pyre-jit-trace/src/jitcode_dispatch.rs

Repository: youknowone/pyre

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant region and nearby call sites.
printf '\n== state.rs outline ==\n'
ast-grep outline pyre/pyre-jit-trace/src/state.rs --view expanded | sed -n '1,220p'

printf '\n== helper call sites ==\n'
rg -n -C 3 'backxlat_py_pc|python_pc_for_jitcode_pc_public|skip_python_trivia_forward_public' pyre/pyre-jit-trace/src

printf '\n== jitcode_dispatch helper definitions ==\n'
rg -n -C 6 'fn (python_pc_for_jitcode_pc|skip_python_trivia_forward)\s*\(' pyre/pyre-jit-trace/src/jitcode_dispatch.rs

Repository: youknowone/pyre

Length of output: 15342


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focused probe for the pc-translation path and data-flow around it.
sed -n '760,805p' pyre/pyre-jit-trace/src/state.rs
printf '\n== nearby reconstruct_inline_recipe call site ==\n'
sed -n '5808,5830p' pyre/pyre-jit-trace/src/state.rs
printf '\n== all references to can_decode_live_vars in state.rs ==\n'
rg -n -C 2 'can_decode_live_vars|resolve_resume_pc_with_jitcode_pc|frame_pc_is_resolved_offset_at|frame_value_count_at|bridge_semantic_maps_at_with_jitcode_pc|const_ref_slots_at_pc_at' pyre/pyre-jit-trace/src/state.rs

Repository: youknowone/pyre

Length of output: 9971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the suspected Rust cast behavior with a tiny standalone probe.
python3 - <<'PY'
import sys
vals = [-1, -2, 0, 1, 2147483647]
for v in vals:
    u = v & ((1 << (8 * sys.getsizeof(0) - 1)) - 1)
    print(v)
PY

Repository: youknowone/pyre

Length of output: 174


Guard negative pc words before converting to usize.

python_pc_for_jitcode_pc_public and skip_python_trivia_forward_public cast i32 inputs to usize without a sign check. A negative pc_word/raw_py_pc wraps to a huge offset before reaching jitcode_dispatch, so backxlat_py_pc should reject < 0 itself instead of relying on every caller to pre-screen. Keep the fallback path, but return it early for negative input.

Proposed fix
 pub fn backxlat_py_pc(jitcode_index: i32, pc_word: i32) -> i32 {
     let fallback = majit_ir::resumedata::decode_resume_pc(pc_word).0;
+    if pc_word < 0 {
+        return fallback;
+    }
     python_pc_for_jitcode_pc_public(jitcode_index, pc_word)
         .and_then(|raw_py_pc| skip_python_trivia_forward_public(jitcode_index, raw_py_pc))
         .map(|(py_pc, _)| py_pc)
         .unwrap_or(fallback)
 }
📝 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
/// Resolve a stored JitCode offset back to its Python instruction coordinate.
pub fn python_pc_for_jitcode_pc_public(jitcode_index: i32, offset: i32) -> Option<i32> {
let payload = pyjitcode_for_jitcode_index(jitcode_index)?;
Some(
crate::jitcode_dispatch::python_pc_for_jitcode_pc(&payload.metadata, offset as usize)
as i32,
)
}
/// Advance a Python instruction coordinate past resume trivia when code is available.
pub fn skip_python_trivia_forward_public(
jitcode_index: i32,
raw_py_pc: i32,
) -> Option<(i32, bool)> {
let payload = pyjitcode_for_jitcode_index(jitcode_index)?;
if payload.code_ptr.is_null() {
return Some((raw_py_pc, false));
}
let code = unsafe { &*payload.code_ptr };
Some((
crate::jitcode_dispatch::skip_python_trivia_forward(code, raw_py_pc as usize) as i32,
true,
))
}
/// Translate a resume-frame pc word to a Python instruction coordinate.
pub fn backxlat_py_pc(jitcode_index: i32, pc_word: i32) -> i32 {
let fallback = majit_ir::resumedata::decode_resume_pc(pc_word).0;
python_pc_for_jitcode_pc_public(jitcode_index, pc_word)
.and_then(|raw_py_pc| skip_python_trivia_forward_public(jitcode_index, raw_py_pc))
.map(|(py_pc, _)| py_pc)
.unwrap_or(fallback)
}
/// Translate a resume-frame pc word to a Python instruction coordinate.
pub fn backxlat_py_pc(jitcode_index: i32, pc_word: i32) -> i32 {
let fallback = majit_ir::resumedata::decode_resume_pc(pc_word).0;
if pc_word < 0 {
return fallback;
}
python_pc_for_jitcode_pc_public(jitcode_index, pc_word)
.and_then(|raw_py_pc| skip_python_trivia_forward_public(jitcode_index, raw_py_pc))
.map(|(py_pc, _)| py_pc)
.unwrap_or(fallback)
}
🤖 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/state.rs` around lines 764 - 797, Guard negative
inputs before the i32-to-usize conversions in python_pc_for_jitcode_pc_public
and skip_python_trivia_forward_public, returning None for values below zero.
Update backxlat_py_pc to detect a negative pc_word and return its existing
decoded fallback immediately, preserving the current fallback behavior for
lookup failures.

@youknowone youknowone changed the title jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape (P1–P5) jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape Jul 14, 2026
@youknowone youknowone merged commit dde2efc into main Jul 14, 2026
28 of 30 checks passed
@youknowone youknowone deleted the rewrite-tracer branch July 14, 2026 09:14
youknowone added a commit that referenced this pull request Jul 14, 2026
python_pc_for_jitcode_pc_public and skip_python_trivia_forward_public cast
their i32 pc inputs to usize without a sign check, so a negative pc word
(a sentinel or branch-orgpc tag) wraps to a huge offset before reaching the
jitcode metadata lookup. Return the decode_resume_pc fallback directly when
the word is negative, preserving the existing fallback behaviour without
relying on every caller to pre-screen.

Addresses the PR #536 review finding on backxlat_py_pc.

Assisted-by: Claude
youknowone added a commit that referenced this pull request Jul 14, 2026
* jit: #369 S4a — delete the pyre recipe/carrier jitcode_pc twins

Remove ReconstructRecipe.pc and BridgeInlineCarrier.root_jitcode_pc, the two
pyre-local resume-coordinate twins left after S5. Post-flip both twins already
hold the JitCode offset, and recipe.pc is definitionally
backxlat_py_pc(recipe.jitcode_index, recipe.jitcode_pc), so every reader
re-derives its value from the surviving coordinate: recipe readers call
backxlat_py_pc; carrier readers use root_pc directly and drop the carried
offset parameters. The recipe and carrier resume frames now store a single
JitCode-offset coordinate.

Byte-identical: a gated census over the corpus on both backends certified
0 recipe divergences, 0 carrier divergences, and 0 sentinel-reaching readers
before the fields were removed.

Assisted-by: Claude
Assisted-by: Codex

* jit: #369 S4.5 — consume the carrier resume coordinate in Python-pc space

After S4a the BridgeInlineCarrier carries a single JitCode-offset coordinate
(root_pc). Several consumers fed that offset into Python-pc-keyed machinery
(set_last_instr_from_next_instr, result_color_at_pc_at, make_green_key /
set_green_key, resume_jitcode_pc_for, the frame-liveness pc argument, and the
WalkContext entry_py_pc / parent resume_py_pc fields), i.e. they read a JitCode
offset as if it were a Python bytecode pc.

Back-translate the offset with backxlat_py_pc(jitcode_index, root_pc) at each
of those py_pc-keyed sinks so the single coordinate is consumed in its correct
space, and add BridgeInlineCarrier.root_jitcode_index (= frames[0].jitcode_index)
as the back-translation key at the one sink (set_last_instr) that runs before a
body index is otherwise in scope; every other sink uses its co-located body
index. The raw offset is preserved wherever it is genuinely needed: the walker
drivers' start_pc, the carried select_recipe_entry argument, root_word, and the
frame-liveness carried argument.

Byte-behaviour-neutral on the current corpus: the multi-frame carrier walk still
declines (P2Framestack::OuterNonTerminate) for an unrelated reason, so no compile
is newly enabled; calls_closures.py (the 299-carrier reproducer) stays stdout
byte-identical to the interpreter and check.py holds 180/180 on both backends.

Assisted-by: Claude
Assisted-by: Codex

* jit: #369 — guard backxlat_py_pc against negative pc words

python_pc_for_jitcode_pc_public and skip_python_trivia_forward_public cast
their i32 pc inputs to usize without a sign check, so a negative pc word
(a sentinel or branch-orgpc tag) wraps to a huge offset before reaching the
jitcode metadata lookup. Return the decode_resume_pc fallback directly when
the word is negative, preserving the existing fallback behaviour without
relying on every caller to pre-screen.

Addresses the PR #536 review finding on backxlat_py_pc.

Assisted-by: Claude

* jit: #369 — screen the no-snapshot sentinel at the rd_numb_pc caller

The rd_numb_pc computation in rebuild_typed_from_rd_numb passed the outer
frame's pc to backxlat_py_pc and cast the result to usize without screening the
`pc == -1` no-snapshot sentinel, so a sentinel frame produced `usize::MAX`. Its
two siblings walking the same rebuilt-frame chain already screen it
(build_resumed_frames `frame.pc >= 0`, the carrier construction
`frames[0].pc >= 0`); mirror that here so the sentinel yields `rd_numb_pc =
None`.

Revert the backxlat_py_pc internal `pc < 0` guard added in the previous commit:
it returned the negative word unchanged (decode_resume_pc passes negatives
through), so it did not protect the usize-casting caller, and the internal
metadata lookups are bounds-checked and never index with the word. The screen
belongs at the caller, per the decode_resume_pc contract.

Addresses the PR #550 review finding on backxlat_py_pc.

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