Repository navigation
Bound V8's stack scan by Node's own stack in Node islands - #1898
Merged
Merged
Conversation
Add the @scriptc/libnode-<platform> packs (linux-x64-gnu, linux-arm64-gnu, darwin-arm64, darwin-x64): the pinned Node.js 24.22.0 built as a stripped shared library with full ICU and its startup snapshot, the scriptc bridge compiled against that release's headers, Node's LICENSE and a manifest with the Node version, NODE_MODULE_VERSION, bridge ABI and file hashes. packages/libnode-common holds the pin, the build (fetch and verify the source tarball, configure --shared, build only the libnode target, strip, stage with headers in a cache keyed by release, flags, platform and toolchain; the bridge rebuilds in seconds against the cached headers), a verifier, and a smoke test: a C program that loads a pack the way the runtime does, starts Node from the snapshot and evaluates expressions synchronously, asynchronously and throwing. The libnode-packs workflow builds the packs on native runners, Linux in the manylinux_2_28 images for a glibc 2.28 floor, caches each libnode build, smoke-tests and uploads every pack. It never runs in the ordinary build or test path. The compiler gains the pack lookup (SCRIPTC_LIBNODE, then the installed package), validation, the nodeIslands.libnode placements and the license notices for the Node island loader to use. The bridge source and its C ABI header are the NI-1 bridge agent's, included here so the packs build; the packs stay private until the in-process island runtime ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s in CI Stack the libnode packs on the in-process loader (#1685) and use its pack assembler and manifest format instead of a second one: build-pack.mjs keeps its CLI and fields (schema, nodeVersion, moduleVersion, bridgeAbi, platform, arch, files), adds the libnode and bridge names, strips and (on macOS) re-signs the bridge and links it with -z defs, and exports buildPack(), which the platform packs call with their package, target and build inputs as extra manifest fields. libnodeRecord falls back to the installed @scriptc/libnode-<platform> pack, found from the entry's directory and then next to the compiler, when neither --libnode nor SCRIPTC_LIBNODE names one. The Linux test lanes restore the libnode build that the libnode-packs workflow cached, assemble the linux-x64 pack from it in seconds (build.mjs --from-cache never builds Node) and export SCRIPTC_LIBNODE, so the Node island corpus and harness tests run instead of skipping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An installed @scriptc/libnode-<platform> pack versions in lockstep with the compiler, whose version every executable cache key already holds; keying on it as well made the full compiler's key differ from the bootstrap's routed key wherever a pack directory was present, so routed cache hits stopped. Only directories with a manifest.json count as installed packs, so an unbuilt workspace package is not one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's island lanes fall back to the development pack (dev-pack.json: Node 24.22.1, no package.json) until the libnode-packs cache exists, so the smoke test, which verifies a @scriptc/libnode-<platform> package against the 24.22.0 pin, failed there. It now runs only when SCRIPTC_LIBNODE names a package directory. The pack package versions follow the 0.4.2 release (scripts/sync-versions.mjs), which pin.test.ts checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Oct 23, 2026
This was referenced Oct 23, 2026
Node islands stack, part 1 of 11 (scriptc-perf/results/islands-prs/PLAN.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cramforce
force-pushed
the
ni/i1-stack-bound
branch
from
October 23, 2026 01:50
987ff9d to
993486e
Compare
This was referenced Oct 23, 2026
The hosted loop's scheduling entries (a tick, a timer or an immediate goes through the bootstrap's helpers; a checkpoint drains ticks and microtasks) and island process-event listeners called the bridge on whatever stack the runtime was on: the main thread's for the first checkpoint, or an async fiber's. JavaScript then ran there, and a collection it triggered failed V8's "Check failed: isolate_->IsOnCentralStack()" (node-island-handles, about 1 run in 15 sanitized: promise reactions run by the first checkpoint on the main stack allocated a microtask). They now run on Node's stack like every other entry, below the mark when they come from a fiber. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host callback marks where it runs on Node's stack, and a call into Node.js from a fiber starts below that mark. Runtime code the callback runs may go deeper on Node's stack before it starts or resumes a fiber, and those frames stay live while the fiber runs: a call into Node.js from the fiber overwrote them. scr_switch now reports where it leaves Node's stack, and the mark moves down to it for the rest of the callback. 6823 runs a timer callback 200 frames deep that starts an async function calling into Node.js before its first await.
# Conflicts: # packages/cli/src/bootstrap.ts # packages/compiler/src/diagnostics/diagnostic.ts # packages/compiler/src/node-islands/libnode.ts
# Conflicts: # packages/cli/src/bootstrap.ts # packages/compiler/src/node-islands/libnode.ts # packages/libnode-common/smoke/smoke.c
# Conflicts: # packages/compiler/src/diagnostics/diagnostic.ts # packages/compiler/src/node-islands/libnode-pack.ts # packages/libnode-darwin-arm64/package.json # packages/libnode-darwin-x64/package.json # packages/libnode-linux-arm64-gnu/package.json # packages/libnode-linux-x64-gnu/package.json # packages/node/package.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The libnode-linux action becomes .github/actions/libnode, which serves the Linux x64 and macOS lanes: the libnode-packs cache for the runner's platform, else the development pack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ame way A nested entry below the mark makes its context with ni_make_context, so macOS clears only the top slice of the range instead of the whole of Node's stack below the mark on every nested call (17 ms per call, and the cross-heap GC test ran past its timeout). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nested trampoline never returns (setcontext back), so the redzones around its locals stayed poisoned in the shadow of Node's stack, and Node's own frames tripped over them later (stack-buffer-underflow in __bzero, sanitized corpus 6822/6823/6831/6832/6894 on macOS). Linux's ASan clears a context's stack in its swapcontext interceptor; macOS's has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First PR of the Node islands stack (plan: NI-4 to NI-7). Base:
ni/islands-base, a merge-only branch of #1696, #1684 and #1691 with no other changes; it retargets toni/3-hosted-looponce #1684 and #1691 are in. This PR can also be folded into #1696.What it does
A garbage collection during a call into Node crashed. V8 records the thread's stack start, which is the main thread's, as the bound of its conservative stack scan. Node runs on a stack that the runtime maps elsewhere, so the scan walked from there into unmapped memory.
v8::base::Stackstart queries with the runtime's stack for callers on it. This is the hoststack_startcallback, bridge ABI 4.p12-island-io-vs-busyis marked@order: race. It is a timer-versus-I/O race that Node also loses sometimes, and it failed under the gate's parallel load.Evidence
6822-node-island-gc. It allocates enough inside sync and async Node calls, from the main program and after timers on Node's loop, for V8 to collect while JavaScript runs. Its output matches Node.bridge.test.tsreads the expected ABI fromscr_node_bridge.h, so every later ABI bump keeps it current.Shortcuts
None of its own. The rest of the islands work follows in the stacked PRs.
Gate results
node-island*.test.ts,npm.test.ts,packages/compiler/src/node-islands, differential68xx): plain 206/206 + diff 36/36; sanitized 206/206 + 36/36.pnpm lint,pnpm format:checkandpnpm test:ts7 --baselines-onlypass.packages/compiler/src/native/cli.ts --emit=llvm) builds.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-failcovers 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