Commit Graph
3145 Commits
Author SHA1 Message Date
Chris Tate a09af4f721 Report what keeps a region with SCRIPTC_REGION_DEBUG (#1095) 2026-10-16 01:12:28 -05:00
Chris Tate d62b19badb Keep int32 field storage when reflection reads values through unknown
Int32Slots keeps a class field as a double when its class reaches
checked-dynamic code and some dynamic store in the program can name the
field. A runtime helper that takes a dynamic value counts as a store of
every property unless it is in DYN_READ_ONLY. Object.keys, Object.values,
Object.entries, for-in, Object.getOwnPropertyNames, Reflect.ownKeys,
Object.hasOwn, Object.prototype.toString.call, toString, typeof and
console.log/util.inspect of an unknown value were not in that list, so one
of them anywhere in a program, reached or not, stored every int32 field of
every class reachable from a value passed to unknown as a double.

The helpers they lower to are added to DYN_READ_ONLY. None of their
runtime functions reaches scr_dyn_typed_ref_commit, the only caller of a
live view's commit, except through scr_dyn_typed_ref_materialize, which
defines properties only on its fresh snapshot (as for dyn.hasKey).
2026-10-16 01:10:45 -05:00
Chris Tate 455ca6c172 Revert "Return unions with reference arms in registers" (#1096) 2026-10-16 01:02:39 -05:00
Chris Tate 6f63520c98 Emit Atomics operations inline as atomic instructions (#1094) 2026-10-16 00:54:32 -05:00
Chris Tate c7c21fe4a5 Convert cyclic dyn values to recursive shapes
Checking or converting a dyn value whose objects or arrays refer back to
each other against a recursive interface (`raw.next = raw; raw as Node`)
recursed without end and overflowed the stack. Matchers and builders of
record and array types that reach themselves now track the dyn values
they visit:

- a matcher answers true for a (value, type) pair it is already checking
  further up the stack, so the outer check decides;
- a builder records the record or array it allocated for a dyn value
  before converting its members, and a later visit of the same value in
  the conversion answers it, so `n.next === n` holds after the cast and a
  value reached twice converts once.

Only values with more than one reference are tracked, since a value
reached twice has at least two; the reference a member read holds is not
counted. Capsules materialized during a conversion keep their first view,
so a nested visit cannot refresh a view an outer visit is reading, and
getters and proxy traps that a member read calls run outside the visit. The
cycle analysis treats record shapes that reach themselves and are targets
of a dynCheck as mutable, so the cyclic records a conversion builds are
collected.
2026-10-16 00:52:19 -05:00
Chris Tate ba72bb6420 Merge main 2026-10-16 00:50:23 -05:00
Chris Tate 30bb7d4703 Add mock.fn and t.mock.fn with Node's calls log and implementation controls (#1092) 2026-10-16 00:49:22 -05:00
Chris Tate 2e3ae368d1 Deliver fs callbacks without a timer turn (#1093) 2026-10-16 00:49:02 -05:00
Chris Tate 6330e26eb8 Open a WriteStream's file when its encoding throws
Node's WriteStream constructor calls setDefaultEncoding after the
Writable constructor has scheduled _construct, so with encoding: 'buffer'
it throws ERR_UNKNOWN_ENCODING but the unreachable stream still opens its
file on a later tick, creating it, and with the default 'w' flags
truncating it. The runtime threw before scheduling the open; it now
constructs the stream first, as Node does.

Corpus 5547 ran that check on the file its other blocks write, so the
late truncation raced with the fd block's write and Node's own output
changed with the timing (it printed an empty file on macOS). The check
now uses its own file, and the program prints that the file was created.
2026-10-16 00:46:22 -05:00
Chris Tate 00bb57ac0a Merge main 2026-10-16 00:37:00 -05:00
Chris Tate 77d9970b8c Revert "Return unions with reference arms in registers" 2026-10-16 00:35:43 -05:00
Chris Tate a8d154f370 Report what keeps a region with SCRIPTC_REGION_DEBUG
A released region that something outside still refers to is kept, and
SCRIPTC_REGION_STATS only said how many references the walk could not
explain. In a build with --optimization=dev (or --sanitize), setting
SCRIPTC_REGION_DEBUG=1 (or a larger number, the line limit) makes each
attempt that keeps a region also print what keeps it:

- per chunk, the references to its objects that the walk did not find
  (from variables, other regions or objects it does not walk), with the
  classes the chunk holds, since counts are kept per chunk;
- each ordinary object the region owns that is referenced from outside,
  named by the region class and field it was reached through, with its
  reference count and the references the walk found.

The emitted region class descriptors gain the names of the fields their
visits report, in visit order. Debug walks give every owned object an
entry, so that each one records where the walk first reached it. The
bookkeeping is compiled only into unoptimized and RC-audit runtimes:
compiled in, it made the release walk 6.5% slower even with the variable
unset. Optimized runtimes walk as before and, when the variable is set,
say once that the reports need a dev build. Retains, releases and
allocation are unchanged.

Adds corpus program 5703.
2026-10-16 00:35:06 -05:00
Chris Tate 93bc902cd8 Emit Atomics operations inline as atomic instructions
Atomics.load, store, exchange, compareExchange, add, sub, and, or and xor
on an integer typed array compile to one sequentially consistent LLVM
atomic instruction on the element. An index that is not an integer in
range takes the runtime operation, which applies ToIndex and raises its
errors.
2026-10-16 00:31:52 -05:00
Chris Tate 8d8e7a7737 Run Atomics on shared storage without a lock (#1090) 2026-10-16 00:27:39 -05:00
Chris Tate 3005acc14f Deliver fs callbacks without a timer turn
The fs error-first callbacks (readFile, writeFile, stat, access, mkdir and
the other callback forms that complete synchronously) and fs.exists
delivered their callback through a zero-delay timer. Timers have a 1 ms
floor, so every callback waited about 1 ms: 5000 sequential readFile
callbacks took 5.5 s, where Node takes about 0.35 s.

The callbacks now go on a FIFO queue that the event loop drains like the
worker-pool completions: one callback per checkpoint, with nextTicks and
microtasks drained between callbacks. A turn delivers the callbacks that
were queued when its timers ran, as libuv's poll phase does, so a
callback that queues another leaves room for timers and immediates.

Corpus 5555 covers the order against the synchronous code and its
microtasks, and timers and immediates during a chain of 2000 callbacks.
2026-10-16 00:26:28 -05:00
Chris Tate 42954a3266 Add mock.fn and t.mock.fn with Node's calls log and implementation controls
node:test's mock.fn(original[, implementation]) and t.mock.fn compile.
A Mock<F> is the function-with-properties record { %call, mock }: the
callable is a wrapper closure that shares the mock context with the mock
field. The context records each call's arguments, result, error and this,
and keeps the current implementation, the original and the one-time
implementations by call index. fn.mock supports, as in Node 24.22:

- calls, read as a copy of the log;
- callCount();
- mockImplementation(fn);
- mockImplementationOnce(fn[, onCall]), for the next call by default, a
  later registration for the same index replacing the earlier one;
- resetCalls();
- restore(), which brings back the original.

A throwing implementation records the error and rethrows it. Functions
with fixed parameters or a single rest array parameter are supported; a
union return type needs an undefined arm. mock.fn() without an original,
mock.method, the tracker's reset and restoreAll, and t.mock.method are
refused with SC2020.

The node:test fixture mock-fn compares the behavior with Node, and
tests/diagnostics/mock-fences.ts covers the refusals.
2026-10-16 00:22:22 -05:00
Chris Tate e97249791c Keep region counting out of the hot path of programs without regions (#1087) 2026-10-16 00:08:07 -05:00
Chris Tate 7b83558f52 Run Atomics on shared storage without a lock
Atomics operations on 8-, 16- and 32-bit integer arrays are sequentially
consistent atomic instructions on the element, and no longer take the
process-wide shared-memory lock. Other reads and writes of shared storage
are relaxed atomic accesses: element reads and writes access the element,
and copies, fills, conversions, searches and sorts read shared bytes into
a private copy and write back only the range they change, so concurrent
accesses to the same bytes are free of data races. Non-shared storage
keeps its plain memory accesses. Atomics.wait and Atomics.notify queue
waiters in buckets chosen by location, each with its own lock and
condition variable, so waiting threads no longer serialize other Atomics.

Atomics.store answers +0 for a value that truncates to -0, as Node does.
The shared storage runtime test also runs under ThreadSanitizer.
2026-10-16 00:06:31 -05:00
Chris Tate de0917284d Merge main 2026-10-16 00:05:07 -05:00
Chris Tate 93dcd9865e Merge main 2026-10-16 00:01:13 -05:00
Chris Tate 524af28870 Add fs read and write streams
fs.createReadStream, fs.createWriteStream and FileHandle
createReadStream, createWriteStream and readLines return runtime
Readable and Writable streams (scr_fs_stream.c), as do new fs.ReadStream
and new fs.WriteStream in JavaScript sources. They follow
lib/internal/fs/streams.js on the stream engine:

- _construct opens the file, then emits 'open' (with the fd) and
  'ready'; _read reads up to end; _write retries short writes as
  writeAll does; _destroy waits for I/O in flight, then closes (with
  fsync first for the flush option). Every open, read, write, fsync and
  close runs on the shared worker pool.
- Options are checked in Node's order: getOptions, options.fd or the
  path, start, end, then highWaterMark, encoding, emitClose and
  autoClose. flags and mode are checked when the file opens, so their
  errors arrive as 'error' events.
- bytesRead, bytesWritten, pending, path and close(cb) compile, also
  through untyped values (close() there).

A FileHandle stream holds a reference on the handle; closing the stream
releases it and closes the handle unless autoClose is false.
FileHandle.close() follows Node's reference count: it waits for the
streams over the handle and runs their close(). readLines is a readline
Interface over createReadStream with crlfDelay: Infinity.

The stream engine gains Node's _construct (reads, buffered writes,
finish and _destroy wait for construction; destroy errors combine with
aggregateTwoErrors) and _writev, both internal. Writable.destroy() now
runs the buffered write callbacks and end() callbacks (errorBuffer),
which it dropped before. highWaterMark in a dynamic options record is
checked as getHighWaterMark does, and for await over a constructing
stream waits for construction.

Numeric flags, the fs, signal and windowsHandle options, non-UTF-8
write encodings, option functions that replace the file implementation,
and instanceof fs.ReadStream/WriteStream are refused.

Corpus 5546, 5547 and 5554 cover both lanes against Node 24.22.
2026-10-15 23:58:50 -05:00
Chris Tate ebbc3d4519 Keep region counting out of the hot path of programs without regions
In a program that constructs a LifetimeRegion, the retain and release of
every ProgramLifetime class count references in the chunk that holds the
instance, once the process has created a region. Speed builds inline those
bodies into every use of an instance. A program that references
LifetimeRegion but never creates a region still carried the counting path
inline at every use, and its walks over ProgramLifetime graphs ran about
10% slower than the same program without the reference.

The flag test that skips counting is now marked likely, so the counting
path is laid out away from the callers' hot code. Programs that create
regions take the same path as before.
2026-10-15 23:45:56 -05:00
Chris Tate 9b452b88de Merge main 2026-10-15 23:33:48 -05:00
Chris Tate 9b9a54e71b Check the certificate against the host when servername is empty
An https.request with servername: "" cleared the TLS hostname, which
also turned off the certificate's name check, so a certificate for any
other name passed verification. Node's empty servername only stops SNI
from being sent; checkServerIdentity still checks the certificate
against the request's host.

The request now sends no SNI and the verify callback checks the leaf
certificate against the host with mbedTLS's name rules, so a mismatch
fails with ERR_TLS_CERT_ALTNAME_INVALID as it does without servername.
Corpus 5654 covers an empty servername against a certificate for the
host and one for another name, and the SNI names the server receives.
2026-10-15 23:33:11 -05:00
Chris Tate 681f5ceee7 Add TestContext hooks, plan and fullName, and Node's todo and only rules
node:test changes, matching Node 24.22:

- t.before, t.after, t.beforeEach and t.afterEach lower. t.before runs
  when it is called; a throw fails the test and the subtests that follow,
  which skip their body and beforeEach hooks and run their afterEach hooks.
  t.beforeEach and t.afterEach wrap every descendant subtest. t.after runs
  after the test's afterEach chain, also when the body throws, and a throw
  fails the test.
- t.plan(count) counts the test's t.assert calls and subtests. A mismatch
  fails the test with Node's "plan expected N assertions but received M"
  string, unless an assertion failed first.
- t.fullName joins the names from the outermost suite or test with " > ".
- Tests and suites under a todo suite or test are todo, with a bare TODO
  directive, unless they are skipped or carry their own todo message. A
  todo suite reports its directive.
- describe.only, suite.only and { only: true } on a suite mark it. At each
  level, when a child is marked or contains a marked test or suite, only
  those children run.
- The failing-tests section lists failures in completion order, so
  subtests come before the test that ran them, and prints thrown strings
  quoted, as Node inspects them.

The fixtures ctx-hooks, ctx-plan, todo-suites, suite-variants,
only-nested and thrown-values compare the reporter output and exit code
with Node.
2026-10-15 23:16:04 -05:00
Chris Tate dcd98ae5e5 Walk owned objects with a single reference without an entry (#1082) 2026-10-15 23:01:00 -05:00
Chris Tate 867f7944b4 Lower util.format %f (#1060) 2026-10-15 22:56:18 -05:00
Chris Tate 386cfd0c65 Accept readline's history and completer options and add the history members
createInterface accepts history, historySize, removeHistoryDuplicates,
completer, tabSize and escapeCodeTimeout, as Node does for a non-terminal
interface: tabSize, escapeCodeTimeout and historySize are checked in the
constructor's order before the interface opens (ERR_OUT_OF_RANGE and
ERR_INVALID_ARG_VALUE with Node's messages), a completer that is not a
function throws ERR_INVALID_ARG_VALUE, and the values are kept on the
interface. Options may be written as shorthand properties.

rl.history answers the history option itself, or the interface's own
empty array, and historySize, historyIndex, crlfDelay, tabSize,
escapeCodeTimeout, line and terminal read as in Node. 'history'
listeners can be registered; without terminal mode Node records no
history, so they never fire.
2026-10-15 22:53:40 -05:00
Chris Tate 63b4dddeca Support array keys in InternTable and free replaced indexes (#1072) 2026-10-15 22:50:08 -05:00
Chris Tate 893de9f4f8 Retry a kept region only when its counted references change (#1081) 2026-10-15 22:44:02 -05:00
Chris Tate 3caf73f814 Take published objects out of the candidate buffers during the walk (#1080) 2026-10-15 22:41:30 -05:00
Chris Tate 54727fc983 Merge main 2026-10-15 22:40:47 -05:00
Chris Tate 26f965c198 Walk owned objects with a single reference without an entry
The release walk gave every ordinary object it reached an entry in an
open-addressing table, to count the references it found to it and the
edges between entries. A region of 1.2M objects that each own an array
spent most of its 200 ms walk inserting into that table.

An object with a single reference, found from one of the region's own
objects or from an object walked like them, has no other referrer, so it
can never be referenced from outside the region. It now gets no entry and
no edges and is walked like the region's own objects, its references into
the region counted as explained. RC-audit builds still enter these objects
and stop when the walk reaches one twice.

Arrays and maps also resolve their elements' release entry point once per
container instead of once per element.
2026-10-15 22:35:54 -05:00
Chris Tate 1b4103889b Add assert.deepEqual, partialDeepStrictEqual and the Assert class (#1079) 2026-10-15 22:22:57 -05:00
Chris Tate 2f510beccf Retry a kept region only when its counted references change
Every region creation, release and pressure retry walked every condemned
region again. A released region that something still refers to was
re-walked on each of them, two or three times per build in a watch loop,
and each walk visits every object of the region.

An attempt that keeps a region now records the references counted to it.
Retries at creation, release and region entry attempt a kept region again
only when that count has changed, or once the thread has allocated 8 MiB
(or the region's size, if larger) in regions since the attempt. That
volume doubles after each attempt that finds the count unchanged, up to
64 times. The volume covers references from ordinary objects the walk
reaches, which can go away without changing the count. The schedule is
per region, so a region that another region's release unblocks is
attempted at once, and the full cycle collection before an attempt runs
only when some region is due.

Adds the region-builds runtime benchmark and a region test for the
schedule.
2026-10-15 22:22:32 -05:00
Chris Tate a87bf8b4b0 Take published objects out of the candidate buffers during the walk
publish() used to remove each published object from this thread's
cycle-collector candidate buffers after the walk, which loaded every
object's collector header a second time. On graphs larger than the
cache that second pass took about a third of the publish.

scr_pub_mark now takes a buffered object out of its buffer when it marks
it, while the header is in cache: the slot is cleared and its index
recorded. When the publish ends, scr_cyc_pub_fill closes the empty slots
with entries from the ends of the buffers. A refused publish puts each
taken object back in its slot first. After the walk, the log is read only
for dyn containers, the RC audit and LeakSanitizer.

The emitted walkers test null and immortal references (parent links,
values published before) before calling scr_pub_push, and the string
walker checks for ASCII 32 bytes at a time.

Adds the published-graphs runtime benchmark, a cycle collector test for
the take, restore and fill hooks, and corpus program 5702.
2026-10-15 22:19:05 -05:00
Chris Tate cfc6ebb340 Add source functions, promise destinations and { signal } to pipeline stages (#1077) 2026-10-15 22:14:49 -05:00
Chris Tate 7c09e1c020 Add assert.deepEqual, partialDeepStrictEqual and the Assert class 2026-10-15 22:01:38 -05:00
Chris Tate 29f21ffeca Evaluate a %f boolean or unit argument without lowering it twice
The util.format %f branch for boolean and unit arguments discarded the
lowered value and lowered the argument node again as a statement, so
anything lowering builds for the argument (lifted array helpers,
closures) was built twice, one copy dead. It now evaluates the value it
already lowered as a statement, and drops it when evaluating it has no
observable effect.

The same branch now also covers void calls and null | undefined unions,
which Node formats as NaN like undefined and null; they were refused.
2026-10-15 21:58:33 -05:00
Chris Tate 087af09210 Wake the event loop when worker-pool jobs complete (#1073) 2026-10-15 21:51:45 -05:00
Chris Tate 647bc2a492 Add source functions, promise destinations and { signal } to pipeline stages
pipeline() accepts a source function (an async generator function called
with { signal }) and a destination async function, following
pipeline.js: the destination's promise value resolves the
stream/promises form and reaches the callback's second argument, and
pipeline() answers a PassThrough for it. A rejected destination destroys
that PassThrough with the reason and fails the pipeline.

Stages may take the { signal } argument: the pipeline creates its own
AbortController when one does, and aborts it when the pipeline finishes
or fails, as finishImpl does. Stage functions take the source and
{ signal } through the closure ABI, and the PassThrough and the value
are released with the pipeline.

The stream unit now links the emitter unit whenever it links, so a
pipeline of functions builds without constructing a stream. An async
generator destination and reading the PassThrough a function destination
returns are refused at compile time.
2026-10-15 21:50:01 -05:00
Chris Tate 77c9a2ff23 Merge main 2026-10-15 21:48:48 -05:00
Chris Tate 8dc5dc1249 Refuse InternTable array keys that are too long to encode
Encoding an array key added each element's size to a running length
with unchecked arithmetic, and wrote into a fixed stack buffer whenever
the sum fit. A sparse array with a large length (2^29 elements or more)
wrapped the sum on 32-bit targets, so the write landed before the buffer;
on every target the encoder also visited every index of such an array.

Array keys now have at most 65536 elements, and their encoding at most
2^31 - 1 bytes. The size is computed first with sums that cannot wrap,
the buffer is sized to it, and keys over either limit make the lookup or
insert throw a TypeError before anything is read or written. The Node.js
implementation throws the same TypeError for keys of more than 65536
elements. Sparse elements are walked in one pass in index order, and the
index growth computation checks its size arithmetic too.
2026-10-15 21:44:38 -05:00
Chris Tate dea734d63a Support request signals, destroy(err), servername, Expect events and maxHeaderSize in http
- http.request accepts `signal`: an abort, or a signal already aborted at
  construction, destroys the request with an AbortError (code ABORT_ERR,
  the signal's reason as cause). The abort listener is removed when the
  request closes, and a value that is not an AbortSignal throws
  ERR_INVALID_ARG_TYPE.
- req.destroy(err) reports err as the request's 'error'. Error listeners
  receive the Error object through scr_event_error_payload, and
  AbortError construction is shared through scr_error_abort.
- https.request accepts `servername`, sent as SNI and used for the
  certificate name check.
- server.on('checkContinue' | 'checkExpectation') take Expect requests
  instead of 'request', and the client emits 'continue' on a 100
  response.
- createServer and http.request accept maxHeaderSize, validated with
  Node's ERR_OUT_OF_RANGE messages.

Corpus 5564 and the extended 5559, 5561 and 5562 cover each API.
2026-10-15 21:43:27 -05:00
Chris Tate 81ed97c5a7 Lower timers/promises resolve values and setInterval under @types/node (#1058) 2026-10-15 21:33:25 -05:00
Chris Tate d30d2619d7 Support crypto.createCipheriv and createDecipheriv (#986) 2026-10-15 21:30:30 -05:00
Chris Tate 3c7db70375 Return unions with reference arms in registers (#1065) 2026-10-15 21:15:36 -05:00
Chris Tate 9e81333bbc Add Readable.from over Sets, iterators and class iterables
Readable.from accepts any iterable or async iterable of strings or
Buffers, following from.js: the iterator is taken when the stream is
built, before the options are read, next() is looked up and called on
each pull, a sync iterator's promise values are awaited, and destroy
calls the iterator's throw() and return() only when it has them, reading
each method when close() runs. A close() that fails with a second error
passes an AggregateError of both to the destroy callback, as
aggregateTwoErrors does.

In typed code this covers Sets of strings, Map and Set iterators,
classes whose [Symbol.iterator]() or [Symbol.asyncIterator]() is a
generator method, iterator classes, and async iterator classes, whose
return() runs at the end of the stream and on destroy. In JavaScript it
covers every iterable; an object whose iterator method is a generator
method throws a not-supported error when the stream is built.
2026-10-15 21:10:22 -05:00
Chris Tate 0ebc146405 Merge main 2026-10-15 21:07:04 -05:00
Chris Tate 293370e2e7 Merge main 2026-10-15 21:06:45 -05:00