Skip to content

VM onto the slot API (#1665 arm D B2) - #1707

Merged
InauguralPhysicist merged 4 commits into
mainfrom
arm-d-B2r2
Oct 11, 2026
Merged

InauguralPhysicist merged 4 commits into
mainfrom
arm-d-B2r2

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Part of #1665: batch B2 of the arm D step-2 design. src/vm.c moves onto the slot API and joins the migrated-files gate. Storage stays Value**.

What changed

  • Reads and writes: list/dict reads, writes, iteration, destructuring, slicing, dispatch and builtin argument-vector assembly go through the slot API.
  • vm_borrow_scan matches only heap slots, and keeps the escaped-view guard.
  • The VM's stack read sites normalise: slot_read_value / slot_read_owned turn a boxed number into an immediate, and the fused version also increfs any other heap value in one branch. list_slot itself stays faithful to storage before the flip, so free_value, the GC clear and the borrow scan still see and release the boxes.
  • Inlining: the slot dict-cache helpers are force-inlined, and dict_value_rewrite_num restores DMG's in-place register-write fast path.
  • Computed-goto handlers use EIGS_VIEW_NOCHECK with an explicit eigs_view_check on every exit edge, because GCC skips cleanup on a computed-goto exit. clang shows no indirect_goto_in_protected_scope. tests/test_borrow_guard.sh plants a moved view in a fused VM handler, and the explicit check aborts.

How it got here

round bench_dmg_shape Ir vs main cause
Codex round 1 +38.4% release list_slot heap-tagged boxed numbers, so every stack push took slot_to_value/make_num; DMG's in-place fast path tested get_ref(...)->type == VAL_NUM, which is never true
decode-always (reverted) +5.5% fixed the speed but leaked boxes (33 ASan leaks) and broke the borrow scan (coalesce double free)
Codex round 2 +0.71% the dict-cache helpers were no longer inlined
final +0.98% storage-faithful list_slot, with normalising stack reads

Every round was measured with cg_diff at function level.

Gates (run locally)

check result
Ir vs main dmg_shape +0.98%, idxset +0.04%, perf −1.46%
tape identity on the trace corpus identical
jit-smoke pass
jit_diff 256 programs pass
container, num-read, shape-contract and changelog gates pass
ASan core sections, detect_leaks=1 218/218, leak tally 0
release suite all green except the pre-existing [99d] failure below
rehearsal suite 4216/4221; the failures are listed below

Rehearsal suite failures:

  • the [99d] failure below;
  • coalesce: builtins.c isn't migrated yet. Under rehearsal, the borrow scan can't match the box the legacy API returns. B3 (arm-d-B3) moves coalesce to list_get_return;
  • [70d] when_live RSS: pre-existing on main under rehearsal, same 123 MB. Filed below.

Pre-existing on main and filed separately:

  • [99d] the binary-fingerprint self-test fails on main too;
  • rehearsal when_live RSS growth.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KC99CmwatKssQYggkBCgwF

xxxxxxxxxxxxx and others added 4 commits October 10, 2026 13:02
…diate via dict_value_set_num

Both fixes for the B2 Ir regression (bench_dmg_shape +38.4% -> +5.5%):
- eigs_container_slot returned a heap-tagged slot for a boxed NUM in
  release builds, so every element read took the slot_to_value/make_num
  slow path. It now decodes to an immediate in every build (the
  rehearsal behaviour), which is what the VM stack expects.
- dict_set_cached_immediate tested dict_value_get_ref(...)->type ==
  VAL_NUM, but get_ref is NULL for a number by contract, so the DMG
  register-write in-place fast path never fired.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC99CmwatKssQYggkBCgwF
…dundant num_guard on normalized slots; computed-goto handlers use EIGS_VIEW_NOCHECK + explicit checks

bench_dmg_shape Ir +0.71% vs main (was +38.4% in round 1), bench_perf -2.25%,
bench_idxset +1.45%. tests/test_borrow_guard.sh plants a moved view in a
fused VM handler and requires the explicit check to abort.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC99CmwatKssQYggkBCgwF
…st and pin fixes

The decode-always fix (an earlier commit on this branch) made every
borrowed read present a boxed number as an immediate. That broke
ownership before the flip: free_value / GC clear skip number slots, so
boxes leaked (33 ASan leaks), and vm_borrow_scan could no longer match
a returned boxed-number child (coalesce double free). Reverted:
list_slot is storage-faithful again (rehearsal still decodes).

The VM read sites that push onto the stack normalise instead:
- slot_read_value: boxed number -> immediate (borrowed);
- slot_read_owned: the same plus incref of a non-number heap value in one
  branch (10 read+incref+push sites fused).
- jit_helper_iter_next pushes the element as a slot instead of
  list_get_owned + vm_push (a make_num/free per iteration).

Ir vs main: bench_dmg_shape +0.98%, bench_idxset +0.04%, bench_perf
-1.46%. ASan core sections 218/218, leak tally 0.

- tests/test_borrow_guard.sh: the VM-view plant program raises in every
  build, so "compiled out" is now detected by comparing against a run
  without the plant (it misreported a failure on release builds).
- num_read allowlist: slot_read_value / slot_read_owned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC99CmwatKssQYggkBCgwF
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T23:35:06.670835Z d0ea882 PR opened
🔒 Security Review ⚠️ Failed 2026-10-10T23:46:43.662231Z d0ea882 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread src/vm.c Dismissed
@codspeed

codspeed Bot commented Oct 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 13 untouched benchmarks


Comparing arm-d-B2r2 (d0ea882) with main (e6ea6da)

Open in CodSpeed

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

ℹ️ 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 thread src/vm.c
Comment on lines +2391 to +2393
EigsSlot item = list_slot(arg, i);
if (slot_is_heap(item) && slot_as_ptr(item) == result) {
val_incref(result); return;

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 numeric child matches in the borrow scan

When EIGS_SLOT_REHEARSAL is enabled, list_slot() converts a boxed numeric child to an immediate slot, so this heap-only comparison cannot recognize a Value * returned directly by a legacy builtin such as coalesce. The caller then decrefs the argument list without adding a result reference, leaving the returned number at refcount zero or on the numeric freelist before it is pushed; this is a use-after-free in the rehearsal build and will carry into the storage flip. Keep a storage-pointer comparison here until all direct-child-returning builtins use the slot return API, or migrate those builtins atomically.

Useful? React with 👍 / 👎.

Comment thread src/container.h
Comment on lines +34 to +38
Value *v = slot_as_ptr(s);
if (v->type == VAL_NUM) return slot_from_num(VAL_NUM_RAW(v));
val_incref(v);
}
return s;

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 Normalize boxed nulls before pushing container reads

When a list or dictionary contains null, this helper leaves its boxed VAL_NULL as a heap slot instead of restoring the immediate-null representation previously produced by vm_push. That leaks through assignments because obs_slot_observable() treats every heap slot as observable: for example, assigning x is xs[0] with xs is [null] makes trajectory of x report "observed": true, while assigning x is null reports false. Normalize heap VAL_NULL here (and in slot_read_value) so container reads do not change observer results based solely on where null came from.

Useful? React with 👍 / 👎.

@InauguralPhysicist
InauguralPhysicist added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit b02d427 Oct 11, 2026
32 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the arm-d-B2r2 branch October 11, 2026 00:59
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.

3 participants