Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate eca349d244 Forward nested zlib callback results and keep self-calling callbacks' error checks
A nested callback lowered inside an outer error path passed the absent
Buffer on into a Buffer slot even when its own operation succeeded, so a
callee such as (err, out) => cb(err, out) received undefined instead of
the result. The forwarded value is now the Buffer when there is one.

A named function expression passed to zlib can call itself, and a
function expression can reach itself through arguments.callee, with an
error and a Buffer. Such functions no longer count as called only by
the adapter, so their success copy keeps the runtime error checks
instead of folding them to the success case.
2026-10-21 15:32:52 -05:00
Malte UblandClaude Opus 5.5 eaefbdf44f Host the runtime's event loop on Node's libuv once Node loads (NI-3)
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>
2026-10-21 13:31:51 -07:00
Malte UblandClaude Opus 5.5 50a34224b9 Pin the CI dev libnode to dockfries v24.22.1
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>
libnode-dev-24.22.1
2026-10-21 13:31:32 -07:00
Chris Tate 1c2b80040b Deliver queued worker messages before immediates, as Node does (#1682) 2026-10-21 15:05:13 -05:00
Chris Tate 4140bcefe2 Walk AST subtrees by their node ranges (#1706) 2026-10-21 15:04:21 -05:00
Chris Tate ca68d63c1c Finish a worker message batch before port messages, and count queued messages as they arrive
- Port messages that a 'message' listener posts while more worker
  messages are queued are delivered after those messages and before
  setImmediate callbacks, as in Node. The ports' poll phase waits while a
  batch's next message is queued, and the loop pass continues once more
  after the batch when a port has something to deliver.
- A mailbox counts its queued events per kind as they are posted and
  taken, so starting a batch reads the count instead of scanning the
  queue under the lock. Messages that alternate with a worker's captured
  stdout or stderr start a batch each, which made delivery quadratic:
  20000 such messages took 5.6 s to drain, and now take 0.5 s (Node 0.57 s).
- The continue hook passes on that the batch's next message is at the
  head, so the dispatch that follows does not lock the mailbox again.
- Corpus 6212 checks the order of worker messages, a MessageChannel
  message posted by the first listener, and setImmediate against Node.
  6211's costs are now taken from its CI timings.
2026-10-21 15:00:22 -05:00
Chris Tate 7ff0ea472d Walk AST subtrees by their node ranges 2026-10-21 14:59:26 -05:00
Malte Ubl 3e26bb85da Merge branch 'main' into ni-2-node-island-report 2026-10-21 12:51:05 -07:00
Chris Tate 543639bdbd Shorten the cycle collector's per-object paths (#1705) 2026-10-21 14:28:10 -05:00
Chris Tate a1f87d817e Resolve the stream-construction fixture's file read on close (#1704) 2026-10-21 14:27:24 -05:00
Chris Tate c1b413e5cc Shorten the cycle collector's per-object paths
markGray's edge visitor kept a stack frame on every call, though most
edges end at an object that is skipped or already gray. Those now return
without one, and the first visit and skipped cross-generation edges
tail-call out of line, as the scan visitors already do.

The walks no longer call the dense-array walker for an empty array. The
routing of nursery candidates past bypassed levels inlines its bypass
test, and the end of each pass tests its work vectors' capacity inline.
What is collected, and when, is unchanged.
2026-10-21 14:25:23 -05:00
Chris Tate 3b35db6b6b Resolve the stream-construction fixture's file read on close
readFileStream resolved on 'end' while its 'close' listener printed
"released". 'close' follows 'end' once the descriptor is closed, so the
line landed wherever that completion fell among the child processes
the fixture starts next, and the comparison with Node depended on
timing. The read now resolves from its 'close' listener, after the line
is printed.
2026-10-21 14:24:57 -05:00
Chris Tate 27855a3835 Resolve conditional member types over literal class type arguments (#1669) 2026-10-21 14:23:50 -05:00
Chris Tate 644f423734 Evaluate fs arguments up to the last one with effects before a URL path conversion
A synchronous fs call with a URL path evaluated only the later arguments
with effects ahead of the path conversion. When such an argument
reassigned the variable an earlier argument reads, as in
fs.chownSync(url, uid, next()), the earlier argument was read after the
reassignment. Every argument from the URL through the last one with
effects is now evaluated in source order before the conversion.

The npm fixture node24-fs-descriptors now reaches its SC2020 refusal for
writeSync with a hex encoding, since openSync accepts its file URL.
2026-10-21 14:22:51 -05:00
Chris Tate ca31b48bfd Revisit only the changed sites of the optional-read fixed point (#1703) 2026-10-21 14:22:33 -05:00
Chris Tate 558fb7c8ba Bind only conditional-dependent class type arguments as checker types
A generic class instance now binds a type parameter to its checker type
only when a conditional type in the class depends on it, and that checker
type keys the instance. Before, every type argument was bound while only
literal arguments of those parameters were in the key, so two instances
such as Holder<R, "a"> and Holder<R, "b"> with a `T[K]` field shared the
first instance's layout. The other parameters get no checker binding, so
member types that depend on their literal identity stay unresolved, as
before.

Detection now follows the type aliases and interfaces a member's type
refers to, so a conditional wrapped in a union alias, such as
`(K extends "required" ? T : T | undefined) | null`, also separates the
instances.
2026-10-21 14:20:20 -05:00
Chris Tate 453bcf8e38 Revisit only the changed sites of the optional-read fixed point 2026-10-21 14:19:48 -05:00
Chris Tate 7c235ce72b Stop importing runtime bitcode into program builds (#1701) 2026-10-21 14:13:58 -05:00
Chris Tate fc38e62791 Stop importing runtime bitcode into program builds 2026-10-21 14:10:23 -05:00
Chris Tateandsaboursepideh a7e84cd48c Support SNICallback in tls.createServer
tls.createServer({ cert, key, SNICallback }) hands each ClientHello's
servername to the callback and serves the SecureContext it answers,
synchronously or later, or the default cert and key when it answers none.
An error answer closes that connection. This uses the SNI handling that
http2.createSecureServer already has. A callback expression with side
effects, or SNICallback combined with ALPNProtocols, minVersion, maxVersion
or the other options the runtime walk reads, fails with SC2020.

A tls.connect or https.request client whose peer closes or resets the
connection before the handshake completes now fails with Node's ECONNRESET
"Client network socket disconnected before secure TLS connection was
established" instead of closing without an error.

Refs #1438

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 13:56:07 -05:00
Chris Tate 3a139a8d40 Negotiate ALPN and bound the TLS version in tls.connect and tls.createServer (#1664) 2026-10-21 13:52:22 -05:00
Chris Tate 9e751b3d38 Give arrays of a known final length exact dense capacity (#1699) 2026-10-21 13:31:21 -05:00
Chris Tate 320abb066e Fix Readable.push and write of string | null and Buffer | null chunks (#1698) 2026-10-21 13:27:12 -05:00
Chris Tate 1d6f26a071 Give arrays of a known final length exact dense capacity 2026-10-21 13:26:50 -05:00
Chris Tate 23ec584deb Read nullable union chunks of push and write by their representation
Readable.push and Writable.write with a union chunk (Buffer, string and
null arms) read the chunk's tag and payload from a tagged union box. A
union with one reference arm and null, such as `string | null` or
`Buffer | null`, is the arm pointer itself, so `push(line)` with
`line: string | null` and `push(done ? null : text)` read past the
string and crashed. Both now read the tag and payload with unionTag and
unionPeek, as the other union consumers do. unshift and end refuse
union chunks and are unchanged.
2026-10-21 13:24:10 -05:00
Chris Tate a4d32a8e38 Use the default TLS version bound for null minVersion and maxVersion in tls.createServer
tls.createServer validated minVersion and maxVersion with its own loop,
which threw ERR_TLS_INVALID_PROTOCOL_VERSION for null. It now uses the
same check as tls.connect, where null and undefined select the default
bound, as in Node.
2026-10-21 13:22:56 -05:00
Chris Tate 5e30e13c87 Read ASCII characters of strings inline in charAt and indexing (#1697) 2026-10-21 13:16:06 -05:00
Chris Tate 7821dccba2 Shrink the array header to 32 bytes (#1695) 2026-10-21 13:14:21 -05:00
Chris Tate 5c851762d6 Read ASCII characters of strings inline in charAt and indexing
charAt and s[i] on a string proven ASCII at an exact in-range index now
return the character from a module-level table of the 128 one-character
strings, emitted with the helper as immortal literals with the same
capacity word and key hash as the runtime's single-character statics.
An exact integer index (a counted-loop counter) skips the double
conversion. Every other string or index calls scr_str_char_at as before.
2026-10-21 13:12:24 -05:00
Chris Tate 96f31cbc18 Shrink the array header to 32 bytes
An array's element ops are a registry id in `spare` instead of a pointer
field: descriptors get their id on their first array (emitted descriptors
are writable for it), and a program with more element types than the id
holds keeps the rest in the array's out-of-line storage.

The out-of-line storage (sparse indices, noncanonical numeric properties,
match metadata) is no longer a header field. An array that has it keeps
its pointer in the 8 bytes before its dense allocation and sets
SCR_ARR_HAS_EXT; such an array always has a dense allocation of its own
(inline slots move out, an empty array gets the prefix alone), and every
allocation, reallocation and free of the dense storage accounts for the
prefix.

Loops over elements look the ops up once, and the state lookup below the
dense capacity reads no out-of-line storage.
2026-10-21 13:04:07 -05:00
Chris Tate 56ff099115 Document when builds use multiple cores (#1694) 2026-10-21 12:55:10 -05:00
Chris TateandQuino Terrasa 1bf49286d1 Document when builds use multiple cores
How It Works gains a Build parallelism section: the frontend runs on one thread, executables divide into
partitions by the size of their LLVM text, the LLVM helper compiles partitions on a thread per hardware thread,
what dev and optimized builds run in parallel and in order, what a rebuild compiles again, and what
profile-guided optimization adds to build time. The CLI reference links to it from --emit, --optimization,
--profile-use, SCRIPTC_CACHE_DIR, and Profile-guided optimization.

Fixes #1340

Co-authored-by: Quino Terrasa <26749475+espetro@users.noreply.github.com>
2026-10-21 12:50:36 -05:00
Chris Tate 349ab75298 Evaluate every ToNumber argument before converting, and convert conditionals
The Math functions and String.fromCharCode/fromCodePoint evaluate all of
their arguments before they convert any, as in Node. When an argument's
conversion can run program code (valueOf or toString on a dynamic value)
and a later argument follows, the arguments now evaluate into temporaries
first and convert in order after (lowerToNumberArguments). Before, a
dynamic argument converted before the next one was evaluated.

A conditional argument whose branches are not both numbers boxes its
value and converts it like any dynamic value, so `isFinite(flag ? "12" :
"abc")` in a JavaScript file returns a boolean instead of reaching a
run-time fence.

A statement inside an expression can end its block (an argument call that
always throws). The expression's later statements and result now emit in
a block with no predecessors, so the instructions there read only values
defined in it.
2026-10-21 12:50:14 -05:00
Chris Tate 8cc2594d6e Place a beside libnode pack on cache hits, key the cache by the pack's manifest, keep the loop for synchronous Node calls, and record the Node call programs' costs
- A --libnode=beside build whose executable comes from the executable
  cache places the pack next to it again (compile, index.ts): the pack
  is an output of the build, and a rebuild after the outputs were
  deleted restored the executable without it.
- The cache key names the libnode pack by the option, the pack
  directory and a digest of its manifest.json (libnodeCacheKey), so a
  pack upgraded in place gives cached executables a new key instead of
  their old recorded Node version.
- nodeIsland.callSync marks the program's event loop live, as
  nodeIsland.call does: the called function may leave Node with ref'd
  timers or I/O, which the loop drives before the program exits.
- scr_runtime.h includes scr_node_island.h, so the LLVM backend's
  declarations of the Node island entry points have prototypes there.
- Corpus costs for 6800-6803, from CI's differential and
  llvm-differential timings of the programs.
2026-10-21 12:45:05 -05:00
Chris Tate 6d19198eed Merge main 2026-10-21 12:43:37 -05:00
Chris Tate 975eda9646 Link scr_bytes_dyn.c into libraries (#1693) 2026-10-21 12:40:07 -05:00
Chris Tate d8265b2209 Link scr_bytes_dyn.c into libraries 2026-10-21 12:35:58 -05:00
Chris Tate a437102af6 Keep object literals' key order through IR rewrites and on shapes typed JSON.parse also builds (#1607) 2026-10-21 12:28:31 -05:00
Chris Tate 0739b81f7a Group record-flow references by symbol once per name (#1690) 2026-10-21 12:22:44 -05:00
Malte UblandClaude Opus 5.5 091b8f1934 Report which packages and modules would run in a Node island
Adds the report-only Node island policy, configuration and detector of
the scope-B plan (milestone NI-2). Coverage now lists the npm packages a
build would run in a Node island and why, gap islands first, without
changing code generation.

- project-config.ts reads the `scriptc` key of the entry's package.json
  with a schema; an unknown value in `scriptc.packages` or
  `scriptc.nodeIslands` is SC8004 instead of being ignored. Package shims
  and the WebAssembly scanner read through it.
- node-islands/: origin (user, npm, shim, builtin; package instances),
  rules R1.1-R2.5 and the four classes, a syntactic pre-scan for R1/R2
  markers, classification of refusals and runtime fences (reason ids at
  the refusal sites that need them), the policy (auto, explicit, off;
  forced node and native packages; marked user modules) and the report
  with island closure.
- coverage: a "Node islands" section, `--explain-island=<pkg>`, a
  `nodeIslands` field in the diagnostics envelope, and `--fail-on`
  `node-islands`, `eager-node-islands` and `island-gaps`.
- CLI: `--node-islands`, `--node-island`, `--native`, `--node` (validated,
  report-only) and `--strict-user-js` (SC8101-SC8105 for R2 constructs in
  user JavaScript).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:22:35 -07:00
Chris Tate 293facfdf6 Group record-flow references by symbol once per name 2026-10-21 12:20:12 -05:00
Chris Tate 9a73f63b06 Pass undefined as the zlib callback result on errors
Node calls a zlib callback with (err, undefined) when the operation
fails. The callback adapter passed a NULL Buffer in the result's static
Buffer slot instead: member reads crashed and typeof printed "object".
This applied to deflate, inflate, deflateRaw, inflateRaw, gzip, gunzip
and unzip, with and without options, for callback literals and for
function values.

A function shaped (err, out), with err typed Error | null and out a
Buffer that the body reads, now tests out on entry (bytes.isAbsent).
With a Buffer, the body runs as before. With the NULL Buffer, a second
copy of the body runs with out read as any holding undefined, so typeof,
comparisons, templates, optional chains and console.log give Node's
answers and member reads throw Node's TypeError. Passed on into a Buffer
slot, out stays the NULL Buffer, which a callee with the same entry test
reads as undefined. The parameter keeps its static type, so callers and
adapters are unchanged. A copy that has no such lowering throws SC2020
when it runs.

Conditions on the error parameter fold in the error-path copy, and in
the success copy for a literal that only the adapter calls. Inside an
error-path copy, a nested callback lowers once with its result read as
any, so nested callbacks grow linearly in code size.
2026-10-21 12:17:22 -05:00
Chris Tate 3546a15a54 Call into Node.js from compiled programs with @scriptc/node (#1686) 2026-10-21 12:13:04 -05:00
Chris Tate ef5fc0fb2c Grow small map entry arrays through 1, 2, 3, 4, 6, 8, 12 and 16 slots (#1688) 2026-10-21 12:09:50 -05:00
Chris Tate 33110f1938 Track unions in nonJsonPart and expect per-platform lifecycle output 2026-10-21 12:08:57 -05:00
Chris Tate d59ec3251e Grow small map entry arrays through 1, 2, 3, 4, 6, 8, 12 and 16 slots 2026-10-21 12:06:08 -05:00
Malte UblandClaude Opus 5.5 51e9812493 Call into Node.js from compiled programs with @scriptc/node
Scope-B milestone NI-1, API side: the explicit call API that replaces the
build container's hand-written node subprocess (api patch 09).

- The new package @scriptc/node exports callSync(specifier, name, ...args)
  and call(...). The module loads with require() from the calling module;
  arguments and results cross as JSON copies; a thrown Error crosses as its
  nearest intrinsic class with its message and string code, other thrown
  values as JSON copies; callSync refuses a function that returns a
  promise. Its Node implementation runs the calls in-process with the same
  copies, so a program prints the same under Node and compiled.
- The compiler lowers calls to the named or namespace imports
  (node-api-import.ts, lower-node-island.ts) to the nodeIsland.callSync
  and nodeIsland.call libCalls: the calling module's real path, the
  specifier, the export name and the boxed argument array, with the result
  checked against the declared type like `JSON.parse(s) as T`. Arguments
  and results whose types are not JSON data are refused at build time, and
  the functions and the namespace refuse as values.
- Corpus programs 6800-6803 (sync and async results, errors, process.exit
  from Node with the program's exit listeners, Node's pending timers
  keeping the program alive) run under Node and as binaries that load
  libnode; `// @requires-libnode` skips them where SCRIPTC_LIBNODE names no
  pack (until the pack CI matrix publishes packs). The node-island harness
  covers loading Node only on the first call, the beside packaging, a
  missing pack, a pack of another Node version or bridge ABI, and the
  executable's symbols.
- Release tooling publishes @scriptc/node with the other packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:59:55 -07:00
Malte UblandClaude Opus 5.5 1081315a49 Run Node on its own stack; provide a libnode pack to the Linux CI test lanes
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>
2026-10-21 09:58:32 -07:00
Chris Tate e6a9e705f8 Keep the write-target AST getters out of the generated region (#1687) 2026-10-21 11:54:17 -05:00
Malte UblandClaude Opus 5.5 86f968c05f Test the key-order pass with checked conversions and IR rewrites
A shape only checked conversions build keeps today's table; one an object
literal also builds takes the literal's order; a literal the IR mapper
rebuilds keeps its witness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:53:39 -07:00