Skip to content

Descend pos_inner for +x, and key a virtual's class word off the layout - #1561

Open
youknowone wants to merge 8 commits into
mainfrom
manual-trace
Open

Descend pos_inner for +x, and key a virtual's class word off the layout#1561
youknowone wants to merge 8 commits into
mainfrom
manual-trace

Conversation

@youknowone

Copy link
Copy Markdown
Owner

What is here

Five commits: a w_class slot fix in the optimizer, an assembler predicate follow-up, and the +x descent that replaces the hand-written unary_positive_int fold as the primary arm.

majit: key a virtual's class-word store off the layout, not off the op's descr

optimize_setfield_gc recorded PyObject.w_class on a virtual at the index_in_parent its own descr carried. The tracer stores through a shared header descr whose index is 0, which on W_BaseException happens to name the class word and on W_LongObject names the payload at offset 16.

Reading the slot from the layout instead — SizeDescr::class_word_index_in_parent, defined against all_fielddescrs, the list force_box reads the vector back through — is right for both. A layout that keeps its class word out of that list answers None and takes the previous arm.

b67e8abdb8d is red without this: pyre/check.py --backend dynasm reported 78 failed / 433 passed with 46 fixtures panicking in force_box on the key 0x80000008. With the fix it is 510 passed, and the two remaining are pre-existing (below).

structinfo_setfield also declines a header word on a virtual: its key comes from heap::OptHeap::field_slot_index's band, which names no position at all.

majit: match the explicit-shell variant owners against the whole path

Review follow-up from #1518. ends_with("result::Result::Ok") also accepts a user enum at mycrate::option::Option::Some, which front::option_ctor never gives a shell to. Matching segment by segment against a core/std root keeps every spelling front/mir produces and rejects the lookalike; a test enumerates the twelve accepted and six rejected spellings.

The +x descent

OpcodeStepExecutor::unary_positive carried a default body. A trait method with a default body is what the graph walker resolves to, so PyFrame's implementation — and with it pos — was never reached, and +x compiles to CALL_INTRINSIC_1, which makes this method the whole route. Declaring it required opened the chain:

graph before after
unary_positive 18 (the stub body) 11
unary_positive_value 0 11
pos 0 47
pos_inner 0 440

pos_inner is pos past its __pos__ override probe, which is dont_look_inside and is the second operation the body executes. The descent then fires 2/2 and 1/1 and takes the site entirely (unary_positive_int consulted=0). The fold stays behind it for a build whose pos_inner jitcode is absent or unlowered.

Gate

pyre/check.py --backend dynasm: 2 failed, 510 passed. cargo fmt --all -- --check, scripts/check-majit-boundary.py and pyre/check.py --check-headers are clean; majit-metainterp (1704) and majit-translate (3326) unit tests pass.

Both remaining failures are pre-existing and not this branch's. Built at the merge base 903151ee679 with none of this branch's commits, the same host reproduces them exactly:

fixture at 903151ee679 at this branch's tip
exception_escape_hot_callee_tb_node_once guard_failures 808 -> 1015 identical
exception_traceback_frame_lineno bridges_compiled 6 -> 4, guard_failures 825 -> 624 identical

903151ee679 is the commit that recorded both snapshots, so the committed numbers come from a different host. Re-recording them here would write this machine's numbers into a shared snapshot, so both are left alone.

commented by Claude

…ion sites that erased it

`ResidualSlot` / `ResidualRet` and the `p*` / `cp*` / `pa*` / `cpa*` publishers
already reject a helper the residual-call ABI cannot describe, and
`push_abi_unsound_*` names the ones it cannot. Three sites reached
`push_raw_fnaddr` without either, so nothing read their signatures.

- `PYRE_TYPE_OBJECT_FNADDRS` carries each accessor as `fn() -> PyObjectRef`,
  so publish through `p0` rather than casting to `*const ()` first.
- The twelve bigint `int_*` arithmetic and comparison helpers are
  `extern "C" fn(i64, i64) -> JitBigIntResult` and `-> i64`; publish them
  through `cp2`, which their five neighbours in the same block already used.
- The six arity-to-address accessors in `runtime_ops` select a helper by a
  runtime count and must erase the signature to return it. `word_fn_addr!`
  ascribes the fn item to an explicit `extern "C" fn(i64, ..) -> i64` before
  the erasure, so the check runs where the type still exists. Writing the
  wrong machine-argument count is the same build error as a helper drifting
  off the word ABI, which is how the check is exercised. `push_alias_pair`
  becomes `push_word_accessor_alias_pair` and records that its address
  arrives already checked.

`push_raw_fnaddr` now lists the three kinds of caller that may reach it.

The published set is unchanged: 1326 entries over 971 distinct addresses,
with the same paths and the same address grouping before and after.

Assisted-by: Claude
…swering void

`getkind` raises for a type it cannot classify.  Both places pyre projects a
`ConcreteType` onto a kind character answered `'v'` for `Unknown`, which is
the kind of an absent argument and of a function returning nothing: the
start-block inputarg projection would describe an argument the callee reads
as not passed, and `graph_result_kind` would tell callers to discard a result
the callee produces.  Neither is a build error, so the mismatch would arrive
as a wrong register count at trace-time call.

`Unknown` is also the state a `Variable` carries before the rtyper sets its
`concretetype` cell, so what reaches these two sites unresolved is a
front-end gap rather than a type outside the kind space.

Measured over the built image before changing anything, by recording every
arm both sites take: 2948 graphs, arguments 3883 `r` / 498 `i` / 7 `f` /
0 `v`, results 4096 `r` / 906 `i` / 552 `v` / 30 `f`, and the `Unknown` arm
taken zero times at either site.  `result_kind_tests` keeps the refusal armed
from a graph still carrying the pre-rtyper `returnvar`, with a resolved-type
control beside it.

Assisted-by: Claude
…p's descr

`optimize_setfield_gc` recorded `PyObject.w_class` on a virtual at the
`index_in_parent` its own descr carried.  The tracer stores through a shared
header descr whose index is 0, which on `W_BaseException` happens to name the
class word and on `W_LongObject` names the payload at offset 16.

Read the slot from the layout instead, through
`SizeDescr::class_word_index_in_parent`, which is defined against
`all_fielddescrs` -- the list `force_box` reads the vector back through.  A
layout that keeps its class word out of that list answers `None` and takes the
previous arm: removable when the allocation already writes that class,
otherwise emitted after forcing.

`structinfo_setfield` declines a header word on a virtual.  Its key comes from
`heap::OptHeap::field_slot_index`'s band, which names no position, and
`force_box` resolves that key to nothing.

`VirtualInfo::fields` states the invariant as "every key is a slot of
`all_fielddescrs()`" and names the two keys that are not, and `force_box`
prints the failing key and the list it was looked up in.

Assisted-by: Claude
`bh_size_spec_from_callcontrol` decided the explicit `[tag@0 | payload@8]`
shell with `ends_with("result::Result::Ok")` and two siblings, which also
accepts a user enum declared at `mycrate::option::Option::Some`.

Match the owner segment by segment as a suffix of a `core`- or `std`-rooted
path, so every spelling `front/mir` produces still resolves and a lookalike
rooted elsewhere does not.  A test enumerates the twelve accepted spellings and
six rejected ones.

Assisted-by: Claude
…e_value

`pos_inner` is `pos` past its `__pos__` override probe, whose
`needs_numeric_unaryop_dispatch` is `dont_look_inside` and is the second
operation the body executes.  `inline(never)` keeps it a graph of its own;
without it rustc folds the body into `unary_positive_value`.

`jit_unary_positive_value` joins its invert and negative siblings in
`jit_trace_fnaddrs`.

Assisted-by: Claude
The method carried a default returning `type_error("unary_positive not
implemented")`.  A trait method with a default body is what the graph walker
resolves to, so `PyFrame`'s implementation -- and with it `pos` -- was never
reached; `+x` compiles to `CALL_INTRINSIC_1`, which makes this method the whole
route.  Declaring it required leaves the single implementation as the
resolution.

`PyFrame::unary_positive` now routes through `opcode_ops::unary_positive_value`,
matching its `list_to_tuple` sibling.

Assisted-by: Claude
`try_walker_orthodox_unary_positive` walks the `pos_inner` body behind a
`GUARD_CLASS`, the shape the invert and negative descents take.  It sits ahead
of the `unary_positive_int` fold, so that fold's `consulted` count reads whether
the descent took the site; the fold stays as the arm for a build whose
`pos_inner` jitcode is absent or unlowered.

`pos_inner_jitcode` resolves the body by graph key and caches it per thread.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 24 minutes.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e62f8b76-dd52-4b1f-8b30-9a7b1aae7a84

📥 Commits

Reviewing files that changed from the base of the PR and between 429f64f and 3be4e02.

📒 Files selected for processing (23)
  • majit/majit-ir/src/ptr_info.rs
  • majit/majit-metainterp/src/optimizeopt/info.rs
  • majit/majit-metainterp/src/optimizeopt/mod.rs
  • majit/majit-metainterp/src/optimizeopt/virtualize.rs
  • majit/majit-translate/src/annotator/builtin.rs
  • majit/majit-translate/src/codewriter/assembler.rs
  • majit/majit-translate/src/codewriter/codewriter.rs
  • majit/majit-translate/src/flowspace/model.rs
  • majit/majit-translate/src/model.rs
  • majit/majit-translate/src/runtime_names.rs
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/jit_fnaddr.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/opcode_ops.rs
  • pyre/pyre-interpreter/src/pyopcode.rs
  • pyre/pyre-interpreter/src/runtime_ops.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/diag.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
  • pyre/pyre-jit-trace/src/jitcode_runtime.rs
  • pyre/pyre-object/src/longobject.rs

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: 3be4e0206a

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

&& matches!(
segments[segments.len() - 1].as_str(),
"malloc_typed" | "malloc_typed_managed"
"malloc_typed" | "malloc_typed_managed" | "malloc_typed_stable"

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 stable allocation when fusing allocator calls

Do not treat malloc_typed_stable as equivalent to the movable allocators here. For any traced allocate_stable/malloc_typed_stable constructor matching this generic aggregate pattern—including the newly traced W_LongObject path—the pass replaces the call with NewWithVtable, while the GC rewriter uses the nursery unless the independently built SizeDescr is marked non_moving; this predicate does not propagate that requirement. Such objects are explicitly allocated stable because raw object pointers or promoted constants must survive collections, so lowering them to movable allocations can leave stale pointers after relocation. Either retain the residual stable allocator or carry the non-moving property into the generated allocation descriptor.

AGENTS.md reference: AGENTS.md:L29-L32

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 3be4e02).
Updated: 2026-08-28T14:37:12.575Z

Files in the reviewed diff
majit/majit-ir/src/ptr_info.rs
majit/majit-metainterp/src/optimizeopt/info.rs
majit/majit-metainterp/src/optimizeopt/mod.rs
majit/majit-metainterp/src/optimizeopt/virtualize.rs
majit/majit-translate/src/annotator/builtin.rs
majit/majit-translate/src/codewriter/assembler.rs
majit/majit-translate/src/codewriter/codewriter.rs
majit/majit-translate/src/flowspace/model.rs
majit/majit-translate/src/model.rs
majit/majit-translate/src/runtime_names.rs
pyre/pyre-interpreter/src/eval.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-interpreter/src/objspace/descroperation.rs
pyre/pyre-interpreter/src/opcode_ops.rs
pyre/pyre-interpreter/src/pyopcode.rs
pyre/pyre-interpreter/src/runtime_ops.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/diag.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
pyre/pyre-jit-trace/src/jitcode_runtime.rs
pyre/pyre-object/src/longobject.rs

Codex did not produce a report (exit 1). Last log lines:


Under each heading, list every finding as a bullet. For each finding cite the
concrete `our_file.rs:line ↔ rpython_or_pypy_file.py:line` pair and quote the
divergence concisely. If a section has no findings, still emit the heading
followed by a single line `None.` so all four sections are always present.
Do not modify any files; produce the report only.

Authoritative changed-file list for this patch (git diff upstream/main --name-only,
minus 0 generated `*.jitstats` baseline file(s)):
majit/majit-ir/src/ptr_info.rs
majit/majit-metainterp/src/optimizeopt/info.rs
majit/majit-metainterp/src/optimizeopt/mod.rs
majit/majit-metainterp/src/optimizeopt/virtualize.rs
majit/majit-translate/src/annotator/builtin.rs
majit/majit-translate/src/codewriter/assembler.rs
majit/majit-translate/src/codewriter/codewriter.rs
majit/majit-translate/src/flowspace/model.rs
majit/majit-translate/src/model.rs
majit/majit-translate/src/runtime_names.rs
pyre/pyre-interpreter/src/eval.rs
pyre/pyre-interpreter/src/jit_fnaddr.rs
pyre/pyre-interpreter/src/objspace/descroperation.rs
pyre/pyre-interpreter/src/opcode_ops.rs
pyre/pyre-interpreter/src/pyopcode.rs
pyre/pyre-interpreter/src/runtime_ops.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/diag.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
pyre/pyre-jit-trace/src/jitcode_runtime.rs
pyre/pyre-object/src/longobject.rs
warning: Codex could not find bubblewrap on PATH. Install bubblewrap with your OS package manager. See the sandbox prerequisites: https://developers.openai.com/codex/concepts/sandboxing#prerequisites. Codex will use the bundled bubblewrap in the meantime.
2026-08-28T14:37:10.473022Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.523119Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.587548Z ERROR rmcp::transport::worker: worker quit with fatal: Transport channel closed, when UnexpectedServerResponse("HTTP 401: {\n  \"error\": {\n    \"message\": \"Provided authentication token is expired. Please try signing in again.\",\n    \"type\": null,\n    \"code\": \"token_expired\",\n    \"param\": null\n  },\n  \"status\": 401,\n  \"detail\": {\n    \"code\": \"token_expired\",\n    \"message\": \"Provided authentication token is expired. Please try signing in again.\"\n  }\n}")
2026-08-28T14:37:10.628423Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.648975Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.650648Z ERROR rmcp::transport::worker: worker quit with fatal: Transport channel closed, when UnexpectedServerResponse("HTTP 401: {\n  \"error\": {\n    \"message\": \"Provided authentication token is expired. Please try signing in again.\",\n    \"type\": null,\n    \"code\": \"token_expired\",\n    \"param\": null\n  },\n  \"status\": 401,\n  \"detail\": {\n    \"code\": \"token_expired\",\n    \"message\": \"Provided authentication token is expired. Please try signing in again.\"\n  }\n}")
2026-08-28T14:37:10.672998Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.867462Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 401 Unauthorized, url: wss://chatgpt.com/backend-api/codex/responses
2026-08-28T14:37:10.901780Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.917732Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:10.933815Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.202473Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 401 Unauthorized, url: wss://chatgpt.com/backend-api/codex/responses
2026-08-28T14:37:11.220626Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.345576Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.501371Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.644814Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.793262Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.808894Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:11.826468Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:12.038117Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 401 Unauthorized, url: wss://chatgpt.com/backend-api/codex/responses
2026-08-28T14:37:12.070896Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:12.086151Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:12.102107Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:12.276930Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 401 Unauthorized, url: wss://chatgpt.com/backend-api/codex/responses
ERROR: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
ERROR: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
2026-08-28T14:37:12.299763Z ERROR codex_login::auth::manager: Failed to refresh token: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.

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