You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The M3 cutover milestone landed on main (PR #365): direct-JitCode-pc kept-stack branch-guard resume is now the unconditional production default, and the m3_jitcode_pc_enabled() gate + PYRE_M3_JITCODE_PC opt-out were deleted. That retired pc_map's lossy kept-stack-collapse role for branch guards — but deleted none of the three data-structure artifacts #73 targets. This issue tracks Artifact 3: the carried jitcode_pc resume side-channel.
Correction to #73's original Artifact-3 framing.#73's body cites ResumedFrame.jitcode_pc: Option<usize> in pyre/pyre-jit/src/call_jit.rs, "written in build_resumed_frames", "read (dual-fallback) by resume_in_blackhole", "a pre-translated cache of pc_map[py_pc]". Every one of those anchors is stale and the semantics are inverted. The artifact is real and undeleted, but:
The field is RebuiltFrame.jitcode_pc: i32 in majit/majit-ir/src/resumedata.rs (sentinel-typed via NO_JITCODE_PC = -1), not an Option<usize> on ResumedFrame (which has no such field — its fields are code / py_pc / rd_numb_pc / frame_ptr / vsd / namespace / values).
It is written by the rd_numb encoder in majit/majit-metainterp/src/resume.rs (and the trace-time snapshot/recorder frames), not by build_resumed_frames (which has zero jitcode_pc references).
The dual-fallback read is resolve_resume_pc_with_jitcode_pc in pyre/pyre-jit-trace/src/pyjitcode.rs, not a function resume_in_blackhole (which does not exist; the blackhole entry point is resume_in_blackhole_from_exit_layout in eval.rs).
Semantically the field is the opposite of "a cache of pc_map[py_pc]": it is the direct JitCode coordinate that bypassespc_map (the M2 mechanism). NO_JITCODE_PC means "no direct coordinate — fall back through pc_map".
Current state (verified in the working tree, branch pc-map)
majit/majit-ir/src/resumedata.rs :: NO_JITCODE_PC (pub const NO_JITCODE_PC: i32 = -1, ~L94) — sentinel "resume through Python pc → pc_map".
majit/majit-ir/src/resumedata.rs :: RebuiltFrame.pc (~L383) — its doc records the deviation: RPython (resume.py:250) stores the JitCode byte offset in pc; pyre stores the Python bytecode PC here, which is why the separate jitcode_pc side-channel exists.
majit/majit-ir/src/resumedata.rs :: rebuild_from_numbering — decodes the per-frame jitcode_pc word out of rd_numb.
Encode / write side (majit-metainterp):
majit/majit-metainterp/src/resume.rs :: SnapshotFrame.jitcode_pc: i32 (~L279) + single_frame_boxes_with_jitcode_pc + multi_frame_boxes_with_jitcode_pc — pushes the per-frame jitcode_pc word into rd_numb (default NO_JITCODE_PC; a branch-guard capture writes the guard's real JitCode byte offset).
pyre/pyre-jit-trace/src/pyjitcode.rs :: resolve_resume_pc_with_jitcode_pc (~L453) — the dual-fallback resolver: if carried != NO_JITCODE_PC && carried >= 0 && jitcode.can_decode_live_vars(carried, op_live) return the carried coordinate directly; else fall through to resolve_resume_pc (→ resume_jitcode_pc_for → pc_map).
pyre/pyre-jit-trace/src/state.rs :: frame_liveness_reg_indices_by_bank_at_with_jitcode_pc (~L1206) — the box-count / liveness side that must resolve to the same coordinate; fed RebuiltFrame.jitcode_pc (~state.rs:5204) and frame0.jitcode_pc (~state.rs:7579).
pyre/pyre-jit/src/call_jit.rs — the two resume closures taking carried_jitcode_pc: i32 (L1590, L5122) invoking resolve_resume_pc_with_jitcode_pc (L1600, L5136). This is where the field enters production resume.
Scope / deliverable
Goal: collapse the jitcode_pc side-channel back into the single pc word, converging on the upstream RPython shape.
Upstream RPython/PyPy resume frames carry exactly one resume coordinate: pc = the JitCode byte offset (resume.py:250; blackhole.py self.setposition(miframe.jitcode, miframe.pc)). There is no second jitcode_pc field and no NO_JITCODE_PC sentinel upstream — those exist in pyre only because the tracer interprets Python bytecode and stores a Python pc in pc.
Once the tracer stores JitCode PCs natively (see Dependencies), RebuiltFrame.pcis the JitCode byte offset, so the deliverable is a mechanical collapse:
Make RebuiltFrame.pc hold the JitCode byte offset (the tracer-rewrite postcondition), matching resume.py:250.
Delete RebuiltFrame.jitcode_pc (majit-ir), SnapshotFrame.jitcode_pc + the *_with_jitcode_pc constructors (majit-metainterp/resume.rs), and the recorder frame's jitcode_pc (recorder.rs). Drop the per-frame jitcode_pc word from the rd_numb encode/decode (encoder in resume.rs, decoder rebuild_from_numbering).
Delete the NO_JITCODE_PC sentinel and the dual-fallback resolver resolve_resume_pc_with_jitcode_pc; every caller (call_jit.rs closures, frame_liveness_reg_indices_by_bank_at_with_jitcode_pc) resolves directly off pc — no translation, no carried_jitcode_pc parameter.
This removes the last resume-path consumer of pc_map / resume_jitcode_pc_for (the resolve_resume_pc_with_jitcode_pc → resolve_resume_pc chain). Note pc_map / resume_jitcode_pc_for still have trace-time consumers (trace.rs callee-entry/root/start pc resolution, trace_opcode.rs live-pc) and mode discriminators (is_populated / is_skeleton / is_portal_bridge); those retire with the tracer rewrite and the Artifact-1 deletion (JIT #73: delete PyJitCode.metadata.pc_map + resume_jitcode_pc_for translation table (Artifact 1) #368), not with this field collapse.
Faithful-porting discipline: the endpoint is RPython's single-pc resume frame. Do not keep jitcode_pc as an always-populated second field (a pyre-only field with no upstream counterpart) — merge it into pc.
Acceptance criteria
RebuiltFrame / SnapshotFrame / the recorder frame carry a single JitCode-pc coordinate; no jitcode_pc field, no NO_JITCODE_PC constant, no *_with_jitcode_pc variant, and no carried_jitcode_pc parameter threaded through call_jit.rs / state.rs remain (verify by Read / ast-grep — rg mangles these identifiers).
resolve_resume_pc_with_jitcode_pc is deleted; the resume path resolves directly off pc.
pyre/check.py fully green after the change.
The branch-guard kept-stack shape the direct coordinate was introduced for stays correct: a depth-1 kept Ref / heap-int operand short-circuit (e.g. x = flag and 11 keeping the falsy operand across the guard — pyre/bench/synth/short_circuit_value_local_kept.py) counts identically under JIT and interpreter. (This is the Ref/heap-int shape the side-channel covers; the separate unboxed Int/Float-bank chained-compare shape is the bank-aware-pcdep item JIT #73: bank-aware (typed) pcdep color-slot map — carry Int/Float-bank kept operand-stack temps through branch-guard resume #367, not this one.)
No new resume-path decline (census neutral or better on the BranchGuardKeptStackUnsupported family).
Dependencies
Update 2026-07-17: the original gates are resolved. #366 and #368 are CLOSED — PR #602
(squash 7b67840) retired the runtime py→jit inversion table (first_jit_pc_by_py_pc, resume_jitcode_pc_for, legacy resolver) via the jitcode-keyed py_floor_by_jit_pc boundary
pivot, WITHOUT the tracer rewrite: python_pc_for_jitcode_pc is piecewise-constant in jit_pc,
so the translation became a compile-time-baked value table (isomorphic to PyPy baking Python
pcs into jitcode as constants). Slices S1–S4 of this epic are MERGED (PR #536, PR #550): the
resume pc word is jitcode-native at the flipped seams and backxlat_py_pc composes over the
pivot.
Remaining scope (S6) needs RE-SCOPING under the post-#602 architecture. The prior S6
verdict ("not bounded, gated on the #73 walker-as-tracer rewrite") was reasoned against the
lossy dense pc_map decode that no longer exists. What survives of the side-channel: resolve_resume_pc_with_jitcode_pc's CHOICE between the carried jitcode coordinate and the
raw py word, the branch-orgpc carry helpers (m73_branch_carry_enabled, default ON), and the NO_JITCODE_PC sentinel plumbing. Whether the carried twin is still load-bearing now that
the decode-side translation is an exact floor-pivot lookup is an open question — re-audit
with a carried-vs-derived divergence census before scoping any deletion.
Entangled with #342 (unify symbolic/concrete + register/value-stack storage) as before.
Independent of #344 and #367.
#73 named three coupled pyre-only artifacts; all tracking issues are now CLOSED except this
one. (1) pc_map — retired by PR #602 (#368 closed); (2) per-Python-PC labels — complete
(one Label(block) per flow block); (3) the carried jitcode_pc side-channel — this
issue, the only remaining artifact. Contrary to the original sequencing note, Artifact 1
retired FIRST (the pivot made the translation exact without deleting the carry), so this
issue is no longer load-bearing for pc_map — it is now a standalone orthodoxy cleanup:
PyPy carries ONE coordinate per frame (MIFrame.pc, a JitCode offset, decode = identity).
— issue drafted by Claude; 2026-07-17 update by Claude
Context
The M3 cutover milestone landed on
main(PR #365): direct-JitCode-pc kept-stack branch-guard resume is now the unconditional production default, and them3_jitcode_pc_enabled()gate +PYRE_M3_JITCODE_PCopt-out were deleted. That retiredpc_map's lossy kept-stack-collapse role for branch guards — but deleted none of the three data-structure artifacts #73 targets. This issue tracks Artifact 3: the carriedjitcode_pcresume side-channel.Current state (verified in the working tree, branch
pc-map)Field + sentinel (majit-ir):
majit/majit-ir/src/resumedata.rs :: RebuiltFrame.jitcode_pc: i32(~L386) — the decoded per-frame direct-JitCode resume coordinate.majit/majit-ir/src/resumedata.rs :: NO_JITCODE_PC(pub const NO_JITCODE_PC: i32 = -1, ~L94) — sentinel "resume through Pythonpc→pc_map".majit/majit-ir/src/resumedata.rs :: RebuiltFrame.pc(~L383) — its doc records the deviation: RPython (resume.py:250) stores the JitCode byte offset inpc; pyre stores the Python bytecode PC here, which is why the separatejitcode_pcside-channel exists.majit/majit-ir/src/resumedata.rs :: rebuild_from_numbering— decodes the per-framejitcode_pcword out ofrd_numb.Encode / write side (majit-metainterp):
majit/majit-metainterp/src/resume.rs :: SnapshotFrame.jitcode_pc: i32(~L279) +single_frame_boxes_with_jitcode_pc+multi_frame_boxes_with_jitcode_pc— pushes the per-framejitcode_pcword intord_numb(defaultNO_JITCODE_PC; a branch-guard capture writes the guard's real JitCode byte offset).majit/majit-metainterp/src/recorder.rs :: <recorder frame>.jitcode_pc: i32.Read side (pyre-jit-trace / pyre-jit):
pyre/pyre-jit-trace/src/pyjitcode.rs :: resolve_resume_pc_with_jitcode_pc(~L453) — the dual-fallback resolver: ifcarried != NO_JITCODE_PC && carried >= 0 && jitcode.can_decode_live_vars(carried, op_live)return the carried coordinate directly; else fall through toresolve_resume_pc(→resume_jitcode_pc_for→pc_map).pyre/pyre-jit-trace/src/state.rs :: frame_liveness_reg_indices_by_bank_at_with_jitcode_pc(~L1206) — the box-count / liveness side that must resolve to the same coordinate; fedRebuiltFrame.jitcode_pc(~state.rs:5204) andframe0.jitcode_pc(~state.rs:7579).pyre/pyre-jit/src/call_jit.rs— the two resume closures takingcarried_jitcode_pc: i32(L1590, L5122) invokingresolve_resume_pc_with_jitcode_pc(L1600, L5136). This is where the field enters production resume.Scope / deliverable
Goal: collapse the
jitcode_pcside-channel back into the singlepcword, converging on the upstream RPython shape.Upstream RPython/PyPy resume frames carry exactly one resume coordinate:
pc= the JitCode byte offset (resume.py:250;blackhole.py self.setposition(miframe.jitcode, miframe.pc)). There is no secondjitcode_pcfield and noNO_JITCODE_PCsentinel upstream — those exist in pyre only because the tracer interprets Python bytecode and stores a Python pc inpc.Once the tracer stores JitCode PCs natively (see Dependencies),
RebuiltFrame.pcis the JitCode byte offset, so the deliverable is a mechanical collapse:RebuiltFrame.pchold the JitCode byte offset (the tracer-rewrite postcondition), matchingresume.py:250.RebuiltFrame.jitcode_pc(majit-ir),SnapshotFrame.jitcode_pc+ the*_with_jitcode_pcconstructors (majit-metainterp/resume.rs), and the recorder frame'sjitcode_pc(recorder.rs). Drop the per-framejitcode_pcword from therd_numbencode/decode (encoder in resume.rs, decoderrebuild_from_numbering).NO_JITCODE_PCsentinel and the dual-fallback resolverresolve_resume_pc_with_jitcode_pc; every caller (call_jit.rsclosures,frame_liveness_reg_indices_by_bank_at_with_jitcode_pc) resolves directly offpc— no translation, nocarried_jitcode_pcparameter.pc_map/resume_jitcode_pc_for(theresolve_resume_pc_with_jitcode_pc → resolve_resume_pcchain). Notepc_map/resume_jitcode_pc_forstill have trace-time consumers (trace.rscallee-entry/root/start pc resolution,trace_opcode.rslive-pc) and mode discriminators (is_populated/is_skeleton/is_portal_bridge); those retire with the tracer rewrite and the Artifact-1 deletion (JIT #73: delete PyJitCode.metadata.pc_map + resume_jitcode_pc_for translation table (Artifact 1) #368), not with this field collapse.Faithful-porting discipline: the endpoint is RPython's single-
pcresume frame. Do not keepjitcode_pcas an always-populated second field (a pyre-only field with no upstream counterpart) — merge it intopc.Acceptance criteria
RebuiltFrame/SnapshotFrame/ the recorder frame carry a single JitCode-pc coordinate; nojitcode_pcfield, noNO_JITCODE_PCconstant, no*_with_jitcode_pcvariant, and nocarried_jitcode_pcparameter threaded throughcall_jit.rs/state.rsremain (verify by Read / ast-grep —rgmangles these identifiers).resolve_resume_pc_with_jitcode_pcis deleted; the resume path resolves directly offpc.pyre/check.pyfully green after the change.x = flag and 11keeping the falsy operand across the guard —pyre/bench/synth/short_circuit_value_local_kept.py) counts identically under JIT and interpreter. (This is the Ref/heap-int shape the side-channel covers; the separate unboxed Int/Float-bank chained-compare shape is the bank-aware-pcdep item JIT #73: bank-aware (typed) pcdep color-slot map — carry Int/Float-bank kept operand-stack temps through branch-guard resume #367, not this one.)BranchGuardKeptStackUnsupportedfamily).Dependencies
Update 2026-07-17: the original gates are resolved. #366 and #368 are CLOSED — PR #602
(squash 7b67840) retired the runtime py→jit inversion table (
first_jit_pc_by_py_pc,resume_jitcode_pc_for, legacy resolver) via the jitcode-keyedpy_floor_by_jit_pcboundarypivot, WITHOUT the tracer rewrite:
python_pc_for_jitcode_pcis piecewise-constant in jit_pc,so the translation became a compile-time-baked value table (isomorphic to PyPy baking Python
pcs into jitcode as constants). Slices S1–S4 of this epic are MERGED (PR #536, PR #550): the
resume pc word is jitcode-native at the flipped seams and
backxlat_py_pccomposes over thepivot.
Remaining scope (S6) needs RE-SCOPING under the post-#602 architecture. The prior S6
verdict ("not bounded, gated on the #73 walker-as-tracer rewrite") was reasoned against the
lossy dense pc_map decode that no longer exists. What survives of the side-channel:
resolve_resume_pc_with_jitcode_pc's CHOICE between the carried jitcode coordinate and theraw py word, the branch-orgpc carry helpers (
m73_branch_carry_enabled, default ON), and theNO_JITCODE_PCsentinel plumbing. Whether the carried twin is still load-bearing now thatthe decode-side translation is an exact floor-pivot lookup is an open question — re-audit
with a carried-vs-derived divergence census before scoping any deletion.
Entangled with #342 (unify symbolic/concrete + register/value-stack storage) as before.
Independent of #344 and #367.
Relation to #73
#73 named three coupled pyre-only artifacts; all tracking issues are now CLOSED except this
one. (1)
pc_map— retired by PR #602 (#368 closed); (2) per-Python-PC labels — complete(one
Label(block)per flow block); (3) the carriedjitcode_pcside-channel — thisissue, the only remaining artifact. Contrary to the original sequencing note, Artifact 1
retired FIRST (the pivot made the translation exact without deleting the carry), so this
issue is no longer load-bearing for pc_map — it is now a standalone orthodoxy cleanup:
PyPy carries ONE coordinate per frame (MIFrame.pc, a JitCode offset, decode = identity).
— issue drafted by Claude; 2026-07-17 update by Claude