Skip to content

Train islands 2: structuredClone clones an Error as an Error, a warning where an island object is copied, leaf records an island held are released, and dynamic programs get back part of what islands 1 cost - #3382

Merged
cramforce merged 17 commits into
mainfrom
train/islands-2
Nov 8, 2026

Conversation

@cramforce

Copy link
Copy Markdown
Contributor

One rollup of three branches about the Node island boundary and one cleanup (landing train islands-2, first cut), merged together and validated as one tree. None of the three branches had a pull request of its own.

It removes the one answer #3377 (train islands-1) landed worse, and pays back part of what #3377 cost every dynamic program. #3377's first paragraph named it: structuredClone of a program error that went native code, island, native code gave fresh:{"%error":true,"message":"plain"} where Node gives fresh:{}, because structuredClone of a native error was already wrong with no island in the program. With this train structuredClone of an error gives an Error, as Node does, and the nine pinned n2i2n/U.clone = wrong cells and the four error control cells leave the island value matrix. #3377 also made 3,709 dynamic programs 320 to 1,064 bytes larger; 3,681 of them get 256 to 744 bytes back here (see "The island member sites" below).

What lands

The member branches are merged, not rebased, so their signed commits are in this branch.

  • Branch fix/structured-clone-error (531b694b5c): structuredClone of an error in compiled code gives a new Error of the class Node picks (the one of Error's six native subclasses the source's name spells, else Error), with the source's own message, its stack, a clone of its cause and nothing else; a DOMException clones to a DOMException; one copy per source error within a call, so an error held twice clones once and a cause may lead back to its error. Before, the clone was a plain object carrying a hidden %error marker, a subclass's fields and an AggregateError's errors, without a class, a name or a stack. Two corpus programs (9917-structured-clone-errors.ts, 43 lines of output; 9918-structured-clone-errors-js.mjs, 19). Runtime only: the built compiler is byte-identical to main's. Loud as on main, and listed in the owner's notes: a static type that is a built-in or user Error subclass (SC1100 at build time, or the failed cast at run time), a cycle through a plain object, a cause that does not clone.
  • Branch fix/island-matrix-wrong (6050e43a2b): a build-time warning, SC6007 "divergence: island value crosses as a copy", where the compiler sees a checked conversion copy an object or array a Node island made: an assertion islandCall() as T (also through a const or an await), a typed binding of such a value, a typed parameter of a callback passed to an island call when the callback writes through it or compares it, and an island call declared to return a Map or Set of native arrays or records. Since Node parity: compile the apps the island scorecard refused #3052 such a conversion copies where it used to throw, so identity is lost and writes are not shared, silently: 299 facts in 166 entries of the value matrix. The entries stay pinned wrong (the fix is a live view); the warning says where. Over the matrix's 239 programs: 44 sites in 20 programs, none in a control program; 298 of the 299 facts are behind a warned conversion. TypeScript and docs only: it changes no observation (the regenerated matrix is byte-identical with and without it). New limitations entry "Objects converted from Node islands", coverage fixture tests/coverage-fixtures/island-copies/.
  • Branch fix/island-collector-leaf-records (cd09b5c6f9): the cross-heap collector releases a record or class instance without a cycle header that an island was handed. Such a record's capsule never had its reference counted by the walk before a major collection, so the record stayed a root for the rest of the run: 90,000 { attempt: number } records handed to an island left 90,000 exports held and two major collections freed none. 35 lines in scr_node_island_gc.c, fixture tests/fixtures/node-island/gc-records.{ts,cjs}, one test in node-island-gc.test.ts.

What the train changed beyond the members

The island member sites of scr_json.c do less (commit 1bda0dd984)

#3377 put five island paths of scr_json.c behind the island table (error_get, error_has, promise_get, promise_has, promise_set). Each site still tested its entry for NULL, and the two error sites looked the error up in the error-view cache before they called. That unit is in every dynamic program, so the tests and the lookup were too: #3377's gate showed 3,709 programs 320 to 1,064 bytes larger, and the lookup (scr_errdyn_find) had stopped being inlined.

The table is installed whole by one initializer, so an entry test decides nothing, and finding the error of a view is only needed in a program that links the island units. A site now tests the table and calls; error_get and error_has take the view and find its error in the island unit. The table's slots keep their places (only the first parameter of those two entries changes type).

function (release runtime object)before #3377#3377this train
scr_dyn_in1,7481,982 (+234)1,854 (+106)
scr_dyn_key_set4,2314,444 (+213)4,302 (+71)
scr_dyn_obj_read_receiver2,2462,357 (+111)2,251 (+5)
scr_dyn_promise_key_get75164 (+89)154 (+79)
scr_errdyn_find as a function of its owninlined163inlined

Each site is proven to still run by switching it off alone in one runtime build of that commit's tree; a named corpus program then stops matching Node, and with none switched off all pass: error_get and error_has: node-island-lifecycle-error-properties; promise_get: node-island-crossing-promise-subclass, node-island-crossing-request-promise and its typed twin; promise_has: node-island-crossing-promise-subclass, node-island-crossing-request-promise; promise_set: node-island-crossing-request-promise.

Validation

All in Vercel Sandboxes, Node 24.22.1, libnode pack built from the tree under test (bridge ABI 19). Three layers: one full round on the three members; then, for main's #3374 and #3029 and the cleanup commit, the gate with bench-runtime, the island suites and the mutation proof on that tree (1bda0dd984); then the short list on the pushed head, which adds only a merge of main.

Full round on the members (b55c3a4fb7 against main 3dda5d9c41's tree; bin/validate --gate-corpus=all with the allow lists below; 43.5 minutes, 93 lane jobs):

  • Typecheck: pass (tsc in packages/cli and packages/compiler on a fresh tree).
  • Zero-cost gate: PASS: 5,735 programs, 5,450 byte-identical, 229 fail to build on both sides (the island programs: the gate has no libnode pack), 56 allowed (the structuredClone rows below), none differing or runtime-only outside the lists. Compile time within tolerance: bench geomean +0.54% [-0.87%, +1.54%], fixtures -0.83% [-1.19%, -0.48%], self +0.03% [-4.08%, +3.37%] (inconclusive at the default run count).
  • bench-runtime: 69 workloads, geomean -0.3%, 69 neutral.
  • Lanes: plain 23,660 passed, 1 failed; sanitized 20,279 passed, 1 failed. The plain native bootstrap, the island harness suites, the island corpus and the value matrix in check mode are among the lane jobs and pass in both lanes. The two failures are timing, and neither program is touched by a member:
  • On the same tree in one Sandbox: pnpm node-compat:check current (4,996 source rows, 3,886 public rows); scripts/checker-identity.mjs --filter 991: 11 programs agree; island value matrix, full update: 239 of 239, byte-identical to the committed table; island conformance lane, check mode: 424 passed, 70 skipped, 0 failed; docs gate (NEXT_DIST_DIR=.next-check pnpm check): 25 of 25.

On the train's last own commit (1bda0dd984: the members, main 14960424a1, the cleanup commit; 32 vCPU, Intel Xeon 2.90 GHz):

  • Build, runtime pack, generated-file checks (node-island-host.mjs, IR codec, IR fields): pass. pnpm lint: 0 warnings, 0 errors, the runtime refusal baseline included. pnpm format:check: clean. pnpm test:ts7 --baselines-only: complete.
  • Guard tests (pack matrix with "a unit that tests SCR_NODE_ISLANDS has a build that defines it" and "the installed island table has every entry the shared units call through it", runtime define sites, builtin attributes, runtime features, executable cache, unit journal, peel guard, CI lanes): 185 passed. Island unit tests: 238 passed.
  • Island harness suites, plain (every tests/harness/*island*.test.ts except the conformance lane; the value matrix in check mode and the new collector test among them): 383 of 383. Island corpus, plain (differential and llvm-differential, every island program): 470 of 470. Island diagnostics: 12 passed.
  • Island conformance lane, check mode, 8 shards: 424 passed, 70 skipped, 0 failed. The list does not move.
  • Sanitized: the island corpus with the cleanup commit on the members' tree passed 470 of 470 (and 470 of 470 plain); the sanitized harness suites and corpus on 1bda0dd984 are running beside CI, and I will add their result as a comment.
  • Zero-cost gate with bench-runtime, --gate-corpus=all --gate-complete, against main 14960424a1: rows below.

On the pushed head (98b69349ad = 1bda0dd984 merged with main 793159ec8a; 16 vCPU, Intel Xeon 2.50 GHz): build, runtime pack, generated-file checks, pnpm lint (0 warnings, 0 errors), pnpm format:check, ts7 baselines, pnpm node-compat:check: pass. Guard tests: 227 passed, 1 failed, and that one is main's since #2567 (551639137b): outer-expressions.test.ts "only outer-expressions.ts defines a function that peels wrappers off an expression" names frontend/binding-writes.ts#stripValueWrappers, a file this train does not touch. Focused corpus (the two new programs, main's re-ordered lifecycle program, island-value-conversions, the mutation proof's programs; both differentials): 16 passed. The island tests of coverage.test.ts: 7 passed. Docs gate: 25 of 25. The gate's identity on the pushed head runs beside CI and goes into a comment.

Gate rows

Against main 14960424a1, 5,735 programs: 1,769 byte-identical, 229 fail to build on both sides, 56 allowed, 3,681 runtime-only, none differing. Compile time within tolerance (bench +0.57% [-0.87%, +1.82%], self -0.62% [-2.14%, +0.40%], fixtures +2.00% [+1.46%, +2.54%], which the tool calls inconclusive at the default run count; the members' own round read -0.83% for the same compiler). bench-runtime: 69 workloads, geomean +0.0%, 67 neutral, 1 faster (stream-buffered-drain -1.8%), 1 slower (csv-numbers +2.8% [+0.2%, +17.5%], whose executable is byte-identical to main's, so that reading is of one binary against itself).

  • All 37 fully typed programs are byte-identical, and so are the 38 benchmarks that link none of the changed functions. No program gains a dynamic-tier or island symbol.
  • The 3,681 runtime-only rows are every one smaller: 256 to 744 bytes (median 272; 1,832 programs -256, 803 -616, 674 -680), at most -0.20%. They are the cleanup commit: IR and LLVM identical on every row; the resized functions are scr_dyn_key_set (3,681 programs), scr_dyn_obj_read_receiver (3,680), scr_dyn_promise_key_get (2,024), scr_dyn_in (1,665), and scr_errdyn_find stops being a symbol in 1,664. 3,657 corpus programs and the 24 dynamic benchmarks. This is recorded as a one-time change in the fleet's gate exceptions, beside Train islands 1: errors, promises, process events and process.stdout.write keep Node's behaviour across the Node island boundary #3377's row, which it pays back in part.
  • The 56 allowed rows are fix/structured-clone-error's, the programs that link the clone: 34 by the source pattern structuredClone (the two new corpus programs among them) and 22 by program id: performance.mark / measure clone their detail (6), JavaScript that reads builtin globals as values links the table that holds structuredClone as a function value (11), npm packages in the program call it (5). In the members' round, where they are the only rows: IR and LLVM identical on every one, the only resized functions scr_sc_clone and scr_structured_clone; 53 programs grow 2,768 to 3,000 bytes (median 2,936) and three that had no error-node code before grow 17,096 to 17,120 bytes (structured-clone-collections.ts +3.2%, 7614-key-order-copies.ts +3.1%, unknown-array-holes.ts +4.2%: the error node builder brings scr_dyn_prototype_unsupported and scr_dyn_prototype_valid), under the fleet's rule that a program using a rare feature may grow (--gate-max-size-growth=5).
  • fix/island-matrix-wrong and fix/island-collector-leaf-records add no row: the first changes no emitted code, the second only an island unit.

Not run

  • The full lanes were not repeated after the cleanup commit and main's later merges; they ran on the members' tree. The cleanup can only change what a program with islands does (in every other program the table is NULL and the sites skip), and the island suites, the gate and bench-runtime ran on its tree.
  • The sanitized native bootstrap and Test262: no member changes what the compiler emits (fix/structured-clone-error leaves the built compiler byte-identical; fix/island-matrix-wrong adds a scan after lowering, and its own round on main 5b64e49735 ran both bootstraps and Test262 with nothing regressed). The plain native bootstrap is among the lane jobs.
  • Darwin. pi.

Sequencing

The start-up train #3350 conflicts with island changes through two generated files (the embedded host header and the conformance list). This train touches neither, but by the fleet's sequencing it merges after #3350; if #3350 moves either list under it, main is merged and the generated island files are regenerated by their scripts before it lands.

🤖 Generated with Claude Code

cramforce and others added 13 commits November 7, 2026 07:46
A record or class instance whose fields cannot form a cycle has no cycle
header, so its capsule's trace does not visit it. The walk before a major
GC never counted the capsule's reference to it, so every such record an
island was handed looked held natively and stayed a root for the rest of
the run: 90,000 { attempt: number } records left 90,000 exports held, and
two major GCs freed none. The walk now counts that reference without
entering the record.

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

Since #3052 a checked conversion accepts an object or array a Node island
made and builds a native value from its members; before, it threw
"expected array at $, got Object from a Node island". The copy is silent:
under Node the value is the island's own object, so the island's later
changes show through it, the program's writes reach the island and it is
=== to the island's object. The island value matrix has 299 such facts in
166 entries (identity lost, a write on either side not seen by the other,
members the target type does not declare dropped).

The divergence scan now reports each conversion the compiler can see as
SC6007, "island value crosses as a copy" (the code PR #3252 uses for island
arrays; the title is the general one the code registry names):

- an assertion whose operand is an island read or call, awaited or not, or
  a const of one, typed any, unknown or by the island (`make() as Item[]`);
- a typed binding of such a value (`const xs: number[] = anyList()`);
- a typed parameter of a callback passed to an island call, when the
  callback writes through the parameter or compares it;
- an island call declared to return a Map or Set whose key, value or
  element type is a native array or record (the collection stays the
  island's; each member read from it is converted).

A target that stays checked-dynamic (unknown, a record of unknown members,
a live view) keeps the island's object and reports nothing, as does a copy
the program writes (`structuredClone(value) as T`) and a value no island
made. The scan reads the syntax tree after lowering and adds no lowerer
state; no generated code changes.

Of the 299 facts, 298 are in a program that now gets the warning at the
conversion they sit behind; the one left is the identity of a nested
element read inside a callback. 44 sites in 20 of the matrix's 239
programs; none in a control program.

Co-Authored-By: Claude Code <noreply@anthropic.com>
structuredClone had no arm for errors: it copied an error's checked-dynamic
node property by property, the hidden `%error` marker included, so the
result was a plain object with no kind, no name and no stack, which kept a
subclass's fields and an AggregateError's errors.

The copy is now what Node's serializer makes: a new error of the one of
Error's six native subclasses the source's name spells (else Error), with
the source's own message, its stack when that is a string, and a clone of
its own cause; nothing else travels. A DOMException copies its name and
message. An error the input holds twice clones once, and a cause may lead
back to its error. The copy is an ordinary error with its ordinary node,
built the way a decoded message builds one.

A class's `name` getter is read through the reader the program installs,
which scr_error.c now exports as scr_error_read_name; only the clone links
it. A cycle through a plain object or array still raises the existing
refusal. Typed call sites are unchanged: the copy checks back into the
static type.

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

# Conflicts:
#	docs/content/docs/limitations.mdx
Co-Authored-By: Claude Code <noreply@anthropic.com>
With structuredClone of a native Error fixed, sixteen pinned observations
leave the table and one loses its clone part: the four error controls
(U.clone), the nine n2i2n/U.clone cells the islands-1 train pinned as wrong,
and the clone part of four clo-n2i/N.callback entries. Nothing is new and
nothing else changes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…the island table

The five island paths the islands-1 train put behind ScrNiDynOps (error_get,
error_has, promise_get, promise_has, promise_set) still tested each entry
for NULL, and the two error sites looked the error up in the error-view
cache before they called. The table is installed whole by one initializer,
so the entry tests decided nothing, and the lookup is only needed in a
program that links the island units. A site now tests the table and calls;
error_get and error_has take the view and find its error in the island
unit. The table's slots keep their places.

In the release runtime object, against the tree before islands-1:
scr_dyn_in +106 bytes (was +234), scr_dyn_key_set +71 (+213),
scr_dyn_obj_read_receiver +5 (+111), scr_dyn_promise_key_get +79 (+89),
and scr_errdyn_find is inlined again (it had become a function of 163
bytes).

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

vercel Bot commented Nov 8, 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 8, 2026 3:42am UTC

Comment thread packages/runtime/src/scr_json.c Resolved
Comment thread packages/runtime/src/scr_json.c Resolved
…a property

Review finding on #3382: an error whose own cause is an accessor
(Object.defineProperty(e, "cause", { get })) cloned to an error with an own
cause holding undefined, where Node's copy has no own cause. The error's
cause slot follows its node's entry, and for an accessor that entry holds a
placeholder, not a value. The clone now copies the cause as it copies the
message: only an own data property. Pinned in
9918-structured-clone-errors-js.mjs for an accessor on an error made
without a cause and on one made with a cause.

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

Copy link
Copy Markdown
Contributor Author

The two results the description promised.

Gate identity on the pushed head (98b69349ad against main 793159ec8a, --gate-corpus=all --gate-complete, 20 minutes): 5,756 programs; 1,772 byte-identical, 229 fail to build on both sides, 56 allowed (fix/structured-clone-error's rows, as in the description), 3,699 runtime-only, none differing. All 37 fully typed programs are byte-identical. The 3,699 runtime-only rows are the cleanup commit and every one is smaller: 256 to 744 bytes, median 256; IR and LLVM identical on each; resized scr_dyn_key_set (3,699), scr_dyn_obj_read_receiver (3,698), scr_dyn_promise_key_get (2,034), scr_dyn_in (1,674). The count is 18 above the description's 3,681 because main gained dynamic programs between the two bases. Compile time within tolerance (bench -0.36% [-0.77%, +0.38%], self +0.17% [-1.02%, +1.64%]); the candidate compiler source builds with the candidate.

Sanitized island suites on 1bda0dd984 (the members, main 14960424a1, the cleanup commit; 32 vCPU, libnode pack from the tree): island harness suites 383 of 383, island corpus (differential and llvm-differential, every island program) 470 of 470.

Main is red in one unit test since #2567 (551639137b), and it will show in this pull request's plain lane: packages/compiler/src/frontend/outer-expressions.test.ts, "only outer-expressions.ts defines a function that peels wrappers off an expression", names frontend/binding-writes.ts#stripValueWrappers. This branch does not touch that file.

@cramforce

Copy link
Copy Markdown
Contributor Author

Pushed 19cd362602, one commit on the head: the review finding about an accessor-only cause (thread above; reproduced against Node, fixed, pinned in 9918-structured-clone-errors-js.mjs). The second finding (a deleted stack) is answered in its thread: the plain forms are named refusals, and in the one form that compiles the difference is in the delete itself, on main as well; it is recorded as a follow-up.

The commit changes one function, scr_sc_clone_error, which only the programs that link the clone carry (the 56 allowed rows of the description), and one corpus program. On it, in a Sandbox (32 vCPU): build, generated-file checks, lint (0 warnings, 0 errors), format, ts7 baselines; the clone programs in both differentials, plain 28 of 28 and sanitized 28 of 28; the island value matrix's error cells in check mode, 18 of 18; scripts/checker-identity.mjs --filter 991, 11 programs agree. Main was not merged again (a trial merge with 659a1caaa5 has no conflict).

cramforce and others added 3 commits November 7, 2026 18:24
Train cli-5 (#3386) and this train each add a divergence warning beside
SC6004: both lines are kept in codes.ts (SC6007, SC6008) and both mentions
in the header of diagnostic.ts.

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

By the test's update mode. One line moves: the fixture's statements in the
dynamic tier go from 12 to 10 on the merge with main 851b59b. The
warnings the snapshot is there for are unchanged.

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

Copy link
Copy Markdown
Contributor Author

Pushed 0f4df1865e: main is merged up to 851b59b90f (trains cli-4, cli-5 and startup-1), and one snapshot is regenerated. This is the push the description's "Sequencing" section announced.

The merges.

The generated island files, each by its script on the merged tree (32 vCPU, libnode pack from the tree, bridge ABI 19):

  • Embedded host header: node scripts/node-island-host.mjs --check is current (this train does not touch the host script).
  • Island value matrix: a full update of all 239 programs writes a file byte-identical to the committed one. Against main's table the difference is this train's 17 entries, as in the description (main's cli-5 removed 29 .ctor refusals of its own in between).
  • Island conformance list: the lane in check mode, 8 shards, 434 passed, 0 failed, so the list on main is exact for this tree and nothing is regenerated.

Checks on the merged tree. Build, runtime pack, generated-file checks, pnpm lint (0 warnings, 0 errors; runtime refusal sites 390, none outside the baseline), pnpm format:check, ts7 baselines, pnpm node-compat:check: pass. Guard and unit tests (pack matrix, runtime define sites, builtin attributes, runtime features, executable cache, unit journal, lowerer, peel guard, CI lanes, diagnostics, node-islands): 523 passed. Island unit tests 321. Island harness suites, plain: 423 of 423. Island diagnostics 12. Island corpus, plain (differential and llvm-differential, every island program): 578 of 578. The island and type-claim tests of coverage.test.ts: 12 passed with the regenerated snapshot. Docs gate: 25 of 25.

Native bootstraps. CI runs them only for pull requests that touch the bootstrap paths, and no round of this train ran both on a tree that differs from the head only by main: the plain and the sanitized native bootstrap (self-hosting-native-driver.test.ts, run by the validation driver's lane tool as a round runs them) are running on this head's tree in two Sandboxes; I add their result here. The sanitized island suites on this tree are running too.

@cramforce

Copy link
Copy Markdown
Contributor Author

The results the last comment said were running, all on the pushed head's tree (0f4df1865e: main 851b59b90f, this train, the regenerated snapshot).

Both native bootstraps pass. tests/harness/self-hosting-native-driver.test.ts, run by the validation driver's lane tool exactly as a round's native lane job runs it, each alone in its own Sandbox:

  • plain (16 vCPU, 32 GB): passed in 2,196 s;
  • sanitized (32 vCPU, 64 GB, all phases in one job): passed in 3,232 s (the seed, the seed's command and library probes, the sanitized native emitter rebuilding the compiler, the sanitized native frontend lowering the compiler, Node's and the native compiler's LLVM compared).

Sanitized island suites (32 vCPU, libnode pack from the tree): island harness suites 423 of 423, island corpus (differential and llvm-differential, every island program) 578 of 578.

@cramforce
cramforce merged commit 3dc1f4a into main Nov 8, 2026
84 checks passed
@cramforce
cramforce deleted the train/islands-2 branch November 8, 2026 04:24
yowainwright pushed a commit to yowainwright/scriptc that referenced this pull request Nov 9, 2026
…nto the restack of vercel-labs#3284

Co-Authored-By: Claude Code <noreply@anthropic.com>
yowainwright pushed a commit to yowainwright/scriptc that referenced this pull request Nov 9, 2026
…e restack of vercel-labs#3284

Co-Authored-By: Claude Code <noreply@anthropic.com>
yowainwright pushed a commit to yowainwright/scriptc that referenced this pull request Nov 9, 2026
Conflict: packages/compiler/src/frontend/lowering/divergences.ts. The
lang train moved the width-copy scans of variable declarations out of
this visitor (vercel-labs#3221); main (islands-2, vercel-labs#3382) added one line to the
same branch, the list of annotated declarations a program with islands
checks. The branch keeps main's line and nothing of the scans the lang
train moved.

Co-Authored-By: Claude Code <noreply@anthropic.com>
yowainwright pushed a commit to yowainwright/scriptc that referenced this pull request Nov 9, 2026
…vercel-labs#3284) into train/landing-1

Byte views and symbols keep one identity across the Node island
boundary. The head sits on vercel-labs#3382's head, so the merge brings only its
own diff. No conflict. (The branch's later head 6eb3826821 is its own
merge of main 3dc1f4a and adds nothing to this tree.)

Co-Authored-By: Claude Code <noreply@anthropic.com>
yowainwright pushed a commit to yowainwright/scriptc that referenced this pull request Nov 9, 2026
Main gained vercel-labs#3394 (the Lowerer is handed over only as itself, with a
guard test), vercel-labs#3382 (islands: structuredClone of errors, the collector's
leaf records, island member sites behind the ops table), vercel-labs#3390, vercel-labs#3389,
vercel-labs#3393 and vercel-labs#2295.

Two conflicts, both with vercel-labs#3394:

- frontend/specifier-bounds.ts: main's two calls pass
  lowerer.bindingScope(); the bounder has no Lowerer any more and keeps
  passing its context, which is a record on both roads
  (Lowerer.specifierBoundContext() and specifierBoundContextOf(program)).
- frontend/lowering/lowerer.ts: the import list, and main's bindingScope()
  beside this branch's specifierBoundContext(); both stay.

One edit outside the conflicts, where a clean merge left a comment that is
no longer true: runtime/src/scr_node_island_handle.c, nh_error_view_ref
(new on main with vercel-labs#3382). Its comment said the ref it returns is borrowed
from the pin. Under the worker routing a pinned lookup returns a counted
reference, taken under the table's lock, and both callers of the helper
(nh_error_get, nh_error_has) release it, as the routing already made them
do. The code merged right by itself; the comment now says +1.

Main's guard test passes on the merged tree: no call passes a Lowerer to
the bounder.

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

This branch was successfully deployed

1 active deployment
Preview — 0f4df186 Deployed Nov 8, 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.

1 participant