Commit Graph
1896 Commits
Author SHA1 Message Date
Chris Tate 2eb70c4b60 Merge pull request #1066 from vercel-labs/ctate/stop-vitest-rpc-e7f0f8
Stop the vitest RPC timeout from failing green jobs, and run every push to main to completion
2026-10-15 19:47:51 -05:00
Chris Tate 6d7ed1bfbf Add async generator stages to pipeline (#1064) 2026-10-15 19:34:35 -05:00
Chris Tate 17c9148e47 Read named properties of unknown values with one receiver retain (#1063) 2026-10-15 19:27:06 -05:00
Chris Tate 346a7c0a70 Match Node's http keep-alive, header and framing behavior (#1046) 2026-10-15 19:21:26 -05:00
Chris Tate 0ae0e9e3b9 Stop the vitest RPC timeout from failing green jobs, and run every push to main to completion 2026-10-15 19:16:04 -05:00
Chris Tate f8d6b64a68 Collect dropped cyclic graphs while they are in cache (#1062) 2026-10-15 19:13:19 -05:00
Chris Tate 9bff898603 Store number-key Map.set and Set.add after a read into the found entry (#1059) 2026-10-15 19:01:03 -05:00
Chris Tate ba3fcfb7f9 Add async generator stages to pipeline
pipeline() accepts a run of async generator function stages between
streams, following pipeline.js: each stage is called during setup with the
previous stream (a Readable is async iterable) or the previous stage's
generator, and the last generator is pumped into the next stream with
pumpToNode's write, drain and end waits. An error in the pump loop returns
the generator before the pipeline finishes, so the stage's finally runs.

A stage's source parameter is typed as what the pipeline passes it and may
only head for await loops. Function sources and destinations, stages that
take the signal argument, and stages separated by streams are refused.
2026-10-15 18:46:41 -05:00
Chris Tate 16d687a42d Read dyn array elements and lengths by number without a key string (#1015) 2026-10-15 18:41:08 -05:00
Chris Tate 0c353a4ae2 Read named properties of unknown values with one receiver retain
A named property read on a value typed unknown goes through the property
helper the class dispatch generates. Its read compared the property bag
with the receiver and then read from it, and the bag expression was
evaluated at each use: three calls that each retained and released the
receiver. Releasing a record capsule whose fields can form cycles also
buffers it as a cycle candidate, so a single `u.id` paid that three times.
The helper now evaluates the bag once into a local.

Typed records seen as unknown also find a field by comparing the key
pointer with the interned field names first, since compiled reads pass
the same literal, before comparing bytes, and records whose fields are all
required skip the presence check.
2026-10-15 18:40:22 -05:00
Chris Tate 5968aec818 Add Readable.from over generators and match Node 24.22 stream timing (#1057) 2026-10-15 18:39:48 -05:00
Chris Tate a8ae330957 Release the in-progress client response when its trailers overflow
A chunked response whose trailers ran past the header limit failed the
request with "Parse Error: Header overflow" and marked the exchange done
while the parser still held the response. The socket-close cleanup then
skipped it, so the response, its socket and the parser kept one another
alive, and every such response leaked. The overflow path now settles the
response ('aborted' and 'close', as a premature close does) and drops the
parser's reference before the exchange completes.

Corpus 5652 repeats response trailer and head overflows and request
trailer overflows; under SCRIPTC_SAN=1 the RC audit fails it without this
change.
2026-10-15 18:35:02 -05:00
Chris Tate ce2e567922 Collect dropped cyclic graphs while they are in cache and skip redundant walks
A candidate release now also runs a pass once the live cycle-headered
count has grown by 4096 objects since the last pass. A dead graph is
usually reachable from one candidate (its root), so the candidate count
alone let a few hundred dead graphs pile up before a pass, which then
walked them after they had left the cache.

markGray records the objects it grays and counts the ones still
externally referenced after the trial decrements. When none is, every
gray object is garbage, so the pass whitens the recorded set and skips
scan. markGray also records each edge it skips into an older generation,
so a restricted pass pins those targets without tracing the white set a
second time.

Adds the cyclic-graphs runtime benchmark and corpus programs 5700 and
5701.
2026-10-15 18:26:48 -05:00
Chris Tate 6103cf5667 Overwrite present number keys in the lookup that checks for growth (#1056) 2026-10-15 18:24:49 -05:00
Chris Tate f8697111b5 Store number-key Map.set and Set.add after a read into the found entry
A number-key write that follows a get or has of the same collection and
key, inside its own arguments (m.set(k, (m.get(k) ?? 0) + 1)) or in the
statement just before it, now looks the key up through the read-only
sc_map_entry_f64 helper. A found entry takes the new scalar value in its
slot (a Set entry already holds its unit value); a new key appends
through the runtime set. LLVM merges that lookup with the read's when no
store intervenes, so the write does not hash and probe the key again.

Other writes, including those guarded by an absent key, keep the single
runtime call, which finds the insertion bucket in the same probe.
Reference values keep the runtime call, which releases the old value.
2026-10-15 18:21:06 -05:00
Chris Tate 4743578e20 Probe hashed number-key Map/Set lookups inline (#1055) 2026-10-15 18:14:17 -05:00
Chris Tate 8e5a0d1cfd Merge main 2026-10-15 18:07:59 -05:00
Chris Tate ae5d9bf81f Add Readable.from over generators and match Node 24.22 stream timing
Readable.from accepts sync and async generators of strings and Buffers,
following from.js: values are pushed while push() accepts, an async
generator's next() promise is awaited once per value, and destroy runs
the generator's throw() and return() before the destroy callback.

for await over a stream follows createAsyncIterator: the loop registers
its 'readable' listener, reads through read(), and a parked loop wakes
through the 'readable' tick. push(null) before the first read ends the
stream after the next read, finished() answers an already settled stream
with its result on a nextTick, read(0) on an object-mode stream reads
nothing, and write(string, 'buffer') throws ERR_UNKNOWN_ENCODING.
2026-10-15 18:07:26 -05:00
Chris Tate 38974c01eb Merge main 2026-10-15 18:03:29 -05:00
Chris Tate 3b2ef1d6bf Add the Readable operators, the Readable.from options and stream.compose (#996) 2026-10-15 17:59:17 -05:00
Chris Tate cdf0598e6e Overwrite present number keys in the lookup that checks for growth
When appending one more entry would grow a hashed number-key map's
index, Map.set and Set.add first look the key up to decide whether the
map should move to the direct index. A key that lookup finds was then
looked up again by the generic set. The found entry now takes the new
value directly, so overwriting a present key in a map at its growth
boundary probes the table once.
2026-10-15 17:51:48 -05:00
Chris Tate da27496ea9 Probe hashed number-key Map/Set lookups inline
Number-key reads of a map without the direct index called
scr_map_entry_f64 for every lookup. The internal sc_map_entry_f64 helper
now calls sc_map_entry_f64_hashed for such maps instead: it hashes the
key as scr_map_find_f64 does (SameValueZero key word, scr_key_mix, the
absent-key filter), scans the entries of a small map or probes a compact
bucket table, and calls the runtime lookup only for a table wider than
the compact limit. It stays out of line so the direct-index path stays
small at every call site.

The runtime fallback is called as a read-only call, so both helpers only
read memory and LLVM merges lookups of one key when nothing is stored in
between, such as a has() followed by get().

The runtime benchmark suite gains number-maps: a node table indexed by
dense ids, scope tables keyed by sparse ids, and get/has/set mixes.
2026-10-15 17:47:34 -05:00
Chris Tate 1be58648bc Merge main 2026-10-15 17:31:00 -05:00
Chris Tate a128918c50 Support dns.lookup's Node forms, lookupService, getServers and the result order (#1053) 2026-10-15 17:26:47 -05:00
Chris Tate afad516b91 Add net socket address fields, write and end callbacks, and server connection limits (#1052) 2026-10-15 17:09:27 -05:00
Chris Tate 404af8fd6e Partition large wasm32 programs like native ones (#1040) 2026-10-15 17:03:31 -05:00
Chris Tate 55a660f0e2 Add dns.lookup's Node forms, lookupService, getServers and the default result order
- dns.lookup(hostname, callback), lookup(hostname, family, callback)
  and lookup(hostname, options, callback) with family (0/4/6, 'IPv4',
  'IPv6'), all, order and verbatim, validated with Node's messages.
  getaddrinfo answers any family, ordered verbatim or one family first;
  literal addresses answer on the next tick, an empty hostname gives
  Node's null answer and DEP0118 warning, and failures carry code,
  errno, syscall and hostname.
- dns.lookupService(address, port, callback) checks the address and
  port as Node does and answers getnameinfo's host and service.
- dns.getServers() lists the nameservers in /etc/resolv.conf, and
  getDefaultResultOrder/setDefaultResultOrder keep the default order
  ('verbatim').
- A typed callback receives "" and NaN (or []) after a failure, where
  Node passes undefined; the limitations page says so.
Corpus 5648-5650 cover each case; the options-record diagnostics no longer
list the all and hints options as refused.
2026-10-15 16:59:20 -05:00
Chris Tate bfb0ca8ffc Print the net corpus's server and client lines in a fixed order
The server's 'connection' and the client's 'connect' race, and macOS runs
them in the other order from Linux. 5644 now prints both sides once both
have run, and 5645 keeps the server's and the client's events in separate
lists printed one after the other.
2026-10-15 16:42:32 -05:00
Chris Tate f65a6fad14 Merge main 2026-10-15 16:34:46 -05:00
Chris Tate aa5e626b06 Merge main 2026-10-15 16:32:19 -05:00
Chris Tate a5a3588d3d Add net socket address fields, write/end/setTimeout callbacks, close(hadError) and server connection limits
- socket.remotePort, remoteFamily, localAddress, localPort, localFamily,
  bytesRead, readyState, connecting and pending. The peer's fields are
  cached at first read as remoteAddress is (undefined while
  connecting), and the local ones are undefined while connecting and
  after close.
- write(data, cb) runs cb with null once the data is flushed, and
  end(cb) / end(data, cb) once the socket finishes.
- 'close' listeners receive hadError, 'ready' fires after 'connect',
  and 'data' listeners may take the string chunks setEncoding gives.
- setTimeout(ms, cb) adds cb as a once('timeout') listener (0 removes
  it), and setTimeout throws ERR_OUT_OF_RANGE for a negative, NaN or
  infinite msecs where it disabled the timer before. setKeepAlive
  chains.
- server.maxConnections closes connections past the cap and emits
  'drop' with their addresses; server.getConnections(cb) answers the
  count on the next tick.
- net.isIP, isIPv4 and isIPv6 follow Node's address patterns.
- A callback adapted to a void return keeps the identity of the
  function it wraps, so removing it by that function finds it.
Corpus 5644-5647 cover each case.
2026-10-15 15:59:17 -05:00
Chris Tate 1211786174 Follow Node's getOptions in the fs option checks (#1050) 2026-10-15 15:26:39 -05:00
Chris Tate 1ffbb28255 Follow Node's getOptions in the fs option checks
The fs validation ladder and the callback dispatch check their options
argument as Node's getOptions does. null, undefined and functions take
the defaults, and a string is the encoding. Numbers, booleans, bigints
and symbols throw ERR_INVALID_ARG_TYPE ('The "options" argument must be
one of type string or object'). assertEncoding lets 'buffer' through;
before, every 'buffer' encoding threw ERR_INVALID_ARG_VALUE.

With 'buffer':
- readFile and readFileSync read the file first, so a missing file
  reports ENOENT. Then buffer.toString('buffer') throws
  ERR_UNKNOWN_ENCODING; an empty file answers "".
- fs.mkdtemp's callback receives the path as a Buffer.
- mkdtempSync, opendirSync, createReadStream and createWriteStream
  throw the fence error.

fs.mkdtemp's callback form now checks its encoding. It also returns its
path in the requested encoding: re-encoded text for names other than
utf8, as the binding does.

Corpus 5545 covers these forms against Node 24.22.
2026-10-15 14:54:07 -05:00
Chris Tate d1dbac3cf2 Keep typed-array indices integral through the shared-buffer check (#1049) 2026-10-15 14:51:42 -05:00
Chris Tate 14cb1651fa Run the npm case fixtures natively and fix what they found (#1048) 2026-10-15 14:47:36 -05:00
Chris Tate d4a51ce968 Keep typed-array indices integral through the shared-buffer check
In worker-enabled executables a typed-array access tests whether the view
is shared. The test used to come before the index check, and the shared
path called scr_bytes_get_or_nan/scr_bytes_set with the index as a
double, then converted the element back to an integer. Both paths now
use the index the inline check produced: the shared test follows the
check (a view's length never changes), and the shared path calls new
runtime entries that take the index as a size_t
(scr_bytes_shared_get_int, scr_bytes_shared_get_f64,
scr_bytes_shared_set). Integer elements join as one 32-bit value and
convert to a number after the join, so compares and ToInt32/ToUint32
of the result stay integer operations in every element kind.

The amortized worker cancellation poll moves from the loop header to
the loop's back edge (the continue target). In the header it was a loop
exit ahead of the body, which kept LLVM from rotating the loop and left
loads such as the view's length inside it.

Adds the typed-array-scan and typed-array-scan-workers runtime
benchmarks: Uint8Array, Uint16Array, Int32Array and Float64Array scan
loops in an ordinary and a worker-enabled executable.
2026-10-15 14:19:47 -05:00
Chris Tate d0cff5e6f3 Run the npm case fixtures natively and fix what they found
The npm case fixtures lost their only native test with the native-only
compilation change. tests/harness/npm.test.ts runs every case again with the
default static npm compile and compares it with Node. A KNOWN table records
the cases the compile refuses (with the diagnostic code), the cases whose
run reaches a deferred refusal (the code in the program's output), and the
two that still diverge, which run as expected failures. The suite joins the
case-sharded CI corpus files.

Fixes from the run:

- A package imported only by subpath (`pkg/env.js` from a package with no
  "main", "exports" or index file) was refused as having no runtime entry.
  Eligibility now resolves the imported specifier when the root has none.
- diagnostics_channel: a throwing subscriber stopped publish, and a
  throwing bindStore transform stopped runStores. Node runs the remaining
  subscribers, skips that store, and reports each error as uncaught on a
  next tick; the runtime now does the same.
- A '#' import that the package's "imports" field maps to another package
  failed at run time with Node's "not defined" error, which Node does not
  raise. It is now a compile-time SC1010 refusal.
- An uncaught runtime refusal whose code is an SC code now names the code.
- Mapping a type in the optional array-element analysis could lower an
  unsupported local class and let its PoisonError escape as a compiler
  crash; the analysis now treats that element ABI as unknown, so the
  program fails with the recorded diagnostic.
- A lexical module binding initialized from an unchecked array element
  holding a function failed validation (SC9001): its widened storage was a
  union, which a TDZ guard cannot hold. Such bindings now keep checked-value
  storage and their guard.
- A dynamically reached ArrayBuffer had no constructor property.
2026-10-15 14:15:33 -05:00
Chris Tate 72fe1e2e9c Fix wasm32 array lengths past 2^31 and pending top-level await exit codes (#1047) 2026-10-15 14:10:20 -05:00
Chris Tate 9aab9af547 Fix wasm32 array lengths past 2^31 and pending top-level await exit codes
The backend converted size_t lengths and indexes to numbers with sitofp. On
wasm32 size_t is 32 bits, and an array length reaches 2^32 - 1, so a sparse
array of length 4294967295 read -1. sizeToDouble picks uitofp for a 32-bit
size_t and keeps sitofp for 64 bits, whose sign bit a length never reaches,
so native output is unchanged.

An unsettled top-level await always exited 13. Node exits with
`process.exitCode || 13` and runs the exit listeners first, which can set
process.exitCode again. scr_promise_finish_top_level now notes 13 only when
the program set no nonzero exit code, and the pending exit returns the
exit code after the listeners, as the other exits do.

Three WASI tests still exercised the embedded-JS fallback that native-only
compilation removed: an embedded-source fetch scan and two npm fixtures
that every target now refuses. They now check the same behavior with
static programs: fetch named only in text compiles, a global fetch call
reports SC3002, and CommonJS and ESM packages compile and match Node.
2026-10-15 13:39:03 -05:00
Chris Tate f7362fc386 Match Node's http keep-alive, header and framing behavior
- server.close() closes idle keep-alive connections, and every open
  connection closes after keepAliveTimeout + keepAliveTimeoutBuffer, so a
  closed server whose clients keep their connections no longer stays up.
  The Keep-Alive response header gives keepAliveTimeout in whole seconds
  and is omitted when it is 0.
- A client request through an Agent with neither keepAlive nor a finite
  maxSockets sends Connection: close.
- "socket hang up" and "aborted" errors carry code ECONNRESET, a client
  head over the header limit fails with HPE_HEADER_OVERFLOW, and TLS
  verification failures carry their openssl code.
- Outgoing header values keep the type they were set with: getHeader and
  getHeaders answer numbers and arrays, appendHeader turns an existing
  value into an array, and setHeader(name, string[]) writes one line per
  element.
- Repeated incoming header fields join with ", " ("; " for cookie),
  Node's single-value fields keep the first value, and set-cookie reads
  as an array under @types/node, on server requests and client responses.
  Fetch reads response headers line by line.
- Expect: 100-continue is answered with 100 Continue before 'request';
  other expectations answer 417.
- HEAD requests and 1xx/204/304 responses send no body or length header,
  and HTTP/1.0 requests without a set length get an unframed body and
  Connection: close.
- The 16 KiB maxHeaderSize applies to the request target plus header
  names and values (431 on the server).
- https.Agent TLS options are refused by name instead of being dropped,
  and server.address() lowers for @types/node's
  string | AddressInfo | null result.

Corpus 5555-5563 and the node-types http-headers-address fixture cover
each case.
2026-10-15 13:06:33 -05:00
Chris Tate 62ef7cafd9 Merge pull request #1045 from vercel-labs/ctate/allow-45-minutes-195b3c
Allow 45 minutes for native release builds
2026-10-15 12:48:04 -05:00
Chris Tate 8fded2ce9a Allow 45 minutes for native release builds 2026-10-15 12:47:20 -05:00
Chris Tate 8c88a8f87f Add fs.opendirSync, fs.promises.opendir and fs.Dir (#1044) v0.3.3 2026-10-15 12:24:47 -05:00
Chris Tate 7bc9c78f00 Keep corpus 5407's requests within one read (#1043) 2026-10-15 12:24:25 -05:00
Chris Tate c53f864e61 v0.3.3 (#1042) 2026-10-15 12:13:13 -05:00
Chris Tate 9377dab890 Add fs.opendirSync, fs.promises.opendir and fs.Dir
opendirSync(path[, options]) and fs.promises.opendir(path[, options])
return a Dir that reads one Dirent at a time in OS order, as libuv's
uv_fs_readdir does. Dir supports path, readSync(), read(), closeSync(),
close() and for await. readSync and read answer null at the end. A
closed Dir throws ERR_DIR_CLOSED from readSync and closeSync, and
rejects with it from read and close.

for await over a Dir reads with read() until null and closes the Dir
in a finally, as Node's Dir.entries() does: normal exhaustion, break
and a throw all close it, and a loop whose body closes the Dir ends
with ERR_DIR_CLOSED.

A Dir also crosses 'unknown' slots as a native handle (Dir), so a
closure returning one can be passed to untyped JavaScript functions.
Through such a slot, path and closeSync() work, and other members throw
the "not supported yet" Error.

Open failures carry the opendir syscall and no path. options.bufferSize
is checked after the directory opens, with Node's ERR_OUT_OF_RANGE
messages. The options must be an object literal with a numeric
bufferSize and a utf8 encoding. Recursive walks, buffer encodings and
the callback forms are not lowered. TypeScript sources get a compile
diagnostic for them; in JavaScript sources, recursive walks and the
callback forms throw the fence error when the call runs.

Corpus 5544 covers both forms against Node 24.22.
2026-10-15 11:34:00 -05:00
Chris Tate b03600d328 Find fs callbacks where Node's signatures put them (#1041) 2026-10-15 11:29:40 -05:00
Chris Tate d7a7409277 Keep corpus 5407's requests within one read
The header-limit program sent heads of up to about 19 KB with long field
names. macOS loopback delivers that in more than one segment, so Node's
server rejected the head with bytes still unread and its close reset the
connection; the client socket had no 'error' listener, so Node exited on
the ECONNRESET after printing the first 431 case.

Every field is now a minimal `h:` line, which keeps each request under
7 KB, and the client socket ignores a reset so the case still ends. The
cases and their output are unchanged.
2026-10-15 11:28:15 -05:00
Chris Tate 2f8aea3279 v0.3.3 2026-10-15 11:23:16 -05:00
Chris Tate 374f9c51cd Inline string retains and dynamic value reference counting in release builds (#1030) 2026-10-15 11:18:39 -05:00