jit: #369 pc-word flip — resume frame collapses to RPython 2-word shape#536
Conversation
…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
WalkthroughChangesResume coordinate simplification
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
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 04ad46b). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patch
2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
There was a problem hiding this comment.
💡 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 { | |||
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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
📒 Files selected for processing (24)
majit/majit-backend-cranelift/src/compiler.rsmajit/majit-ir/src/resumedata.rsmajit/majit-metainterp/src/blackhole.rsmajit/majit-metainterp/src/compile.rsmajit/majit-metainterp/src/history.rsmajit/majit-metainterp/src/jitdriver.rsmajit/majit-metainterp/src/lib.rsmajit/majit-metainterp/src/optimizeopt/mod.rsmajit/majit-metainterp/src/pyjitpl.rsmajit/majit-metainterp/src/pyjitpl/dispatch.rsmajit/majit-metainterp/src/recorder.rsmajit/majit-metainterp/src/resume.rsmajit/majit-metainterp/tests/jit_driver_runtime_parity.rsmajit/majit-metainterp/tests/resume_parity.rspyre/pyre-jit-trace/src/canonical_bridge.rspyre/pyre-jit-trace/src/jitcode_dispatch.rspyre/pyre-jit-trace/src/lib.rspyre/pyre-jit-trace/src/liveness.rspyre/pyre-jit-trace/src/pyjitcode.rspyre/pyre-jit-trace/src/state.rspyre/pyre-jit-trace/src/trace.rspyre/pyre-jit-trace/src/trace_opcode.rspyre/pyre-jit/src/call_jit.rspyre/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
| /// 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) | ||
| } | ||
|
|
There was a problem hiding this comment.
🩺 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.rsRepository: 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.rsRepository: 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.rsRepository: 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)
PYRepository: 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.
| /// 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.
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 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
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
pcword and a redundant
jitcode_pctwin side-channel (a per-frame 3rdrd_numbword). RPython's
resume.pynumbers 2 words/frame(jitcode_index, pc)where
pcis the JitCode byte offset. This stack makes thepcword the JitCodeoffset and physically removes the twin.
Phases
jitcode coordinate or is skeleton; deleted the production-dead portal-bridge
PyJitCodemode (no undefined-coordinate frame class remains).jitcode→pyat thethree python-resume readers; the flip-obstruction rows were all shown benign
(single-frame guards whose exact resume pc survives in the virtualizable
last_instr).pcword; decode py_pc-readers back-translate; single-frame resume_pc sourcedfrom vable
ni. Landed gated, then flipped default-ON (s3). Full A/Bgreen with the gate OFF and ON.
s0/s1),collapsed the decode callbacks and readers onto the
pcword (s2/s3a),derived the bridge-resume gates and recipe/carrier twin sources from
frame.pc(
s3b/s3c), and finally deleted therd_numb3rd word (s5): the frameheader is now
(jitcode_index, pc). Both encoders and all three decoders dropthe twin word in lockstep;
SnapshotFrame.jitcode_pc/RebuiltFrame.jitcode_pcand the
*_with_jitcode_pcconstructors are gone. The encode-side twincomputation is retained — it still feeds
resolve_resume_pc_with_jitcode_pc,which bakes the resume coordinate into the surviving
pcword.Verification
pyre/check.py --backend dynasm180/180,--backend cranelift180/180.cargo test -p pyre-jit-trace279/0-fail,cargo test -p majit-metainterp1465/0-fail.cargo fmt --check+git diff --checkclean.Follow-up (not in this PR)
S4— deleting the pyre-sideReconstructRecipe.jitcode_pcandBridgeInlineCarrier.root_jitcode_pctwins — is deferred. It is notbyte-identical and has two genuine blockers (carrier
root_pcoffset consumedas
py_pc;recipe.pcis apy_pcand 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
Bug Fixes
Cleanup