Commit Graph
4679 Commits
Author SHA1 Message Date
Chris Tate 972e44a11a Hold any later value in JavaScript properties initialized to undefined or null
A JavaScript object literal's property whose initializer is `undefined` or
`null` (`[{ oldPos: -1, lastComponent: undefined }]`, `{ user: null }`)
mapped to the unit-only union, so a later write of another value threw:
the diff package's diffLines failed with "a '{ added: boolean; count:
number; previousComponent: unknown; removed: boolean }' value is not
representable in the target union" when it stored its first path
component. Nothing checks a JavaScript file's writes, so such a property
is now checked-dynamic and holds whatever the program stores. TypeScript
properties keep the unit-only union, and `__proto__: null` still sets the
prototype.
2026-10-22 11:40:01 -05:00
Chris Tate eace8e1a17 Give the zod 3 npm case the long per-case timeout 2026-10-22 11:31:36 -05:00
Chris Tate f37dca56f0 Merge main 2026-10-22 11:20:04 -05:00
Chris Tate fd40a8c84a Try each PATH entry with exec when spawning with a replacement env
The PATH lookup for spawns with an `env` option picked the first
executable regular file and stopped there, so a script whose interpreter
is missing ended the search with ENOENT. Node's libuv runs execvp in the
child, which moves on to the next PATH entry when an exec fails with
ENOENT, ENOTDIR or EACCES. Each candidate now runs through posix_spawn in
order with the same rule, and relative entries are resolved by the child
in its cwd.
2026-10-22 11:18:30 -05:00
Chris Tate 74fcda0ad9 Evaluate every Promise.all entry before a dynamic entry's then lookup
A checked-dynamic entry's PromiseResolve reads its `then` property,
which can be a getter. The tuple form resolved each entry as the array
was built, so that getter ran before later entries evaluated; Node
evaluates the whole array first. With a checked-dynamic entry, every
entry now evaluates into a hidden local before the entries resolve.
2026-10-22 11:17:42 -05:00
Chris Tate 65a68470cf Free a typed teardown's blocks in the order it met them
A typed teardown freed each block onto its free list as it went, and the
free lists, which are LIFO, handed a torn-down structure's blocks to the
next allocations in the reverse order. A structure that a loop rebuilds
the same way each time was therefore laid out mirrored every other
generation, and every walk over it, its teardown included, ran through
memory backwards, which the hardware prefetchers handle three to four times
worse. A program that rebuilds a tree of 2^21 - 1 object literals 12
times, summing each generation before dropping it, took 26 to 28 ns per
node to build, 24 to sum and 16 to drop in those generations, against
6.8, 6.3 and 5.3 in the others. With this change, every generation runs
at the second set of times, and the program takes 0.50 s instead of
1.18 s (Node: 0.91 s).

The teardown of a class or record type with children of its own type now
keeps the blocks it frees in a segment, linked through their first words
and built in order through a tail pointer. A node releases its own block
first, places it ahead of its children's when its address is the lower
(object literals allocate before their fields) and behind them otherwise
(constructor arguments build the children first), and the chain's first
teardown frees the whole segment onto the free list in one step. A
teardown past DIRECT_DESTROY_CHAIN appends through scr_rc_destroy_seg,
which keeps scr_rc_destroy's depth budget and queue. Blocks outside the
calling thread's arena are freed as before, and targets without the
inline allocation paths keep the old teardown.
2026-10-22 11:12:23 -05:00
Chris Tate 2b657e6e3c Format the split tools' output with the tools checkout's oxfmt configuration
The split tools ran oxfmt in the checkout they rewrite. oxfmt looks up
its configuration from that directory and from the formatted files'
directories, and runs an oxfmt.config.ts it finds there, so a branch that
rehome-split rewrites could run code with the maintainer's permissions.
The tools now format through format.mjs, which passes the tools
checkout's .oxfmtrc.jsonc with --config and disables the nested lookup,
so no configuration file of the rewritten checkout is read or run. With
no files to format it does nothing, where oxfmt would format the whole
directory.

The split-tools tests cover split-methods and move-state on a checkout
whose root and src directory commit an oxfmt.config.ts that records it
ran.
2026-10-22 11:12:02 -05:00
Chris Tate 5ceec5659c Keep key orders exact through write values, live views and spreads
Three cases where a value's key order could differ from Node:

- A write of an optional key moved the key before its value was
  evaluated. A value that adds another key to the same record, as in
  `o.b = ((o.c = 1), 2)`, then put the keys in the wrong order. The write
  now evaluates its value first, then tests and moves the key.
- A value seen as a JavaScript value bound the field hooks of its order
  when it was boxed, and a later write that moved a key did not change
  them. Shapes whose values can change order after they are built
  (IrRecordShape.orderMoves) now read through the value's view, which
  follows the current order.
- A literal that spreads a value with orders and then sets a key the
  value may lack, as in `{ ...value, tag: "x" }`, put that key where the
  source's order has it. Node puts it there only when the value has it,
  and after the value's keys otherwise. The literal now picks its order
  from the source's order and which of those keys the source has.

Corpus 7136 and 7138 cover each case.
2026-10-22 11:06:14 -05:00
Chris Tate a6a1eb2b95 Support Duplex.from, compose stages, readable.compose and pipeline function destinations 2026-10-22 10:57:53 -05:00
Chris Tate 84cf0ec19f Inspect dynamic classes, functions and %o arguments as Node does
- The inspector throws the 'not supported yet' Error for checked-dynamic
  values it does not render (a promise, a URL, a RegExp reached through
  `unknown`). Its calls were not marked as throwing, so
  `console.log("x", p)` printed "x " and the program went on; records and
  arrays with such a field printed `{ a:  }` and `[  ]`. insp.dyn and
  insp.dynS are now may-throw library calls, so the Error propagates from
  the call that inspects the value, before anything is written.
- A class reached through `unknown` prints `[class C]`,
  `[class C extends B]` or `[class (anonymous)]` instead of
  `[Function: C]`. Functions and classes reached as dynamic values print
  their enumerable own properties (`[Function: f] { x: 1 }`,
  `[class S extends A] { count: 3 }`), cycles through them as
  `[Circular *N]`, and `[Function]` past the depth limit. Class objects
  carry their base class's name. A compiled class's static fields are
  module globals that the class object does not carry, so inspecting a
  class that declares static fields through `unknown` throws the
  'not supported yet' Error.
- util.format's %o renders a checked-dynamic argument with showHidden: an
  array's `[length]` entry and an object's non-enumerable own properties,
  string- and symbol-keyed, in brackets. An array with more than 100
  output entries keeps the last one out of the grid, as Node's
  groupArrayElements does. %o of a function, an Error, a typed array or a
  native handle other than Map, Set and Date throws the
  'not supported yet' Error.

ScrClassObj has two new fields, so RUNTIME_ABI_VERSION is now 27.
2026-10-22 10:54:02 -05:00
Chris Tate 4d7de03054 Store arrays of ProgramLifetime instances in 4-byte slots on Linux 2026-10-22 10:46:16 -05:00
Chris Tate b774307a4d Merge main 2026-10-22 10:46:00 -05:00
Chris Tate de6dd13370 Run net, tls, http and https events in Node's AsyncLocalStorage contexts
The runtime fired every socket, server, request and response event from
the event loop with no store. Node runs them in the context of the
resource or request that produced them, so getStore() answered
undefined in listeners where Node answers the caller's store.

Handles now keep the context Node uses and the runtime enters it around
each dispatch:
- a socket the context of its creation (net.connect, tls.connect, an
  http request's socket), for 'connect', 'data', 'end', 'close',
  'error' and 'timeout';
- a server the contexts of its listen() call ('listening', 'connection',
  listen errors) and its close() call ('close' and close callbacks when
  no connection is open; a close that waits for the last connection
  settles in that connection's dispatch, as in Node);
- socket write() callbacks and end() callbacks with 'finish' the
  contexts of their calls;
- http server requests and responses, which are created in their
  connection's dispatch, the server's listen() context, and client
  requests and their responses the http.request() context, including a
  keep-alive socket an agent hands to a later request.

An accepted net socket runs its own events with no store, as in Node;
the sockets of an HTTP or TLS server run in the server's listen()
context. Events and callbacks of other streams are unchanged.

The compiler is unchanged. Programs without net or http link the same
runtime code; net and http programs gain the capture and dispatch
helpers (about 1.7 KB of text in a net program, 2.2 KB in an http
server), and bench:runtime http-api is neutral.

Corpus 7119 and 7380.
2026-10-22 10:45:31 -05:00
Chris Tate 057cc02326 Order a spread source's keys after key-order witnesses settle
The merge helper for a fixed-shape spread into an index-signature record
wrote the source's keys in the shape's declared order when it was
lowered. Key-order witnesses can replace that order afterwards with the
order the program builds the source's values in, so a source declared
`{ start, end }` but built as `{ end, start }` merged as `start,end`
where Node gives `end,start`. The source's writes now go into one block
that a shape-order finalizer fills in the settled order, as the other
order-snapshotting helpers do. Corpus program 6916 covers sources built
in another order than declared, with an absent optional field and with
two spreads.
2026-10-22 10:43:08 -05:00
Chris Tate a8a545a81f Read the characters of ASCII strings in versioned scan loops as bytes
`for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++`
tested the string's ASCII bit and the index against the length on every
character, and its fallback is an opaque runtime call, so LLVM could not
hoist either test or vectorize the loop.

A typed-array loop version now also covers `s.charCodeAt(index)` of a
string binding the loop does not write, at a counter index plus a
constant or another index the version proves. A string is immutable, so
its bytes, its length and its ASCII bit are fixed when it is built. The
guard requires the ASCII bit, so each UTF-16 unit is one byte and the
length is the byte length, and the fast copy reads each unit with one
byte load. Other strings and indices run the checked loop.
2026-10-22 10:36:22 -05:00
Chris Tate 76d7d29b13 Merge main 2026-10-22 10:34:23 -05:00
Chris Tate ccfca67aec Fix chalk builder field calls and hot-shots send failures and tcp completion 2026-10-22 10:30:56 -05:00
Chris Tate f691918e27 Merge main 2026-10-22 10:29:26 -05:00
Chris Tate 5bbf2c97b8 Encode posted records from their fields, and queue a message in one block (#1824) 2026-10-22 10:25:40 -05:00
Chris Tate 08081ca1a2 Encode each field of a posted record completely before the next
A typed record encoded from its field hooks queued a plain-object field on
the visit stack and went on to the next field, so getters on later fields
ran before the properties of earlier ones were read. Node's serializer
finishes each field, nested properties included, before the next.

The record path now drains the visit stack down to where it started after
each field, so properties are read in Node's order. Corpus 7139 posts a
record whose fields hold getter-bearing objects and prints the read order.
2026-10-22 10:22:22 -05:00
Chris Tate 6e79566c25 Keep key order and absent optional fields in filter predicate record copies
A record conversion behind a written type predicate built the copy in the
target shape's field order and wrote every optional field, so a source
object without an optional key came out with that key present and holding
undefined, and Object.keys listed the target's order. The copy now keeps
the source's key order, as a width copy does, and an optional field the
source lacks stays absent.
2026-10-22 10:18:57 -05:00
Chris Tate 3a3610d12e Look up spawn and execFileSync commands on the replacement environment's PATH
spawn and execFileSync with an `env` option searched the parent's PATH
for a command without a '/'. They now resolve it the way spawnSync does,
as Node (libuv) does: on the replacement environment's PATH, or the
default path when it has none, with relative entries read from the cwd
option's directory.
2026-10-22 10:15:10 -05:00
Chris Tate de6ef6590f Read optional elements at a proven integer index without a round trip
An element read that may be undefined, lowered inline from `%arr.idxOr`
(`a[i]` as `T | undefined`, and the read in `a[x] = a[y]!`), took an
integer index only from a loop's integer slot. Any other index, such as a
double counter with a proven integer range or `k | 0`, was converted and
tested for an exact integer on every read. The read now takes the same
exact integer as a borrowed read does (`exactInteger`), on 64-bit targets.
The loop-induction treatment above the hole-free prefix still applies only
to a loop's integer slot.
2026-10-22 10:12:31 -05:00
Chris Tate 0078dc2c46 Check the elements a spread push stores into an evolving empty array
The evolving-array check made the binding checked-dynamic for any spread
write, so `xs.push(...values)` of an array whose elements match the
binding's agreed element type turned a typed `number[]` into a
checked-dynamic array, and the spread call through it was refused with
SC1090 "spread arguments in calls through 'unknown' values". A spread now
stores the source array's element type, or each of a tuple's element
types, and the binding stays typed when they match. A spread of another
iterable still makes it checked-dynamic.
2026-10-22 10:10:59 -05:00
Chris Tate affe386bbf Throw for every object on the right of an arrow instanceof, and give blocked bodies their this
- An arrow function or method on the right of an untyped `instanceof`
  has no prototype. Node throws "Function has non-object prototype
  'undefined' in instanceof check" for any object, but the runtime threw
  only for plain objects, arrays and functions, and answered false for
  typed arrays, native handles, promises and typed references. It now
  answers false only for primitives.
- Coverage lowers the body of a signature-blocked function for its
  diagnostics. A declared `this` parameter now gives that body its
  receiver, as lowerFunction does, so `this.label` reports nothing.
  Before, each use reported SC1080.
2026-10-22 10:09:43 -05:00
Chris Tate e4dc0900d7 Fence arguments.length in function expressions that call themselves by name
A function expression stored in a function type with more parameters
folds `arguments.length` to that type's parameter count, the count every
call through the slot passes. A named expression that calls itself
through its own name passes only its declared parameters there, so no
constant answers both calls. Such a read now reports SC1090 with the
reason, and expressions that do not refer to their own name keep the
fold.
2026-10-22 10:09:08 -05:00
Chris Tate 03495c2198 Index dispatch receivers by name instead of testing each one (#1814) 2026-10-22 10:07:29 -05:00
Chris Tate ca579641a4 Index quoted get: and set: method names under their own keys 2026-10-22 10:04:30 -05:00
Chris Tate 44bd4e1d6e Run counters that step beside a counted loop's counter as integers (#1829) 2026-10-22 10:00:28 -05:00
Chris Tate aaedce7b4a Keep a callback's captured scalars in registers in loops that call it (#1818) 2026-10-22 09:59:36 -05:00
Chris Tate d14c2ee05f Read AsyncLocalStorage options as JavaScript does and call snapshot callbacks without a receiver
new AsyncLocalStorage(options) read defaultValue and name as own data
properties, so inherited values and getters were ignored, and a name
that was not a string reached the compiled code unconverted. The options
are now read with a full property get (prototypes, getters, proxies,
the snapshot of a typed record or class instance) in Node's order:
defaultValue once, name to see whether it is defined and again for its
value. The name is kept as String(name); a Symbol throws Node's
TypeError, and an exception from a getter propagates. A class instance
whose class itself declares one of the two members, which a capsule does
not expose to the runtime, is refused with a "not supported yet" error.

A snapshot's runner called its callback with the runner's own receiver;
Node applies the callback to an undefined receiver.
2026-10-22 09:59:13 -05:00
Chris Tate c9ae4d8019 Look up spawnSync commands on the replacement environment's PATH
With an `env` option, spawnSync searched the parent's PATH for a command
without a '/'. Node (libuv) searches the PATH of the child's environment,
or the default path when that environment has none, so a command only on
the child's PATH ran as ENOENT and one only on the parent's PATH ran
where Node reports ENOENT. The lookup now follows libuv: each PATH entry
in order, an empty entry as the current directory, and EACCES when a
candidate exists but cannot run.
2026-10-22 09:55:30 -05:00
Chris Tate 4e06e72b75 Key flatMap union helpers by their arm plan and copy array elements as stored
The union-returning flatMap helper was interned by element and result
types alone, so two callbacks with one union result type, one with an
empty `never[]` arm and one with a populated array arm, shared the first
helper and the second dropped its elements. The key now includes the
per-arm plan. Array arms copy their elements as stored, a present
undefined included, so an array arm's element type must be the result's,
and a unit-typed result (`undefined`) appends itself.
2026-10-22 09:54:40 -05:00
Chris Tate 0ec1d47b4d Build typed 'message' listener parameters from the message (#1813) 2026-10-22 09:51:04 -05:00
Chris Tate ef8cc7f74f Settle Promise.all over different entry types on Node's turns
`Promise.all([a, b])` whose entries have different types (the checker's
tuple overload) ran through an async helper that awaited the combinator
and then each fulfilled entry, so its awaits resumed three microtask
turns after Node's. The combinator now stores the entry promises, and an
adapter run inside its settlement reads them into the tuple, so the
result settles on the combinator's turn: awaiting it, storing it first,
chaining it with then and returning it from an async function all land
on Node's turns. Plain entries join the combinator through
Promise.resolve, as in Node, and unit-typed entries compile.
2026-10-22 09:48:55 -05:00
Chris Tate 8487742019 Read the shared decoded value after an untyped listener, and skip discriminated unions
- Once a 'message' listener without a typed entry has decoded the
  message, the listeners after it read that decoded value, so a typed
  listener sees what an earlier untyped listener changed, as in Node.
- A union with a literal discriminator gets no typed message entry: its
  checked conversion also tests the discriminator, which the message
  readers do not.

Corpus 7361 checks a typed listener after an untyped one that changes
the message, and a listener on a discriminated union.
2026-10-22 09:48:41 -05:00
Chris Tate b80baf92ef Filter regex start positions by their leading character sets (#1784) 2026-10-22 09:45:21 -05:00
Chris Tate 9aa3b15174 Support construct, writev, decodeStrings and defaultEncoding on streams 2026-10-22 09:44:27 -05:00
Chris Tate 2d6ef06fcb Version while loops over typed arrays on their counters' bounds
Typed-array loop versions applied only to for loops, so a scanner written
with while loops, such as `while (pos < end && bytes[pos] === 32) pos++`,
kept one bounds test per access. A while loop counter is now a number
local that the loop changes only by positive integer increments (`c = c +
k`, `c++`, `c = (c + k) | 0`) written as statements of its body, blocks,
branches and nested while loops. An access at `counter + d` after a
condition compared the counter below a loop-invariant bound, with no
increment since, is in bounds for the whole loop when the guard finds the
counter's entry value an exact integer of at least -d and the bound at
most the length minus the access's extent. A counter with a `| 0`
increment also needs its loops' bounds at most 2^31 minus its increments,
so it never wraps.

A fixed check (a statically ranged index) now needs an end below 2^31.
The counter of a loop to a number[]'s length whose length the loop may
change reaches 2^32 - 2, and its typed-array accesses got a guard no
typed array passes and a second copy of the loop.
2026-10-22 09:40:22 -05:00
Chris Tate 091f280cec Read the union payload inline when comparing it with a number or boolean
`u === 0`, `u !== true` and `Object.is(u, x)` against a scalar arm tested
the union's tag inline but read its payload through scr_union_get_f64 or
scr_union_get_bool. The call passed the union's stack box to the runtime,
so LLVM could not promote the box to registers, and an element read of a
`number[]` compared with a number kept its value, tag and box in memory.

The comparison now loads the payload slot directly, as other union
extractions already do, and the box no longer escapes.
2026-10-22 09:32:19 -05:00
Chris Tate 20f4d1ff12 Support the legacy createCipheriv modes and crypto.checkPrime/generatePrime 2026-10-22 09:08:35 -05:00
Chris Tate 60c51b9535 Merge main 2026-10-22 09:08:21 -05:00
Chris Tate 1e0dabd748 Declare every http2 listener event for once and addListener
The lowering accepts on, once and addListener alike for each server,
session and stream kind, but the fallback Node types (used when a project
has no @types/node) declared fewer events for once than for on on the
http2 servers, sessions and streams, and no addListener on any of them.
`http2Server.once("connection", ...)` and `server.addListener("stream",
...)` failed to type-check (SC0001) where Node runs them.

once and addListener on Http2Server, Http2SecureServer, Http2Session,
Http2Stream, ServerHttp2Stream and ClientHttp2Stream now declare the same
overloads as on.

Corpus 7281 runs an HTTP CONNECT proxy on node:http, which #1791's
declaration of the http.Server 'connect' event made compile: it tunnels
to a local target and pipes both ways, with a non-empty head, once and
addListener registrations, and an 'upgrade' handover. Corpus 7282
registers http2 server, session and stream listeners with once and
addListener.

Closes #1439
2026-10-22 09:05:46 -05:00
Chris Tate 17b70e6d6a Find Buffer search candidates with memchr 2026-10-22 08:57:59 -05:00
Chris Tate 90d74a3193 Finish bailed speculative iterations outside the ordinary loop copy (#1823) 2026-10-22 08:57:41 -05:00
Chris Tate 142e10a0c4 Run counters that step beside a counted loop's counter as integers
`for (let i = 0, p = 0; i < count; i++, p += 4)` kept both counters as
doubles: the counted-loop match takes a single declaration and a single
update. A counted loop now also accepts further counters declared in its
head and stepped by a constant in its update, when the condition does not
read them and the body does not write them. On body entry k such a counter
holds `start + step * k`, so its range follows from the driver's: k is at
most the driver's span over its step.

When that range can pass 2^53, the versioned loop tests
`|start| + |step| * span / |driver step| <= 2^52` at run time, and an
unproven start is tested for an exact safe integer, as the driver's is.
Nested update blocks (`i++, j += 2, k--`) are flattened. Byte windows and
dense loops still take only single-counter loops.
2026-10-22 08:55:08 -05:00
Chris Tate 8aac437ed2 Keep reduce accumulators and callback argument counts as Node runs them
Array reduce typed its accumulator by the call's checked type, widened
with undefined only when the callback could return a missing value or a
receiver without an initial value could have holes. Three runtime values
fell outside it and threw "not representable in the target union":

- an initial value read from a missing element;
- a callback result of another primitive (`missing[0] ?? "fallback"`);
- a callback wrapped in parentheses, which the analysis and the callback
  lowering did not see as the arrow it wraps.

The accumulator now also takes undefined from a possibly missing initial
value, and the primitive arms the callback's result can have. Array
callbacks are taken without their parentheses.

`arguments.length` in a fixed-arity function expression folded to its
declared parameter count. When the expression flows into a fixed-arity
function type with more parameters, as array callbacks do (value, index,
array, and the accumulator for reduce), every call passes that type's
count, and the length is that count.
2026-10-22 08:53:50 -05:00
Chris Tate f9bed65a65 Name the union destructuring fence without printing the union
The fence for a field the arms of a union type differently printed the
whole union, whose arm order follows the order record shapes are
interned in and changes with unrelated compiler changes. It now names
the union without its arms, as the other union fences do.
2026-10-22 08:45:10 -05:00
Chris Tate 8db39e6c63 Fence non-Buffer chunk parameters on byte-mode stream callbacks again (#1810) 2026-10-22 08:40:52 -05:00
Chris Tate 2f3e25b431 Accept Buffer-compatible chunk annotations and keep super(undefined) in byte mode 2026-10-22 08:35:56 -05:00