Skip to content

Trace w_int_new's allocation again, and cover fuse_boxing_alloc's cross-block walk - #1159

Merged
youknowone merged 3 commits into
mainfrom
fib_recursive
Aug 11, 2026
Merged

Trace w_int_new's allocation again, and cover fuse_boxing_alloc's cross-block walk#1159
youknowone merged 3 commits into
mainfrom
fib_recursive

Conversation

@youknowone

@youknowone youknowone commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Three commits on top of origin/main.

jit: print the orthodox list sub-walk decline pc under PYRE_FBW_DEBUG_ABORT

OrthodoxSubWalkTraceUnsupported carried a pc that never reached a log line, so
the two list sub-walk declines were indistinguishable from every other abort.
Two [decline-why] prints behind the existing PYRE_FBW_DEBUG_ABORT gate.

intobject: trace w_int_new's allocation again, as wrapint does

w_int_new routed both its boxing arms through a #[dont_look_inside]
w_int_box_slow, which residualises the call and so makes the box
unvirtualizable by construction. The oracle, intobject.py:903-921 wrapint,
carries no @dont_look_inside — its comment at :908-910 says the function
"is getting inlined into every caller" — and allocates then initialises
(:913-920), which is new_with_vtable + setfield_gc.

The boundary's own stated blocker was that the fusion could not resolve the
header pointers across the block boundary each call ends. #1141 fixed exactly
that, so the arm is traced again. The collector arm keeps its boundary as
w_int_gc_alloc, scoped to the one deviation that is still real (the wasm
backend's offset-0 field store).

Measured on this base: the compiled lst.append(i); lst.pop() loop emits
NewWithVtable (size 24, type_id 1) with SetfieldGc … descr=W_IntObject.intval
and no residual w_int_* boxing call. synth/list_pop_append 2.6x / 3.3x /
2.4x — the bench does not discriminate this mechanism in either direction, and
the commit message says so; the decision rests on the shape.

majit: test fuse_boxing_alloc across the links a split cluster crosses

resolve_addr (added by #1141) steps through Block.inputargs and takes an
answer only when every predecessor agrees. Neither behaviour was reached by a
test: the case added with it, and all six that predate it, build their cluster
in a single block, so they resolve without entering the phi arm.

Four rows over one W_FloatObject cluster, differing only in where the header
values come from — one relay block, two relay blocks, two predecessors of a
merge naming one type, two naming different types. Each asserts the fused count,
the address stamped on the NewWithVtable, and whether the malloc_typed
survives as a residual. Asserting the address is what separates "walked to the
predecessor" from "read some other constant in the graph".

Non-vacuity measured by ablation on this tree:

ablation result
resolve_addr returns None for a phi "one link crossing" fails, 6/6 pre-existing pass
the disagreement arm is dropped "predecessors naming two types" fails, 6/6 pre-existing pass

The diff is a 196-line addition inside mod tests with no deletions;
resolve_addr is unchanged.

Verification

  • check.py: dynasm 417/417, cranelift 416/416, wasm 412/412 (HEAD pinned
    identical before and after the run).
  • cargo test --workspace: 106 suites, 7623 passed, 0 failed.

An earlier commit on this branch ported locale-aware 'n' formatting; it was
dropped on rebase because #1147 landed a superset of it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved integer object allocation for collector-enabled execution, with a fallback path when allocation is unavailable.
    • Fixed boxing fusion across block boundaries, including cases with relayed and multiple matching predecessors.
    • Prevented fusion when predecessor type information conflicts.
  • Diagnostics

    • Added debug logging for unsupported list operations during trace specialization, including the relevant program counter.

…_ABORT

`OrthodoxSubWalkTraceUnsupported` carries the pc but the three decline
arms dropped it. The identifier the decline names elsewhere is a symbolic
fnaddr minted in `pyre-jit-trace/build.rs`, so a runtime reverse-name
registry has nothing to read; the pc is what pairs the decline with a
bytecode offset.

Assisted-by: Claude
#1131 put a `dont_look_inside` boundary around the int box's allocating tail
(`w_int_box_slow`) because the sub-jitcode walk declined on the
`SyntheticTransparentCtor` the `malloc_typed` arm lowered to, and named its
own exit condition: "Drop the boundary once the fusion resolves a vtable
there." #1141 landed that resolution -- `fuse_boxing_alloc` now follows the
header pointers across the block boundary each call ends -- and its comment
records the condition met, "it does now fire here", while keeping the
boundary.

`wrapint` (`objspace/std/intobject.py:903-921`) carries no
`@dont_look_inside`; its comment reads "this whole function is getting
inlined into every caller", and it allocates with `instantiate(W_IntObject)`
then `w_res.intval = x`, the alloc-then-init pair the rtyper lowers to
`new_with_vtable` + `setfield_gc`. Put the `malloc_typed` arm back in
`w_int_new` where the fusion rewrites it into that pair, and keep the
collector-heap arm as `w_int_gc_alloc` behind its own boundary: that arm
carries a blocker that is still real, the wasm backend not lowering the
offset-0 `ob_type` store faithfully.

`bench/synth/list_pop_append` does not decide this. It reads the same either
way (2.6/2.3/2.6 against 2.5/2.5/2.3, three runs each), and #1141 records a
negative control -- boundary removed with the fusion reverted -- that failed
to reproduce the regression the boundary was added for, so the bench is
uninformative in both directions. The trace shape is what differs and it is
observable: with this change the compiled loop carries a `NewWithVtable`
whose descr is `W_IntObject.intval` and no residual call to any `w_int_*`
boxing symbol. A residual call can never be virtualized.

check.py ALL PASSED on all three backends -- dynasm 417/417, cranelift
416/416, wasm 412/412 -- and cargo test --workspace green.

The box is still not virtualized away, for a reason outside this change:
`orthodox_list_append_commit` deliberately forces the value with a
ptr->int->ptr identity pair so the descended sub-walk reads the current
iteration's payload, and that force lands before the class guard that would
otherwise fold.

Assisted-by: Claude
`resolve_addr` steps through `Block.inputargs` and requires every
predecessor to agree, but every `fuse_boxing_alloc` case built its cluster
in a single block, so neither behaviour was reached by a test: the only
case added with the walk is a decline that resolves inside one block.

Four rows over the same one-payload `W_FloatObject` cluster, differing only
in where the header values come from: one relay block between the producer
and the ctor, two relay blocks, two predecessors of a merge block naming
one type, and two naming different types. Each asserts the fused count, the
address stamped on the `NewWithVtable`, and whether the `malloc_typed`
survives as a residual.

Measured by ablation on this tree: returning `None` for a phi fails the
"one link crossing" row, and dropping the disagreement arm fails the
"predecessors naming two types" row. The six pre-existing
`fuse_boxing_alloc` tests pass under both ablations.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 11, 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: 9d798cb1-c5e7-449a-b821-f9198a96936a

📥 Commits

Reviewing files that changed from the base of the PR and between d6b6547 and 6d24dfe.

📒 Files selected for processing (4)
  • majit/majit-translate/src/model.rs
  • pyre/pyre-interpreter/src/jit_fnaddr.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
  • pyre/pyre-object/src/intobject.rs

Walkthrough

The change separates GC integer allocation from fallback allocation, updates the JIT function-address binding, adds boxing-fusion regression coverage for split clusters, and logs unsupported list sub-walk locations during specialization fallback.

Changes

Integer allocation and JIT behavior

Layer / File(s) Summary
GC integer allocation and trampoline binding
pyre/pyre-object/src/intobject.rs, pyre/pyre-interpreter/src/jit_fnaddr.rs
w_int_new now uses w_int_gc_alloc for collector-enabled allocation and retains malloc_typed fallback behavior. The fnaddr registry binds w_int_gc_alloc.
Split-cluster boxing fusion regression coverage
majit/majit-translate/src/model.rs
The test covers relay links, matching and conflicting predecessor type pointers, fused NewWithVtable results, and residual malloc_typed calls.
Unsupported list sub-walk diagnostics
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
LIST_APPEND and LIST_POP fallback paths optionally log the program counter before rollback and fallback.

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

Possibly related PRs

Poem

I’m a small rabbit beside the JIT,
Watching GC allocation fit.
Relay links agree, conflicts depart,
Fallback logs now mark the start.
New trampolines hop in time—
Boxed integers compile 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 identifies the two main changes: tracing w_int_new allocation and adding fuse_boxing_alloc cross-block coverage.
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 fib_recursive

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

https://github.com/youknowone/pyre/blob/6d24dfebe251a293d4a28b4e03f56627b46be91b/pyre-object/src/intobject.rs#L157-L158
P1 Badge Trace the allocation on the default GC-enabled path

With the normal configuration (PYRE_GC_INTERP unset), gc_interp::enabled() returns true by default (gc_interp.rs:188-219), and the JIT installs the stable-allocation hook (pyre-jit/src/eval.rs:4114-4120), so this branch calls the #[dont_look_inside] w_int_gc_alloc and ordinarily returns its non-null result. Consequently, the newly inline malloc_typed block is reached only with the rollback flag or allocation failure and default JIT integer boxing remains unvirtualizable; compared with the previous single residual w_int_box_slow, it now also residualizes the enabled() check. The default collector path itself must lower to the traceable allocation shape for this parity change to take effect.

AGENTS.md reference: AGENTS.md:L231-L233

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

@github-actions

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 6d24dfe).
Updated: 2026-08-11T15:09:16.723Z

Files in the reviewed diff
majit/majit-translate/src/model.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
pyre/pyre-object/src/intobject.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

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

  • pyre/pyre-object/src/intobject.rs:153 ↔ pypy/objspace/std/intobject.py:911: with prebuilt ints enabled, Pyre immediately returns the cached object; PyPy performs the additional w_res.intval = x write at intobject.py:920. This was already present in upstream/main; it is behaviorally inert for this exact indexed cache but is not line-for-line parity.

4. Structural adaptations

  • pyre/pyre-object/src/intobject.rs:157 ↔ rpython/memory/gctransform/framework.py:803: Pyre’s runtime-selected collector arm (gc_interp::enabled() → residualized w_int_gc_alloc) has no direct source-level PyPy helper. RPython inserts GC allocation during lowering; this split is a Rust/GC-runtime adaptation, while the ordinary malloc_typed path now remains traceable like wrapint.
  • pyre/pyre-object/src/intobject.rs:197 ↔ rpython/rlib/jit.py:133: #[dont_look_inside] on only the collector helper is a Rust JIT boundary for the hook/wasm field-store limitation; PyPy’s wrapint itself has no dont_look_inside annotation.
  • pyre/pyre-interpreter/src/jit_fnaddr.rs:964 ↔ rpython/rlib/jit.py:133: registering the macro-generated extern "C" trampoline is Rust/wasm ABI plumbing; PyPy’s decorator only sets _jit_look_inside_ and has no function-address registry.
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs:9702 ↔ pypy/objspace/std/listobject.py:1331: the gated "[decline-why]" stderr diagnostic has no PyPy list-strategy counterpart. It does not alter the rollback/decline path.
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs:10242 ↔ pypy/objspace/std/listobject.py:1435: same debug-only diagnostic adaptation for the list.pop() descended-walk decline.
  • majit/majit-translate/src/model.rs:7287 ↔ rpython/rtyper/rbuiltin.py:349: the added split-CFG fixture is Rust translator test scaffolding for allocation lowering, not a PyPy runtime semantic change.

@youknowone
youknowone merged commit ca0c4cb into main Aug 11, 2026
17 checks passed
@youknowone
youknowone deleted the fib_recursive branch August 11, 2026 17:18
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