Commit Graph
743 Commits
Author SHA1 Message Date
Chris Tate 755b7d997b Read untyped listener arrays for tuple-less events, and route typed meta listeners beside checked-dynamic ones
- listeners() and rawListeners() in TypeScript of an event that no literal
  emit or listener gives a tuple (one registered only under computed names)
  read the untyped array, so each returned listener takes every argument a
  call passes. The array was checked into zero-argument functions, which
  called the listener without its arguments.
- A typed 'newListener' or 'removeListener' listener in a program with a
  checked-dynamic listener of the other meta event registers through the
  adapter. The meta lowering's check looked only at the listener's own
  event and refused it with SC2020.
2026-10-29 23:11:27 -05:00
Chris Tate f1825611fb Collect garbage cycles across the scriptc and V8 heaps (NI-8) (#1908) 2026-10-29 15:33:24 -05:00
Chris Tate d972a6bc13 Defer island modules whose recorded load is deterministic (NI-7b/7d/7e/7f) (#1906) 2026-10-29 15:18:28 -05:00
Chris Tate 43bafa67d1 Test262 group C: early errors, undeclared reads, async-iterator relaxation, compareArray harness (#2500) 2026-10-29 15:17:08 -05:00
Chris Tate 145f8fcd5e fs.watch names the changed entry; fs.watchFile polls in its own gated unit (#2507) 2026-10-29 15:13:12 -05:00
Chris Tate 59eced7a6c Lower ReadableStream tee, pipeTo and pipeThrough in TypeScript (#2513) 2026-10-29 15:09:05 -05:00
Chris Tate a391eef92a Read and write cached regular files on the loop thread in fs streams (#2540) 2026-10-29 15:06:47 -05:00
Chris Tate d67f378a50 Release the web view of an abandoned fetch body while its transfer runs
A Response body's stream gets a web ReadableStream view when a member the
fetch stream lacks is read (for example `body.tee`). The view's source holds
the stream, and the stream released the view only when that source was its
last holder. While the transfer runs it holds the stream too, so a dropped
body with a view kept a reference count of 2: the unobserved-response
discard never ran, the paused socket was never resumed, and a compressed
response dropped before HTTP EOF kept the process alive.

The stream now releases the view when only the view's source and the live
transfer hold it, and then takes the unobserved-response discard, as a
body without a view does.

The static-abandon-view fixture reads `body.tee` and `body.locked` of a
4 MiB incompressible gzip response from the new /gzip-incompressible route
and drops it. It hung until the test's timeout before this change and now
exits as Node does.
2026-10-29 13:44:32 -05:00
Chris Tate 352842f020 Scope early errors to function bodies and class strictness, and match the upstream compareArray reads
- A function body or class static block treats its top-level function
  declarations as var-scoped: duplicates are legal in strict code, and a
  let/const/class of the same name is SC0006.
- Strictness is the code around a node: a function declaration's own
  "use strict" no longer makes its declaring context strict.
- Every part of a class is strict; object literal methods and accessors
  are strict only by their own prologue or their surroundings, and an
  arrow function's block body can carry "use strict".
- The bare compareArray reads b.length, then a.length, and a.length again
  before every element pair, converting it as the upstream `i < a.length`
  does, so a string length compares its elements.
- Tests that relied on the old handling of undeclared names now use
  constructs that are still refused or still compile: the installed-entry
  diagnostics fixture reports a syntax error (a read of an undeclared name
  is now Node's run-time ReferenceError), and static-objects-order's
  loops.js stores from its for-in head into `undefined` instead of an
  undeclared name (a store into an undeclared name refuses its package with
  SC1091).
2026-10-29 08:37:25 -05:00
Chris Tate d478dc9ec5 Send a zero from an inline RWF_NOWAIT read to the pool
Linux 5.9 and 5.10 answer 0 instead of EAGAIN for an RWF_NOWAIT read of
data that is not cached. The inline read took that 0 as end of file and
ended the ReadStream early. A zero from the inline read now goes to the
pool, whose read returns the data or confirms end of file; this costs one
pool round trip at the end of each file.

fs-stream-nowait.test.ts preloads a preadv2 shim that answers 0 for every
RWF_NOWAIT read and checks that a ReadStream still reads the whole file,
as Node does.
2026-10-29 07:05:55 -05:00
Chris Tate 60be717d92 Merge main 2026-10-29 04:24:50 -05:00
Chris Tate 089a8e1799 fs.watch/fs.watchFile: fix listener removal, adapter ownership and argument checks
- watcher.off/removeListener of a listener that is not the last one moves
  the later listeners down instead of up, so the removed closure leaves
  the list.
- The watchFile adapters for zero- and one-parameter listeners release
  the Stats their listener has no parameter for, and the fs.watch adapter
  releases the event string once a dynamic value holds it.
- A one-parameter fs.watch listener whose eventType is dynamic receives a
  boxed string through the emitted adapter.
- fs.watch, fs.watchFile and fs.unwatchFile throw ERR_INVALID_ARG_VALUE
  for a path holding a null byte, and a new watchFile poller throws
  ERR_OUT_OF_RANGE when interval is not a uint32, as Node does. Both
  watchFile libCalls check for the pending exception.
- fs.watchFile and fs.unwatchFile have surface-manifest entries, so a
  fence can deny them.
- The self-hosted feature records list watchFile, and the node-types
  fence fixture uses fs.openAsBlob in place of the now-lowered
  fs.watchFile.

Tests: tests/corpus/fs-watch-listener-shapes.ts (listener shapes, off of
a middle listener, null-byte path) with its order baseline and cost file,
the validation cases in fs-watch-file-polling.ts, and
tests/harness/fs-watch-leaks.test.ts, which runs both programs under
LeakSanitizer in the sanitized lane.
2026-10-29 01:47:31 -05:00
Chris Tate b260a13dda Make fewer syscalls per fs thread-pool completion and pool-thread write (#2519) 2026-10-29 00:20:58 -05:00
Chris Tate 50aa42883f Cache the pool-thread signal check per OS thread
scr_fs_write_once cached whether the calling thread blocks SIGPIPE and
SIGXFSZ in an SCR_TL variable. SCR_TL is empty in builds without thread
instances, so a pool thread's answer applied to the main thread too, and
a later writeSync to a closed pipe ended the process with SIGPIPE
instead of throwing EPIPE. The cache is _Thread_local now. The
fs-write-sync fixture covers a write stream followed by writeSync on a
closed stdout.
2026-10-29 00:18:11 -05:00
Chris Tate 5f27c4d233 Exit cleanly with Node loaded and keep nested entries on Node's stack (NI-7c, NI-3b) (#1905) 2026-10-28 23:43:21 -05:00
Chris Tate f909bdf6c8 Make fewer syscalls per fs thread-pool completion and pool-thread write
- A worker writes the completion wake byte only when the pipe has none.
  The loop reads it, with the queue lock held, when it takes the last
  waiting completion. The pipe holds a byte exactly while completions
  wait, so a burst of completions costs one write and one read instead of
  one write per completion and a read loop per dispatch.
- The fs pool's threads keep SIGPIPE and SIGXFSZ blocked for their
  lifetime. scr_fs_write_once checks once per thread whether both are
  blocked; on such a thread a write makes no signal-mask calls, and a
  write that fails with EPIPE or EFBIG consumes the signal it raised.
  Other threads keep the per-write guard.

fs-write-sync.test.ts covers EPIPE and EFBIG from write streams, whose
writes run on the pool's threads, and matches Node's errors.
2026-10-28 23:34:34 -05:00
Chris Tate 22357e8bed Lower ReadableStream tee, pipeTo and pipeThrough in TypeScript
In TypeScript, tee(), pipeTo() and pipeThrough() on a ReadableStream were
refused with SC2020, and the WritableStream constructor and a
TransformStream with a transformer had no lowering, while JavaScript
programs reach all of them through the web stream classes
(scr_webstreams.c).

A TypeScript program that uses one of them now builds its ReadableStream,
WritableStream and TransformStream values with those classes, as
JavaScript does, and %main installs them, so a native stream such as a
Response body answers the three methods through its web view. A program
that uses only getReader(), cancel(), locked and the identity transform
keeps the native streams and builds to the same binary.

The fallback declarations gain UnderlyingSink and Transformer, the
WritableStream constructor takes a sink and the TransformStream
constructor a transformer and queuing strategies, and their controllers
map to checked-dynamic handles. The fetch profile lists the three methods
as static with the new static-web-streams fetch fixture as their
differential evidence, and keeps them in the set that links the classes.
The surface manifest is regenerated, and the surface-manifest probe that
kept tee() and pipeTo() fenced now fences values() and the async
iterator.

Adds the static-web-streams fixture and the corpus program
web-streams-typed-pipes.ts, with its corpus cost and order baseline.
2026-10-28 22:18:11 -05:00
Chris Tate 2ae08e4002 Route more packages and values through Node islands (NI-4c–4g) (#1904) 2026-10-28 21:53:40 -05:00
Malte Ubl db617bd594 Merge remote-tracking branch 'origin/main' into fix/pi-conversions-layouts 2026-10-28 18:30:14 -07:00
Chris Tate e0c692593c Evaluate a for..of source beside never[] once, keep collection views through a second view, and combine typed promises in untyped JavaScript
- lowerForOfEmptyArrayArm binds the `Set | Map | string | never[]` source
  to a hidden local before the tag test, so a call or getter in the loop
  head runs once and the loop walks the value the test saw.
- markTarget marks a structural view as a collection view when its source
  is already one, and reruns the fixpoint when a view marked earlier
  becomes a collection view, so a Map or Set handed through two views keeps
  answering through the collection.
- Promise.all in a JavaScript source whose call types as Promise<any[]>
  (an unannotated parameter) runs the engine's Promise.all over the entries
  as checked-dynamic values when they are typed promises. A merged
  `Promise<number | string>[]` argument reaches that call since union
  promise arms merge.

Corpus 8419 and 6956 and the js-own-tostring npm fixture cover each case.
2026-10-28 20:22:14 -05:00
Chris Tate 18ab17848e Keep the held head across a cork() and uncork() with no write
An uncork() flushed a head writeHead stored even when nothing was
written while corked. Node keeps the stored head until write(), end() or
flushHeaders(), so the flush now sends it only when a write() or
flushHeaders() ran while corked. The http-held-head fixture covers the
three cases.
2026-10-28 18:23:49 -05:00
Chris Tate 2814b57e20 Send the head writeHead stores with the response's first write or end()
res.writeHead() builds the head and keeps it until the first write(),
end() or flushHeaders(), which sends it with that call's data in one
socket write, as Node's _storeHeader does. A response with an Expect
header sends the head at once, as in Node. The http-held-head server
fixture compares the wire timing with Node's.
2026-10-28 16:35:39 -05:00
Chris Tate d83a5dc929 Fix census WP-2 stdlib/@types sites: void-slot lambda ABI, ChildProcess.off/signalCode, writeFileSync const options, path sub-namespaces, stdout writableLength (#2468) 2026-10-28 15:04:48 -05:00
Malte UblandClaude Opus 5.5 f88019a8d1 Merge remote-tracking branch 'origin/ni/i10-js-island-requires' into ni/i11-cross-heap-gc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:58:05 -07:00
Malte UblandClaude Opus 5.5 afa84f5c5f Merge remote-tracking branch 'origin/ni/i8-exit' into ni/i9-recorded-deferral
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:37 -07:00
Malte UblandClaude Opus 5.5 c3e4f2ced8 Merge remote-tracking branch 'origin/ni/i7-routing' into ni/i8-exit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:23 -07:00
Malte UblandClaude Opus 5.5 d4445ec628 Merge remote-tracking branch 'origin/ni/i6-subclasses' into ni/i7-routing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:10 -07:00
Chris Tate ad368e353d Lower Response.clone and Request.clone on typed values (#2473) 2026-10-28 13:37:52 -05:00
Chris Tate 577f086658 Space the timer delays in two order-sensitive tests and raise the static-spatial timeout
mock-timers' "named and namespace imports keep the loop's timers" and
6811-node-island-uncaught-listener check the order in which real timers
fire, with delays 1 to 5 ms apart that are scheduled one after another.
On a busy runner more time than that can pass between scheduling two of
them, and the order flips in either lane: the sanitized node:test run saw
Node's reference print 'named promise' first, and the sanitized
llvm-differential run saw 'island timer 1' before 'native timer after'.
The delays are now 100 ms apart in mock-timers (100, 200, 300) and the
island's timers (150, 250) start 100 ms after the native ones (10, 50).
Node prints the same output as before.

static-spatial bundles three.js and builds it with the sanitizer, which
passed the default 300 s test timeout on a busy runner. Its cases take
three.test.ts's 1200 s timeout.
2026-10-28 12:49:21 -05:00
Chris Tate 5a1021dcd1 Fence Response.formData in the node-types fenced fixture
Response.clone lowers now, so the fixture's unlowered Response
operation is formData, which keeps the same fence and hint.
2026-10-28 12:42:13 -05:00
Malte UblandClaude Opus 5.5 10f974aab9 Merge ni/i6-subclasses into ni/i7-routing
Resolve tests/harness/npm.test.ts by keeping main's npm-suite split and
porting this branch's island KNOWN entry and stderr frame normalisation
into tests/harness/npm-suite.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:59:57 -07:00
Malte Ubl d1566d2746 Merge branch 'ni/i10-js-island-requires' into ni/i11-cross-heap-gc 2026-10-28 08:48:18 -07:00
Malte Ubl bda1292e5c Merge branch 'ni/i8-exit' into ni/i9-recorded-deferral 2026-10-28 08:47:58 -07:00
Malte Ubl 8a918e7aff Merge branch 'ni/i7-routing' into ni/i8-exit 2026-10-28 08:47:47 -07:00
Malte UblandClaude Opus 5.5 0fb9e25464 Merge ni/i6-subclasses into ni/i7-routing
Resolve tests/harness/npm.test.ts by keeping main's npm-suite split and
porting this branch's island KNOWN entry and stderr frame normalisation
into tests/harness/npm-suite.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:47:41 -07:00
Malte Ubl 29a31446d3 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-28 08:43:06 -07:00
Chris Tate 1cf59d42e4 Lower Response.clone and Request.clone on typed values
Response.clone() and Request.clone() on a value typed Response or Request
were refused: in TypeScript the fallback declarations had no clone, and
the fetch profile listed both as unsupported, so the call compiled to an
SC2020 refusal, also in JavaScript where the checker types the value. The
checked-dynamic handles already implement them (the body tees, and the
clone gets a copy of the headers with the same guard), so typed calls now
dispatch to those implementations, as text() and json() do.

The declarations gain clone(), and the fetch profile lists both as static
methods with the new static-clone fetch fixture as their differential
evidence. The surface manifest is regenerated, and the surface-manifest
probes that kept clone fenced now fence blob() and formData().

Adds the static-clone fixture and corpus programs fetch-clone.ts and
fetch-clone-untyped.mjs, with their corpus costs and preflight/order
baselines.
2026-10-28 09:33:31 -05:00
Malte UblandClaude Opus 5.5 08ec7fd291 Merge ni/i10-js-island-requires into ni/i11-cross-heap-gc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:43:05 -07:00
Malte UblandClaude Opus 5.5 433dc179ef Merge ni/i8-exit into ni/i9-recorded-deferral
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:42:55 -07:00
Malte UblandClaude Opus 5.5 6ec5b3ff58 Merge ni/i7-routing into ni/i8-exit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:42:43 -07:00
Malte UblandClaude Opus 5.5 9edf1d3aa3 Merge ni/i6-subclasses into ni/i7-routing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:42:18 -07:00
Malte UblandClaude Opus 5.5 882c87d425 process/child: void slots, listener removal by identity, signalCode
Three shapes from pi's process/child helpers:

1. A cleanup arrow such as `() => process.off(signal, handler)` sits in
   @types/node's `Process`-returning slot. The lambda's ABI was decided
   from its inferred return type before the contextual void slot was
   consulted, so the arrow promised a `Process` value the lowering cannot
   produce and the registration fenced. `lambdaSignature` now takes the
   contextual void answer first; the adoption block further down already
   computes it, so every program that compiles today keeps its IR.
2. `child.off`/`removeListener` were unimplemented. They join the
   `child.on` family, are limited to the removable literals `spawn`,
   `exit`, `close` and `error`, and require the listener value that was
   registered — removal is by closure identity, scanning the registry
   from the end as Node does. New C runtime entry points
   `scr_child_off_spawn/_exit/_close/_error` release the entry and shift
   the tail.
3. `child.signalCode` reads `string | null` from the recorded exit
   signal, with `scr_child_has_signal_code`/`scr_child_signal_code`
   behind the same typed-union wrap as `spawnRes.signal`; the IR
   validator pins the `string | null` result.

The process listener registration paths (stdin events, IPC message and
disconnect, uncaughtException and uncaughtExceptionMonitor,
unhandledRejection, warning, signals, exit) now accept any discarded
expression, matching `child.on` — `resultIsDiscarded` rather than a bare
expression statement — so `handlers.push(() => process.off(…))` compiles.

Evidence: corpus 8412 (removal through `off` and `removeListener` for
all four events, `signalCode` while running, after a normal exit, after
a fatal signal and after a failed spawn, idempotent double removal) and
the `node-types` fixture plus project-config test that pins pi's exact
`() => process.off(...)` cleanup closure. Ambient declarations gain the
`signalCode` property and the four `off`/`removeListener` overloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:38:15 -07:00
Chris Tate 98c178d093 Check the stores of for-in and for-of heads before converting a constructor
A `for (k in o)` or `for (k of a)` whose head is an expression stores into
it on every iteration. The strict-mode check of constructor and method
bodies now treats such a head like the target of `=`: a property store
not made through `this`, a store into a read-only global, and a store into
an undeclared name keep the constructor function, since sloppy mode drops
or completes those stores where a class body throws.

The static-objects-order fixture gains loops.js, whose methods store from
a for-in head into an undeclared name and from a for-of head into a
property of a string.
2026-10-28 03:08:04 -05:00
Chris Tate 1278729267 Merge main 2026-10-27 23:21:09 -05:00
Chris Tate 18a47fedd5 Run npm packages in Node islands: object handles and compiler routing (NI-4) (#1899) 2026-10-27 22:42:29 -05:00
Chris Tate 93081ea679 Host the runtime's event loop on Node's libuv once Node loads (NI-3) (#1696) 2026-10-27 22:19:38 -05:00
Chris Tate d5c215c684 Merge main 2026-10-27 22:03:19 -05:00
Malte Ubl f43332a0b6 Merge remote-tracking branch 'origin/ni/i10-js-island-requires' into ni/i11-cross-heap-gc 2026-10-27 19:07:22 -07:00
Malte UblandClaude Opus 5.5 d06155048e Only bare binding getters are pure; compare getter exports for mutation
A member read in an export getter (a.b or a[b]) can invoke another
accessor that runs code on every read, so the recorder now accepts only a
bare identifier as a plain binding getter. Everything else keeps its
runtime read and is not snapshotted.

The mutation check that makes an earlier island module eager copied only
data properties of a module value, so an object exported through a
binding getter could stay stale when a later load changed it. The shape
now reads binding getters the same way the snapshot does, and compares
them by reading again, so those modules no longer defer.

Corpus fixtures pin both: a getter that reads a member counts every read,
and a later load that mutates a getter-exported object keeps both modules
eager.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:06:41 -07:00
Malte UblandClaude Opus 5.5 9ad04922b2 Reject a literal import() of a module inside a rejected import graph
A module reached by an already-rejected literal import() is `covered`, but a
direct import() of that module still needs its own rejection entry: its graph
rejects when that import runs, so it must not join the compiled graph merely
because another rejected import also reaches it. Without the entry the module
is compiled and lowering fails on the unloaded binding instead of the
import() rejecting at run time as Node does.

Walk each direct root independently and extend the unloaded-dynamic-import
fixture with a direct import() of the rejected graph's dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:05:26 -07:00