Skip to content

gc: drive the interpreter safepoint from the collector, and root the __main__ frame across the bootstrap - #861

Merged
youknowone merged 5 commits into
mainfrom
ec-wiring
Jul 28, 2026
Merged

gc: drive the interpreter safepoint from the collector, and root the __main__ frame across the bootstrap#861
youknowone merged 5 commits into
mainfrom
ec-wiring

Conversation

@youknowone

@youknowone youknowone commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Four commits on the interpreter GC safepoint. The first two replace the safepoint's hand-rolled heap model and its jitframe gate with the collector's own answers; in between sits the frame-lifetime bug that removing the gate exposes, and the diagnostic that found it.

gc: ask the collector when to run the interpreter safepoint's major

The safepoint kept its own model of heap growth: bytes charged by note_alloc at eight allocation sites, compared against a threshold re-derived after each collection from get_total_memory_used() times a growth delta. Both halves described a different heap from the one being collected — the tally saw only the sites that remembered to report (dicts, lists, tuples, instances and every JIT promotion grow the old generation without charging it), the charges were payload-only while the collector's total counts each GcHeader, and the re-derived threshold dropped the growth_rate_max / max_delta caps and the max_heap_size bound that set_major_threshold_from (incminimark.py:575-594) applies.

All of that is already ported in majit-gc. threshold_reached (incminimark.py:1288-1290) weighs get_total_memory_used() against the threshold set from the last major's survivors, and do_collect_oldgen_nonmoving already runs major_collection_step through finish_incremental_cycle, which sets the next one. Exposed as GcAllocator::major_threshold_reached. What remains on the pyre side is a poll interval carrying no heap semantics.

Interleaved min-of-7 against the previous policy: every workload within 1% with PYRE_GC_INTERP off and on, completed majors per run unchanged (14/13, 34/33, 12/12), RSS over a s.lower() loop flat at 154 MB either way.

pyframe: name the code object and pc when the value stack underflows

pop's bare depth assertion said an opcode popped from an empty stack, but not which opcode of which code object — the whole question when the underflow means the frame is no longer the one the loop started with. Replaced with a #[cold] reporter carrying depth/base/nlocals/ncells, pc, last_instr, the code object's name and file, the frame/pycode/f_backref pointers, and an opcode window.

interp: root the __main__ frame across the bootstrap imports

run_source built the __main__ frame and then ran the sys importhook, the importlib bootstrap, seed_main_loader and import_site before handing it to eval_with_jit. The frame is a GC-managed FrameBox, and the only root that reaches one is the CURRENT_FRAME / f_backref chain walk_pyframe_roots follows — which it joins when eval_with_jit installs it. A collection driven by any bootstrap step therefore reclaimed a live frame and handed its block to the next stable frame allocation.

With min_heap_size near 1 MB the first major lands inside import_site and the block goes to importlib's _get_module_lock weakref callback, which leaves last_instr at its ReturnValue and a value stack at base; eval_with_jit then enters that block and pop underflows. Confirmed under lldb: the FrameBox pointer returned by new_with_context is the frame the panic names.

Pin the frame for the span, as FrameBox::new, call_function_impl_result and finalize_weakrefs already do for the same reason — an RPython local holding a GC object is a shadow-stack root, a Rust local is not. pyre-wasm's run_python_impl has the same shape over a shorter window and gets the same pin.

gc: drop the jitframe-empty gate from the interpreter safepoint

The safepoint only ran a major when no compiled trace was suspended on this thread. A trace suspends by making a residual call into the interpreter, and a call site is where the backend emits a gcmap, so enumerate_root_walker_values already reaches a suspended frame's live refs by expanding each jitframe on the JF shadow stack through trace_libc_jitframe. The gate has no upstream counterpart and suppressed most of the safepoint's collections.

Removed root and branch, along with its hook layer, trampoline and fn-addr aliases. On p_calleeloop.py this takes the safepoint from 12 majors to 23, against a JIT-off baseline of 26.


Verification is running locally (repro matrix, check.py on both backends × default and gated, cargo test, perf A/B); this PR will be updated if anything comes back red.

opened by Claude

Summary by CodeRabbit

  • Bug Fixes
    • Improved garbage-collection scheduling by using the collector’s major-threshold status directly.
    • Reduced unnecessary allocation bookkeeping during object creation.
    • Improved frame lifetime handling during Python execution to prevent premature collection.
    • Added detailed diagnostics for interpreter stack underflow errors.
    • Improved compatibility across native, WebAssembly, and JIT execution backends.
  • Refactor
    • Simplified garbage-collection safepoint and polling behavior for more consistent collection decisions.

The safepoint kept its own model of heap growth: bytes charged by `note_alloc`
at eight allocation sites, compared against a threshold re-derived after each
collection from `get_total_memory_used()` times a growth delta. Both halves
described a different heap from the one being collected.

The tally saw only the sites that remembered to report. Dicts, lists, tuples,
instances and every JIT promotion grow the old generation without charging it,
so the comparison was not the rearrangement of `total >= live * threshold` its
comment claimed, and the heap could pass the threshold with the safepoint still
counting. The charges were payload-only while the collector's total counts each
`GcHeader`, and the string charge added `byte_len` for a WTF-8 buffer that lives
on the Rust heap, not in the collector's. The re-derived threshold applied
`major_collection_threshold` alone, dropping the `growth_rate_max` and
`max_delta` caps and the `max_heap_size` bound that `set_major_threshold_from`
(incminimark.py:575-594) applies.

All of that is already ported in `majit-gc`. `threshold_reached`
(incminimark.py:1288-1290) weighs `get_total_memory_used()` -- every byte the
collector is responsible for, whatever allocated it -- against the threshold set
from the last major's survivors, and `do_collect_oldgen_nonmoving` already runs
`major_collection_step` through `finish_incremental_cycle`, which sets the next
one. Exposed as a `GcAllocator::major_threshold_reached` query shaped like
`heap_byte_stats` and routed to pyre through the same hook layer.

Deleted: `note_alloc` and its eight call sites, `ALLOC_BYTES_SINCE_GC`,
`NEXT_MAJOR_BYTES`, the growth-delta and min-heap constants, and the heap-stats
hook added for the re-derivation. What remains on the pyre side is a poll
interval. It carries no heap semantics and cannot cause a collection the
collector did not ask for; it only bounds how often the query is made, because
reaching the collector costs a thread-local borrow.

Interleaved min-of-7 against the previous policy built from the same base: every
workload within 1% with `PYRE_GC_INTERP` off and on, and the completed majors
per run unchanged (14/13, 34/33, 12/12), so the collector's own threshold
reproduces the cadence the hand-rolled one approximated. RSS over a
`s.lower()` loop stays flat at 154 MB either way.

Assisted-by: Claude
`pop` asserted `valuestackdepth > stack_base()`. Replace the bare
assertion with a `#[cold]` reporter that panics with the depth, the
base and its nlocals/ncells split, the pc, `last_instr`, the
instruction count, the code object's name and file, the frame,
pycode and f_backref pointers, and an eight-unit opcode window
around the pc.

Assisted-by: Claude
`run_source` built the `__main__` frame and then ran the `sys`
importhook, the importlib bootstrap, `seed_main_loader` and
`import_site` before handing it to `eval_with_jit`. The frame is a
GC-managed `FrameBox`, and the only root that reaches one is the
`CURRENT_FRAME` / `f_backref` chain `walk_pyframe_roots` follows —
which it joins when `eval_with_jit` installs it. A collection driven
by any of those bootstrap steps therefore reclaimed a live frame and
handed its block to the next stable frame allocation; with
min_heap_size near 1 MB the first major lands inside `import_site`
and the block goes to importlib's `_get_module_lock` weakref
callback, which leaves `last_instr` at its `ReturnValue` and a
value stack at base. `eval_with_jit` then entered that block and
`pop` underflowed.

Pin the frame for the span, as `FrameBox::new`, `call_function_impl_result`
and `finalize_weakrefs` already do for the same reason: an RPython
local holding a GC object is a shadow-stack root, a Rust local is not.

`pyre-wasm`'s `run_python_impl` has the same shape over a shorter
window — the `__main__` module registration allocates — and gets the
same pin. `pyre-wasm-test` enters the frame immediately and is left
alone.

Assisted-by: Claude
The safepoint only ran a major when no compiled trace was suspended
on this thread. A trace suspends by making a residual call into the
interpreter, and a call site is where the backend emits a gcmap, so
`enumerate_root_walker_values` already reaches a suspended frame's
live refs by expanding each jitframe on the JF shadow stack through
`trace_libc_jitframe`; the gate has no upstream counterpart and
suppressed most of the safepoint's collections.

Remove it root and branch: `majit_gc::jitframe_shadow_stack_empty`,
`pyre-object`'s `GcJitframeEmptyHookFn` / `GC_JITFRAME_EMPTY_HOOK` /
`register_gc_jitframe_empty_hook` / `clear_gc_jitframe_empty_hook` /
`try_gc_jitframe_empty`, the `pyre-jit` trampoline and its
registration, and the two `jit_trace_fnaddrs` aliases with their
test assertions.

On p_calleeloop.py this takes the safepoint from 12 majors to 23
against a JIT-off baseline of 26.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds collector-provided major-threshold queries across GC backends, replaces interpreter allocation accounting with periodic safepoint polling, updates JIT hook wiring, removes allocation notifications, improves stack-underflow diagnostics, and pins execution frames as GC roots.

Changes

Collector-driven major GC thresholds

Layer / File(s) Summary
Major-threshold GC contract
majit/majit-gc/*
GcAllocator, GcHandle, and process-global GC hooks expose major-threshold status.
Active backend threshold hooks
majit/majit-backend-*/...
Cranelift, Dynasm, and wasm register threshold callbacks against their active collectors.
Collector-driven interpreter safepoint
pyre/pyre-object/src/gc_interp.rs, pyre/pyre-object/src/gc_hook.rs
Periodic polling replaces allocation-byte scheduling, and safepoints query the collector before old-generation collection.
JIT safepoint wiring
pyre/pyre-jit/src/eval.rs, pyre/pyre-interpreter/src/jit_fnaddr.rs
JIT registration uses the major-threshold hook and removes heap-statistics and jitframe-empty hooks.
Allocation and frame lifetime updates
pyre/pyre-object/*, pyre/pyre-interpreter/*, pyre/pyre-wasm/src/lib.rs, pyrex/src/lib.rs
Object allocation notifications are removed, stack-underflow reporting is expanded, and newly created execution frames are pinned as GC roots.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: lifthrasiir

Poem

A rabbit checks the GC gate,
No byte-count crumbs to calculate.
Thresholds hop from heap to hook,
Safepoints pause for one quick look.
Frames wear roots, safe and bright—
Then old-gen hops into the night.

🚥 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 matches the main changes: collector-driven safepoints and rooting the main frame during bootstrap.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 ec-wiring

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.45.0)
majit/majit-backend-cranelift/src/compiler.rs

ast-grep timed out on this file


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/134fee11694038253d3ceb00246fa7a9d8fae710/pyre-object/src/gc_interp.rs#L137
P1 Badge Drive threshold polling from global allocation progress

When PYRE_GC_INTERP is enabled, an opcode can allocate far more than the assumed few dozen bytes—for example, s.lower() can allocate an entire large managed string—yet the collector threshold is not queried until 1,024 eligible dispatches have elapsed. Such a loop can therefore add hundreds or thousands of large old-gen objects after the threshold is exceeded and exhaust memory before collection; moreover, the TLS counter resets independently per thread even though the standalone collector's heap threshold is shared, so workloads spread across short-lived threads may never reach 1,024 dispatches anywhere. Pace this from global allocation progress or arrange for crossing the collector threshold to force a query at the next safepoint.

AGENTS.md reference: AGENTS.md:L157-L162

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

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 32ef2ee).
Updated: 2026-07-28T19:03:05.939Z

Files in the reviewed diff
majit/majit-backend-cranelift/src/compiler.rs
majit/majit-backend-dynasm/src/runner.rs
majit/majit-backend-wasm/src/lib.rs
majit/majit-gc/src/collector.rs
majit/majit-gc/src/lib.rs
pyre/pyre-interpreter/src/call.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-interpreter/src/pyframe.rs
pyre/pyre-interpreter/src/pytraceback.rs
pyre/pyre-jit/src/eval.rs
pyre/pyre-object/src/floatobject.rs
pyre/pyre-object/src/gc_hook.rs
pyre/pyre-object/src/gc_interp.rs
pyre/pyre-object/src/generator.rs
pyre/pyre-object/src/interp_exceptions.rs
pyre/pyre-object/src/intobject.rs
pyre/pyre-object/src/longobject.rs
pyre/pyre-object/src/unicodeobject.rs
pyre/pyre-wasm/src/lib.rs
pyre/pyrex/src/lib.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-object/src/gc_interp.rs:127-166 ↔ rpython/memory/gc/incminimark.py:824-847: Pyre adds a thread-local POLL_TICK and queries the collector only every 1,024 eligible bytecode dispatches; MiniMark evaluates threshold_reached() on each relevant allocation/minor-collection path. This can delay automatic collection and therefore observable weakref/finalizer timing. It is not required by Rust’s implementation model and should not be TLS state controlling collection cadence.

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

  • pyre/pyre-object/src/gc_interp.rs:239-244 ↔ rpython/memory/gc/incminimark.py:824-833: the automatic interpreter safepoint invokes try_gc_collect_oldgen() without consulting the collector’s enabled flag. Upstream returns after the minor collection when gc.disable() has disabled automatic major progress. This omission already existed in upstream/main; the patch changes the trigger predicate but retains the ungated collection call.

4. Structural adaptations

  • majit/majit-gc/src/collector.rs:4018-4019 ↔ rpython/memory/gc/incminimark.py:1288-1290: major_threshold_reached() is a faithful Rust callback wrapper around MiniMark’s strict threshold_reached(0) predicate. The divergence is the backend hook/fn-pointer routing used to cross crate and backend boundaries.

  • pyre/pyre-jit/src/eval.rs:135-142 ↔ rpython/memory/gc/incminimark.py:824-850: Pyre completes a non-moving old-generation collection, while upstream makes incremental major progress coupled to moving minor collections. This is required because Rust-stack PyObjectRefs are not universally transformed into shadow-stack roots; a moving minor at this safepoint could invalidate them.

  • majit/majit-gc/src/lib.rs:303-312 ↔ rpython/memory/gc/incminimark.py:987-995: stable old-generation allocation deliberately cannot perform upstream’s pre-allocation collection check. RPython’s GC transform roots live locals across that call; Pyre’s raw Rust-stack pointer is intentionally unrooted until stored, so collecting there is unsafe.

  • pyre/pyrex/src/lib.rs:1127-1136 ↔ rpython/memory/gctransform/framework.py:853-856: explicit push_roots/pin_root preserves the startup frame across allocation before it joins the active-frame chain. This is the Rust equivalent of the translator-inserted root bracket around GC-capable calls.

  • pyre/pyre-wasm/src/lib.rs:436-443 ↔ rpython/memory/gctransform/framework.py:853-856: the wasm runner uses the same explicit root bracket for its startup frame; this is likewise required because Rust does not receive RPython’s automatic local-root transformation.

…shold

`external_malloc` (incminimark.py:987-994) tests
`threshold_reached(raw_malloc_usage(totalsize))` before allocating and
drives `minor_collection_with_major_progress` when it holds.
`alloc_oldgen_typed` is the structural counterpart and has no such
check, because the callers it exists for hold the returned raw pointer
on the Rust stack, where it is not a root — the reason upstream can
collect mid-allocation and this entry cannot. Note the divergence and
where old-gen growth is answered instead.

Assisted-by: Claude
@youknowone

Copy link
Copy Markdown
Owner Author

Local verification

Built on the rebased base (b8a00beb94), both backends, release.

Defect-3 repro matrix — the eight configurations that previously died with a value-stack underflow in importlib's cb:

binary result
pyre-g9-dyn (this branch) 8/8 pass
pyre-g9-cl (this branch) 8/8 pass
pre-fix control (gate removed, frame not rooted) 6 fail / 2 pass — the 2 passes are the two controls that never armed the safepoint

Every control failure is the same frame: code="cb" pc=77 last_instr=76 depth=2 base=2 back=0x0, window 75:ReturnValue 76:PushExcInfo. Under the previously-breaking config (PYPY_GC_NURSERY=262144 PYPY_GC_MIN=1048576) majors now proceed to 7 on both backends instead of dying on the first.

check.py — serially, both backends × default and PYRE_GC_INTERP=1: 331 passed, 1 failed in all four runs. The failure is synth/ast_compile_roundtrip, reported as cpython/pypy output mismatch; it is an oracle version skew in my local environment, not a pyre defect:

CI's ubuntu run does not hit it (331 passed there too).

cargo test (-p pyre-object -p pyre-interpreter -p pyre-jit -p majit-gc --features dynasm): 1231 passed, 0 failed, 21 ignored.

Perf A/BPYRE_GC_INTERP=1 vs =0 on the same binary, interleaved arms, 9 rounds, p25 (min in parentheses):

bench ratio p25 ratio min
fib_loop 0.970 0.997
int_loop 0.963 0.859
nested_loop 1.011 1.016
raise_catch_loop 0.943 0.857
inline_helper 1.034 0.968
float_loop 0.983 1.224

No systematic cost from removing the gate. float_loop's min outlier is noise — its p25 ratio is 0.983.

On the CI pyre/check.py (ubuntu-24.04) failure

FAIL cranelift int_loop exec 0.63s > pypy 0.31s ratio 2.0x > gate 2x, 331 passed.

PR #858 — which is this branch's base commit b8a00beb94 — failed the identical gate at ratio 2.1x and was merged. The bench sits exactly on the 2x boundary on that runner; this run is marginally under the base's ratio, so it is not a regression from these commits.

Follow-up that was investigated and dropped

The planned next step was an EB_GC eval-breaker bit armed by the collector, so hot compiled loops would deopt at their back edge into the dispatch-loop safepoint. The premise does not reproduce. Measured with MAJIT_LOG=1 on three probe designs — s.lower() (N=400k), {"a": i, "b": i+1} (N=1.2M), and d = {} (N=2M, the stable-allocation path with no nursery storage) — old-gen is flat in every configuration: 341,536 B with PYRE_GC_INTERP=1, 293,608 B with it off, unchanged across a 4× longer run. PYRE_GC_INTERP=1 also adds majors (20 vs 15), so compiled loops demonstrably do return to the dispatch loop.

The reason is that any real Python loop allocates something in the nursery, nursery fill drives a minor, and run_major_progress_after_minor (incminimark.py:832, 849-860) already runs major_collection_step after every minor. Adding EB_GC would introduce a pyre-only mechanism plus a deopt-storm risk (an armed bit the safepoint declines to service re-fails every back edge) for a problem with no measurement behind it. Not implemented.

What is real is the structural gap the parity review flagged independently: alloc_oldgen_typed has no counterpart to external_malloc's threshold_reached check (incminimark.py:987-994). Porting it needs the callers to be root-safe, which is exactly what the __main__ frame bug in this PR shows pyre is not yet. Documented at the allocator in 32ef2eeb9c rather than worked around.

commented 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: 32ef2eeb9c

ℹ️ 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 +143 to +146
/// Thread-local rather than atomic because the query it paces is itself
/// per-thread, and a `fetch_add` on every dispatch would cost about what it
/// is here to avoid.
static POLL_TICK: Cell<u32> = const { Cell::new(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.

P2 Badge Keep the collector poll cadence process-wide

With PYRE_GC_INTERP=1, an application that repeatedly creates Python threads which each execute fewer than 1,024 eligible dispatches can grow the process-global old generation past its threshold without ever querying it: each thread's POLL_TICK is discarded before reaching the interval, while its stable int/float/frame allocations remain charged to the shared collector. This can leave interpreter-routed garbage uncollected indefinitely, so pace the shared collector with process/interpreter-owned state or force a threshold query when a mutator thread exits.

AGENTS.md reference: AGENTS.md:L148-L162

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pyre/pyre-interpreter/src/jit_fnaddr.rs (1)

3068-3117: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Test doesn't cover the two new safepoint residual bindings it should pin.

The updated comment at lines 1014-1020 calls out "five toucher residuals," but jit_trace_fnaddrs_covers_interp_gc_safepoint_readers only asserts on three of them (collect_enabled, at_outermost_activation, try_gc_collect_oldgen) plus the odometer/strategy-gate entries — it's missing assertions for the newly-registered poll_due and try_gc_major_threshold_reached bindings. This test exists specifically to catch a typo'd alias silently degrading to a symbolic_fnaddr_for_path hash (a SEGV-at-trace-time risk per this file's own rationale), so the two new residuals should be pinned the same way the others are.

✅ Suggested additions to the test
         let collect_oldgen =
             pyre_object::gc_hook::try_gc_collect_oldgen as *const () as usize as i64;
         assert_eq!(
             bindings["pyre_object::gc_hook::try_gc_collect_oldgen"],
             collect_oldgen
         );
         assert_eq!(
             bindings["pyre_object::try_gc_collect_oldgen"],
             collect_oldgen
         );

+        let poll_due = pyre_object::gc_interp::poll_due as *const () as usize as i64;
+        assert_eq!(bindings["pyre_object::gc_interp::poll_due"], poll_due);
+        assert_eq!(bindings["pyre_object::poll_due"], poll_due);
+
+        let threshold_reached =
+            pyre_object::gc_hook::try_gc_major_threshold_reached as *const () as usize as i64;
+        assert_eq!(
+            bindings["pyre_object::gc_hook::try_gc_major_threshold_reached"],
+            threshold_reached
+        );
+        assert_eq!(
+            bindings["pyre_object::try_gc_major_threshold_reached"],
+            threshold_reached
+        );
+
         let itemsblock =
             pyre_object::object_array::itemsblock_gc_enabled as *const () as usize as i64;
🤖 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-interpreter/src/jit_fnaddr.rs` around lines 3068 - 3117, Extend
jit_trace_fnaddrs_covers_interp_gc_safepoint_readers to validate the newly
registered poll_due and try_gc_major_threshold_reached bindings. Resolve each
function pointer to its address and assert both the module-qualified and public
alias entries match, following the existing collect_enabled,
at_outermost_activation, and try_gc_collect_oldgen assertions.
🤖 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.

Outside diff comments:
In `@pyre/pyre-interpreter/src/jit_fnaddr.rs`:
- Around line 3068-3117: Extend
jit_trace_fnaddrs_covers_interp_gc_safepoint_readers to validate the newly
registered poll_due and try_gc_major_threshold_reached bindings. Resolve each
function pointer to its address and assert both the module-qualified and public
alias entries match, following the existing collect_enabled,
at_outermost_activation, and try_gc_collect_oldgen assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f58bd12c-70f6-4634-ae43-ed2bcf6d82f1

📥 Commits

Reviewing files that changed from the base of the PR and between b8a00be and 32ef2ee.

📒 Files selected for processing (20)
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-backend-dynasm/src/runner.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-gc/src/collector.rs
  • majit/majit-gc/src/lib.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/jit_fnaddr.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/pytraceback.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-object/src/floatobject.rs
  • pyre/pyre-object/src/gc_hook.rs
  • pyre/pyre-object/src/gc_interp.rs
  • pyre/pyre-object/src/generator.rs
  • pyre/pyre-object/src/interp_exceptions.rs
  • pyre/pyre-object/src/intobject.rs
  • pyre/pyre-object/src/longobject.rs
  • pyre/pyre-object/src/unicodeobject.rs
  • pyre/pyre-wasm/src/lib.rs
  • pyre/pyrex/src/lib.rs
💤 Files with no reviewable changes (7)
  • pyre/pyre-object/src/generator.rs
  • pyre/pyre-object/src/floatobject.rs
  • pyre/pyre-object/src/unicodeobject.rs
  • pyre/pyre-object/src/intobject.rs
  • pyre/pyre-object/src/interp_exceptions.rs
  • pyre/pyre-interpreter/src/pytraceback.rs
  • pyre/pyre-object/src/longobject.rs

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