Skip to content

Fix main: island objects as weak keys after #3135, and the runtime refusal baseline after #3137 - #3371

Merged
ctate merged 4 commits into
mainfrom
fix/main-red-weak-keys
Nov 7, 2026
Merged

ctate merged 4 commits into
mainfrom
fix/main-red-weak-keys

Conversation

@cramforce

Copy link
Copy Markdown
Contributor

Main is red since 246041d (the merge of #3325) in six CI jobs, and its "lint and format" job is red since 038fff5 (the merge of #3137). Every PR that takes main inherits both. This PR turns both green; each cause has its own commit.

  1. Since 246041d: tests/harness/node-island-weak-keys.test.ts fails 3 of 3, the corpus programs 8754-node-island-weak-keys and 8850-node-island-weak-cache fail in differential.test.ts and llvm-differential.test.ts, and two guard tests fail (runtime-pack-matrix.test.ts "a unit that tests SCR_NODE_ISLANDS has a build that defines it", runtime-define-sites.test.ts).
  2. Since 038fff5: pnpm lint fails in node scripts/runtime-refusals.mjs --strict with seven refusal sites the baseline does not list. Because lint fails first, CI has skipped format:check, the policy contracts and the generated IR checks behind it on every PR since 21:41 UTC.

1. Island objects as weak keys

Cause

Two PRs that each passed their own CI and merged without a conflict:

On main those blocks are compiled into no program, so a WeakMap, WeakSet or FinalizationRegistry that is given an island object falls through to the older refusal: Weak collection keys of this native reference type have no weak lifetime lowering [SC2020]. This is the third crossing of this kind against #3135 (after #3161 and #3234); the two guard tests exist for it and name scr_json.c, but #3325's CI never ran with #3135 in its base.

Fix

The same as for the earlier two: the blocks leave the #ifdef and call through the table.

  • ScrNiDynOps gets three entries at the end, weak_get, weak_test and weak_set; scr_node_island_handle.c installs scr_ni_weak_get, scr_ni_weak_test and scr_ni_weak_set there. New entries go last because the runtime every program links reads the table by offset.
  • scr_json.c: the weak-collection and FinalizationRegistry paths test the key's island tag and call the entries. A value with that tag exists only in a program that links the island units, so the sites do not test the table for NULL. ScrWeakMap.island is always a member and stays NULL in a program without islands.
  • The rule is unchanged: an island object as a key keeps its entry inside the island; a native value of a kind without a weak lifetime lowering is still refused by name; an island symbol as a FinalizationRegistry target or token is still refused as a native one is.

scr_json.c keeps one build, so #3135's guard tests pass again without changing what they expect.

Test that pins it

runtime-pack-matrix.test.ts, "the installed island table has every entry the shared units call through it": every entry of ScrNiDynOps is installed by nh_dyn_ops in the struct's order, and every scr_ni_dyn_ops->x( call in a runtime unit names an installed entry (the weak entries among them). It needs no libnode pack. A missing initializer line would otherwise be a NULL call that only an island program reaches.

No test can catch this kind of crossing in the second PR's own CI when that CI ran on an older base: the guards fail on the merged tree, which is where main's CI showed them.

Other island paths

After this change scr_async.c is the only runtime unit that tests SCR_NODE_ISLANDS, and it has builds that define it. No other island block on main is compiled out.

Open PRs that are cut before #3135 and keep island blocks of scr_json.c under the define will meet the same guards when they take main: #3124 (15 conditionals in that unit at 9cdb212) and #3043 (13 at a132a9a). Each has to move its own blocks to the table and append its entries (json_query; iterator_method) after weak_set, with the initializer lines in the same order.

2. The runtime refusal baseline

#3137 made pnpm lint check that every runtime refusal names a diagnostic code and is listed in packages/runtime/refusal-sites.json. Its list was written before seven sites that merged while it was open. All 390 sites on main already name a code (390 name a diagnostic code, 0 do not), so no site needed one; only the list was behind.

  • Six sites are new refusals of other PRs: scr_console_native.c:70, scr_dyn_invoke.c:2797, scr_fs_path_value.c:29 and :44, scr_pki.c:3693 and :3695.
  • The seventh, scr_json.c:21367, is not a new refusal. It is scr_weak_set's, which the list has. Keep weak-collection entries keyed by Node island objects inside the island #3325 put a one-line function at column 0 above scr_weak_set, and the scan reads a definition up to the next closing brace in column 0, so it attributed the refusal to that wrapper. The second commit removes the wrapper (its three callers test scr_ni_dyn_handle directly), and the site is scr_weak_set's again.

The third commit is the output of node scripts/runtime-refusals.mjs --write on the final tree. Its diff is those six sites and three things the strict check does not report, because it compares keys and only looks for unlisted ones:

  • the message of the scr_console_native.c site replaces the one it had before (same site, new wording);
  • scr_finalization_key in scr_json.c is listed twice: Keep weak-collection entries keyed by Node island objects inside the island #3325 added a second raise of the same message in that function (an island symbol as a target or token);
  • three scr_webstreams.c entries leave the list: their sites no longer exist.

Evidence

All in Vercel Sandboxes, Linux x64, CI=1, with a libnode pack assembled from the tree under test.

Control, plain main 0d142e0: node-island-weak-keys.test.ts fails 3 of 3 with the SC2020 refusal; runtime-pack-matrix.test.ts fails with expected [ 'scr_json.c' ] to deeply equal []. node scripts/runtime-refusals.mjs --strict on main 511f1e8: 7 are not in the baseline, exit 1.

On this branch's head (main 511f1e8 merged in):

  • pnpm lint: 390 name a diagnostic code, 0 do not; 0 are not in the baseline, oxlint 0 warnings and 0 errors. pnpm format:check: clean.
  • The checks behind lint in CI: code-quality.test.ts and ci-stack-scope.test.ts 39 of 39; pnpm generate:ts7-ast --check, generate:ir-fields --check and generate:ir-codec --check exit 0.
  • node-island-weak-keys.test.ts 3 of 3; runtime-pack-matrix.test.ts 13 of 13 (the new test among them); runtime-define-sites.test.ts 1 of 1.
  • differential.test.ts and llvm-differential.test.ts (release and dev) for 8754-node-island-weak-keys, 8850-node-island-weak-cache and 9241-node-island-weak-import: pass.

On the commits before the merge of main (base 0d142e0):

  • SCRIPTC_SAN=1: node-island-weak-keys.test.ts 3 of 3, and the three corpus programs pass.
  • The island harness files and the value matrix (tests/harness/node-island*.test.ts, island-value-matrix.test.ts): 11 files, 380 tests, all pass; no matrix entry moves.
  • Every corpus program with island in its name, differential.test.ts: 131 pass.
  • The second commit changes no release object: all scr_json.o builds of the release, library-release and speed flavors are byte-identical before and after it (the eight dev builds differ, they carry debug information).

Still running when this PR was opened, results follow in a comment: the island conformance lane in check mode, and the zero-cost gate with --gate-corpus=all plus the runtime benchmarks.

Zero cost

scr_json.c is linked by every program, and this change is a call through an installed table in it: the one-time exception recorded for #3135 (one build per runtime unit; DECISIONS.md, 2026-11-07). Executables that link the weak-collection functions of scr_json.o change once: a tag test per weak operation, a NULL test in the collection's trace and release, and 8 bytes per collection. IR and LLVM do not change (no compiler source is touched). The row count and sizes come with the gate's result.

🤖 Generated with Claude Code

cramforce and others added 4 commits November 7, 2026 14:09
#3135 builds scr_json.c once: no build of it defines SCR_NODE_ISLANDS
any more, and its island paths call through ScrNiDynOps. #3325 was cut
before that and keeps its paths for weak collections keyed by island
objects under the define, so on main they are compiled into no program:
a WeakMap, WeakSet or FinalizationRegistry given an island object
refuses it with SC2020 again.

The blocks are unconditional now and call three entries appended to the
end of the table (weak_get, weak_test, weak_set), which the island
handle unit installs. A value with the island tag exists only in a
program that links that unit, so no site tests the table for NULL. The
collection's `island` member is always there and NULL without islands.

runtime-pack-matrix.test.ts also checks that every entry of the table is
installed, in the struct's order, and that every call through the table
names an installed entry.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The one-line wrapper sat at column 0 above scr_weak_set, and the refusal
scan (scripts/runtime-refusals.mjs) reads a definition up to the next
closing brace in column 0: it attributed scr_weak_set's refusal to the
wrapper, a site the baseline does not list. The three callers test
scr_ni_dyn_handle directly.

Co-Authored-By: Claude Code <noreply@anthropic.com>
pnpm lint fails on main since #3137 (038fff5): its baseline of
refusal sites was written before six sites that merged beside it
(console methods through a stored value, Object.defineProperties with an
unsupported property map, two fs path values, two Sign/Verify stream
members). Written by node scripts/runtime-refusals.mjs --write: those
six, the second symbol refusal of scr_finalization_key, and three
scr_webstreams.c sites that no longer exist leave the list.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Nov 7, 2026 10:29pm UTC

@cramforce
cramforce requested a review from ctate November 7, 2026 22:29
@ctate
ctate merged commit 1d4a9be into main Nov 7, 2026
27 checks passed
@ctate
ctate deleted the fix/main-red-weak-keys branch November 7, 2026 22:35
@cramforce

Copy link
Copy Markdown
Contributor Author

Results that were still running when the PR was opened.

Zero-cost gate, --gate-corpus=all, commits 0637835..a796870 against their base 0d142e0 (the merge of main adds nothing of this PR's):

  • 5,605 programs: 5,199 byte-identical, 281 runtime-only, 125 fail to build on both sides. No differs row.
  • All 62 benchmark executables and all 37 fully typed programs are byte-identical.
  • The 281 rows are all corpus programs that link the weak-collection functions of scr_json.o, none of them fully typed (126 .js, 75 .ts, 40 .mjs, 40 .cjs). IR and LLVM are identical on every row; no symbol is gained, no dynamic-tier or island symbol added.
  • Size: +320 to +448 bytes per executable, median +384, at most +0.07%. The functions that changed size: scr_weak_map_invoke 656 to 824 bytes, scr_weak_set 432 to 496, scr_weak_map_trace 57 to 93, scr_weak_map_release 291 to 305 (and scr_weak_collection_new, same size).
  • Changed pack objects: scr_json.o and scr_node_island_handle.o, nothing else.
  • The compiler is byte-identical to the base's (no compiler source is touched), so the self-compile timing shards were skipped.

This is the one-time cost of a call through an installed table in an always-linked unit, the exception recorded for #3135.

Runtime benchmarks: 69 workloads, geomean -0.2%, 68 neutral, 1 faster (startup-islands-cold -1.9%), none slower.

Island conformance lane (check mode) on this branch: 408 pass, 9 fail. The failure shown is a signature that now ends in a diagnostic code, … cannot cross into a Node island yet (scope-B NI-5 exports native values by reference) [SC8010]: #3137 appends the code to an uncaught refusal, and the lane's list was regenerated (#3367) before #3137 merged. The lane does not run on pushes to main, so this is a third thing red on main for every PR that touches island code, this one included. I am regenerating the list on plain main 511f1e8 and on this branch's head side by side to confirm that the nine entries are the same on both and move for that reason only; the result follows here.

Not run in a sandbox: the native bootstraps (the compiler's own source creates no weak collection; CI's bootstrap job is the check) and the full test lanes.

cramforce added a commit that referenced this pull request Nov 7, 2026
…hic-lowering-crashes

Main's fix for its own red (the weak-collection island blocks of #3325
against #3135's single build of scr_json.c, and the runtime refusal
baseline), merged as the same commits. No conflict.

Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 7, 2026
…ys after #3135 and its refusal baseline) into train/pi-1

Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 7, 2026
Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 7, 2026
Main's fix for its own red (the weak-collection island blocks of #3325
against #3135's single build of scr_json.c, and the runtime refusal
baseline), merged as the same commits. No conflict.

Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 7, 2026
No conflict. Main now holds #3371, whose branch the train had merged: the
same commits.

Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 7, 2026
The fix for main's red tests since #3325 met #3135 (the island's weak collections leave scr_json.c for the installed table; the runtime refusal baseline is regenerated), merged as its own commits. One conflict, in runtime-pack-matrix.test.ts: the train's two tests and the fix's one were added at the same place; all three kept.

Co-Authored-By: Claude Code <noreply@anthropic.com>
espetro pushed a commit to espetro/scriptc that referenced this pull request Nov 8, 2026
Main's fix for island objects as weak keys (vercel-labs#3371) adds weak_get, weak_test
and weak_set to ScrNiDynOps. They keep main's place after report_uncaught;
the train's error_has, promise_get, promise_has and promise_set follow.

Co-Authored-By: Claude Code <noreply@anthropic.com>
espetro pushed a commit to espetro/scriptc that referenced this pull request Nov 8, 2026
The lane's list pins what each program prints. vercel-labs#3137 changed what every
runtime refusal prints without touching a path that turns the lane on,
so its own pull request did not run it, pushes to main never run it, and
the stale list failed in the next pull request that touched island code
instead (vercel-labs#3371). On a push the lane fails on the commit that moved the
entries.

Pushes to main and merge queue groups now select every lane except
macos. Cost: the lane's four shards, 27 runner-minutes per push as
measured on vercel-labs#3371's run (5.2, 9.0, 6.9 and 5.9 minutes), after the Linux
native build a push runs anyway.

Co-Authored-By: Claude Code <noreply@anthropic.com>
cramforce added a commit that referenced this pull request Nov 8, 2026
No conflicts. Main brings #3371 (the weak-keys set and the refusal
baseline) and #2761 (a record's layout kept when an annotated
declaration names an existing value).

Co-Authored-By: Claude Code <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 396d18c2 Deployed Nov 7, 2026 by vercel[bot]
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.

2 participants