Commit Graph
6750 Commits
Author SHA1 Message Date
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 5266cb7cbe Check with tsc-ts 0.0.7 and batch contextual types and signatures (#1844) 2026-10-22 10:50:12 -05:00
Chris Tate eef3471159 Stop asking batch methods the checker server answers as unknown
tsgo has no getContextualTypes or getResolvedSignatures. Each prefetch
asked for them anyway, and a failed chunk was split and retried down to
single nodes, so a tsgo build paid about two failed requests per
prefetched node. The checker facade now remembers a batch method the
server answers with "unknown API method" and asks those queries node by
node from then on.
2026-10-22 10:46:38 -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 8e8abbe153 Rebuild after edits to source text the output carries 2026-10-22 10:21:53 -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 c43d4002e7 Skip only always-throwing calls as record producers in the key-order witness
The witness skipped every libCall whose signature result is VOID. VOID is
also the signature of runtime calls that take their record type from the
use site and build the record at run time, such as net.serverAddress
(`server.address()`). A literal of the same shape then set the shape's key
order, and the runtime record enumerated in the literal's order instead of
Node's (`port, family, address` for `address, family, port`).

The witness now skips only the calls in ALWAYS_THROW_LIB_FNS, which never
produce a value. The set moves from validate.ts's result-type check to
builtin-effects.ts, and both use it.
2026-10-22 09:59:57 -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 05acb4d247 Update the DiffieHellman runtime test for the prime search
test_dh.c includes scr_dh.c and still called sd_generate_safe_prime
without its new top_two argument, so dh.test.ts failed to compile. It
now passes false, as the DiffieHellman paths do, and a new case checks
generatePrime's search: the sizes and add forms with one possible
result, top-two-bit safe-prime starts, and checks above OpenSSL's round
count.
2026-10-22 09:58:47 -05:00
Chris Tate b82c70d5de Bound a follower's trip count only by a safe-integer span
The trip count that bounds a follower's range was the driver's span over
its step, computed in doubles. A span above 2^53 - 1 can round down: from
-(2^53 - 1) to below 2^52 - 1 the span 3 * 2^52 - 3 becomes 3 * 2^52 - 4,
so a follower that reaches 2^53 got the range limit 2^53 - 1 and ran as an
unchecked integer. Such a span now bounds no trip count, and the follower
is tested at run time (or the loop is refused when every value is known).
Below it the quotient rounds to at least its floor, and the follower's
products and sums are exact within the safe range.
2026-10-22 09:56:19 -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 cd2b1e2574 Keep callback loop versions from nesting
A callback loop version emits its loop twice. A loop inside either copy
could be versioned again, so a nest of loops that each call a callback
built just before them (`const g = adder(i)`) doubled at every level: a
three-deep nest was emitted seven times per function.

A loop inside a callback version, or the versioned loop itself emitted
again for each copy, is no longer versioned, so a loop nest is emitted at
most twice. The outer loop keeps its version; inner loops keep their
calls as they are.
2026-10-22 09:55:01 -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 ddf7eb8a2b Check with tsc-ts 0.0.7 and batch contextual types and signatures
tsc-ts 0.0.7 answers getContextualTypes and getResolvedSignatures, so
NODE_BATCH_QUERIES is on: lowering asks the checker for the contextual
types of identity-flow sites, array literals and conditionals, and the
resolved signatures of calls, in batches instead of node by node.

The release-age exclusions move to tsc-ts 0.0.7 and its platform
packages, and add @scriptc/threads 0.4.1, which tsc-ts 0.0.7 depends on.
2026-10-22 09:50:32 -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 a020e8ea6c Name source files relative to the entry's directory in structural positions (#1840) 2026-10-22 09:45:58 -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 279cdcffae Name source files relative to the entry's directory in structural positions
Structural positions started with a file's absolute path, so lowered
names (closures, recursive records and unions, and every name keyed by a
type that holds one) changed with the directory the program was compiled
in. Union arms are ordered by their types' keys, so a program compiled in
a new temporary directory could also get its union arms in another order.
union-retype.test.ts compiles each program in a fresh temporary
directory, and in about one run in ten the arms of the visitor's unions
came out in an order where no retag keeps its tags, which failed "lowered
identity retags share the box".

Positions now name a file by its path relative to the entry's directory
(default library files keep their base name), so the same program gets
the same names wherever it sits.
2026-10-22 09:39:07 -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 8b4333b166 Record the order-parity baseline for the byte-mode chunk fence fixture (#1838) 2026-10-22 09:28:54 -05:00
Chris Tate ed66338cb6 Record the order-parity baseline for the byte-mode chunk fence fixture 2026-10-22 09:25:44 -05:00
Chris Tate 9433c2ad35 Reuse the class dispatch passes' per-function scans while bodies are unchanged (#1833) 2026-10-22 09:25:09 -05:00
Chris Tate 0318671352 Reuse the class dispatch passes' per-function scans while bodies are unchanged 2026-10-22 09:17:49 -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