Scope-B milestone NI-3. When a program loads Node in-process, its pending
ticks, promise jobs, timers and immediates move onto Node's queues in
order (timers keep their deadlines relative to the moment Node began
loading; the work of a phase the switch interrupted finishes first) and
every later one is scheduled there, so the jobs of both sides interleave
as in Node. The runtime's I/O hooks are watched from Node's loop, and its
liveness is re-read before each poll phase, after each check phase and
after every microtask checkpoint. Node's loop runs on Node's stack, and
V8's limit follows whichever stack an entry comes from.
Island signal and exit listeners join the runtime's registries at their
position, so the runtime keeps owning every signal disposition; Node's own
'exit' emission is left to the runtime's exit sequence, and the exit code
island code leaves in process.exitCode becomes the program's. Bridge ABI 3.
Ordering programs (tests/corpus-node-islands-order, harness
node-island-order.test.ts), corpus programs 6804-6811 and the
node-island-process harness pin the behavior against Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The community build's v24.22.0 asset was withdrawn when Node 24.22.1
shipped, so every Linux test shard failed to fetch it (HTTP 404). Pin the
v24.22.1 build and Node's 24.22.1 headers by sha256 instead; the island
tests pass with it in the plain and sanitized lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls into Node now run on one dedicated 64 MB stack with a guard page,
reached by a context switch, and the bridge sets V8's stack limit on it
(bridge ABI 2). Before, a call from an async fiber (512 KB stacks without a
guard page) failed to bootstrap Node, which the dev-mode LLVM differential
showed. The runtime's stack guard follows the switch, and the switch
carries ASan's fiber annotations.
The Node island tests load Node from a libnode pack. Until the NI-13 packs
are published, .github/actions/libnode-dev-pack assembles one for the
Linux x64 test lanes (plain and sanitized): a pinned, checksum-verified
prebuilt libnode of the repository's Node release (24.22.0) and Node's
official headers (packages/libnode-common/dev-pack.json), with the bridge
built against them by build-pack.mjs. The pack is cached by its inputs and
the bridge source, so only a cache miss downloads and builds; the normal
build path never does.
tests/harness/libnode.ts makes a missing pack a failure in those lanes:
island tests skip only off Linux x64 CI without a configured pack. The
bridge smoke test (packages/libnode-common/test/bridge-smoke.c) loads the
pack the way scr_node_island.c does and makes synchronous, throwing and
asynchronous calls through the bridge with the runtime's bootstrap.
IR, LLVM and object outputs of programs that call into Node no longer need
a pack at build time; only executables record one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scope-B milestone NI-1, runtime side. Programs whose IR has a
`nodeIsland.*` operation link scr_node_island.c and the SCR_NODE_ISLANDS
build of scr_async.c; every other program links neither, byte for byte.
- scr_node_island.c dlopens the bridge of a libnode pack on the first call
(RTLD_NOW | RTLD_GLOBAL), checks its bridge ABI and that it links exactly
the Node version recorded at build time, starts one Node environment and
runs the embedded bootstrap (scr_node_island_host.cjs). A run that never
calls into Node never maps libnode. The pack directory is SCRIPTC_LIBNODE
at run time, else the recorded path; a `$ORIGIN/` path resolves against
the executable's directory (the beside packaging, and the hook for
shipping Node with the program).
- scr_node_bridge.h is the C ABI between the runtime and the bridge; the
runtime includes no Node or V8 header. The bridge
(packages/libnode-common/bridge/scr_node_bridge.cc) initializes Node once
with no default signal handling, no debug-signal handler or inspector,
no stdio reinitialization and one platform worker, creates the
environment from the built-in snapshot, routes Node's process exit to
the runtime's exit sequence, promotes libnode to the global scope for
native addons, and runs calls in a CallbackScope with a TryCatch.
build-pack.mjs assembles a pack (libnode, the bridge, manifest.json) from
a Node --shared build and the matching headers.
- The SCR_NODE_ISLANDS build of scr_async.c adds a loop slot through which
the runtime's loop drives Node's until NI-3 hosts the runtime's queues on
Node's libuv: it keeps the program alive while Node has ref'd work,
sleeps on Node's backend fd up to its next timer, and runs one
non-blocking turn per loop turn.
- The `nodeIsland` runtime feature (rule, link features, unit table, pack
matrix row and scr_async.c variant), the nodeIsland.call/callSync
libCalls, the install call in main, and the build-time record of the
pack (node-islands/libnode.ts: --libnode=<pack>|beside or
SCRIPTC_LIBNODE, SC8005 when none is usable, Node >=24.13.1).
- The zero-cost gate and the bench nm test require that no good-code
executable defines a Node island symbol.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>