Skip to content

jit: FBW bridge always-commit slices — CA adopted-frame completion, orphan stamp default-on, merge-point cut#508

Merged
youknowone merged 16 commits into
mainfrom
rename
Jul 13, 2026
Merged

jit: FBW bridge always-commit slices — CA adopted-frame completion, orphan stamp default-on, merge-point cut#508
youknowone merged 16 commits into
mainfrom
rename

Conversation

@youknowone

@youknowone youknowone commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Epic #493 (FBW always-commit protocol) slice series, plus supporting descr/observability work. Rebased onto main (#509 WalkContext refactor).

What's in here

  • jit: split residual decline causes, add walk-end censusResidualExecOutcome split (ValueUnavailable / Symbolic), executed-residual counters, PYRE_FBW_CENSUS walk-end line. Observability only.
  • jit: gated bridge-decode orphan stamping — stamp decoded concrete values onto orphan-color OpRefs for divergent bridge resumes (initially behind PYRE_FBW_BRIDGE_STAMP).
  • majit: attach OBJECT parent SizeDescr to the typeptr field descr / jit: route items_block_capacity_descr through a descr group — parentless-descr fixes (descr.py:218-239 cyclic OBJECT group; DictStorage-style group for the ItemsBlock capacity read), draining the FieldDescrMissingParentDescr walk-abort class.
  • jit: complete committed CA bridge walk from adopted frame — the CALL_ASSEMBLER slow path ran the bridge hook and then unconditionally re-ran the guard-state blackhole, dropping a committed walk's end state (m4 repro returned a stale accumulator). The blackhole hook now completes the callee from the adopted frame via the portal runner (raise_continue_running_normally parity, pyjitpl.py:3048-3091 + warmspot.py:970-983).
  • jit: default bridge orphan stamp on — with the CA fix in place, stamp-on matches stamp-off on every anchor and closes the e_min iteration deficit (9997 → 10000) by default; PYRE_FBW_BRIDGE_STAMP=0 opts out.
  • jit: cut root-loop compile at the registered merge point — upstream compile_loop always compiles from the registered merge point (pyjitpl.py:3018-3030). Pyre gated the cut on the cross-loop marker, so a guard-origin bridge closing at its own key never compiled. The cut is now unconditional; portal traces (position 0) are unaffected.
  • jit: clear last_exc_value at residual-call dispatch entry — upstream execute_varargs clears the exception register at every residual-call entry (pyjitpl.py:1940-1941; catch_exception assert pyjitpl.py:504); pyre cleared it only on the concrete-exec success arm, so a stale exception declined the next residual call (CatchExceptionWithActiveException class, 9 → 0).
  • jit: no-replay finish for CALL_ASSEMBLER bridge Terminate walks — the CA callback returns a bare bool and had no channel for a concrete finish result, so a CA bridge walk running the callee to finishframe concretely was rolled back and blackhole-re-run (double execution). The callback now records the finished frame in a thread-local handshake and the back-to-back blackhole consumes the kept stash (DoneWithThisFrame/ExitFrameWithExceptionRef parity, pyjitpl.py:1688-1698). Also closes the PYRE_FBW_BRIDGE_STAMP=0 e_min deficit (9997 → 10000).
  • jit: model 24 missing opcode stack effects in livenessstack_effects default arm modeled 24 real stack-moving opcodes (CALL_FUNCTION_EX net −3, CALL_KW −(argc+2), UNPACK_EX, …) as net-0, inflating depth_at_py_pc past each occurrence; the walk-end flush then read phantom NULL stack slots and declined (CloseLoop non-commit class). All 24 modeled against the eval.rs implementations; Send registered as a branch instruction.
  • jit: flush kept-stack Unsupported abort at FOR_ITER header — user-defined-iterator FOR_ITER walks executed their residuals then replayed from loop entry on the kept-stack abort. The in-flight entry at that abort is stale (entries are replaced on the next successful consume, never popped at body completion), so plain Shape-A delivery would re-run the last body. New body_completed tracking marked at every for_iter_next dispatch start; the delivery selector refuses completed entries; a new no-delivery flush arm (Unsupported variant only) adopts the walk end state at the header and lets the interpreter re-attempt the consume. The Permanent variant is byte-identical.
  • jit: log residual value-unavailable decline site under debugPYRE_FBW_DEBUG_ABORT prints op_pc/arg/helper when a residual declines with ValueUnavailable, attributing the remaining unj_val census walks without re-instrumenting.
  • rtyper: fill deferred annlowlevel mix-level helper bodies, plus warning cleanups and rustfmt.

Verification

  • Post-rebase full gate: check.py dynasm + cranelift + wasm all 162/162, zero flakes; cargo test --all --features dynasm green.
  • Corpus hazard census (PYRE_FBW_CENSUS=1): non-committed effectful walks 77 → 1 across the series (remaining: list_ops CompileTracePending+unj — box_value-None on residual args, the Epic: FBW always-commit protocol — retire replay-from-entry #493 P1 epic).
  • Anchors: e_min 10000 (default), m4_300 2661 (stamp on == off at every probed N), gcd_kept_while 2663, repro_467 300000.

Full root-cause records: #493 comments 6-17.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@youknowone, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 63889c0e-8880-4d2c-9979-3299f7a036c5

📥 Commits

Reviewing files that changed from the base of the PR and between beff3e8 and c3c6ef0.

📒 Files selected for processing (13)
  • majit/majit-ir/src/descr.rs
  • majit/majit-macros/src/jit_interp/mod.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • majit/majit-translate/src/annotator/bookkeeper.rs
  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs
  • pyre/pyre-interpreter/src/host_seam.rs
  • pyre/pyre-jit-trace/src/descr.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/liveness.rs
  • pyre/pyre-jit-trace/src/state.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-jit/src/eval.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rename

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.

@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: 763ec26416

ℹ️ 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 +5173 to +5175
let cross_loop_cut = ctx
.get_merge_point_at(green_key, ctx.header_pc)
.filter(|mp| mp.position._pos > 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard all cut traces against null backedges

When this new unconditional lookup produces a cut for a guard-origin same-key trace (mp.position > 0 while cut_inner_green_key is None), the existing safety check later in compile_loop_body is skipped because it is still gated on cut_inner_green_key.is_some(). For any such cut trace where cross_loop_cut_label_jump_null_guard_slot() would detect a class-guarded LABEL input fed back as Const(NULL), the loop is now installed instead of aborting, which the local comment describes as a NULL dereference on the loop back edge. Please gate that rejection on cross_loop_cut.is_some() (or the same condition introduced here), not only on the old cross-loop marker.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

🤖 Codex parity review

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

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:225 ↔ rpython/rtyper/annlowlevel.py:581 — Rust accepts "" and rejects lowercase Unicode/digit-containing placeholders via chars().all(is_ascii_lowercase); Python’s s.islower() rejects empty strings but accepts strings such as "item2" and lowercase Unicode.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:284 ↔ rpython/rtyper/annlowlevel.py:599 — the same incorrect ASCII-only/empty-string predicate exists in typemeth_placeholder_sigarg.

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

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1164 ↔ rpython/rtyper/annlowlevel.py:632ADTInterface.__call__ always returns an error; PyPy installs each signature as meth._annenforceargs_ and returns adtmeths.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1837 ↔ rpython/rtyper/annlowlevel.py:277backend_optimize is an unconditional “port pending” error; PyPy runs backend optimizations for newgraphs and clears that set.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1894 ↔ rpython/rtyper/annlowlevel.py:380 — string-entry helpers always fail; PyPy’s make_string_entries provides working hlstr/llstr conversions and their registry specializations.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1954 ↔ rpython/rtyper/annlowlevel.py:453cast_object_to_ptr is unimplemented; PyPy creates a typed low-level pointer, including null-pointer handling.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1965 ↔ rpython/rtyper/annlowlevel.py:482cast_instance_to_gcref is unimplemented; PyPy casts the base object pointer to GCREF.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1976 ↔ rpython/rtyper/annlowlevel.py:492cast_nongc_instance_to_adr is unimplemented; PyPy converts the non-GC base pointer to an address.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1986 ↔ rpython/rtyper/annlowlevel.py:526cast_base_ptr_to_instance is unimplemented; PyPy unwraps/checks the pointer and returns the requested instance.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:2003 ↔ rpython/rtyper/annlowlevel.py:540cast_gcref_to_instance is unimplemented; PyPy converts GCREF to OBJECTPTR before the base-pointer cast.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:2014 ↔ rpython/rtyper/annlowlevel.py:546cast_adr_to_nongc_instance is unimplemented; PyPy converts the address to NONGCOBJECTPTR.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:1983 ↔ rpython/rtyper/annlowlevel.py:496CastObjectToPtrEntry has no annotation/specialization behavior; PyPy supplies compute_result_annotation and emits cast_pointer.

  • majit/majit-translate/src/translator/rtyper/annlowlevel.rs:2025 ↔ rpython/rtyper/annlowlevel.py:552CastBasePtrToInstanceEntry has no annotation/specialization behavior; PyPy supplies both and emits cast_pointer.

4. Structural adaptations

  • pyre/pyre-jit-trace/src/liveness.rs:565 ↔ pypy/interpreter/pyopcode.py:332 — stack-effect coverage for CPython 3.14 instructions is necessarily pyre-specific; PyPy’s opcode set does not include the full 3.14 instruction family.

  • pyre/pyre-jit/src/eval.rs:4891 ↔ rpython/jit/metainterp/blackhole.py:1679 — Rust returns PyError for an uncaught bridge-walk exception where RPython raises ExitFrameWithExceptionRef; this is control-flow representation, not semantic divergence.

  • majit/majit-ir/src/descr.rs:4789 ↔ rpython/jit/backend/llsupport/descr.py:218 — the cyclic Arc/Weak descriptor group replaces Python object references and descriptor caches while preserving the required field-to-parent SizeDescr relationship.

@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: 64ea55efb4

ℹ️ 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 +7167 to +7170
; mov [Rq(scratch)], Rq(new_free_reg) // *nursery_free = base + 16
; mov [Rq(base_reg)], Rq(value_reg) // value @ base+0
; mov [Rq(base_reg) + 8], Rq(next_reg) // next @ base+8
; mov rax, Rq(base_reg) // result = base

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 GC header in the nursery fast path

When a NurseryAllocRef call takes this fast path under the normal majit GC, it advances the active GC nursery and returns base as a Ref after writing only the two Node payload words. The collector's nursery objects are laid out as GcHeader followed by the payload (Collector::alloc_with_type initializes the header and returns ptr + GcHeader::SIZE), and JIT Ref values are later treated as GC roots; a subsequent collection will therefore subtract a header from this headerless pointer and interpret the Node fields / preceding bytes as a GC header, causing invalid type ids or memory corruption. Please either allocate through a valid typed GC-object layout on the fast path or avoid using the GC nursery/Ref rooting protocol for headerless nodes.

Useful? React with 👍 / 👎.

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

ℹ️ 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 +5555 to +5557
dynasm!(self.mc ; .arch aarch64
; ldr x17, [x16] // x17 = base = *nursery_free
; add x14, x17, NURSERY_ALLOC_NODE_SIZE // x14 = newf = base + 16

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 GC headers in the AArch64 nursery fast path

On AArch64, when NurseryAllocRef takes this new fast path with the normal majit GC installed, it reserves only the 16-byte Node payload and returns x17 as the Ref; the collector's nursery API allocates GcHeader::SIZE + payload and returns the payload pointer after initializing the header. Any later GC root scan or type guard on this returned value will subtract GcHeader::SIZE and interpret the preceding bytes as a GC header, so this corrupts object metadata or crashes under collection. The same fast path was added to AArch64 here, so it needs the same typed/headered layout or must not participate in the GC Ref protocol.

Useful? React with 👍 / 👎.

@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: 49901b552a

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

static ITEMS_BLOCK_DESCR_GROUP: LazyLock<PyreObjectDescrGroup> = LazyLock::new(|| {
build_object_descr_group_with_def_path(
pyre_object::object_array::ITEMS_BLOCK_ITEMS_OFFSET,
0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use the ItemsBlock GC type id for capacity guards

When items_block_capacity_descr() now supplies a parent SizeDescr, ensure_ptr_info_arg0 can create a StructPtrInfo whose short-preamble guards use SizeDescr.type_id(). This line hardcodes that type id to 0, but object-strategy ItemsBlocks are allocated as PY_OBJECT_ARRAY_GC_TYPE_ID blocks (pyre-object/src/object_array.rs:290, constant value 9), so any optimized bridge/loop that guards a capacity read emits GUARD_GC_TYPE(..., 0) and rejects real list/tuple item blocks. Please use the actual PY_OBJECT_ARRAY_GC_TYPE_ID for this parent descriptor, or mark it non-GC-managed if this is meant to describe the std-alloc fallback.

Useful? React with 👍 / 👎.

Port llhelper/LLHelperEntry annotation+rtyping specialization and the
placeholder_sigarg/ADTInterface signature surfaces against
annlowlevel.py; add the LLHelper emulated-pbc-call key to the
bookkeeper. Surfaces still lacking a Rust carrier keep returning
structured pending errors naming the missing primitive.

cargo test -p majit-translate --lib: 2983 passed / 0 failed / 4 ignored
(+6 new tests over the 2977 baseline).

Assisted-by: Claude
try_execute_residual_call_via_executor now returns
ResidualExecOutcome::{Executed, Declined(ResidualDecline)} instead of
Option, distinguishing ValueUnavailable (an argument's concrete value
is unknown to the walker) from Symbolic (deliberate leave-symbolic
contracts). The unjournaled-effect flag splits into the two causes;
fbw_has_unjournaled_effect() keeps its OR semantics, so no consumer
behavior changes.

Count executed residuals per walk (void / may-force / plain, on both
the normal and raised arms) and, under PYRE_FBW_CENSUS, print one
walk-end disposition line per authoritative walk: end variant,
committed, bridge, decline kinds, executed-residual counts, journal
length.

No behavior change with the env var unset. check.py 162/162 on
dynasm, cranelift and wasm; e_min/m4/gcd-kept-while/dict-store repro
outputs unchanged.

Assisted-by: Claude
Behind PYRE_FBW_BRIDGE_STAMP (default off), setup_bridge_sym seeds
concrete values onto orphaned bridge-resume colors: colors whose
decoded value exists but whose OpRef sits in no semantic-mirror slot,
so the deferred overlay stage never re-stamps them and bridge-walk
residuals later decline with ValueUnavailable. NONE oprefs,
semantic-mirror members, Void and null refs are skipped; the off path
allocates nothing.

With the gate on, the nested-FOR_ITER varying-trip repro corrects to
10000 and its walk-end census shows the declining bridges committing;
the 3-nested gcd repro regresses (2661 to 1073) because concrete
residual execution exposes the FOR_ITER in-flight accounting - the
known hazard the next slice addresses. Gate off is byte-identical:
check.py 162/162 on dynasm, cranelift and wasm; all anchor repros
unchanged.

Assisted-by: Claude
make_vtable_field_descr minted the typeptr FieldDescr with
parent_descr None, so recording a getfield with it would panic the
optimizer's ensure_ptr_info_arg0 parent lookup. get_field_descr
always attaches parent_descr = get_size_descr(STRUCT, vtable), and
for rclass.OBJECT the vtable is a nullptr (descr.py:234-238), so the
parent SizeDescr reports is_object() false.

Rebuild the singleton as a cyclic group: the OBJECT-modelling
SizeDescr (word-sized, no vtable, all_fielddescrs = [typeptr]) owns
the field descr, which holds the weak parent backreference. All field
values are unchanged; consumers keep the same singleton identity.

majit-ir 295, majit-metainterp 1395 tests pass; check.py 162/162 on
dynasm, cranelift and wasm; anchor repros unchanged.

Assisted-by: Claude
items_block_capacity_descr minted a fresh parentless PyreFieldDescr on
every call, so an inlined sub-walk reading the ItemsBlock capacity
header (rlist.py:84/251 len(l.items)) aborted at the missing-parent
guard rather than record a getfield the optimizer's
ensure_ptr_info_arg0 parent lookup would reject - measured as 15
effects-executed-then-replayed walks across the list synth benches.

Add ITEMS_BLOCK_DESCR_GROUP (DictStorage-group precedent: header-sized
SizeDescr, immutable capacity field at offset 0) and return the cached
group entry, which also stabilizes the descr identity.

FieldDescrMissingParentDescr census ends drop 5 to 0 on
list_ops/list_pop_append/list_reverse; bench outputs and anchor repros
unchanged; check.py 162/162 on dynasm, cranelift and wasm.

Assisted-by: Claude
The CALL_ASSEMBLER guard-failure slow path runs the bridge hook and
then unconditionally runs the blackhole hook from guard-time values.
When the bridge walk committed its end-of-walk state and the callee
frame adopted it, that guard-state blackhole re-runs the region over
the already-advanced heap and its stale return value reaches the CA
caller, dropping the committed state.

Record the adopted callee frame in a thread-local when the CA bridge
walk commits (raise_continue_running_normally analogue,
pyjitpl.py:3048-3091 + warmspot.py:970-983); the blackhole hook
consumes it and completes the callee from the adopted state via the
portal runner instead of the guard-state resume. Uncommitted walks
fall through to the legacy path unchanged.

Assisted-by: Claude
The orphan-color concrete stamp for divergent bridge resumes was
opt-in behind PYRE_FBW_BRIDGE_STAMP while a committed CALL_ASSEMBLER
bridge walk could still be dropped by the guard-state blackhole.
With that fixed, stamp-on matches stamp-off on every anchor
(m4 N-sweep, gcd_kept_while, repro_467) and closes the e_min
iteration deficit (9997 -> 10000). Flip the gate to default-on with
a =0 opt-out, the same idiom as PYRE_FBW_BRIDGE_LOCAL_SEED.

check.py: cranelift and wasm 162/162; dynasm failures limited to the
documented nested_loop/raise_catch borderline timing flakes, which
show no on/off timing difference when measured directly.

Assisted-by: Claude
compile_loop applied the merge-point trace cut only under the
cross-loop-cut marker. Upstream compile_loop always compiles from the
merge point the first header visit registered (pyjitpl.py:3018-3030 —
start and original_boxes come from current_merge_points regardless of
where tracing began), which is a no-op for portal traces starting at
their own header.

A guard-origin bridge walk that closes at its own green key — the
target procedure is FINISH-only (a CALL_ASSEMBLER callee) so it has
no jumpable front target token and the compile_trace attach path is
skipped — registers the header mid-trace. Compiling the whole trace
paired the guard's fail-arg inputargs with the merge point's
full-shape JUMP: the unroll pass declined with InvalidLoop and the
no-unroll retry aborted on the root entry/jump arity mismatch, so the
bridge never produced compiled code. Apply the cut unconditionally
(position 0 filters itself out); the bridge region now compiles as a
root loop for the key.

Verified: default anchors unchanged (e_min 10000, m4_300 2661,
gcd_kept_while 2663, repro_467 300001; m4 N-sweep stamp-on == off),
cargo test --all dynasm green, check.py cranelift/wasm 162/162 and
dynasm 161/162 (raise_catch borderline timing flake; the cut fires in
neither flaky bench).

Assisted-by: Claude
JitInterpConfig::uses_inline_pipeline has no callers. The libc stat
type-only re-export triggers non_camel_case_types.

Assisted-by: Claude
A top-level walk that ends in an uncaught raise records
Finish(exc, exit_frame_with_exception) but stashed no finish-concrete,
so the terminate-no-replay predicate declined: the journal rolled back
and the caller re-ran the region from the guard, double-executing the
residuals the walk had already applied (the exception shape of the
value-return case). Upstream propagates the exception directly —
opimpl_raise → finishframe_exception → compile_exit_frame_with_exception
(pyjitpl.py:1688-1698 + 3238-3242) raises ExitFrameWithExceptionRef
(jitexc.py:44); nothing re-runs.

Widen the finish-concrete stash to a FinishConcrete Return/Raise
disposition (single cell keeps the reset/rooting/leak discipline). The
three top-level exit-frame-with-exception sites stash the concrete
exception when it is a non-null Ref. The bridge consumer surfaces it as
BridgeResolution::FinishedException → HandleFailOutcome::BridgeRaised,
built with the same PyError::from_exc_object as the blackhole
ExitFrameWithExceptionRef path; the two portal consumers return it as
the frame's exception result. CA callback stays disarmed.

Census: exceptions.py Terminate-bridge non-commit walks 38 → 0; bench
outputs identical to the opt-out path. Anchors unchanged (e_min 10000,
m4_300 2661, gcd_kept_while 2663, repro_467 300000). cargo test --all
dynasm green.

Assisted-by: Claude
execute_varargs (pyjitpl.py:1940-1941) opens every residual call with
metainterp.clear_exception(); the opimpl_catch_exception assert
(pyjitpl.py:504) relies on that invariant. The walker cleared
last_exc_value only on the concrete-execution success arm, so
declined/folded residual paths kept a stale caught exception and the
walk aborted at the next catch_exception/L
(CatchExceptionWithActiveException) after effects were executed.

Clear at the entry of the three residual dispatch arms instead.
Corpus census hazard walks: exception_loop_warmup 9 -> 0.

Assisted-by: Claude
The bridge Terminate no-replay shortcut was armed only for the general
guard path; the CALL_ASSEMBLER callback returns a bare bool and had no
channel for a concrete result, so a CA bridge walk that ran the callee
to its finishframe concretely was rolled back and re-run from the guard
state by the unconditional blackhole (double-applying the walked
region's residual effects).

Arm the shortcut for any single-frame resume. The CA callback leaves a
kept stash in its GC-rooted cell, records the callee frame in a
CA_WALK_FINISHED_FRAME handshake (the CA_WALK_ADOPTED_FRAME pattern),
and returns ResumeBlackhole; jit_blackhole_resume_from_guard consumes
the stash to complete the callee — Return becomes the call result,
Raise publishes the exception (publish_residual_call_exception) —
matching the DoneWithThisFrame/ExitFrameWithExceptionRef an assembler
caller catches when a retrace reaches finishframe.

Corpus census: Terminate-bridge non-commit walks 1 -> 0
(generator_tree_recursion); e_min with PYRE_FBW_BRIDGE_STAMP=0 moves
9997 -> 10000.

Assisted-by: Claude
stack_effects fell through to the conservative same-depth default for
24 instructions that really move the operand stack, so depth_at_py_pc
drifted upward past each occurrence. CALL_FUNCTION_EX (pop 4, push 1)
alone inflated the depth by +3; two of them before a hot while loop
reported depth 6 at a header whose real stack is empty, and the
walk-end flush read six phantom NULL stack slots and declined
(seqiter_pickle_parity CloseLoop non-commit walk).

Add explicit arms mirroring the interpreter implementations in
pyre-interpreter eval.rs/pyopcode.rs: CallFunctionEx -3, CallKw
-(argc+2), CallIntrinsic1 0 / CallIntrinsic2 -1, BuildSlice -1/-2,
BuildTemplate -1, BuildInterpolation -1/-2, UnpackEx -1+before+1+after,
FormatWithSpec -1, EndAsyncFor/CleanupThrow -2, WithExceptStart/
GetAnext/LoadCommonConstant/ReturnGenerator +1, and net-0 arms for
ConvertValue/FormatSimple/ToBool/LoadFromDictOrDeref/
LoadFromDictOrGlobals/MakeFunction/MakeCell/SetupAnnotations/
ExitInitCheck/Send. Send is also registered as a branch instruction in
the forward worklist and target_pc (equal depth on both paths).

The remaining default-arm variants are adaptive specializations,
Instrumented* forms, and internal dispatch controls that never appear
in canonical bytecode.

Corpus census hazard walks 4 -> 3; the seqiter_pickle_parity CloseLoop
walk now commits. Anchors unchanged.

Assisted-by: Claude
BranchGuardKeptStackUnsupported walks (user-defined-iterator FOR_ITER
loops) executed their residual calls concretely and then replayed from
loop entry on abort. The abort maps to the FOR_ITER header py-pc, but
the in-flight entry there is stale: entries are replaced on the next
successful consume and never popped at body completion, so an abort
firing during the NEXT consume attempt still holds the prior item whose
body already ran — Shape-A delivery would re-run that body (seqiter
repro returned 200004/800024).

- InflightForiter.body_completed, set by fbw_foriter_inflight_mark_attempt
  at the start of every for_iter_next residual dispatch for the same
  body_pc (pre-call, so a mid-attempt abort still records it).
- fbw_foriter_inflight_take_for_resume refuses completed entries;
  delivery stays a fresh-item continuation only.
- New no-delivery arm in the branch-flush leg, Unsupported variant only:
  a body-completed header entry with no effect since the consume adopts
  the walk end state via the same all-or-nothing flush; the interpreter
  re-attempts the consume against the advanced iterator. The Permanent
  variant's behavior is unchanged.
- The Unsupported raise site stamps abort_in_subwalk like the Permanent
  one, so a callee-coordinate abort cannot flush the outer frame.

Corpus hazard census 3 -> 1 (list_ops only). iteration_protocol and
seqiter_getitem_lazy outputs byte-identical to the interpreter; anchors
e_min 10000 / m4_300 2661 / gcd_kept_while 2663 / repro_467 300000
unchanged; cargo test green; check.py 162/162 x3 backends.

Assisted-by: Claude
PYRE_FBW_DEBUG_ABORT now prints op_pc, arg_index, helper kind and call
opcode when a residual call declines with ValueUnavailable (NO_CONCRETE
Ref or box_value None), so a census run can attribute the unj_val walks
without re-instrumenting. Debug-gated output only; no behavior change.

Assisted-by: Claude

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c3c6ef076d

ℹ️ 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 +4814 to +4815
SimpleSizeDescr::new(u32::MAX, std::mem::size_of::<usize>(), 0)
.with_all_fielddescrs(all_fielddescrs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark OBJECT typeptr parent as non-GC-managed

When a non-virtual heap object reaches a getfield_gc_* read of object.typeptr, this new parent descriptor makes ensure_ptr_info_arg0 install a StructPtrInfo; StructPtrInfo.make_guards then emits GUARD_GC_TYPE using this parent SizeDescr's type id (0). Most real PyObjects are allocated with their concrete GC header ids (e.g. ints/lists/types), not the OBJECT root id, so short-preamble or bridge guards derived from a typeptr read will reject valid objects. Please either avoid creating a GC-managed StructPtrInfo for the typeptr header field (as with the w_class special case) or mark this synthetic OBJECT parent as not GC-managed so it only proves non-nullness.

Useful? React with 👍 / 👎.

@youknowone youknowone merged commit 1d45b6d into main Jul 13, 2026
30 checks passed
@youknowone youknowone deleted the rename branch July 13, 2026 05:58
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