Repository navigation
Conversation
A statically imported Node island module stayed eager whenever its load read an environment variable outside scriptc.nodeIslands.publicEnv, even when the variable was unset at build time. The boot guard of an unset variable records only that it was unset (E <key> 0), never a value, and the runtime already checks it, so such a load now defers without a listing. A run that sets the variable fails the guard and loads the module at its import, as before. A variable that has a value at build time still needs publicEnv, or the module stays eager. The late evaluation restores only the variables the program changed since the import. It used to put every guarded variable back to its value from before the evaluation, which erased a write the module's own evaluation made to a variable it had read. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ograms Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
The build decides which statically imported island modules defer from one
run of its probe, which loads every candidate in one process. A file two
candidates share evaluates in the load of whichever candidate comes first:
its effects, inputs and environment reads are recorded as that module's,
and the other finds the file in the module cache and records nothing. The
candidates were probed in the order of the IR's functions, where a
module's initializer comes after those of the modules it imports, so an
importer's first island import was probed after the island imports of its
later dependencies.
import { a } from "pkg-a"; // requires common-pkg, whose load prints
import "./between.ts"; // a native module that prints
import { kind } from "./dep.ts"; // imports pkg-b, which requires common-pkg
The probe loaded pkg-b first, recorded the print as pkg-b's and deferred
pkg-a with an empty record. The program then printed the native module's
line before common-pkg's, where Node prints them the other way round. An
environment read, a file read or a builtin mutation of a shared file was
hidden the same way, and "a later island module's load changes its
exports" compared against the wrong earlier modules.
The probe now takes the candidates in the program's evaluation order,
read from the IR: the entry's statements in order, into each module
initializer where it is called, an initializer that awaits continued after
the code that called it. Loads the walk does not reach (inside functions,
dynamic imports) keep the IR's order after the others.
Across the 106 island programs of the corpus and the deferral fixtures
(60 with deferral decisions), the only program whose set of deferred
modules changes is the new one.
Co-Authored-By: Claude Code <noreply@anthropic.com>
When every static island import of a program deferred, each group of modules evaluated at the first use of one of its stubs (per-module mode). Node evaluated each of them at its import, before any call, and a call into a module that already evaluated can write island state (a global, a patched builtin) that a later load reads. Such a program printed what Node does not: corpus 8750 calls a function that patches a builtin and then uses a package that captured that builtin at its top level. There is one mode now. Starting Node evaluates every deferred module registered so far, in import order, before the operation that needed Node, and a deferred import reached after that loads at its position. No island code other than deferred loads runs before a deferred load, so the island executes the loads in Node's order. The M snapshot record, the component computation and the runtime's per-module state are gone. A program that never uses an island still never starts Node. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…/closed-island-init
The probe loads a file two island modules share once, in the load of whichever module reaches it first, and records what that evaluation read (an environment variable, the terminal, the clock, an input no guard covers) in that module only. The other module's exports can hold values computed from the same read, but its record was empty: it got no guard and kept its constants. With the variable set at run time the first module failed its guard and loaded at its import, while the second could stay deferred and answer a constant from the build's snapshot (corpus 8751: `constants debug info` where Node prints `constants debug debug`). Modules that share a file now carry each other's reads: the environment and terminal guards, the inputs that keep a module eager, and the clock or random reads that keep constants out of a snapshot. Co-Authored-By: Claude Code <noreply@anthropic.com>
import { Type } from "schema-pkg"; // an island package
const item = Type.Object({ id: Type.String() });
export const list = Type.Array(item);
Lowered call by call, each initializer crosses into the island once per
call with its literal arguments, and the first of them starts Node while
the program's modules still initialize, whatever the build deferred. An
initializer built only from literals, island imports, other such
constants, and member reads, calls and `new` on those holds no native
value, so the island can evaluate it itself.
The build writes the initializers of each run of consecutive such
constants into an island module of the shipped tree
(.scriptc/closed/<module>.<digest>.<n>.mjs), loads it where the run's
first constant is declared through nodeIsland.defer, and each constant's
initializer becomes a read of the module's export, met with the conversion
the original expression's value met. The rest is the deferral of a static
island import: the probe decides, the interface snapshot stands in until
Node starts, reconciliation keeps identity. The native constant keeps its
global, type, exports and initialization flag. One module per run: a
native statement between two closed constants still runs between them.
A constant is closed when its value is the island's own on both sides (a
handle or an immutable primitive) and every operand is initialized before
it in every execution; anything else keeps the call-by-call lowering.
A generated module computes from state other island code left behind, so:
- it defers only where the program reaches it at one fixed point of its
start (before any await, outside conditions, in an initializer only the
static import graph calls), where the modules probed before it are the
ones evaluated before it; elsewhere it evaluates at its position;
- a program with generated modules does not use per-module mode, where a
snapshot would answer for a module while Node runs;
- the probe withholds the effects and inputs of these modules instead of
performing them: the build does not run the program's effects.
A literal in a closed initializer is now made by the island. On main an
island-made object fails the checked conversion when the program receives
it through an untyped value and asserts it to a native record type; that
case becomes a TypeError until island-made values pass checked
conversions (#3029). Reads typed by the island's declarations are views.
No runtime change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com> # Conflicts: # packages/compiler/src/node-islands/snapshot.ts
…ile tests Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…ependency Two island packages share a dependency whose top level reads an environment variable, and a native module imported between them sets the variable. The second package fails its guard and loads at its import. The first, still deferred, has to evaluate before it, against the environment of its own import, as Node evaluated the dependency at the first import. With per-module evaluation the second load evaluated the dependency first, with the variable set. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The walk that orders the probe followed a load where it is a whole
statement, through the sequence an inline require() lowers to, and
through the two operands of a string comparison. A require() the module's
own evaluation runs anywhere else inside an expression,
const isFunction = typeof require("pkg-a").a === "function";
sits inside a member read inside a `typeof` inside the comparison, was not
reached, and kept the IR's order after every load the walk did reach.
With a file shared between that package and one a later module imports,
the later one was probed first again and the wrong build of the first
commit came back: the shared file's output moved past the native module
between them (tests/corpus/8707-node-island-probe-order-nested).
One rule instead of a case per expression kind: the walk follows what an
expression holds, in the order it is written. A sequence keeps its own
case, as the only expression that holds statements; the string comparison
is the general rule now.
Across the 103 island programs of the corpus and the deferral fixtures,
the only program whose deferral decisions change is the new one.
Co-Authored-By: Claude Code <noreply@anthropic.com>
… closed constants
Three follow-ups to the review fixes of the commit below.
A refusal that is caught. For a generated module the probe refuses the
program's file reads and write-opens before performing them. The two
filesystem wrappers threw without recording the call, so when the island
package caught the refusal and went on with a fallback,
exports.motto = () => {
try { return fs.readFileSync(file, "utf8").trim(); } catch { return "no motto"; }
};
const text: string = motto(); // a closed constant
the evaluation completed with nothing in its record, the module was
deferred, and the executable printed the probe's "no motto" where Node
prints the file. The wrappers now refuse through `note`, as every other
withheld call does: it records the call before it throws, so the record
names it whether or not the package catches, and the module evaluates in
the run (tests/corpus/8736-node-island-closed-const-caught-refusal, a
recorder unit test). Timers are refused the same way: a closed
initializer sets no timer in the build.
One form. With one module per constant, a "run" holds exactly one
constant; the run type, its list and the lookups through it are deleted,
and a constant carries its own module key and index. No behaviour change.
Tests. The harness test of the printing initializers expected two
generated modules; with one per constant there are three (two that print
and stay where they are, one that only names the second and defers). The
test that the build does not perform an initializer's effects runs for a
second program, whose initializer opens a file for writing: the file does
not exist after the build and exists after the run.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The walk of the fixed-point rule takes the probe-order branch's descent into expressions (renamed to `inside` here, where `within` is the initializer being walked); a load inside an expression counts as reached under a condition. The recorder tests of both sides are kept. Co-Authored-By: Claude Code <noreply@anthropic.com>
8736 is taken on main (8736-new-set-map-dyn-iterable.cjs); the program of the commit before last is tests/corpus/8932-node-island-closed-const-caught-refusal. Co-Authored-By: Claude Code <noreply@anthropic.com>
…o fix/island-probe-order-main 41f67e2 places the conditional part of an expression after the loads the walk reaches. That is right for a run that skips it and wrong for a run that takes it (the 'taken' pair of 8746-node-island-probe-order-conditional), which is why the decisions rest on the shared-file rule and not on the order; both are kept, and the walk's comment says what the order is. Co-Authored-By: Claude Code <noreply@anthropic.com>
…at/closed-island-init 700632c keeps a generated module eager whenever a call was withheld, caught or not. It sits beside the recording of the call (the refusal goes through note): the decision says why, and the record names the call. The tests of both are kept; the ones that read the reason expect the new text. Co-Authored-By: Claude Code <noreply@anthropic.com>
Brings 41f67e2's ordering of the conditional parts of expressions. In the fixed-point walk such a part is reached under a condition, with the initializer it stands in. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
The sanitized native bootstrap of the validation run named in the description (dff0374 against 75cca60) finished: passed. Final lane totals of that run: plain 22,069 passed, 0 failed; sanitized 18,835 passed, 1 failed, which is the known flake |
The one conflict was the generated IR codec; it is main's file, regenerated (node scripts/generate-ir-codec.mjs) with this branch's `generated` field of a module tree entry. Co-Authored-By: Claude Code <noreply@anthropic.com>
… line The test of #3222 (in #3212's branch) compares a run's whole boot trace. Since #3216 on main, the first run of an executable also prints "installing the module tree", so the test fails on any tree that has both, on a fresh cache. The line is left out of the comparison. Co-Authored-By: Claude Code <noreply@anthropic.com>
Brings main (through #3264's merge of it, with the regenerated IR codec). Co-Authored-By: Claude Code <noreply@anthropic.com>
islandRooted stays exported (closed-island-init.ts asks it) and takes main's name for the helper that looks through parentheses and type wrappers; closed-island-init.ts calls that helper too, now that typed-arguments.ts no longer has its own. Co-Authored-By: Claude Code <noreply@anthropic.com>
With zod forced into a Node island the program used to end at its first
schema: `z.strictObject({ … })` handed the island a native record, which
the boundary refuses ("this native object cannot cross into a Node island
yet"). Its three schemas are island-closed constants now, so the island
builds them from its own literals and the program goes on: it prints 11 of
its lines and then ends where a handler receives zod's parsed value, an
island object, as the program's own record type ("expected object at $,
got Object from a Node island"). That conversion is the boundary's rule
for every object an island makes, on main too; the program only did not
reach it before.
The entry is the lane's own output for this head (SCRIPTC_ISLAND_CONFORMANCE
=update for this program, then a run in check mode: 4 of 4). Still a
failure, one step later.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Main builds again (#3338). The IR codec is regenerated for the merged ir.ts (main's changes and this branch's `generated` field of a module tree file). Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Pushed 04d0005: main merged in (the wrapper helper #3254 renamed, used here by The red "Node island conformance 3/4" job (
So the forced-island build still fails, one step later, at the boundary's rule for every object an island makes (the difference this PR's description accepts; #3029 lifts it). The entry in Checked on the pushed tree in a Sandbox: |
|
Closing as superseded by #3350, the 🤖 Generated with Claude Code |
Main gained the two ci-tests trains (#3349, #3351), #3324, #3203, #3135, #3333, #3109, #3103 and #3102. Two conflicts, both sides kept: - frontend/lowering/lower-node-island.ts: main's fromIsland (#3203: the declaration that types an island value), exported as the closed-constants lowering needs it (#3264). - frontend/lowering/module-namespace-elements.ts: #3290's in-progress guard with main's lookup of a lifted function through the index (#3192's guard). The runtime feature tables take #3211's deferredFence beside main's conversionTrace (#3324): named keys, no slot shared. Co-Authored-By: Claude Code <noreply@anthropic.com>
Regenerated by the lane's update mode on the train's tree (the train plus main 293036c) and compared with #3367's list, which was generated on the same main. Three entries differ, and the value matrix is identical: - npm/hono-zod-validator, island:zod: no entry. With #3264's closed constants and main's conversions of island values the program matches Node. - npm/hono-zod-default-hook and npm/hono-zod-validator, island:@hono/zod-validator: #3312 fixes the build bug behind main's SC8006 (the island tree listed a file that is on no disk); the programs build and differ from Node at run time (a 500 answer; exit 1, Malformed JSON). The entry #3290 added for corpus/js-package-tuple-reflection is gone: with main's JSON.stringify of island values the program matches Node. Checked on that tree: conformance lane 432 passed, 68 skipped, 0 failed; value matrix 239 of 239. Co-Authored-By: Claude Code <noreply@anthropic.com>
The build writes the probe's copies of generated island modules (#3264) into the temporary directory and tells its own modules from the program's files by their paths. Node names a module by its real path, so where the temporary directory is reached through a link the copy was taken for a file of the program ('the build does not evaluate …, a file of the program'), and every island-closed constant stayed eager. macOS is such a system (/var links to /private/var): there the feature was off, and ten tests of node-island-closed-init.test.ts failed in CI's macOS jobs on this branch's first head. Linux was not affected. The scratch directory is taken by its real path. The new test points TMPDIR at a link outside any node_modules directory (the probe never refuses a file under one): it fails without the change with that message and passes with it. Co-Authored-By: Claude Code <noreply@anthropic.com>
The fixture's `const session = open("x", 30);` is a module-level constant
closed over an island package: this branch evaluates it inside the island
(#3264), so it is no longer a statement of the top-level initializer,
which is in the dynamic tier. With `let session`, which is not closed, the
report says 11 as on main. The list of checked conversions that trust an
unverified type is unchanged. Written by the test's update mode.
Co-Authored-By: Claude Code <noreply@anthropic.com>
One conflict, tests/harness/island-conformance/known-failures.json: main rewrote the signature of npm/hono-zod-validator (island:zod) for the diagnostic code refusals now name; this branch has no entry there, because the program matches Node here (#3264 builds its zod schemas inside the island). Kept: no entry. Every other entry is main's. Co-Authored-By: Claude Code <noreply@anthropic.com>
A closed island constant (#3264) is a module the build writes, which Node loads when the program reaches the declaration. Main's #3377 lets a program replace `process.stdout.write` beside a module of its own that patches a builtin (`fs.readFileSync`). With both, the constant's module was loaded after the patch, Node's loader read the generated file through it, and the program ended with `ReferenceError: patched is not defined` where main prints its lines (tests/harness/node-island.test.ts, "replacing process.stdout.write beside an island builds, and the island's write is the program's"). When a module of the program is in the island because it patches a builtin module, no constant of the program is closed: every initializer is evaluated where it is written, as on main. The pre-scan's marker is carried on the island module record for that. Not covered: a package that patches the loader's inputs at run time without the pre-scan seeing it. A generated module is still read through Node's public loader. Co-Authored-By: Claude Code <noreply@anthropic.com>
…osed island constants, their boot guard, deferred program modules Train startup-1 members. Head b26b0df of feat/defer-program-islands, which contains vercel-labs#3264 (70aae07) and vercel-labs#3313 (d9be99b). Conflicts with vercel-labs#3201's later commits (d82f116, 5d63c2e), resolved once: - snapshot.ts: one loadReads, vercel-labs#3201's (reads and effects shared by the loads of a file, the clock rule), in place of the stack's sharedEffects and its earlier copy of loadReads; the stack's unset-variable guard, P guard and probe copies kept. - probe-recorder.test.ts: both added tests kept. - lower-node-island.ts: islandRooted exported, with main's withoutParenthesesTypeWrappers. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ones vercel-labs#3201 keeps a module eager when a load it shares a file with read the clock, and reports shared effects as 'a load it shares a file with has effects'. vercel-labs#3212's shared-file test and vercel-labs#3264's closed-constants test pinned the behaviour before that rule. Co-Authored-By: Claude Code <noreply@anthropic.com>
…plan computed at build time Train startup-1 member. Head 3bfe420 of feat/island-link. Conflict in node-islands/modules.ts (readNodeIslandModules): the tree's bytes come from the build's own files (the load plan, vercel-labs#3236) first, then a placeholder, then a generated module's text (vercel-labs#3264), then the disk. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ormat one test closed-island-init.ts (vercel-labs#3264) imported stripTypeWrappers from typed-arguments.ts, which main's vercel-labs#3254 replaced by withoutParenthesesTypeWrappers in outer-expressions.ts (the same wrapper set). load-plan-hooks.test.ts (vercel-labs#3236) had one line the formatter rewraps. Co-Authored-By: Claude Code <noreply@anthropic.com>
Stacked on #3201 (probe order) and #3212 (unset environment variables, which now holds #3222's single evaluation mode); the two merge first. This PR's own commits are "Evaluate island-closed module-level constants inside the island", the review fixes on top of it ("Address review feedback", "Record a refusal the island package catches, and keep one form of the closed constants"), and the two merges that bring in the heads of #3201 and #3212.
What changes
Lowered call by call, each initializer crosses into the island once per call with its literal arguments, and the first of them starts Node while the program's modules still initialize, whatever the build deferred. An initializer built only from literals, island imports, other such constants, and member reads, calls and
newon those holds no native value, so the island can evaluate it itself. The build now writes each such initializer into an island module of its own, which ships in the module tree:The module loads where its constant is declared, through
nodeIsland.defer, and the constant's initializer becomes a read of its export, met with the conversion the original expression's value met. Everything after that is the existing deferral of a static island import: the build-time probe decides whether the module evaluates there or on first use, the interface snapshot stands in until Node starts, and reconciliation gives the stub its identity. The native constant is unchanged: the same global, type, exports and initialization flag, so a read before the declaration still throws Node's ReferenceError.A program that declares such constants and does not use them on a run no longer starts Node for them.
What counts as closed
A top-level
conststatement of an ES module written in TypeScript that declares one constant, an identifier, whose initializer is:undefined,NaN,Infinity; island imports (named, default, an ES module's namespace, also through a native module'sexport { x } from "<island>"); other closed constants;a.b,a[k], calls,new; object and array literals of those (shorthand of a closed constant, spreads of closed parts). Type syntax is dropped;islandRooted) with at least one call ornew;unsafe<Colors>({ fg: "red" })) is converted from native data, which an island literal is not, so it stays as it is;Anything else keeps the call-by-call lowering:
let/var/destructuring, a native binding, an enum or namespace member, a global such asJSON,this,await, closures, operators, conditionals, templates with substitutions, optional chains, a statement that declares more than one constant, CommonJS and JavaScript source modules.One module per constant: each initializer evaluates between the native statements around it exactly as before. A native statement between two closed constants still runs between them, and when a later initializer makes the island call back into the program (a listener the program registered), that code finds the earlier constants assigned (
node-island-closed-const-callback).Why it is the same program
When the load is eager it evaluates the initializer against the same island objects, at the position the statement had. Deferring it is decided by the probe as for any island module (no throw, no effect, no input). Two things are specific to generated modules, because their code, unlike a package's top level, computes from state other island code left behind (
registry.count(), an id counter):await, or under a condition, the module evaluates where its constants are declared.process.exit) is recorded and then thrown, and the module is not deferred. Recorded first, so that a package that catches the refusal and goes on with a fallback does not have its fallback taken for the constant (8932-node-island-closed-const-caught-refusal). Harness tests check that a file an initializer writes, or only opens for writing, does not exist after the build and does after the run.A load reached while Node already runs evaluates at its position (#3222's single mode, in #3212), so a constant declared after the program's own island calls sees them:
registry.add("x"); const n = registry.count();reads 1 (8939-node-island-closed-const-after-call).One difference from main, loud and narrow
A literal in a closed initializer is now made by the island instead of crossing as a native dynamic object. If the island hands it back through an untyped value and the program asserts it to a native record type (
registry.get("k") as Config), the checked conversion throws a TypeError where it converted before. This is what main does for every object island code made, and #3029 removes it for all of them. Reads typed by the island's declarations are unaffected (#3093: a program type that receives island values there is a live view of the handle);6899-node-island-unsafe-recordand6900-node-island-merged-record-inpin the two neighbouring cases and pass.Before Node starts
typeof X: the stub's kind, no Node.X === Y: stub identity; the probe numbers objects across all modules of its run, so two constants bound to one island object are one stub, and reconciliation keeps it.X instanceof C: starts Node.const major: number = semver.major("1.2.3")): the snapshot's value through the checked conversion, no Node; none when the evaluation read the clock or random numbers (then the declaration starts Node, as today).Files
frontend/lowering/closed-island-init.ts(new): the analysis, the printer of closed expressions, the load and the initializer's read, andSCRIPTC_NODE_ISLANDS_TRACE=closed(one line per module-level constant whose initializer is an island operation: closed, or what keeps it native).lower-node-island.ts: five exports and one call at the top oflowerIslandExpr.lower-modules.ts: one call inlowerFileInit.lowerer.ts: two memos, and generated loads get their tree path.node-islands/generated.ts(new),modules.ts: generated files in the module tree; the IR's tree entry carries their text (generated), codec regenerated.node-islands/snapshot.ts: probe copies in a private temporary directory, the fixed-point rule, no boot guard for a copy.probe-recorder.ts: the generated files the probe may evaluate, and the withholding.After review
Four findings of the review bot on this PR, each reproduced with a program first. The maintainer's commit on this branch (64fea0b) is kept and built on.
fs.openSync(path, "w")went through a recorder wrapper that records only after the call). Fixed in 64fea0b; reproduced on e66fcd9: the file exists after the build. Harness testeffects-open.bus.on(() => { seen = first.label; }); const first = make("first"); const count = bus.emit();printedfirst 1under Node and died with "Cannot access 'first' before initialization" natively. Fixed in 64fea0b by one module per constant (node-island-closed-const-callback); 9e542a7 deletes the run type that was left with one element.try { return fs.readFileSync(file) } catch { return "no motto" }completed with an empty record and the executable printed the fallback (reproduced at 64fea0b). 9e542a7 routes every refusal through the function that records before it throws (8932-node-island-closed-const-caught-refusal, a recorder unit test, a harness test).8918: all three modules evaluate where their constants are declared).Proof
The focused suites ran on the head (ccc383a). The gate and the lanes ran on dff0374, two merges earlier: what came after is 700632c (one condition in the recorder and its test) and #3201's 41f67e2 (where the walk places the conditional part of an expression), each covered by the focused suites.
8910: libnode is never mapped) and used (8911); a chain across two modules (8915); one constant seen through two importers, identical before and after Node starts (8916); an initializer whose island function prints, between two nativeconsole.log(8918); one that throws, uncaught, exit 1 (8919);process.exitbefore any use, exit 3 (8920); a read before the declaration, ReferenceError as in Node (8930); a refusal the island package catches (8932); primitive constants from a CommonJS package (8935); constants declared before and after the program's own island calls (8939); a native operand and an island array changed through its handle (8940); constants of a dynamically imported module (8941); a listener that reads an earlier constant (node-island-closed-const-callback).tests/harness/node-island-closed-init.test.ts, 9 tests, from the build's traces and the run's boot trace: deferred generated modules and Node never starts when the constants are unused; primitive constants without Node; exit before use; initializers with effects evaluate in place; a file an initializer writes, or only opens for writing, does not exist after the build and exists after the run; a caught refusal does not make the fallback the constant; theclosedtrace names what keeps a constant native; a constant declared after Node started evaluates there, not from its snapshot.node-islands(the generated text and its probe copy, the decisions for generated modules, the recorder's refusals and file lists): 220 of 220.--gate-corpus=allagainst 75cca60 (the commit below this PR's first one; the candidate also holds the heads of Probe deferred island modules in the order the program evaluates them #3201 and Defer island modules that read only unset environment variables, and evaluate deferred modules in Node's order #3212): PASS, 5,237 programs identical, 108 fail to build on both sides, none differ; compile time within tolerance (bench +0.21%, fixtures -0.17%; self +0.75% with a confidence interval that spans zero). The gate builds without a libnode pack, so the programs this change touches are the island programs, which it leaves out; they are the corpus programs above.2963-child-fork-dispatchin the sanitized llvm-differential suite, the known flake of Stop two corpus programs racing, and set three harness budgets from measured times #3300 (the program prints at 'close', which Node may emit before 'disconnect' on a busy machine); alone on this head it passes 3 of 3. The sanitized native bootstrap of this run was still going when this was written; its result follows in a comment.pnpm lint,pnpm format:check,pnpm test:ts7 --baselines-only, the IR codec check: clean.After merging main
Head 70aae07 holds main at 97d3722. The one conflict was the generated IR codec (main's file, regenerated with this branch's
generatedfield). On the merged head: lint, format, the codec check and the baselines are clean; unit tests innode-islands220 of 220; the closed-constants harness file 9 of 9; the island corpus 139 of 139, plain (both suites) and sanitized; the island harness suites 112 of 113, the one beingnode-island-deferral.test.ts"all of them when Node starts…", a test of #3222 that compares a whole boot trace and meets main's new first-run line "installing the module tree" (#3216). It fails on any tree with both; the last commit leaves that line out of the comparison, and the file passes on a fresh and on a warm cache. The island value matrix shows main's five known cells (#3298) and nothing else. The gate and the lanes were not repeated on the merged head.What it does for start-up, and what it does not do yet
Neutral programs (corpus programs of this PR; main 979a652 against this branch at e66fcd9, Intel Xeon @ 2.90GHz, 8 vCPU, 30 warm runs each):
8910)8935)8911)pi's start-up does not move with this PR. Measured on the pi run tree (a scratch tree with the open pi PRs, not this branch; Intel Xeon @ 2.50GHz, 32 vCPU), both binaries with #3201 and #3212:
pi --versionwarm 1,160 and 1,300 ms before, 1,300 and 1,250 ms after (two measurements each, alternating); cold 2,530 and 2,680 ms before, 2,720 and 2,550 ms after. Crossings made by native initializers during start go from 337 to 297 function-value calls, 391 to 332 method calls and 1,014 to 893 property reads. Two reasons, both visible with the traces this PR adds:{ a: Type.Number() }held as a native record), or a template has substitutions.SCRIPTC_NODE_ISLANDS_TRACE=closedprints one line per such constant, closed or what keeps it native.🤖 Generated with Claude Code