Skip to content

The GC pins the wasm guest never received, and four parity/test fixes - #1073

Merged
youknowone merged 5 commits into
mainfrom
fib_recursive
Aug 6, 2026
Merged

The GC pins the wasm guest never received, and four parity/test fixes#1073
youknowone merged 5 commits into
mainfrom
fib_recursive

Conversation

@youknowone

@youknowone youknowone commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Six commits. One measurement fix plus four small parity/test fixes.

Local, on this base (fd709f26ae1, #1079): dynasm 388/388, cranelift 388/388,
wasm 384/384, and pyre/extra_tests/parity_tests all green.

Rebase note. This PR opened with an _io.BytesIO interp-level port as its
first half. #1079 has since landed that work as a superset — BytesIO and
StringIO, GC-move-safe receiver re-derivation, test_memoryio at 183 tests —
including the same pickle_ctor_args / pickle_terminal_raise_resume
re-record with the same numbers. Those three commits are dropped; what one of
them found and #1079 does not fix is section 3 below.


1. The wasm guest never saw check.py's GC pins

check.py pins PYPY_GC_NURSERY / PYPY_GC_MIN so the major-collection
threshold is a property of the tree rather than of the machine (#1059). Both
pins reached the two native backends and neither reached the wasm one: the
guest is built for wasm32-unknown-unknown, whose std::env is permanently
empty, and majit-gc resolves those names through std::env::var. The guest
kept min_heap_size = nursery * 8 = 32MB, crossed it mid-run, and counted the
back-edge eval-breaker poll's bailouts the natives no longer see.

Measured — recursive_call_frame_relocation's guest-side guard_failures
against PYPY_GC_MIN, with the native backend as the control:

unset 8MB 256MB (the pin)
wasm, before 648 648 648
wasm, after 648 695 638
dynasm (control) 639 (+fib_loop 189→193) 636

One side responding and the other flat is what identified it. Forcing a wrong
value rather than the right one is what makes the row informative — with
env.setdefault semantics, pinning the correct value teaches nothing.

majit-gc takes an embedder-supplied environment, read only where std::env
misses, and publishes the names it resolves (GC_ENV_NAMES); pyre-wasm
exports pyre_set_gc_env / pyre_gc_env_names over it, and the runner forwards
whatever its own environment carries. The pair pyre_set_launch_env /
pyre_launch_env_names already exist for the launcher's variables, for the same
reason and in the same shape.

Re-record: 48 rows across 46 wasm fixtures, and every one of them falls
guard_failures by 1 to 202 (exception_escape_hot_callee_tb_node_once
1016→814, closure_per_call 470→420), plus bridges_compiled 5→4 on two, the
bridge those loops had spent on the eval-breaker poll. Nothing rises anywhere,
and no other backend is touched because the pins already reached those.

The pin moves the guest toward the natives without closing the gap entirely —
exception_escape_hot_callee_tb_node_once lands exactly on their 814, while
recursive_call_frame_relocation reads 638 against 636 and
exception_inline_callee_tb_frames 807 against 606. That residual is
pre-existing and separate: on wasm gc_interp::enabled() defaults to
cfg!(target_arch = "wasm32"), so the guest genuinely arms EB_GC where the
natives do not. This PR does not claim to close it.

These are the four rows main has been red on in its own ubuntu CI. #1071
re-recorded three of them at the free-threshold value and #1077 re-recorded
the top-level wasm files without moving any counter, so they are still open on
main; this replaces them with the pinned measurement.

2. Three class-creation hooks through the frameless dispatcher

build_class's non-type metaclass call, its __prepare__ call and
_init_subclass each resolved a caller frame with gettopframe_raw() only to
satisfy call_with_kwargs(frame, …), and each took a fallback arm when that
frame was null: the first two dropped the class-definition keywords, the third
raised TypeError("__init_subclass__() takes no keyword arguments").

Upstream passes the keywords unconditionally through the frameless
space.call_args (descroperation.py:189) at all three sites. None of the three
fallback arms has an upstream counterpart, and none was reachable from a script.
What this removes is three force_vref calls and three arms with no upstream
basis.

3. seek's whence is a C int, on the index protocol

Both memory streams unwrapped seek's whence with the machine-int converter, so
a value outside a C int's range reached the 0/1/2 range check and came back as
ValueError: invalid whence (4294967296, should be 0, 1 or 2).
@unwrap_spec(pos=r_longlong, whence=int) (interp_bytesio.py:162) and
@unwrap_spec(pos=int, mode=int) (interp_stringio.py:403) take it as a C int,
and 3.14 raises OverflowError for one that does not fit.

Neither existing converter is that parameter, and reaching for the nearest one
is a trap this PR walked into and backed out of: c_int_w has the range check
but resolves through gateway_int_w = int_w (baseobjspace.py:2043), which
converts via __int__ first. That is observable, not cosmetic — an object
defining __index__ as 0 and __int__ as 2 seeks to the end rather than the
start, silently, and one defining only __int__ is accepted where 3.14 raises
TypeError. So the commit adds index_c_int_w (space_index, then the 32-bit
check) and the PyIndexCInt alias over it.

seek(0, w) on BytesIO(b"abcdefgh") 3.14 main c_int_w this PR
w = 2**32 OverflowError ValueError OverflowError OverflowError
__index__→0, __int__→2 0 0 8 0
only __int__→0 TypeError TypeError 0 TypeError

The position is left alone: upstream's pos=int on StringIO is a C int too,
but 3.14 takes it as a Py_ssize_t and accepts seek(2**32).

The parity script asserts the whence by value against a non-empty buffer, so
the two middle rows cannot pass by accident, and it is non-vacuous against a
real control — a main build carrying #1079 fails it with
expected OverflowError, got ValueError('invalid whence (4294967296, …)').

4. Two test-harness fixes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • WebAssembly hosts can now provide supported garbage-collection environment settings, with host values taking precedence.
    • Parity tests support per-script environment directives for more configurable execution scenarios.
  • Bug Fixes

    • Improved class construction with keyword arguments across metaclass and subclass initialization paths.
    • BytesIO.seek() and StringIO.seek() now correctly validate whence, support __index__, and reject out-of-range or invalid values.
  • Tests

    • Added coverage for environment forwarding and seek argument conversion and range handling.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1fe1890d-b0bc-4668-933e-e1c3951d43c8

📥 Commits

Reviewing files that changed from the base of the PR and between f828557 and 9f041c7.

📒 Files selected for processing (59)
  • majit/majit-gc/src/collector.rs
  • pyre/bench/synth/arith_int_bool.wasm.jitstats
  • pyre/bench/synth/binary_slice_index.wasm.jitstats
  • pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats
  • pyre/bench/synth/build_set_hashability.wasm.jitstats
  • pyre/bench/synth/closure_per_call.wasm.jitstats
  • pyre/bench/synth/dict_set.wasm.jitstats
  • pyre/bench/synth/divmod_long_int_pair.wasm.jitstats
  • pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats
  • pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats
  • pyre/bench/synth/exception_oserror_fields.wasm.jitstats
  • pyre/bench/synth/exception_subclass_attrs.wasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats
  • pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats
  • pyre/bench/synth/exception_value_op_caught.wasm.jitstats
  • pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats
  • pyre/bench/synth/foriter_body_return.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats
  • pyre/bench/synth/gc_deque_backing_list.wasm.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats
  • pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats
  • pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats
  • pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats
  • pyre/bench/synth/list_ops.wasm.jitstats
  • pyre/bench/synth/loops_comprehension.wasm.jitstats
  • pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats
  • pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats
  • pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats
  • pyre/bench/synth/newslice_step_hot.wasm.jitstats
  • pyre/bench/synth/recursion_memo_branch.wasm.jitstats
  • pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.wasm.jitstats
  • pyre/bench/synth/str_fstring.wasm.jitstats
  • pyre/bench/synth/type_name_setter.wasm.jitstats
  • pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats
  • pyre/bench/synth/unary_negative.wasm.jitstats
  • pyre/bench/synth/unary_positive_resume.wasm.jitstats
  • pyre/bench/synth/unpack_ex_hot.wasm.jitstats
  • pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats
  • pyre/check.py
  • pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py
  • pyre/extra_tests/parity_tests/run.py
  • pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/module/_io/bytesio.rs
  • pyre/pyre-interpreter/src/module/_io/stringio.rs
  • pyre/pyre-jit/src/lib.rs
  • pyre/pyre-macros/src/lib.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm/src/lib.rs

Walkthrough

The PR adds wasm GC environment forwarding, per-script parity environments, frameless class keyword dispatch, C-int seek conversion, related parity tests, and updated synthetic JIT statistics.

Changes

GC environment forwarding

Layer / File(s) Summary
Collector environment resolution
majit/majit-gc/src/collector.rs
The collector accepts supplied GC environment values, prefers process values, and tests fallback and clearing behavior.
Wasm environment ABI
pyre/pyre-jit/src/lib.rs, pyre/pyre-wasm/src/lib.rs
The wasm ABI exposes supported GC names and accepts NUL-separated environment assignments.
Host environment forwarding
pyre/pyre-wasm-runner/src/main.rs, pyre/check.py
The runner reads requested host variables and forwards valid values to the wasm guest.
Parity environment handling
pyre/extra_tests/parity_tests/run.py, pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
Parity scripts support parity-env directives and apply the resulting variables during execution.

Frameless class calls

Layer / File(s) Summary
Class keyword dispatch
pyre/pyre-interpreter/src/call.rs
Metaclass calls, __prepare__, and __init_subclass__ now forward keywords through execution-context calls without caller-frame lookup.

C-int seek conversion

Layer / File(s) Summary
Seek argument conversion and parity coverage
pyre/pyre-interpreter/src/baseobjspace.rs, pyre/pyre-macros/src/lib.rs, pyre/pyre-interpreter/src/module/_io/bytesio.rs, pyre/pyre-interpreter/src/module/_io/stringio.rs, pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py
whence uses checked C-int index conversion for BytesIO and StringIO. Tests cover range errors and conversion protocols.

JIT benchmark statistics

Layer / File(s) Summary
Benchmark counter updates
pyre/bench/synth/*.wasm.jitstats
Synthetic wasm benchmark fixtures record updated guard_failures and compiled-bridge counters.

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

Sequence Diagram(s)

sequenceDiagram
  participant pyre_wasm_runner
  participant pyre_wasm
  participant majit_gc
  pyre_wasm_runner->>pyre_wasm: query pyre_gc_env_names
  pyre_wasm-->>pyre_wasm_runner: return supported GC names
  pyre_wasm_runner->>pyre_wasm: send NAME=value entries
  pyre_wasm->>majit_gc: call set_supplied_env
  majit_gc->>majit_gc: resolve process values before supplied values
Loading

Possibly related PRs

Poem

A rabbit carried settings through the wasm night,
While class calls hopped frame-free and light.
C-int seeks checked each bound,
New parity tests gathered round,
And JIT counters settled right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main GC forwarding change and the related parity and test fixes.
✨ 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.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 9f041c7).
Updated: 2026-08-06T13:04:40.595Z

Files in the reviewed diff
majit/majit-gc/src/collector.rs
pyre/check.py
pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py
pyre/extra_tests/parity_tests/run.py
pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
pyre/pyre-interpreter/src/baseobjspace.rs
pyre/pyre-interpreter/src/call.rs
pyre/pyre-interpreter/src/module/_io/bytesio.rs
pyre/pyre-interpreter/src/module/_io/stringio.rs
pyre/pyre-jit/src/lib.rs
pyre/pyre-macros/src/lib.rs
pyre/pyre-wasm-runner/src/main.rs
pyre/pyre-wasm/src/lib.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)

  • majit/majit-gc/src/collector.rs:142 ↔ rpython/memory/gc/env.py:18 — "raw.trim()" accepts GC values with surrounding whitespace; PyPy tests the untrimmed environment string, so e.g. "1k " is unset upstream but becomes 1 KiB here.

  • majit/majit-gc/src/collector.rs:174 ↔ rpython/memory/gc/env.py:42 — "bytes > 0.0" treats negative PYPY_GC_* byte values as unset, whereas PyPy’s r_uint(value * factor) wraps them to an unsigned value.

  • pyre/pyre-interpreter/src/module/_io/bytesio.rs:391 ↔ pypy/module/_io/interp_bytesio.py:162 — pre-existing PyIndexInt routes a negative integer below i64::MIN to the later negative-position ValueError; PyPy’s r_longlong conversion overflows before seek_w executes.

  • pyre/pyre-interpreter/src/module/_io/stringio.rs:395 ↔ pypy/module/_io/interp_stringio.py:403 — the same pre-existing index_int_w_preserve_negative sentinel means an undersized negative position reaches the domain check instead of failing conversion.

4. Structural adaptations

  • pyre/pyre-interpreter/src/baseobjspace.rs:13028 ↔ pypy/module/_io/interp_bytesio.py:162 — "index_c_int_w" deliberately uses CPython 3.14 Argument Clinic semantics: __index__ plus signed-C-int range checking. PyPy’s older unwrap_spec(..., whence=int) uses its machine-int gateway. This is a permitted Python-version/compiler adaptation and fixes the intended BytesIO behavior.

  • pyre/pyre-interpreter/src/module/_io/stringio.rs:396 ↔ pypy/module/_io/interp_stringio.py:403 — StringIO likewise uses the CPython-3.14 C-int/index-protocol conversion for whence, rather than PyPy’s legacy mode=int gateway.

  • pyre/pyre-interpreter/src/call.rs:3662 ↔ pypy/objspace/descroperation.py:189 — Pyre passes an execution context to its frameless keyword-call helper instead of recovering a live caller frame. PyPy’s space.call_args itself takes no frame; the Rust context is required to construct/evaluate callee frames without forcing Pyre’s virtualizable caller frame.

  • majit/majit-gc/src/collector.rs:112 ↔ rpython/memory/gc/env.py:18 — the RwLock<Vec<(String, String)>> supplied-environment bridge has no direct PyPy counterpart. It is necessary for wasm32-unknown-unknown, whose guest has no process environment, while preserving native process-environment precedence.

@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: 307d3d045b

ℹ️ 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 +9 to +13
//! `bytearray` is already the growable mutable buffer `__bigbuffer` stands
//! for, so the two tiers — and with them the `AT_END` position sentinel that
//! only exists to keep the builder tier live — collapse into a single store
//! here. Holding it as a `bytearray` object rather than a Rust `Vec` is what
//! lets `getbuffer` hand out a real view of the stream and lets

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 RStringIO's two-tier storage

Replace this explicit storage collapse with the literal RStringIO field and method structure: the repository requires PyPy/RPython ports to retain strict line-by-line structural parity rather than substituting a locally convenient representation. The single bytearray changes where resize/export state lives and makes later methods depend on bytearray-specific behavior instead of the upstream StringBuilder/__bigbuffer/AT_END invariants.

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

Useful? React with 👍 / 👎.

/// `interp_bytesio.py:159` `seek_w`.
fn seek(&mut self, pos: i64, #[default(0)] whence: i64) -> Result<PyObjectRef, crate::PyError> {
self.check_closed()?;
self.seek_to(pos, whence as i32)?;

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 Reject out-of-range whence values before narrowing

Validate or bind whence as the intended integer type instead of casting it: values such as 4294967296 wrap to 0 and 4294967298 wraps to 2, so seek() performs a valid seek rather than rejecting an unsupported whence. This affects any caller supplying an integer outside the i32 range and silently gives that integer unrelated semantics.

Useful? React with 👍 / 👎.

return Vec::new();
}
let data = self.data();
let start = self.pos.max(0) as usize;

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 Preserve large logical offsets on wasm32

Use a checked conversion for positions rather than as usize: on wasm32, after seek(1 << 32), this cast wraps the logical position to zero, so read() returns data from the beginning instead of EOF. The same unchecked conversion pattern in read_line, write_bytes, and truncate_to can likewise read, overwrite, or truncate the wrong part of the stream for offsets or sizes above u32::MAX.

Useful? React with 👍 / 👎.

@youknowone youknowone changed the title _io: port BytesIO to an interp-level type, and call the class-creation hooks framelessly _io.BytesIO at interp level, and the GC pins the wasm guest never received Aug 6, 2026

@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/aa397e75e02d7c371d9e2f878c3eddb03d2d2361/pyre-wasm-runner/src/main.rs#L461-L465
P2 Badge Forward the GC environment through wasmi too

When the runner is invoked with --engine wasmi (or PYRE_WASM_ENGINE=wasmi), the dispatch goes to wasmi_host::run and never executes this new forwarding block; I inspected that path through its pyre_alloc/pyre_run_python setup, and it does not query or call either GC-environment export before starting the guest. Consequently PYPY_GC_NURSERY, PYPY_GC_MIN, PYPY_GC_MAX, and the other advertised settings still resolve to defaults under the supported wasmi engine, so configured heap limits and the pinned JIT-stat collection schedule differ from wasmtime. Mirror this exchange in wasmi_host::run or share it between both engine paths.

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

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyre/pyre-interpreter/src/call.rs`:
- Around line 4853-4861: In the function containing the `init_subclass_kwargs`
handling, pin every key and value from the source dictionary at function entry
before any `super_check` or `getattr_str` execution. When constructing `kwds`,
reload both objects from the shadow stack and use those rooted references for
`w_str_get_wtf8` and the value passed to `call_with_kwargs_in_ctx`, preserving
the existing RPython/PyPy storage shape and line-by-line structure.

In `@pyre/pyre-interpreter/src/module/_io/bytesio.rs`:
- Around line 79-82: Update the error messages in check_closed and seek_to to
match the upstream strings exactly: remove the trailing period from the
closed-file message and use the specified invalid-whence format with the actual
whence value. Preserve the existing exception types and control flow.
- Around line 398-403: Guard both buffer mutations with check_exports(): update
close() to validate exports before replacing w_buffer, and update __setstate__()
to validate exports before truncating the existing buffer ahead of write(). Add
regression tests covering getbuffer() followed by close() and __setstate__(),
ensuring live views prevent the prohibited length changes.
- Around line 193-196: Update the relative-seek calculation around
`base.checked_add(position)` so negative underflow is clamped to zero before
addition, while positive overflow still returns `OverflowError`. Preserve the
existing `self.pos = target.max(0)` behavior and apply this to both `SEEK_CUR`
and `SEEK_END` paths.
- Around line 107-113: Update the BytesIO read, truncate, and write paths around
the position and size conversions to range-check i64 values before converting
them to usize, including the logic using start and count. Treat positions above
usize::MAX as EOF for reads, compare truncate sizes before conversion, and
return OverflowError before writes mutate storage when the requested size is
unaddressable; preserve normal behavior for representable values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2b16c566-12aa-48b2-93c3-83c3f89c512e

📥 Commits

Reviewing files that changed from the base of the PR and between 1de95e0 and aa397e7.

📒 Files selected for processing (66)
  • majit/majit-gc/src/collector.rs
  • pyre/bench/synth/arith_int_bool.wasm.jitstats
  • pyre/bench/synth/binary_slice_index.wasm.jitstats
  • pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats
  • pyre/bench/synth/build_set_hashability.wasm.jitstats
  • pyre/bench/synth/closure_per_call.wasm.jitstats
  • pyre/bench/synth/dict_set.wasm.jitstats
  • pyre/bench/synth/divmod_long_int_pair.wasm.jitstats
  • pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats
  • pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats
  • pyre/bench/synth/exception_oserror_fields.wasm.jitstats
  • pyre/bench/synth/exception_subclass_attrs.wasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats
  • pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats
  • pyre/bench/synth/exception_value_op_caught.wasm.jitstats
  • pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats
  • pyre/bench/synth/foriter_body_return.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats
  • pyre/bench/synth/gc_deque_backing_list.wasm.jitstats
  • pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats
  • pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats
  • pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats
  • pyre/bench/synth/list_ops.wasm.jitstats
  • pyre/bench/synth/loops_comprehension.wasm.jitstats
  • pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats
  • pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats
  • pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats
  • pyre/bench/synth/newslice_step_hot.wasm.jitstats
  • pyre/bench/synth/pickle_ctor_args.cranelift.jitstats
  • pyre/bench/synth/pickle_ctor_args.dynasm.jitstats
  • pyre/bench/synth/pickle_ctor_args.wasm.jitstats
  • pyre/bench/synth/pickle_terminal_raise_resume.cranelift.jitstats
  • pyre/bench/synth/pickle_terminal_raise_resume.dynasm.jitstats
  • pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats
  • pyre/bench/synth/recursion_memo_branch.wasm.jitstats
  • pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.wasm.jitstats
  • pyre/bench/synth/str_fstring.wasm.jitstats
  • pyre/bench/synth/type_metatype_method_call.wasm.jitstats
  • pyre/bench/synth/type_name_setter.wasm.jitstats
  • pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats
  • pyre/bench/synth/unary_negative.wasm.jitstats
  • pyre/bench/synth/unary_positive_resume.wasm.jitstats
  • pyre/bench/synth/unpack_ex_hot.wasm.jitstats
  • pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats
  • pyre/check.py
  • pyre/extra_tests/parity_tests/run.py
  • pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/lib.rs
  • pyre/pyre-interpreter/src/module/_io/_io_app.py
  • pyre/pyre-interpreter/src/module/_io/bytesio.rs
  • pyre/pyre-interpreter/src/module/_io/mod.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-jit/src/lib.rs
  • pyre/pyre-object/src/pyobject.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm/src/lib.rs

Comment thread pyre/pyre-interpreter/src/call.rs
Comment on lines +79 to +82
fn check_closed(&self) -> Result<(), crate::PyError> {
if self.closed {
return Err(crate::PyError::value_error("I/O operation on closed file."));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the upstream exception messages.

check_closed adds a trailing period. seek_to uses a different invalid-whence message. These strings are observable API behavior. Use "I/O operation on closed file" and "whence must be between 0 and 2, not {whence}". (raw.githubusercontent.com)

Also applies to: 187-190

🤖 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/module/_io/bytesio.rs` around lines 79 - 82, Update
the error messages in check_closed and seek_to to match the upstream strings
exactly: remove the trailing period from the closed-file message and use the
specified invalid-whence format with the actual whence value. Preserve the
existing exception types and control flow.

Source: Coding guidelines

Comment on lines +107 to +113
let start = self.pos.max(0) as usize;
if start >= data.len() {
return Vec::new();
}
let mut count = data.len() - start;
if size >= 0 {
count = count.min(size as usize);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent i64 to usize truncation on wasm32.

On wasm32, these casts wrap positions and sizes above usize::MAX. For example, seek(1 << 32); read() can read from offset zero, and truncate(1 << 32) can truncate the wrong buffer range. Convert only after range checks. Treat an unaddressable read position as EOF, compare read and truncate sizes before conversion, and return OverflowError before a write mutates storage.

Also applies to: 129-135, 150-160, 164-170

🤖 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/module/_io/bytesio.rs` around lines 107 - 113,
Update the BytesIO read, truncate, and write paths around the position and size
conversions to range-check i64 values before converting them to usize, including
the logic using start and count. Treat positions above usize::MAX as EOF for
reads, compare truncate sizes before conversion, and return OverflowError before
writes mutate storage when the requested size is unaddressable; preserve normal
behavior for representable values.

Comment thread pyre/pyre-interpreter/src/module/_io/bytesio.rs Outdated
Comment on lines +398 to +403
fn close(&mut self) {
self.closed = true;
self.w_buffer = pyre_object::bytearrayobject::w_bytearray_from_bytes(&[]);
self.pos = 0;
pyre_object::gc_hook::try_gc_write_barrier(self as *mut Self as *mut u8);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Block buffer replacement and truncation while a view is exported.

getbuffer() exports w_buffer. close() replaces that buffer, and __setstate__() truncates it before write() checks exports. A live memoryview can therefore observe a prohibited length change. Call check_exports() before either mutation. Add regression tests for getbuffer() followed by close() and __setstate__().

Proposed fix
-    fn close(&mut self) {
+    fn close(&mut self) -> Result<(), crate::PyError> {
+        self.check_exports()?;
         self.closed = true;
         self.w_buffer = pyre_object::bytearrayobject::w_bytearray_from_bytes(&[]);
         self.pos = 0;
         pyre_object::gc_hook::try_gc_write_barrier(self as *mut Self as *mut u8);
+        Ok(())
     }

     fn __setstate__(&mut self, w_state: PyObjectRef) -> Result<(), crate::PyError> {
         self.check_closed()?;
+        self.check_exports()?;
         // validate state
         self.truncate_to(0);

Also applies to: 444-446

🤖 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/module/_io/bytesio.rs` around lines 398 - 403,
Guard both buffer mutations with check_exports(): update close() to validate
exports before replacing w_buffer, and update __setstate__() to validate exports
before truncating the existing buffer ahead of write(). Add regression tests
covering getbuffer() followed by close() and __setstate__(), ensuring live views
prevent the prohibited length changes.

Source: Coding guidelines

@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: 85ebd1ea0f

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

// (`add_to_autoflusher=False`) — an in-memory buffer has nothing to
// write out at exit.
let needs_finalizer = !cls.is_null() && !std::ptr::eq(cls, type_object());
super::tag_io_instance_with_finalizer(obj, cls, needs_finalizer)

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 Keep BytesIO out of the autoflusher

Use a registration path that does not call autoflusher_add: despite the comment documenting add_to_autoflusher=False, tag_io_instance_with_finalizer unconditionally adds the object before conditionally registering its finalizer. Consequently, every exact BytesIO is now visited by flush_all_streams; for example, a still-live instance whose flush attribute was replaced gets that callback invoked during _io shutdown, whereas both the previous exact-class allocation path and PyPy's W_BytesIO.__init__ exclude it from the autoflusher.

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

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@youknowone youknowone changed the title _io.BytesIO at interp level, and the GC pins the wasm guest never received The GC pins the wasm guest never received, and four parity/test fixes Aug 6, 2026

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

ℹ️ 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 +461 to +465
let gc_env_names = instance
.get_typed_func::<(), u64>(&mut store, "pyre_gc_env_names")
.ok();
let set_gc_env = instance
.get_typed_func::<(u32, u32), ()>(&mut store, "pyre_set_gc_env")

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 Forward the GC environment through the wasmi runner

When --engine wasmi or PYRE_WASM_ENGINE=wasmi is selected, main dispatches to wasmi_host::run, which goes directly from resolving pyre_alloc/pyre_run_python to setting the script path and never invokes these new pyre_gc_env_names/pyre_set_gc_env exports. Consequently, check.py's PYPY_GC_NURSERY and PYPY_GC_MIN pins still remain invisible to that supported wasm engine, so it uses the old collection thresholds and can produce guard counts inconsistent with the newly recorded shared .wasm.jitstats baselines. Add the same handshake to wasmi_host::run before its call to pyre_run_python.

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyre/pyre-interpreter/src/call.rs`:
- Around line 4071-4085: Root managed call inputs before allocation at both
sites in pyre/pyre-interpreter/src/call.rs:4071-4085 and 4853-4861. In the
__prepare__ path, pin prepare, bases, and every prepare_kwds value before
w_str_new(name), then reload them from the shadow stack when constructing the
call; in the __init_subclass__ path, pin every init_subclass_kwargs key and
value at function entry, then reload both after super_check and getattr_str.
Preserve RPython/PyPy storage shape and strict line-by-line structural parity.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 11da880b-950b-4d12-966f-444a952b5a6a

📥 Commits

Reviewing files that changed from the base of the PR and between fd709f2 and e43956d.

📒 Files selected for processing (60)
  • majit/majit-gc/src/collector.rs
  • pyre/bench/synth/arith_int_bool.wasm.jitstats
  • pyre/bench/synth/binary_slice_index.wasm.jitstats
  • pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats
  • pyre/bench/synth/build_set_hashability.wasm.jitstats
  • pyre/bench/synth/closure_per_call.wasm.jitstats
  • pyre/bench/synth/dict_set.wasm.jitstats
  • pyre/bench/synth/divmod_long_int_pair.wasm.jitstats
  • pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats
  • pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats
  • pyre/bench/synth/exception_oserror_fields.wasm.jitstats
  • pyre/bench/synth/exception_subclass_attrs.wasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats
  • pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats
  • pyre/bench/synth/exception_value_op_caught.wasm.jitstats
  • pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats
  • pyre/bench/synth/foriter_body_return.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats
  • pyre/bench/synth/gc_deque_backing_list.wasm.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats
  • pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats
  • pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats
  • pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats
  • pyre/bench/synth/list_ops.wasm.jitstats
  • pyre/bench/synth/loops_comprehension.wasm.jitstats
  • pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats
  • pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats
  • pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats
  • pyre/bench/synth/newslice_step_hot.wasm.jitstats
  • pyre/bench/synth/recursion_memo_branch.wasm.jitstats
  • pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.wasm.jitstats
  • pyre/bench/synth/str_fstring.wasm.jitstats
  • pyre/bench/synth/type_name_setter.wasm.jitstats
  • pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats
  • pyre/bench/synth/unary_negative.wasm.jitstats
  • pyre/bench/synth/unary_positive_resume.wasm.jitstats
  • pyre/bench/synth/unpack_ex_hot.wasm.jitstats
  • pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats
  • pyre/check.py
  • pyre/extra_tests/parity_tests/dict_subscript_fold.py
  • pyre/extra_tests/parity_tests/exception_instance_dict_attr.py
  • pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py
  • pyre/extra_tests/parity_tests/object_init_text_signature.py
  • pyre/extra_tests/parity_tests/run.py
  • pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/module/_io/bytesio.rs
  • pyre/pyre-interpreter/src/module/_io/stringio.rs
  • pyre/pyre-jit/src/lib.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm/src/lib.rs

Comment thread pyre/pyre-interpreter/src/call.rs
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: 2f017d5fe0

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

/// object carrying both dunders would be read from the wrong one, and one
/// carrying only `__int__` would be accepted where 3.14 raises TypeError.
pub fn index_c_int_w(obj: PyObjectRef) -> Result<i32, PyError> {
let value = int_w(space_index(obj)?)?;

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 PyPy's C-int converter for whence

For objects defining only __int__, or defining disagreeing __int__ and __index__, this new helper changes both memory streams from the checked-in PyPy behavior: interp_bytesio.py and interp_stringio.py declare whence/mode with unwrap_spec(...=int), whose c_int_w calls gateway_int_w = int_w, while this line forces space_index. Consequently an IntOnly value is rejected and a Both value selects a different seek origin; retain the C-int range check but route through the existing c_int_w converter rather than introducing an index-only protocol.

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

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.

♻️ Duplicate comments (1)
pyre/pyre-interpreter/src/call.rs (1)

4071-4085: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Root keyword-call inputs before caller-side allocations.

call_with_kwargs_in_ctx installs roots only after its arguments are evaluated. The caller can therefore pass stale managed pointers after a moving collection.

  • pyre/pyre-interpreter/src/call.rs#L4071-L4085: Pin prepare, bases, and every prepare_kwds value before w_str_new(name). Reload all rooted pointers when building both the keyword and positional call paths.
  • pyre/pyre-interpreter/src/call.rs#L4853-L4861: Pin every init_subclass_kwargs key and value before super_check. Reload both objects from the root scope when constructing kwds after super_check and getattr_str.

As per coding guidelines, preserve strict line-by-line structural parity with RPython/PyPy.

🤖 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/call.rs` around lines 4071 - 4085, The
class-preparation call path at pyre/pyre-interpreter/src/call.rs:4071-4085 must
root prepare, bases, and every prepare_kwds value before w_str_new(name), then
reload all rooted pointers when constructing both keyword and positional call
arguments; preserve strict line-by-line parity with RPython/PyPy. The
init-subclass path at pyre/pyre-interpreter/src/call.rs:4853-4861 must root
every init_subclass_kwargs key and value before super_check, then reload both
objects from the root scope when constructing kwds after super_check and
getattr_str.

Source: Coding guidelines

🤖 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.

Duplicate comments:
In `@pyre/pyre-interpreter/src/call.rs`:
- Around line 4071-4085: The class-preparation call path at
pyre/pyre-interpreter/src/call.rs:4071-4085 must root prepare, bases, and every
prepare_kwds value before w_str_new(name), then reload all rooted pointers when
constructing both keyword and positional call arguments; preserve strict
line-by-line parity with RPython/PyPy. The init-subclass path at
pyre/pyre-interpreter/src/call.rs:4853-4861 must root every init_subclass_kwargs
key and value before super_check, then reload both objects from the root scope
when constructing kwds after super_check and getattr_str.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a0e20657-eadb-4e49-8a3d-3eb335c06077

📥 Commits

Reviewing files that changed from the base of the PR and between fd709f2 and 2f017d5.

📒 Files selected for processing (62)
  • majit/majit-gc/src/collector.rs
  • pyre/bench/synth/arith_int_bool.wasm.jitstats
  • pyre/bench/synth/binary_slice_index.wasm.jitstats
  • pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats
  • pyre/bench/synth/build_set_hashability.wasm.jitstats
  • pyre/bench/synth/closure_per_call.wasm.jitstats
  • pyre/bench/synth/dict_set.wasm.jitstats
  • pyre/bench/synth/divmod_long_int_pair.wasm.jitstats
  • pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats
  • pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats
  • pyre/bench/synth/exception_oserror_fields.wasm.jitstats
  • pyre/bench/synth/exception_subclass_attrs.wasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats
  • pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats
  • pyre/bench/synth/exception_value_op_caught.wasm.jitstats
  • pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats
  • pyre/bench/synth/foriter_body_return.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats
  • pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats
  • pyre/bench/synth/gc_deque_backing_list.wasm.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats
  • pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats
  • pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats
  • pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats
  • pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats
  • pyre/bench/synth/list_ops.wasm.jitstats
  • pyre/bench/synth/loops_comprehension.wasm.jitstats
  • pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats
  • pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats
  • pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats
  • pyre/bench/synth/newslice_step_hot.wasm.jitstats
  • pyre/bench/synth/recursion_memo_branch.wasm.jitstats
  • pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.wasm.jitstats
  • pyre/bench/synth/str_fstring.wasm.jitstats
  • pyre/bench/synth/type_name_setter.wasm.jitstats
  • pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats
  • pyre/bench/synth/unary_negative.wasm.jitstats
  • pyre/bench/synth/unary_positive_resume.wasm.jitstats
  • pyre/bench/synth/unpack_ex_hot.wasm.jitstats
  • pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats
  • pyre/check.py
  • pyre/extra_tests/parity_tests/dict_subscript_fold.py
  • pyre/extra_tests/parity_tests/exception_instance_dict_attr.py
  • pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py
  • pyre/extra_tests/parity_tests/object_init_text_signature.py
  • pyre/extra_tests/parity_tests/run.py
  • pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/module/_io/bytesio.rs
  • pyre/pyre-interpreter/src/module/_io/stringio.rs
  • pyre/pyre-jit/src/lib.rs
  • pyre/pyre-macros/src/lib.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm/src/lib.rs

`run.py` reads `# parity-env: NAME=VALUE` lines from each script and adds
them to the environment of every runner for that script only.

`thread_start_walk_abort_no_replay.py` pins `PYRE_FBW_CALLEE_VSTACK=0`.
The three kept-stack branch-guard decline hazards are scoped to
`!ctx.vstack_valid`, so with the callee operand-stack mirror on by default
(7b87e22) an inline sub-walk describes its own stack and the aborting
guard is never reached.  Measured on a binary built from the commit before
the walk-abort fix: the script passes 4/4 at the default setting and fails
4/4 with the mirror off, in both cases at round 97.

Verified through `run.py` itself: with the pre-fix binary in
`target/release` the run reports this script as its single failure; with
both backends built at HEAD the full suite passes.

Assisted-by: Claude
…patcher

`build_class`'s non-type metaclass call, its `__prepare__` call and
`_init_subclass` each resolved a caller frame with `gettopframe_raw()` only
to satisfy `call_with_kwargs(frame, …)`, and each took a fallback arm when
that frame was null: the first two dropped the class-definition keywords,
and the third raised `TypeError("__init_subclass__() takes no keyword
arguments")`.

Upstream passes the keywords unconditionally through the frameless
`space.call_args` (descroperation.py:189) at all three sites —
compiling.py:199 for `__prepare__`, :221 for the metaclass, and
typeobject.py:1025-1026 `args = __args__.replace_arguments([])` /
`space.call_args(w_func, args)` for `__init_subclass__`.  None of the three
fallback arms has an upstream counterpart.  Call `call_with_kwargs_in_ctx`
at each site and delete them.

The fallbacks were not reachable from a script: the execution context's
frame is non-null throughout normal execution, and a metaclass with
`__prepare__` plus class keywords, and `__init_subclass__` with keywords,
already matched CPython 3.14 before this change.  What this removes is
three `force_vref` calls and three arms with no upstream basis, leaving
`c_profile_frame` as the only `gettopframe_raw()` in the file.

Also correct two nearby line citations: compiling.py:190-196 -> :194-199
and :213-219 -> :214-221.

Assisted-by: Claude
`check.py` pins `PYPY_GC_NURSERY` and `PYPY_GC_MIN` so the major-collection
threshold is a property of the tree rather than of the machine. Both pins
reached the two native backends and neither reached the wasm one: the guest is
built for `wasm32-unknown-unknown`, whose `std::env` is permanently empty, and
`majit-gc` resolves those names through `std::env::var`
(collector.rs:93). The guest therefore kept `min_heap_size = nursery * 8` = 32MB,
crossed it mid-run, and counted the back-edge eval-breaker poll's bailouts that
the natives no longer see. `warn_inert_guest_env` did not report it either — it
matches the `PYRE_` and `MAJIT_` prefixes, and these are `PYPY_`.

Measured, `recursive_call_frame_relocation`'s guest-side `guard_failures`
against `PYPY_GC_MIN`: unset 648, 8MB 695, 256MB 638. Before this change all
three read 648. The native backend moves under the same override — 636 -> 639,
and `fib_loop` 189 -> 193 — which is what said the variable was reaching one
side and not the other.

`majit-gc` takes an embedder-supplied environment, read only where `std::env`
misses, and publishes the names it resolves; `pyre-wasm` exports
`pyre_set_gc_env` / `pyre_gc_env_names` over it and the runner forwards whatever
its own environment carries. That is the pair `pyre_set_launch_env` /
`pyre_launch_env_names` already form for the launcher's variables, for the same
reason.

The wasmi engine path is left as it is: it forwards neither this nor the launch
environment, so it already runs without `PYTHONSAFEPATH` and is not a jit-stats
engine.

Assisted-by: Claude
The preceding commit lets `check.py`'s `PYPY_GC_NURSERY` / `PYPY_GC_MIN` reach
the wasm guest, which pushes the major-collection threshold past every
fixture's working set. The collection those readings were counting is gone, so
the counters that counted it move.

Forty-nine rows across 47 fixtures, and **every one of them falls**:
`guard_failures` by 1 to 202 (`exception_escape_hot_callee_tb_node_once`
1016 -> 814, `exception_inline_callee_tb_frames` 1008 -> 807,
`closure_per_call` 470 -> 420), plus `bridges_compiled` 5 -> 4 on those same
two — the bridge the loop had spent on the eval-breaker poll. Nothing rises
anywhere, and no other backend's baselines are touched, because the pins were
already reaching those.

Four of these are rows the base measures from a threshold that was still free
to move, three of them re-recorded at that free value as recently as #1071:
`closure_per_call` 470 -> 468 -> 420, `exception_traceback_frame_lineno`
820 -> 819 -> 817, `recursive_call_frame_relocation` 649 -> 648 -> 638, and
`gc_iterator_source_drop` 613 -> 614 -> 613.

Recorded with `check.py --snapshot --backend wasm`, which then reports
`ALL PASSED: wasm 384/384`. Its unrelated side effect of adding two new
`field_pos_*_misplaced` fields to every wasm file was stripped, except on the
four files whose committed baselines already carry them.

Assisted-by: Claude
`BytesIO.seek` and `StringIO.seek` unwrapped their whence with the machine-int
converter, so a value outside a C int's range reached the `0/1/2` range check
and came back as `ValueError: invalid whence (4294967296, should be 0, 1 or 2)`.

Both `@unwrap_spec(pos=r_longlong, whence=int)` (interp_bytesio.py:162) and
`@unwrap_spec(pos=int, mode=int)` (interp_stringio.py:403) take it as a C int,
and 3.14 raises `OverflowError: Python int too large to convert to C int` for
one that does not fit.

Neither existing converter is that parameter. `c_int_w` has the range check but
reaches the value through `gateway_int_w` = `int_w` (baseobjspace.py:2043),
which converts through `__int__` first; `index_int_w_preserve_negative` is on
the index protocol but is a machine int. A whence needs both, so add
`index_c_int_w` — `space_index` followed by the 32-bit check — and the
`PyIndexCInt` unwrap alias over it, and use it at both sites.

Reading the whence through `__int__` is observable, not merely a message
difference: an object defining `__index__` as 0 and `__int__` as 2 seeks to the
end rather than the start, silently, and one defining only `__int__` is accepted
where 3.14 raises TypeError. The parity script asserts the whence by value
against a non-empty buffer so neither case can pass, and pins the out-of-range
half and the position's wider `Py_ssize_t` range beside it.

The position is left alone: upstream's `pos=int` on `StringIO` is a C int too,
but 3.14 takes it as a `Py_ssize_t` and accepts `seek(2**32)`.

Assisted-by: Claude
@youknowone
youknowone merged commit bed04a9 into main Aug 6, 2026
7 of 8 checks passed
@youknowone
youknowone deleted the fib_recursive branch August 6, 2026 13:00
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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