Skip to content

jit-trace: stop indexing the Python-keyed depth table with a JitCode offset - #1145

Merged
youknowone merged 3 commits into
mainfrom
rewrite-tracer
Aug 10, 2026
Merged

jit-trace: stop indexing the Python-keyed depth table with a JitCode offset#1145
youknowone merged 3 commits into
mainfrom
rewrite-tracer

Conversation

@youknowone

@youknowone youknowone commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

bridge_semantic_maps_at_with_jitcode_pc indexes the Python-PC-keyed
LiveVars::depth_at_py_pc with its second parameter. Six of its seven call
sites reach it holding a JitCode byte offset
— the thing py_coord.rs's module
invariant forbids:

the forward-carried SnapshotFrame.py_pc resume channel [and the codewriter
twins] are the only Python-coordinate sources; runtime consumers never project
a JitCode PC through a Python-keyed map.

Five arrived through bridge_semantic_maps_from_pc(idx, pc), which fed one word
into both the Python slot and the JitCode slot. The sixth passed
inline_call_return_marker's decode_op_at(..).next_pc.

The parameter becomes Option<i32>, and the fallback answers 0 without
touching the table when it is None. bridge_semantic_maps_from_pc becomes
bridge_semantic_maps_from_jitcode_pc, named for the coordinate it carries so
the word cannot be re-routed into the Python slot. Only
walker_capture_snapshot_for_last_guard_impl's liveness_py_pc stays Some.
The sibling try_frame_liveness_reg_indices_by_bank_at_with_jitcode_pc already
declines on an absent coordinate rather than reconstructing one; this matches it.

Each of the seven coordinates was classified independently from its producer
(not from the comments under test), and all seven agreed: six JitCode byte
offsets, one genuine Python PC.

Measurement

399-file synth corpus, census before and after: the same 20 files reach the leg,
moving bucket=out_of_range (rp 231..1214 against table lengths 28..257) →
bucket=no_py_pc. Both answer 0, so this is inert on the corpus. The defect
it removes is the unmeasured case — a short function whose JitCode offset lands
inside the table and returns a foreign instruction's depth.

What this deliberately does not do, and the number that says it matters

Answering 0 is a decline, not the best available answer. This PR measures how
lossy the decline is, rather than asserting it away:

executions of the leg 20 / 399 files
carrying a usable jit_pc >= 0 20
what depth_containing_for_jitcode_pc would answer there Some(3) ×14, Some(4) ×4, Some(5) ×2
would answer None or Some(0) 0

depth_containing_for_jitcode_pc is built to reproduce
depth_at_py_pc[containing_py] for every offset, and
collect_outer_active_boxes — the encode half this function mirrors — already
sources its own stack_depth_at_pc from those twins with no Python PC in hand.
So routing this leg through them is invariant-conforming and not a no-op: it
would widen setup_bridge_sym's semantic_prefix_len by 3–5 slots wherever the
leg runs. That is a change in reconstructed frame width, not a coordinate repair,
so it is out of scope here and left as follow-up.

Stated as open: the census counters are per leg, not per call site, so
those 20 non-zero counterfactuals are not yet attributed to setup_bridge_sym
the only caller whose stack_depth_at_pc read is unconditional
(stack_only.max(..)). At the other five an empty pcdep_entries makes
semantic_ref_slot_for_reg_color return None regardless of the depth, so the
value is inert there. Per-call-site attribution is step 1 of that follow-up.

Retractions

The third commit retracts two claims from the second commit's message: the
codewriter.rs:14808 citation (it is the closing brace of an unrelated
[pcmap-residual] block — the passage meant is finalize_jitcode's
static_depth construction, which records the converse), and
"in_range_nonzero — the only bucket reaching setup_bridge_sym's
semantic_prefix_len", which that same commit made unreachable from there. The
first commit's 415/415 and 414/414 was measured on a base that has since moved
and does not describe this tree.

Verification

  • LLBC: all five artefacts re-extracted and verified FRESH before the gate.
    (pyre-jit's fingerprint covers its dependency closure, so a comment-only edit
    in pyre-jit-trace staled it — and a plain cargo build returned 0 without
    re-running the check.)
  • LC_ALL=C python3 pyre/check.py --backend dynasm,cranelift: ALL PASSED —
    dynasm 417/417, cranelift 416/416
    , exit 0.
  • ⚠️ Run it without LC_ALL=C and dynasm reports test.test_format PASS -> FAIL at test_locale. That is environmental, not this patch: it reproduces
    under PYRE_NO_JIT=1, reproduces on cranelift too (which simply has no
    test.test_format entry in pyre/cpython_tests/baseline.json — only
    {"dynasm": "PASS"} is recorded), and the assertion is
    assertIn(',', '123456789')format(n, 'n') does not apply the locale's
    thousands_sep. The recorded PASS was taken where sep was empty, so
    assertIn('', text) was vacuously true. No workflow in .github/workflows/
    pins a locale, so which way this gate reads depends on the runner's LANG.
    Flagging it; not fixing it here.
  • HEAD sentinel identical at the start and end of the gate run.

Self-review

Reviewed in a separate session from the one that generated the code, two ways:
the repo's Codex parity prompt (.github/codex-review-prompt.md), and a
seven-way independent classification of the call-site coordinates plus four
adversarial refutation passes over value-safety, census completeness,
instrumentation, and comment accuracy.

Those reviews found real defects in this patch, all of them in what the patch
asserted rather than what it computed, and all are fixed in the third commit: a
line citation that a rebase had turned into a closing brace; a negative existence
claim ("no jitcode-keyed spelling of this read exists") refuted by three live
ones; a causal claim about the census that this patch's own change inverted; a
from_jitcode_pc doc premise false at two of five callers; and a binding left
unused by the change.

  • I fully resolved all reasonable code review comments from Codex and CodeRabbit.
    • Auto-review section 1 is clear. This check is mandatory.
    • Auto-review section 2 is clear. If this is not checked, please add a comment explaining why.

Section 2 carried one finding: that the (0, empty pcdep) fallback "silently
fabricates an empty stack map" and these paths "should decline/abort
reconstruction when their required JitCode metadata is absent". It is not
resolved here, deliberately, and it is not introduced by this patch — the same
leg produced (0, empty) before, by an accident of the offset falling out of
range rather than by decision. Turning it into a genuine decline changes
reconstruction outcomes on the 20 benches above and needs its own before/after
measurement and gate. What this patch does instead is make the decline
representable and measured, which is the precondition for that work. The
counterfactual table above is the evidence for the follow-up.

  • I did not use AI to write the code of this patch.
    • Commits carry Assisted-by: Claude.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of JIT-compiled code during tracing and execution resumption.
    • Corrected stack and frame reconstruction for residual calls and nested inline frames.
    • Prevented mismatches between compiled-code positions and Python source positions.
    • Improved semantic mapping and liveness tracking to support more reliable debugging and runtime behavior.
    • Added fallback telemetry to help identify mapping issues in affected execution paths.

`bridge_semantic_maps_at_with_jitcode_pc` indexes the Python-PC-keyed
`LiveVars::depth_at_py_pc` with its `pc` parameter on two legs: when the
carried jitcode coordinate decodes but the twin tables hold no entry, and
when no coordinate is carried at all. `bridge_semantic_maps_from_pc`
passes one word into both the `pc` and `jitcode_pc` slots, and all five of
its callers supply a JitCode byte offset -- three named `jitcode_pc` /
`op_pc`, and two `RebuiltFrame::pc`, which `majit-ir/src/resumedata.rs`
documents as the byte offset with the Python coordinate held in the
separate `py_pc` field.

Adds `PYRE_M73_EMPTYTWIN_CENSUS`, which sorts each fallback read into
null_code / out_of_range / in_range_zero / in_range_nonzero and prints the
first witness of every bucket per call site. Only in_range_nonzero reaches
`setup_bridge_sym`'s `semantic_prefix_len`.

Over the 397-file synth corpus the leg runs in 20 files, every one of them
`site=non_decodable bucket=out_of_range`; in_range_nonzero is 0 and
site=twin_miss does not execute.

With the variable unset every counter path is skipped.
`check.py --backend dynasm,cranelift` is 415/415 and 414/414.

Assisted-by: Claude
…inate

`bridge_semantic_maps_at_with_jitcode_pc` indexes the Python-PC-keyed
`LiveVars::depth_at_py_pc` with its second parameter. Six of its seven call
sites reach it holding a JitCode byte offset: the five through
`bridge_semantic_maps_from_pc`, which passed one word into both the Python
and the JitCode slot, and `resume_snapshot.rs`'s `marker`, which is
`inline_call_return_marker`'s `decode_op_at(..).next_pc`. `py_coord.rs`
states the invariant they violate: "runtime consumers never project a
JitCode PC through a Python-keyed map".

The second parameter becomes `Option<i32>`, and the fallback answers 0
without reading the table when it is `None`. `bridge_semantic_maps_from_pc`
becomes `bridge_semantic_maps_from_jitcode_pc` and passes `None`; only
`walker_capture_snapshot_for_last_guard_impl`'s `liveness_py_pc` stays
`Some`. The sibling `try_frame_liveness_reg_indices_by_bank_at_with_jitcode_pc`
already declines on an absent coordinate rather than reconstructing one.

This does not derive a depth from the JitCode offset. The fallback reads the
static dense liveness, while the jitcode-keyed twin carries the walk-visited
`depth_at_pc`; `codewriter.rs:14808` records these as deliberately different
tables, and `depth_at_pc` is not published into `PyJitCodeMetadata`.

Census over the synth corpus, before and after: the same 20 files reach the
leg, moving from `bucket=out_of_range` (rp=231..1214 against table lengths
28..257) to `bucket=no_py_pc`. `in_range_nonzero` — the only bucket reaching
`setup_bridge_sym`'s `semantic_prefix_len` — is 0 in both.

The census now shares `py_coord::emptytwin_census_enabled` instead of
re-reading `PYRE_M73_EMPTYTWIN_CENSUS`, which that module already owns, and
prints `[m73-bridge-maps]` so its lines stay distinct from
`note_empty_twin_fallback`'s.

`check.py --backend dynasm,cranelift`: cranelift 416/416; dynasm 416 passed
with `test.test_format` failing at `test_locale`, which reproduces under
`PYRE_NO_JIT=1`.

Assisted-by: Claude
…nsus

The comment justifying the `None` fallback's 0 asserted that "there is no
jitcode-keyed spelling of this read to fall back to either", citing
`codewriter.rs:14808`. Three such spellings exist:
`depth_containing_for_jitcode_pc`, built in `finalize_jitcode`'s
`depth_containing_by_jit_pc` block to reproduce
`depth_at_py_pc[containing_py]` for every offset, plus the trivia and
block-head twins. `collect_outer_active_boxes`, the encode half this
function mirrors, already reads its own `stack_depth_at_pc` from those
twins with no Python PC in hand. The cited line is the closing brace of an
unrelated `[pcmap-residual]` block; the passage it meant is
`finalize_jitcode`'s `static_depth` construction, and it records the
converse — the trivia twins deliberately carry the static `depth_at_py_pc`,
and `depth_pred_by_jit_pc` alone carries the walk-visited `depth_at_pc`.
The replacement cites by symbol rather than by line.

`empty_twin_census` gains `jit_pc` and `twin`: the coordinate the leg
declined on, and what `depth_containing_for_jitcode_pc` would answer there.
Over the 399-file synth corpus all 20 executions carry `jit_pc >= 0` with
`twin = Some(3..=5)`, so routing the fallback through the twin would change
the reconstructed frame's width rather than nothing. The leg still answers
0; what it declines is now recorded instead of asserted away. The counters
remain per leg, so those 20 are still unattributed to a call site — the
comment says so, because `setup_bridge_sym` is the only caller whose
`stack_depth_at_pc` read is unconditional.

The census doc claimed `in_range_nonzero` is the only bucket reaching
`setup_bridge_sym`'s `semantic_prefix_len`. The preceding commit made that
site pass `None`, so the bucket is unreachable from there; the doc now
names `no_py_pc` as that site's only bucket, and `semantic_limit` in
`walker_capture_snapshot_for_last_guard_impl` as the consumer of the one
`Some` caller's depth. `EMPTY_TWIN_NON_DECODABLE`'s doc said no coordinate
was carried, while its branch also covers a carried offset that does not
decode.

`bridge_semantic_maps_from_jitcode_pc`'s doc said its callers hold only a
JitCode coordinate; the two `RebuiltFrame` callers also hold `py_pc`, which
`py_coord.rs` names as a sanctioned Python-coordinate source, so the doc
now records that and why it is declined. `resume_marker_jit_pc`'s binding
in `compute_nested_inline_caller_frame` no longer names a value and becomes
`is_some()`. The kept-stack comment in `setup_bridge_sym`'s caller
attributed `stack_depth_at_pc` to `depth_at_py_pc`; it reads the
`depth_pred_by_jit_pc` twin.

Retracts two claims from 397320f's message: the `codewriter.rs:14808`
citation together with its reading of which table the jitcode-keyed twins
carry, and "`in_range_nonzero` — the only bucket reaching
`setup_bridge_sym`'s `semantic_prefix_len`". 6b30254's `415/415 and
414/414` was measured on a base that has since moved twice and does not
describe this tree.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0521d172-20db-4a38-b43a-5faaf3e0426b

📥 Commits

Reviewing files that changed from the base of the PR and between ab70bc2 and a2bb94d.

📒 Files selected for processing (4)
  • pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
  • pyre/pyre-jit-trace/src/state.rs

Walkthrough

The change separates JitCode PCs from optional Python PCs during bridge semantic-map lookup. State fallback handling now uses explicit coordinates and telemetry. Bridge dispatch and resume reconstruction call the JitCode-aware lookup paths.

Changes

JitCode-PC bridge reconstruction

Layer / File(s) Summary
Coordinate-aware semantic-map lookup
pyre/pyre-jit-trace/src/state.rs
Bridge lookup now accepts an optional Python PC while retaining the JitCode PC. JitCode-keyed metadata remains primary. Fallback cases now use the optional Python coordinate and record telemetry.
JitCode-coordinate dispatch callers
pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs, pyre/pyre-jit-trace/src/state.rs
Bridge reconstruction, residual-call mapping, inline recipe reconstruction, and bridge setup now use the JitCode-PC lookup helper.
Resume snapshot coordinate propagation
pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
Resume handling passes resolved Python liveness coordinates only when available. Nested-frame and callee recovery paths use JitCode coordinates without treating them as Python PCs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Poem

A rabbit hops through JitCode light,
Keeping Python PCs in sight.
Maps now follow the proper track,
Fallback counters mark the back.
Bridge and resume paths align—
“Hop!” says the rabbit, “works just fine!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing JitCode offsets from indexing the Python-keyed depth table.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate 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 a2bb94d).
Updated: 2026-08-10T14:49:54.758Z

Files in the reviewed diff
pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
pyre/pyre-jit-trace/src/state.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-jit-trace/src/state.rs:1994 ↔ rpython/jit/metainterp/pyjitpl.py:174 — when a JitCode-PC twin is missing or non-decodable, the new None path returns stack depth 0 and empty pcdep data. PyPy always obtains liveness from the frame’s JitCode PC (get_live_vars_info(self.pc, ...)); an invalid position raises rather than silently reconstructing a zero-depth frame. This can omit live operands. The new behavior is reached through all changed from_jitcode_pc call sites: bridge_subwalk.rs:929, residual_call.rs:2175, resume_snapshot.rs:2534, and state.rs:7805/10090, plus the explicit None call at resume_snapshot.rs:2065.

  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs:2065 ↔ rpython/jit/metainterp/pyjitpl.py:177 — the nested-inline caller flush now deliberately supplies no Python coordinate and consequently skips semantic slot recovery whenever the JitCode pcdep twin is absent. PyPy enumerates and snapshots every live register at the caller frame’s actual JitCode position; it has no “continue with no live stack” fallback.

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

  • pyre/pyre-jit-trace/src/state.rs:1985 ↔ rpython/jit/codewriter/jitcode.py:82 — before this patch, bridge_semantic_maps_from_pc passed a JitCode byte offset into the CPython Python-PC-indexed depth table. PyPy’s liveness API indexes JitCode directly. This patch correctly removes that coordinate conflation, but replaces it with the incomplete zero-depth fallback noted above.

4. Structural adaptations

  • pyre/pyre-jit-trace/src/state.rs:1930 ↔ rpython/jit/metainterp/pyjitpl.py:174 — splitting py_pc: Option<i32> from jitcode_pc is a necessary Rust/CPython-compiler adaptation: PyPy’s MIFrame.pc is solely a JitCode coordinate, whereas pyre also maintains CPython bytecode coordinates. The separation itself is appropriate; only its zero-depth fallback is not parity-equivalent.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +10089 to +10090
let maps =
crate::state::bridge_semantic_maps_from_jitcode_pc(frame0.jitcode_index, frame0.pc);

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 Preserve the Python PC when rebuilding the root frame

When the carried JitCode position is non-decodable or lacks a depth/pcdep twin, this helper now returns stack_depth_at_pc = 0, even though frame0.py_pc is the forward-carried Python coordinate available for the fallback. For a kept-stack guard whose virtualizable depth reflects the shallower merge target, line 10097 consequently fails to widen semantic_prefix_len; live guard-time stack slots are then excluded from the reconstructed frame, potentially aborting or mis-seeding the bridge. Pass Some(frame0.py_pc) to bridge_semantic_maps_at_with_jitcode_pc here rather than deliberately discarding the valid coordinate.

Useful? React with 👍 / 👎.

@youknowone
youknowone merged commit 1188a78 into main Aug 10, 2026
15 of 17 checks passed
@youknowone
youknowone deleted the rewrite-tracer branch August 10, 2026 20:01
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