Skip to content

jit: BUILD_TUPLE fold order and three baselines; builtins: type.__new__ arity dispatch and a buffer ljust/rjust fill - #1100

Merged
youknowone merged 4 commits into
mainfrom
buitlins
Aug 7, 2026
Merged

jit: BUILD_TUPLE fold order and three baselines; builtins: type.__new__ arity dispatch and a buffer ljust/rjust fill#1100
youknowone merged 4 commits into
mainfrom
buitlins

Conversation

@youknowone

@youknowone youknowone commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Four commits against main: two JIT, two builtins.

1. check.py is red on main for three benches

#1063 was squash-merged at the commit that restored
try_walker_specialize_newtuple_object's arity-2 decline, but the baseline
re-record lived in the commit after it. main therefore carries the decline in
its code and the pre-decline numbers in its baselines:

bench recorded measured
binary_int_overflow_local_resume 6 / 686 5 / 647
exc_bridge_entry_guard_not_removed 5 / 1009 4 / 809
list_append_write_barrier_gc 6 / 1562 5 / 1345

(bridges_compiled / guard_failures, identical on all three backends.)

The measured values are byte-for-byte what these files held before the arity-2
virtualization landed: the extra bridges were that fold's
mixed-representation side exits, and declining it again removed them. The
re-record restores those counters and keeps the fbw_* / field_pos_* keys
the branch added.

The same commit puts try_walker_specialize_newtuple back ahead of
try_walker_specialize_newtuple_object in residual_call, which is the order
every comment in both functions describes. With arity 2 declined the two arms
cover disjoint arities, so the order no longer decides which claims a pair —
this removes a deviation, not a behaviour. A paragraph records what a side
exit does if the decline is ever lifted: the trace hands a real pair, with
inline value0 / value1 and no wrappeditems block, to a consumer picked
for the canonical layout, and try_walker_specialize_subscr_specialised_pair
reads a field that is not there.

2. The single-callee-frame vable promote guard

walker_capture_inline_nonstandard_vable_guard published the callee's own
resume coordinate only when the paused-caller chain covered the full inline
depth (n_parents > 0 && n_parents == n_callees). A chain of one callee frame
and no paused caller is covered by the frame list just as directly, so it takes
the same publish; every other shape still falls through to the single-frame
sentinel, which is the shape that cannot resume wrong.

walker_capture_multi_frame_inline_snapshot reaches
publish_outermost_parent_vable_scalars with an empty parent_frames on that
new shape, so the call is made only when there is a parent to publish.

3. type.__new__ dispatched on a str scan instead of the argument count

type_descr_new located (name, bases, dict) by scanning the positional
arguments for the first str with two more behind it, then took the metatype
to be the last preceding type object. Nothing on that path called
_precheck_for_new, and it returned before the arm that does, so
type.__new__(42, 'A', (), {}) created a class where pypy raises
X is not a type object (int). Two of the eight measured rows were a wrong
answer rather than a wrong message.

descr__new__ (typeobject.py:886-911) switches on the count and nothing else:
w_typetype = pos[0], arguments_w = &pos[1..], count first — touching
w_typetype only to word the refusal — then _precheck_for_new, then the
one-argument type(x) case, then _create_new_type. That is what this
implements. Three things the port needed beyond the switch:

  • pos.is_empty(). Upstream's gateway supplies w_typetype from a declared
    parameter, so descr__new__ never sees that shape. pyre reads it out of the
    same flat slice and refuses it here, in tp_new_wrapper's words.
  • The _ast caller. module/_ast/moduledef.rs built [name, bases, dict]
    with no metatype and relied on the scan matching at index 0; a count-first
    port silently reinterprets that as w_typetype = name. It now passes
    crate::typedef::w_type(). _ctypes/metaclass.rs already forwarded the
    4-argument shape.
  • calculate_metaclass must see the bases as written. pyre substituted
    (object,) for an empty tuple before the winner was computed;
    W_TypeObject.__init__ does it after (bases_w or [space.w_object]). That
    substitution is what reported metaclass conflict for
    type.__new__(int, 'A', (), {}) — it weighed int against type(object),
    where upstream refuses the metatype itself. With a base written out both
    weigh the same thing and already agreed, so only the empty tuple diverged.

int is not a subtype of type has no producer in descr__new__: it comes from
W_TypeObject.check_user_subclass (typeobject.py:555-567), which
space.allocate_instance(W_TypeObject, w_typetype) runs on the way in.

The header comment justifying the scan claimed super() may prepend a bound
receiver, giving [self, metatype, name, bases, dict]. It does not.
super().__new__(mcls) inside a metaclass raises
M.__new__() takes exactly 3 arguments (1 given)pos == [M] — identical to
pypy3 character for character; prepended it would be [type, M] and would have
returned <class 'type'>. pyre's descriptor kind is also right:
type.__dict__['__new__'] is a builtin_function_or_method whose __self__ is
the owning type, in pyre and CPython alike. The calling convention needed no
change.

Also here: bool(1, 2) reported got more, the only instance of that wording
in the tree; it now carries the gateway's count wording.

4. A buffer ljust / rjust fill operand

pad_fillchar's own comment already named the gap ("the decode itself is not
imported"). convert_arg_to_w_unicode (unicodeobject.py:175-184) sends a
non-str operand through decode_objectspace.bufferstr_w + strict utf-8,
so 'x'.ljust(5, bytearray(b'-')) pads. The decode is strict, so
bytearray(b'\xff') raises UnicodeDecodeError rather than being refused by
type, and a multi-byte fill is rejected by its code-point count, not its byte
count. center takes space.utf8_w and is unchanged.

Coverage

extra_tests/parity_tests/builtin_new_argument_count.py and
str_padding_fill_operand.py. Both branch on sys.implementation.name, which
lets one fixture pin both sides of a deliberate divergence — CPython 3.14
dropped the one-argument type.__new__ form and refuses a buffer fill, and
those rows are asserted under the cpython arm. Both fixtures pass under
pypy3 unmodified, which is what proved the target wording before any Rust was
written.

A 44-row three-way matrix over these shapes now has zero rows matching
neither reference.

Rebased onto d936eb4

One conflict. #1097 deleted widened_method_foriter_admissible outright, along
with method_form_callee_body_supported and
InlineBodyFacts::method_form_supported — the type-receiver attribute read
folds now, so the proof that gate demanded is no longer what admits the callee.
The commit that fixed how the gate read its operand had nothing left to fix;
main's deletion is taken and the method_form_receiver binding it introduced is
dropped with it. That commit now carries only the resume_snapshot.rs change
described in §2 and is worded for it.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This change updates JIT tuple specialization, method receiver checks, inline snapshot publication, and benchmark statistics for blackhole adoption and store-journal rollback failures.

Changes

JIT dispatch updates

Layer / File(s) Summary
Tuple specialization order and documentation
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
Arity-2 plain-int tuple specialization now runs before the canonical array-backed path. Comments describe the arity-2 exclusion and concrete shadow construction.
Method receiver preservation
pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
Method-form calls preserve the receiver before vararg packing. FOR_ITER checks use the preserved receiver and pass its object reference to is_type.
Inline snapshot publication
pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
Parentless single-callee frames and fully covered paused-caller chains now publish multi-frame snapshots. Parent scalar publication is skipped when no parents exist.
Blackhole and rollback statistics
pyre/bench/synth/*.jitstats
Cranelift, DynASM, and Wasm benchmark statistics add counters for single-frame adoption, multi-frame adoption, and failed store-journal rollback.

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

Possibly related PRs

Poem

I’m a small rabbit hopping through code,
Counting blackholes along the road.
Tuples choose paths, snapshots align,
Receivers stay clear, and counters shine.
Thump-thump—JIT updates are fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title correctly mentions BUILD_TUPLE fold order and three baselines, but it also claims unrelated type.new and buffer ljust/rjust changes. Remove the unrelated type.new and buffer ljust/rjust claims, and mention the FOR_ITER receiver guard fix instead.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 buitlins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83a63e10ae

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

assert!(instance_node_setdictvalue(obj_ref, &sur, sentinel(0x55)));

let w_dict = pyre_object::w_dict_new_with(&MAP_DICT_STRATEGY_REF, obj_ref as *mut u8);
let w_dict = pyre_object::w_dict_new_with(&MAP_DICT_STRATEGY, obj_ref 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.

P1 Badge Restore the DictStrategyRef holder in this test

When the pyre-interpreter test target is compiled, this passes &MapDictStrategy to w_dict_new_with, whose signature requires &'static DictStrategyRef (pyre-object/src/dictmultiobject.rs:1316); MapDictStrategy has no conversion to that holder, so cargo test --features dynasm fails with a mismatched-type error before running the tests. Keep passing &MAP_DICT_STRATEGY_REF.

AGENTS.md reference: AGENTS.md:L236-L238

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5efa960e1

ℹ️ 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 +190 to +191
let publish_inline_frames =
(n_parents == 0 && n_callees == 1) || (n_parents > 0 && n_parents == n_callees);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the caller in one-callee resume snapshots

When an inline sub-walk has one callee but no captured parent, this new arm calls the multi-frame publisher with an empty parent_frames, so the snapshot contains only the callee. On guard failure, however, eval.rs:11038-11057 and eval.rs:11450-11469 unconditionally interpret frames[0] as the outer JIT-driver frame, restore its values into the physical virtualizable, and obtain its code from the caller's vable data; thus a failure of this promote guard restores callee locals under the caller's code and omits the actual caller continuation, causing corrupt locals/namespace or a bad resume. Keep declining to the caller boundary until the caller frame can also be encoded.

AGENTS.md reference: AGENTS.md:L24-L30

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

🤖 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-jit-trace/src/jitcode_dispatch/inline_call.rs`:
- Around line 2674-2680: Replace the pre-packing raw `method_form_receiver`
concrete snapshot with the receiver’s `OpRef` from the recorded call arguments
before vararg packing. After `w_tuple_new_array_backed` completes, resolve that
`OpRef` through `concrete_from_recorded_opref` or the equivalent `ctx.trace_ctx`
lookup, reject tagged immediates, and only then pass the forwarded object to
`pyre_object::is_type` in the FOR_ITER admission check.

In `@pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs`:
- Around line 4933-4955: Run cargo check --features dynasm and cargo test
--features dynasm after the newtuple specialization paths in
try_walker_specialize_newtuple and try_walker_specialize_newtuple_object.
Execute all eight required benchmarks, record their results, and explain any
regressions before committing; do not revert parity-correct code solely due to a
measured regression.
🪄 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: 94cb4fe1-9633-4749-a726-573ce734df69

📥 Commits

Reviewing files that changed from the base of the PR and between 128590c and e5efa96.

📒 Files selected for processing (13)
  • pyre/bench/synth/binary_int_overflow_local_resume.cranelift.jitstats
  • pyre/bench/synth/binary_int_overflow_local_resume.dynasm.jitstats
  • pyre/bench/synth/binary_int_overflow_local_resume.wasm.jitstats
  • pyre/bench/synth/exc_bridge_entry_guard_not_removed.cranelift.jitstats
  • pyre/bench/synth/exc_bridge_entry_guard_not_removed.dynasm.jitstats
  • pyre/bench/synth/exc_bridge_entry_guard_not_removed.wasm.jitstats
  • pyre/bench/synth/list_append_write_barrier_gc.cranelift.jitstats
  • pyre/bench/synth/list_append_write_barrier_gc.dynasm.jitstats
  • pyre/bench/synth/list_append_write_barrier_gc.wasm.jitstats
  • pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs

Comment on lines +2674 to +2680
// The receiver a method-form call passes as its first positional argument.
// Read before the vararg packing below, which folds that argument into the
// surplus tuple whenever the callee has no positional parameter to hold it
// and so leaves the first seeded local holding the tuple instead. The
// FOR_ITER admission gate further down asks whether the receiver is a type
// object, and a tuple would answer that question for the wrong object.
let method_form_receiver = callee_arg_concretes.first().copied();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Do not keep the receiver as a raw pointer across tuple allocation.

When vararg packing runs, pyre_object::w_tuple_new_array_backed allocates a tuple at Line [2715]. That allocation can move a nursery object. method_form_receiver stores the pre-forwarding ConcreteValue::Ref, and Line [2981] uses it after the allocation. The pyre_object::is_type call can then read a stale pointer.

Save the receiver OpRef before packing. Resolve its forwarded concrete value after packing through concrete_from_recorded_opref or the equivalent ctx.trace_ctx lookup. Reject tagged immediates before calling pyre_object::is_type; the tuple specializer documents that they have no real object header.

Use the forwarding channel for the receiver
-    let method_form_receiver = callee_arg_concretes.first().copied();
+    let method_form_receiver_op = callee_args.first().copied();
...
-        || method_form_receiver.is_some_and(|concrete| {
+        || method_form_receiver_op
+            .map(|opref| concrete_from_recorded_opref(ctx, opref))
+            .is_some_and(|concrete| {
             matches!(
                 concrete,
                 ConcreteValue::Ref(receiver)
-                    if !receiver.is_null() && !unsafe { pyre_object::is_type(receiver) }
+                    if !receiver.is_null()
+                        && !pyre_object::tagged_int::is_tagged_int(receiver)
+                        && !unsafe { pyre_object::is_type(receiver) }
             )
         });

Also applies to: 2977-2981

🤖 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-jit-trace/src/jitcode_dispatch/inline_call.rs` around lines 2674 -
2680, Replace the pre-packing raw `method_form_receiver` concrete snapshot with
the receiver’s `OpRef` from the recorded call arguments before vararg packing.
After `w_tuple_new_array_backed` completes, resolve that `OpRef` through
`concrete_from_recorded_opref` or the equivalent `ctx.trace_ctx` lookup, reject
tagged immediates, and only then pass the forwarded object to
`pyre_object::is_type` in the FOR_ITER admission check.

Comment thread pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 3bd7873).
Updated: 2026-08-07T14:14:26.337Z

Files in the reviewed diff
pyre/extra_tests/parity_tests/builtin_new_argument_count.py
pyre/extra_tests/parity_tests/str_padding_fill_operand.py
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/module/_ast/moduledef.rs
pyre/pyre-interpreter/src/type_methods.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-interpreter/src/builtins.rs:5415 ↔ pypy/objspace/std/typeobject.py:959: the new inline substitute for space.allocate_instance(W_TypeObject, w_typetype) checks only issubtype_w(meta, type). PyPy’s allocation calls W_TypeObject.check_user_subclass, which also rejects an uninitialized metatype and incompatible layout (typeobject.py:555-571). The new path therefore omits two required checks; it should call the existing complete typedef::check_user_subclass(w_type(), w_metaclass) rather than duplicate only its subtype arm.

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

  • pyre/pyre-interpreter/src/typedef.rs:4367 ↔ pypy/objspace/std/typeobject.py:560: the pre-existing check_user_subclass omits PyPy’s if not w_subtype.layout rejection: “uninitialized type … may not be instantiated yet.” This is the helper the patch should reuse, so its omission remains relevant to the new type-construction path.

4. Structural adaptations

  • pyre/pyre-interpreter/src/builtins.rs:4908 ↔ pypy/objspace/std/typeobject.py:886: PyPy receives w_typetype as a gateway parameter; pyre’s Rust builtin ABI supplies one positional slice, so extracting pos[0] and treating pos[1..] as __args__.arguments_w is an ABI adaptation.

  • pyre/pyre-interpreter/src/module/_ast/moduledef.rs:174 ↔ pypy/objspace/std/typeobject.py:886: the direct Rust _ast heap-type builder must explicitly supply w_type() because it bypasses PyPy’s descriptor gateway. This is required by the preceding ABI adaptation.

  • pyre/pyre-interpreter/src/typedef.rs:3453 ↔ pypy/objspace/std/boolobject.py:41: reporting the total positional count, including cls, follows pyre’s CPython-compatible 3.14 constructor-wrapper convention; PyPy’s @unwrap_spec gateway owns that count outside descr_new.

  • pyre/pyre-interpreter/src/type_methods.rs:5206 ↔ pypy/objspace/std/unicodeobject.py:1727: simple_buffer_bytes plus explicit release is the Rust ownership/borrow equivalent of PyPy’s space.bufferstr_w; the patch preserves the relevant decode_object(..., 'utf8', 'strict') behavior.

  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs:190 ↔ rpython/jit/metainterp/opencoder.py:819: pyre reconstructs snapshots from compiler-generated JitCode coordinates and explicit paused-frame metadata, whereas RPython reads live MIFrame objects. The single-frame/empty-parent branch is therefore a representation adaptation, not a semantic deviation.

…ding

`try_walker_specialize_newtuple` runs before
`try_walker_specialize_newtuple_object` again, and the two dispatch comments
in `residual_call` go back to describing that order.  With arity 2 declined
in the canonical fold the two arms cover disjoint arities, so the order no
longer decides which one claims a pair.

`try_walker_specialize_newtuple_object`'s header and the concrete-shadow
comment go back to naming arity 2 as declined.  A paragraph records what a
side exit does when the decline is lifted: the trace hands a real pair, with
inline `value0` / `value1` and no `wrappeditems` block, to a consumer picked
for the canonical layout, and `try_walker_specialize_subscr_specialised_pair`
reads a field that is not there.

Assisted-by: Claude
`walker_capture_inline_nonstandard_vable_guard` published the callee's own
resume coordinate only when the paused-caller chain covered the full inline
depth (`n_parents > 0 && n_parents == n_callees`).  A chain of one callee
frame and no paused caller is covered by the frame list just as directly, so
it takes the same publish; every other shape still falls through to the
single-frame sentinel.

`walker_capture_multi_frame_inline_snapshot` reaches
`publish_outermost_parent_vable_scalars` with an empty `parent_frames` on that
new shape, so the call is made only when there is a parent to publish.

Assisted-by: Claude
`type_descr_new` located `(name, bases, dict)` by scanning the positional
arguments for the first `str` with two more behind it, and took the metatype as
the last preceding type. `descr__new__` (typeobject.py:886-911) keys the
one-versus-three form on `len(__args__.arguments_w)` instead, runs
`_precheck_for_new` unconditionally after that decision, and only then reads the
name. The scan reached `type_descr_new_with_metaclass` before the branch that
prechecks, so a non-type metatype was dropped rather than refused and
`type.__new__(42, 'A', (), {})` built a class.

The scan's header cited a `[self, metatype, name, bases, dict]` shape from
`super()`. No receiver is prepended: `super().__new__(mcls)` inside a metaclass
raises `M.__new__() takes exactly 3 arguments (1 given)`, which is the one-element
`pos` arm, and `super().__new__ is type.__new__` holds with `__self__` the type.
`pos[0]` is always the metatype.

`pos` being empty is a shape upstream's gateway cannot produce, since it supplies
`w_typetype` from a declared parameter; it is refused here in `tp_new_wrapper`'s
words rather than through the three-argument message.

`_calculate_metaclass` (typeobject.py:945) now receives the bases as written.
`(object,)` is substituted for an empty tuple in `W_TypeObject.__init__`
(`bases_w or [space.w_object]`), after the winner is settled; supplying it earlier
weighed an explicit metatype against `type(object)` and reported a metaclass
conflict for `type.__new__(int, 'A', (), {})`. The winner then passes
`check_user_subclass` (typeobject.py:555-567), which `allocate_instance` runs on
the way in and which is what names `int is not a subtype of type`.

`_ast`'s heap-type creation passed `[name, bases, dict]` and relied on the scan
matching at index 0; it now passes the metatype. The `_ctypes` metaclasses
already forwarded `[metatype, name, bases, dict]`.

`bool.__new__`'s over-arity refusal reported `got more`, the only such wording in
the tree; the gateway counts the class argument along with the value.

Assisted-by: Claude
`pad_fillchar` refused every non-`str` fill, and its own header recorded the
gap: "The decode itself is not imported". `descr_ljust` and `descr_rjust`
(unicodeobject.py:1352,1371) convert the operand with
`convert_arg_to_w_unicode` (unicodeobject.py:175-184), which declines `bytes`
by name and hands everything else to `decode_object`
(unicodeobject.py:1727-1739); that reads the operand as a buffer and decodes it
strict UTF-8, so a `bytearray` or `memoryview` becomes a fill character.
`descr_center` (unicodeobject.py:1101) reads its operand with `space.utf8_w`
and is unchanged.

The `"decoding to str: ..."` arm now reports only an operand that exports no
buffer at all, which is what `decode_object` maps the buffer error to. A buffer
whose bytes are not valid UTF-8 raises from the decode. The single-character
check applies to the decoded operand, so a multi-byte fill is rejected by its
code-point count.

Assisted-by: Claude
@youknowone youknowone changed the title jit: restore the BUILD_TUPLE fold order, re-record three baselines, and fix the FOR_ITER receiver guard jit: BUILD_TUPLE fold order and three baselines; builtins: type.__new__ arity dispatch and a buffer ljust/rjust fill Aug 7, 2026
@youknowone
youknowone merged commit 40257cf into main Aug 7, 2026
13 of 17 checks passed
@youknowone
youknowone deleted the buitlins branch August 7, 2026 16:13
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