Skip to content

list, str, _collections, _pickle: list strategies, percent formatting, a bounded deque repeat, and a value-keyed pickle memo - #1510

Open
youknowone wants to merge 14 commits into
mainfrom
builtins
Open

list, str, _collections, _pickle: list strategies, percent formatting, a bounded deque repeat, and a value-keyed pickle memo#1510
youknowone wants to merge 14 commits into
mainfrom
builtins

Conversation

@youknowone

@youknowone youknowone commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Rebased onto current main. Thirteen commits.

List strategies

list: port PyPy AsciiListStrategylistobject.py's AsciiListStrategy
with unicode_array.rs as its erased [rpython str] storage, plus the
jit-trace descrs, helpers and specialisation arms that reach it, and the
W_UnicodeObject.is_w / immutable_unique_id storage-identity arms the
re-wrapping makes observable.

list: port PyPy SizeListStrategy — the storage-less strategy that carries
an allocation hint until the first append selects a concrete one.
extra_tests/snippets/pypy_list_sizehint.py walks __pypy__.newlist_hint into
each of the five concrete strategies and checks the physical size the hint
bought.

list: port PyPy range list strategiesSimpleRangeListStrategy and
RangeListStrategy.

bench: reconcile list strategy stats after main rebase — jitstats only.

Percent formatting

str, bytes: preserve percent format operand order,
str, bytes: defer incomplete percent format errors,
str, bytes: defer oversized percent quantities,
str, bytes: preserve incomplete percent acquisition order — four
formatting.rs fixes to %-formatting.

PR #1471 review follow-ups

_collections, mapdict: trim the deque repeat per round, and install a map before reporting its strategy
— two findings, both reproduced first:

deque_repeat / W_Deque.__imul__ accumulated the whole product in a Vec and trimmed to maxlen once at the end, so deque([1, 2], maxlen=2) * (2**40) allocated the untrimmed length instead of answering
mapdict_strategy_repr reported nothing for an instance whose map had not been installed yet, so class C: pass; __pypy__.strategy(C()) raised TypeError until some attribute was touched

interp_deque.py's W_Deque.mul builds its answer by extending a
maxlen-bounded copy, and every append behind that extend runs trimleft
— the product is never materialised. Both entry points now trim after each
round the same way, and take the overflow edge from ovfcheck's
machine-signed multiplication rather than from the usize accumulator, which
does not overflow where the signed one does. imul keeps the upstream shape of
starting from self and extending num - 1 times.

user_setup installs the type's terminator while building the instance, so an
object carrying the mixin never holds a null map; pyre installs it on first
touch, and the diagnostic now asks for it rather than observing the difference.

extra_tests/snippets/stdlib_collections_deque_repeat.py (gated) covers the
bounded repeat at counts that exhaust memory under the old accumulator, the
short-circuiting counts, and the MemoryError edge. It is written to PyPy's
observables, not CPython's: _collectionsmodule.c's deque_inplace_repeat
reduces the repetition count so that deque([1], maxlen=1) * (2**62) answers
instantly, while upstream loops — the snippet asserts only what upstream also
answers.

The eleven remaining findings from that review were verified and rejected; the
four GC "reload the owner across a safepoint" ones rest on a mechanism pyre does
not have (alloc_with_type_no_collect and alloc_in_oldgen never collect, and
the only park point is gc_sync::safepoint_poll, called from the two eval loops
and the dynasm assembler). Six stale comments still assert the opposite premise
and cite a gc_op_slow that does not exist; they are left for a separate pass.

The one CI regression this branch had

_pickle: memoize str and bytes by valuememoize recorded only the
wrapper's identity and save resolved the memo by pointer identity against a
freshly read memo-list element, so once AsciiListStrategy landed, two list
slots naming one erased rpython string reached the memo as different objects:
data = [str(i) for i in range(257)]; data.append(data[-1]) pickled its
repeated element twice instead of emitting a GET, and unpickling produced two
objects where one is expected (test.test_pickletools
test_optimize_long_binget, PASS -> FAIL). Measured first: PyPy 3.11.13 and
CPython 3.14.2 both answer unpickled[-1] is unpickled[-2]True.

interp_pickle.py keeps str_memo and bytes_memo beside the identity memo,
keyed on space.utf8_w / space.bytes_w, and save consults them first; both
are ported, seeded from the entries a reused Pickler carries into dump.

Note the deliberate consequence, which is upstream's: two equal but distinct
strings now share a memo entry, so unpickling returns one object for both where
CPython returns two. That is interp_pickle.py's
7817dc4eec4 use the byte/str underlying w_bytes/w_str for memoizing, whose
comment names the same cause — "PyPy list strategies wrap stored values in new
W_* objects on each getitem, so identity checks miss even for repeated
references to the same logical value".

Rest

generator: finalize locals at close boundarycollector.rs,
baseobjspace.rs, pyframe.rs.

builtins: compare the build-class code through builtin_code_fn_eq
is_build_class_builtin was the one BuiltinCodeFn comparison still spelled
std::ptr::fn_addr_eq.

majit: serialize the two label-entry deopt tests on their shared counter
both install the same capture-free compile callback and read one process-global
COMPILES, and Rust runs sibling tests concurrently.

Codex parity review

The three findings filed against the commits above do not hold:

  • pickler.rs "the value memo can return a GET before reducer_override
    runs" — dispatch_save sends exact str/bytes to save_str/save_bytes
    before the reducer_override block, so that hook never sees either type,
    memo or no memo. Measured on CPython 3.14.2: a Pickler subclass whose
    reducer_override records what it is offered sees nothing at all for
    [str, str, bytes, int, float].
  • deque getindex_repeat (__index__ vs int_w) and deque.__doc__ — absent
    from this PR's diff; both come from b08493ae325 (jit-trace, _weakref: gate the class-attribute fold on a fold census, and reject repr with a foreign self #1471), already on main.

Verification: cargo check -p pyre-interpreter --features dynasm and
cargo check -p majit-metainterp --tests --no-default-features --features dynasm
both clean; the new deque snippet passes under CPython 3.14.2. Release binaries
were not rebuilt here — the LLBC extraction was invalidated three times by the
tree moving under it — so the two-backend runs are CI's. The previous head's
run was cancelled at 31 minutes with every long job killed, so the pickle fix
has not had a CI verdict yet.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 120 files, which is 20 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc3e5217-9b05-4f13-acc9-b133b2756950

📥 Commits

Reviewing files that changed from the base of the PR and between 8624de8 and 008e576.

📒 Files selected for processing (120)
  • majit/majit-gc/src/collector.rs
  • majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs
  • pyre/bench/synth/bound_method_builtin_fold.cranelift.jitstats
  • pyre/bench/synth/bound_method_builtin_fold.dynasm.jitstats
  • pyre/bench/synth/bound_method_builtin_fold.wasm.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats
  • pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats
  • pyre/bench/synth/exception_catching_frame_tb_node.cranelift.jitstats
  • pyre/bench/synth/exception_catching_frame_tb_node.dynasm.jitstats
  • pyre/bench/synth/exception_catching_frame_tb_node.wasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.cranelift.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.dynasm.jitstats
  • pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.cranelift.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.dynasm.jitstats
  • pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats
  • pyre/bench/synth/exception_reentry_guard_finally_residual.cranelift.jitstats
  • pyre/bench/synth/exception_reentry_guard_finally_residual.dynasm.jitstats
  • pyre/bench/synth/exception_reentry_guard_finally_residual.wasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.cranelift.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.dynasm.jitstats
  • pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats
  • pyre/bench/synth/exception_traceback_lineno_chain.cranelift.jitstats
  • pyre/bench/synth/exception_traceback_lineno_chain.dynasm.jitstats
  • pyre/bench/synth/exception_traceback_lineno_chain.wasm.jitstats
  • pyre/bench/synth/foriter_user_iter_kept_stack.cranelift.jitstats
  • pyre/bench/synth/foriter_user_iter_kept_stack.dynasm.jitstats
  • pyre/bench/synth/foriter_user_iter_kept_stack.wasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.cranelift.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.dynasm.jitstats
  • pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.cranelift.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.dynasm.jitstats
  • pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats
  • pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats
  • pyre/bench/synth/inlined_helper_mutation.cranelift.jitstats
  • pyre/bench/synth/inlined_helper_mutation.dynasm.jitstats
  • pyre/bench/synth/inlined_helper_mutation.wasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.cranelift.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.dynasm.jitstats
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats
  • pyre/bench/synth/list_append_funcentry_helper.cranelift.jitstats
  • pyre/bench/synth/list_append_funcentry_helper.dynasm.jitstats
  • pyre/bench/synth/list_append_funcentry_helper.wasm.jitstats
  • pyre/bench/synth/list_append_subscr_fresh.py
  • pyre/bench/synth/list_append_virtual_payload.cranelift.jitstats
  • pyre/bench/synth/list_append_virtual_payload.dynasm.jitstats
  • pyre/bench/synth/list_append_virtual_payload.wasm.jitstats
  • pyre/bench/synth/list_bound_method_mutation.cranelift.jitstats
  • pyre/bench/synth/list_bound_method_mutation.dynasm.jitstats
  • pyre/bench/synth/list_bound_method_mutation.wasm.jitstats
  • pyre/bench/synth/list_reverse.cranelift.jitstats
  • pyre/bench/synth/list_reverse.dynasm.jitstats
  • pyre/bench/synth/list_reverse.wasm.jitstats
  • pyre/bench/synth/loop_callee_shared_mutation.cranelift.jitstats
  • pyre/bench/synth/loop_callee_shared_mutation.dynasm.jitstats
  • pyre/bench/synth/loop_callee_shared_mutation.wasm.jitstats
  • pyre/bench/synth/mapdict_frozen_unboxing_fold.cranelift.jitstats
  • pyre/bench/synth/mapdict_frozen_unboxing_fold.dynasm.jitstats
  • pyre/bench/synth/mapdict_frozen_unboxing_fold.wasm.jitstats
  • pyre/bench/synth/minmax_key_rooting.cranelift.jitstats
  • pyre/bench/synth/minmax_key_rooting.dynasm.jitstats
  • pyre/bench/synth/minmax_key_rooting.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/range_ctor_in_loop.cranelift.jitstats
  • pyre/bench/synth/range_ctor_in_loop.dynasm.jitstats
  • pyre/bench/synth/range_ctor_in_loop.wasm.jitstats
  • pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats
  • pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats
  • pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats
  • pyre/bench/synth/selfrec_bridge_nontail_promote.cranelift.jitstats
  • pyre/bench/synth/selfrec_bridge_nontail_promote.dynasm.jitstats
  • pyre/bench/synth/selfrec_bridge_nontail_promote.wasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.cranelift.jitstats
  • pyre/bench/synth/sre_pattern_methods.dynasm.jitstats
  • pyre/bench/synth/sre_pattern_methods.wasm.jitstats
  • pyre/bench/synth/sre_wasm_min.cranelift.jitstats
  • pyre/bench/synth/sre_wasm_min.dynasm.jitstats
  • pyre/bench/synth/sre_wasm_min.wasm.jitstats
  • pyre/bench/synth/sre_wasm_min1.cranelift.jitstats
  • pyre/bench/synth/sre_wasm_min1.dynasm.jitstats
  • pyre/bench/synth/sre_wasm_min1.wasm.jitstats
  • pyre/bench/synth/str_index_bytes_iter_surface.cranelift.jitstats
  • pyre/bench/synth/str_index_bytes_iter_surface.dynasm.jitstats
  • pyre/bench/synth/str_index_bytes_iter_surface.wasm.jitstats
  • pyre/extra_tests/snippets/pypy_list_sizehint.py
  • pyre/extra_tests/snippets/stdlib_collections_deque_repeat.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/function.rs
  • pyre/pyre-interpreter/src/jit_fnaddr.rs
  • pyre/pyre-interpreter/src/module/__pypy__/mod.rs
  • pyre/pyre-interpreter/src/module/_collections/mod.rs
  • pyre/pyre-interpreter/src/module/_pickle/pickler.rs
  • pyre/pyre-interpreter/src/module/gc/mod.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/objspace/std/formatting.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/type_methods.rs
  • pyre/pyre-jit-trace/src/descr.rs
  • pyre/pyre-jit-trace/src/helpers.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-object/src/bytes_array.rs
  • pyre/pyre-object/src/float_array.rs
  • pyre/pyre-object/src/int_array.rs
  • pyre/pyre-object/src/lib.rs
  • pyre/pyre-object/src/listobject.rs
  • pyre/pyre-object/src/unicode_array.rs
  • pyre/pyre-object/src/unicodeobject.rs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration 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.

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 008e576).
Updated: 2026-08-27T09:58:41.619Z

Files in the reviewed diff
majit/majit-gc/src/collector.rs
majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs
pyre/bench/synth/list_append_subscr_fresh.py
pyre/extra_tests/snippets/pypy_list_sizehint.py
pyre/extra_tests/snippets/stdlib_collections_deque_repeat.py
pyre/pyre-interpreter/src/baseobjspace.rs
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/function.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-interpreter/src/module/__pypy__/mod.rs
pyre/pyre-interpreter/src/module/_collections/mod.rs
pyre/pyre-interpreter/src/module/_pickle/pickler.rs
pyre/pyre-interpreter/src/module/gc/mod.rs
pyre/pyre-interpreter/src/objspace/descroperation.rs
pyre/pyre-interpreter/src/objspace/std/formatting.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-interpreter/src/pyframe.rs
pyre/pyre-interpreter/src/type_methods.rs
pyre/pyre-jit-trace/src/descr.rs
pyre/pyre-jit-trace/src/helpers.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
pyre/pyre-jit/src/eval.rs
pyre/pyre-object/src/bytes_array.rs
pyre/pyre-object/src/float_array.rs
pyre/pyre-object/src/int_array.rs
pyre/pyre-object/src/lib.rs
pyre/pyre-object/src/listobject.rs
pyre/pyre-object/src/unicode_array.rs
pyre/pyre-object/src/unicodeobject.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-interpreter/src/module/_collections/mod.rs:860 ↔ pypy/module/_collections/interp_deque.py:230 — bounded deque * n reserves up to maxlen + len(base) and batch-appends each whole source deque before trimming; PyPy constructs a deque and calls extend/append per item. This can raise MemoryError where PyPy’s bounded growth succeeds.

  • pyre/pyre-interpreter/src/module/_collections/mod.rs:840 ↔ pypy/module/_collections/interp_deque.py:223 — the patch changes deque repetition from PyPy’s space.int_w() gateway to __index__ coercion. This may be a CPython-spec adaptation, but no admissible pinned-3.14 artefact establishes it, so it cannot be filed as structural under the requested rule.

  • pyre/pyre-interpreter/src/objspace/std/formatting.rs:405 ↔ pypy/objspace/std/formatting.py:209 — keyed %* formatting consumes the mapped value as the width/precision argument, whereas PyPy’s parse_fmt calls nextinputvalue() for *. The claimed CPython behaviour has no cited in-tree 3.14 artefact for this operand-order case.

  • pyre/pyre-interpreter/src/objspace/std/formatting.rs:437 ↔ pypy/objspace/std/formatting.py:322 — unsupported-percent conversions acquire an operand before raising, while PyPy validates the conversion character before nextinputvalue(). lib-python/3/test/test_format.py:629 verifies rejection of %z, but not this ordering; the required CPython evidence is incomplete.

  • pyre/pyre-interpreter/src/baseobjspace.rs:18177 ↔ pypy/interpreter/generator.py:313 — closing a generator now forces an old-generation collection and finalizer drain. PyPy merely releases the frame and calls rgc.may_ignore_finalizer; no admissible pinned-3.14 artefact is cited for the changed finalization timing.

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

None.

4. Structural adaptations

  • pyre/pyre-object/src/listobject.rs:220 ↔ pypy/objspace/std/listobject.py:1264SizeListStrategy state is represented by a GC-managed typed block stored in Rust’s inactive items field, rather than an RPython strategy object. This preserves the strategy-owned size hint while adapting object representation.

  • pyre/pyre-object/src/listobject.rs:240 ↔ pypy/objspace/std/listobject.py:1293 — range-list erased tuples are represented as GC-managed typed blocks instead of RPython erased tuples. The stored (length,) / (start, step, length) semantics match PyPy.

  • pyre/pyre-object/src/listobject.rs:33 ↔ pypy/objspace/std/listobject.py:1331 — address-striped reentrant list locks provide free-threaded mutation safety where PyPy relies on its GIL; this is a GIL/free-threading structural adaptation.

@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: 5d4f4a086d

ℹ️ 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 +3833 to +3834
if matches!(list.strategy, ListStrategy::Empty | ListStrategy::Size) {
if matches!(other.strategy, ListStrategy::Empty | ListStrategy::Size) {

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 Adopt the donor strategy on empty slice assignment

When both lists use Empty/Size storage, this early return incorrectly preserves the receiver's strategy. PyPy's EmptyListStrategy.setslice assigns the donor's strategy and copied storage, so after dst = __pypy__.newlist_hint(5); src = __pypy__.newlist_hint(9); dst[:] = src, the first append should consume the donor's hint of 9, whereas this implementation retains 5; assigning an ordinary empty donor to a Size list similarly should discard its hint. Handle the Empty/Size combinations by adopting the donor rather than returning unchanged.

AGENTS.md reference: AGENTS.md:L223-L226

Useful? React with 👍 / 👎.

@youknowone youknowone changed the title list, _collections: AsciiListStrategy and SizeListStrategy, and a bounded deque repeat list, str, _collections, _pickle: list strategies, percent formatting, a bounded deque repeat, and a value-keyed pickle memo Aug 27, 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/39134f70ecccad7dd498b7ae9480c605ad7f88ea/pyre-object/src/listobject.rs#L1371-L1372
P1 Badge Propagate allocation failure from size-hint promotion

When a caller supplies a representable but impossible hint, such as items = __pypy__.newlist_hint(sys.maxsize); items.append(1), this new capacity flows into IntArray::with_capacity, whose allocator invokes handle_alloc_error rather than returning a Python error. The Python process therefore aborts instead of raising MemoryError; the float, bytes, ASCII, and object promotion arms have the same problem. Make the hinted allocation fallible and propagate MemoryError through the append path.

AGENTS.md reference: AGENTS.md:L225-L226


https://github.com/youknowone/pyre/blob/39134f70ecccad7dd498b7ae9480c605ad7f88ea/pyre-object/src/listobject.rs#L233-L234
P2 Badge Synchronize the shared size-hint cell

After y = x.copy() on a SizeListStrategy list, both lists deliberately share this state block but have distinct list locks. If one thread calls __pypy__.resizelist_hint(x, n) while another appends to or queries y, this plain i64 store races the plain load in sizehint_state_value, causing undefined behavior in the free-threaded build. Store the shared cell atomically or protect it with synchronization shared by every clone.

AGENTS.md reference: AGENTS.md:L159-L161


https://github.com/youknowone/pyre/blob/39134f70ecccad7dd498b7ae9480c605ad7f88ea/pyre-interpreter/src/builtins.rs#L15989-L15990
P2 Badge Preserve SizeListStrategy in no-key sort

For items = __pypy__.newlist_hint(13); items.sort(), PyPy dispatches to the inherited EmptyListStrategy.sort no-op, so the Size strategy and hint remain available for the first append. This fast-path chain handles the other non-object strategies but not Empty/Size, so the generic path clears and reinitializes the list as Empty; the next append consequently allocates the default capacity instead of 13. Add the Empty/Size no-op alongside the strategy sort dispatch.

AGENTS.md reference: AGENTS.md:L225-L226

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

… map before reporting its strategy

`deque_repeat` and `W_Deque.__imul__` accumulated the whole product in a
`Vec` and trimmed it to `maxlen` once at the end, so a bounded deque
times a large count allocated the untrimmed length.  Both now trim after
every round, mirroring the `append`/`trimleft` pair behind
`W_Deque.mul`'s `extend`, and take the overflow edge from `ovfcheck`'s
machine-signed multiplication rather than from the `usize` accumulator.

`mapdict_strategy_repr` returned nothing for an instance whose map had
not been installed yet, so `__pypy__.strategy()` raised TypeError for a
freshly constructed object; `user_setup` installs the terminator while
building the instance.

extra_tests/snippets/stdlib_collections_deque_repeat.py covers the
bounded repeat, the short-circuiting counts, and the overflow edge.

Assisted-by: Claude
`memoize` recorded only the wrapper's identity and `save` resolved the memo
by pointer identity against a freshly read memo-list element.  An unboxing
list strategy stores the erased rpython string and wraps a fresh object per
read, so two slots naming one string reach the memo as different objects:
`data = [str(i) for i in range(257)]; data.append(data[-1])` pickled its
repeated element twice instead of emitting a GET, and unpickling produced
two objects where one is expected (`test.test_pickletools`
`test_optimize_long_binget`, `PASS -> FAIL` since the AsciiListStrategy
port).

`interp_pickle.py` keeps `str_memo` and `bytes_memo` beside the identity
memo, keyed on `space.utf8_w` / `space.bytes_w`, and `save` consults them
before it.  Port both, seeded from the entries a reused `Pickler` carries
into `dump`.

Assisted-by: Claude
`is_build_class_builtin` was the one `BuiltinCodeFn` comparison still
spelled `std::ptr::fn_addr_eq`; the nine other comparisons in this file go
through `gateway::builtin_code_fn_eq`.

Assisted-by: Claude
Both tests install the same capture-free compile callback and read one
process-global `COMPILES`, and Rust runs sibling tests concurrently, so
one test's reset/run/read interval could observe the other's compiles.
A mutex keeps each interval indivisible.

Assisted-by: Claude

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

https://github.com/youknowone/pyre/blob/84ff43bc1ec1b99a1990e6322b67ad55bebc2c2d/pyre-interpreter/src/module/_pickle/pickler.rs#L1329-L1331
P1 Badge Preserve distinct identities in the pickle memo

When a graph contains two equal but distinct exact strings or bytes, this value-based lookup emits a GET for the second object solely because its contents match the first. For example, round-tripping two separately constructed large equal strings changes result[0] is result[1] from false to true, corrupting the graph's aliasing compared with CPython 3.14; memoization must distinguish separate backing-storage identities while still recognizing wrappers for the same stored object.

AGENTS.md reference: AGENTS.md:L187-L190


https://github.com/youknowone/pyre/blob/84ff43bc1ec1b99a1990e6322b67ad55bebc2c2d/pyre-object/src/listobject.rs#L3820-L3821
P1 Badge Reload the list after dropping ASCII storage

When clearing an Ascii-strategy list while another thread advances GC, UnicodeArray::install drops the outgoing block and its try_gc_owns_object call is a safepoint that can relocate the rooted list. The subsequent strategy, length, and allocation stores then use the stale list reference, so the live list can remain uncleared or retain inconsistent storage; re-read root_base after this install as switch_to_object_strategy already does after each storage teardown.

AGENTS.md reference: AGENTS.md:L159-L161

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

Four wasm fixtures stopped matching their snapshot, which is what
`pyre/check.py dynasm (ubuntu-24.04)` reported (`wasm 4 failed, 490
passed`; dynasm itself 501/501):

  exception_catching_frame_tb_node        bridges_compiled 3 -> 2
  exception_reentry_guard_finally_residual bridges_compiled 12 -> 11
  list_append_virtual_payload             bridges_compiled 7 -> 6,
                                          guard_failures 1403 -> 1203
  gc_bug_bridge_flavor_traceback_names    guard_failures 1054 -> 1038

Only those counters are edited; every other key keeps its recorded value.
The numbers are the ones that run observed on this branch's head.

Assisted-by: Claude

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 008e5766a9

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

}
let action = crate::executioncontext::space_user_del_action();
if !action.is_null() {
pyre_object::gc_hook::try_gc_collect_oldgen();

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 Avoid collecting unrelated finalizers during generator close

Whenever any object anywhere in the process has a registered finalizer, gc_has_pending_finalizers() remains true, so closing or clearing an unrelated generator now performs a whole-old-generation collection and immediately runs every newly discovered __del__. For example, an unreachable finalizable cycle left under gc.disable() will be finalized merely because an unrelated generator is closed; even a reachable long-lived finalizable object causes every generator close to pay for a full heap scan. Neither CPython's frame-local refcount release nor PyPy's frame_is_finished introduces this unrelated collection/callback boundary, so the eager behavior needs to be scoped to objects actually released from this frame rather than using the global finalizer census.

AGENTS.md reference: AGENTS.md:L187-L191

Useful? React with 👍 / 👎.

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