Skip to content

Pass native closures, promises and objects into Node islands by reference (NI-5) - #1900

Merged
ctate merged 27 commits into
mainfrom
ni/i3-exports
Oct 28, 2026
Merged

ctate merged 27 commits into
mainfrom
ni/i3-exports

Conversation

@cramforce

@cramforce cramforce commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1899. Third of nine Node islands PRs.

What it does

Runtime. scr_node_island_export.c adds an export table. A value's identity is its referent: a closure, a promise, a typed capsule's native pointer, or otherwise the dyn box. Host callbacks run over the generic dyn operations: calls go through the closure's thunk, so its contract checks apply, and records and class instances go through Reflect.get/set. A native promise's V8 representative resolves inside the native settlement (scr_promise_on_settle_sync, SCR_NODE_ISLANDS only). An island Error passed to a native callback arrives as its pinned ScrError, so native instanceof RangeError holds.

Bridge, ABI 6.

  • Exported functions are Function::New with the export id as data. Promises use a Promise::Resolver. Objects use one ObjectTemplate with named and indexed interceptors.
  • A private scriptc:export symbol carries the export id, so representatives come back into the program as the native value.
  • Globals are weak, and releases are delivered at the microtask checkpoint.

Compiler. These arguments cross by reference: functions, Promises, and object types other than arrays, tuples, Map and Set. any/unknown values cross as dyn. Class instances answer their prototype methods through a per-class method table. A result typed with a native object type is a checked cast, so an export comes back as itself. A native class X extends <island value> is SC8011.

This PR also includes NI-4a's follow-up in the harness: only runtime-class fences make islands.

At exit, after Node stops, the runtime releases the export table, its ids, the class method tables and the last conversion's keep. This keeps the sanitized lane's RC audit clean. NI-6 (#1902) extends this.

Evidence

  • Corpus programs 6830–6836, byte-identical with Node. They cover callbacks, promises, objects and typed callbacks. They also cover an island EventEmitter subclass with native listeners, Node-style async callbacks carrying island errors, and native throws through island frames. Promise interleavings are tested in both directions, as are native records mutated by the island and class instance fields and methods.
  • Diagnostics snapshot node-island-subclass (SC8011).

Shortcuts

  • Native arrays still cross as JSON copies. Typed arrays, Maps and Sets as arguments are SC8010 (lifted in the subclasses PR).
  • Exported objects share one template whose prototype is Object.prototype. Island instanceof against native classes is not supported, and symbol-keyed properties are not intercepted.
  • Cycles through both heaps leak.
  • Class method tables are keyed by the static type.

Gate results

  • Island set in a Vercel Sandbox, with the libnode dev pack rebuilt from this branch's bridge (node-island*.test.ts, npm.test.ts, packages/compiler/src/node-islands, differential 68xx): plain 207/207 + diff 49/49; sanitized 207/207 + 49/49.
  • pnpm lint, pnpm format:check and pnpm test:ts7 --baselines-only pass.
  • The self-hosted compiler (packages/compiler/src/native/cli.ts --emit=llvm) builds.
  • Zero-cost gate: 2838 programs identical (benchmarks, self-hosted compiler, TypeScript corpus) against the base branch. The only DIFFERS is corpus/fs-extra-package (%m0.settleVoid). It differs in every gate of the stack, including Bound V8's stack scan by Node's own stack in Node islands #1898, which does not touch the compiler, so it is nondeterministic on the base. both-fail covers only the island programs, because the gate host has no libnode pack. Compile time stays within tolerance.

CI on the first push found failures outside the focused island set: the ABI in bridge.test.ts, the runtime ABI header declarations, the effect gate on the ts7 allowlist, weak collections in the compiler, the pipeline scout and the Node version line. The fixes are folded into the PRs that introduce each piece, and they are re-checked in a Sandbox on the final tree.

Stack

#1898
#1899
→ #1900
#1901
#1902
#1903
#1904
#1905
#1906
#1907
#1908

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 23, 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 Oct 28, 2026 6:47am UTC

@vercel-internal-security-reviewer

Copy link
Copy Markdown

Security review details

…ence (NI-5)

Node islands stack, part 3 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
structuredClone in an island of a value holding native records, class
instances or objects (interceptor objects V8's serializer cannot clone)
clones a plain copy of that data, as Node clones the objects themselves.
An island array of primitives or strings (or arrays of those) returned
directly or through a promise crosses as a copy, which the checked cast
then meets, instead of failing the cast as an object handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/compiler/src/backend/native/executable.ts
#	packages/runtime-pack-common/runtime-pack-matrix.mjs
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-classes.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cramforce

cramforce commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Round complete (head 42df54873). Every thread on this PR was already fixed in 0b4301b and re-verified against the branch:

  • HostCall lifetime (bridge/scr_node_bridge.cc): all four named-property interceptors construct HostCall before recording scratch storage, so it outlives a deferred timer/microtask delivery.
  • Promise identity (runtime/scr_node_island_export.c): the export table keys promises by identity (scr_promise_identity_key/identity_hold), so the same Promise passed in twice does not become two V8 promises.
  • Sparse clone (runtime/scr_node_island_host.cjs): the fallback clone uses new Array(value.length) and copies Object.keys(value) (indices and named properties), so sparse arrays stay sparse.

No code change on this branch this round; it was merged forward with the fixed #1899 heads (7d1a7f388, then c959443a8 for the corpus coverage follow-up) so the stack stays consistent. CI is green on the new head.

The zero-cost gate against a4a844f44 reports only the two differences the base branch ni/3-hosted-loop already shows on its own gate: @requires-libnode corpus programs that cannot compile in the gate sandbox (no libnode pack there; both sides error identically) and 4032-inline-rc-fast-paths gaining the scr_dyn_*.scrunit.scr_json symbols, which reproduces at ca4a162e3. No new difference originates on this branch.

🤖 Generated with Claude Code

Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
Base automatically changed from ni/i2-handles to main October 28, 2026 03:42
g05-zlib starts a zlib.gzip from a 0 ms island timer and arms a 2 ms
timer on the native side. The 0 ms timer's callback runs before the 2 ms
timer is due, so the gzip job races that timer: if the job completes on
libuv's thread pool before the timer's turn, its callback runs first.
Node orders the two the other way only because its first gzip usually
takes longer than the wait. With zlib already loaded and the thread pool
started, Node prints i-zlib first in about a quarter of runs. On fast
macOS hosts the native runs took the other order.

Mark it `// @order: race`, as g01 (fs), g03 (child process) and g04
(dns) already are, so its native runs must produce the lines of one of
Node's outputs in any order, and name thread-pool work among the races
the harness describes.
@ctate
ctate merged commit e8fdfa6 into main Oct 28, 2026
82 checks passed
@ctate
ctate deleted the ni/i3-exports branch October 28, 2026 07:40

This branch was successfully deployed

1 active deployment
Preview — d7198f01 Deployed Oct 28, 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