Commit Graph
1308 Commits
Author SHA1 Message Date
Chris Tate 145268ed55 Inspect program-declared Error subclasses with Node's constructor-name header (#1520) 2026-10-20 18:43:16 -05:00
Chris Tate 710d203a97 Shrink the array header to 48 bytes (#1489) 2026-10-20 18:30:40 -05:00
Chris Tate 5b20bf1e77 Merge main 2026-10-20 18:25:49 -05:00
Chris Tate 7f777f36a3 Carry runtime-decided dns.lookup answers dynamically, dial Node-typed net lookups, and keep tsc enum exports undefined until assigned (#1528) 2026-10-20 18:25:22 -05:00
Chris Tate 90315ca7ea Bind builtin static functions and read Buffer.alloc, fs.realpathSync and String methods as values (#1553) 2026-10-20 18:21:56 -05:00
Chris Tate 40ed0e3aff Merge main 2026-10-20 18:16:00 -05:00
Chris Tate 6bb2711c4b Continue a failed checked cast with its value in a dynamic tail (#1561) 2026-10-20 18:15:01 -05:00
Chris Tate cd833e68a4 Keep the class id of hierarchy instances in the rc word (#1486) 2026-10-20 18:12:51 -05:00
Chris Tate fa098689f1 Check for a class id before reading a stream's vtable word
The stream probes for untyped values (pipe destinations, pipeline
stages, readline input and output, and the checks against the stream
classes) read the vtable word of any boxed object. Instances with a
class id in the rc word have no vtable word, so a hierarchy instance
whose first field is a number crashed those probes. scr_stream_vt_of
answers NULL for them, and each probe treats that as no stream.
2026-10-20 18:09:22 -05:00
Chris Tate bb78ea2ce7 Run buffer-crc32's and yazl's capability probes as Node does (#1549) 2026-10-20 18:02:43 -05:00
Chris Tate 37834ed189 Merge main 2026-10-20 17:57:40 -05:00
Chris Tate dc5e0cb5bb Merge main 2026-10-20 17:46:26 -05:00
Chris Tate 85936f9035 Merge main 2026-10-20 17:44:58 -05:00
Chris Tate 8dd2b95e16 Merge main 2026-10-20 17:44:17 -05:00
Malte UblandClaude Opus 5.5 1ef31a1b74 Continue a failed checked cast with its value in a dynamic tail
`JSON.parse(text) as T`, `const x: T = anyValue` and `await res.json()
as T` threw `TypeError: expected X at $.path, got Y` when the value did
not fit `T`. Node never checks an assertion and continues with the value
it has, so the build-container listener rejected an order Node accepts
and a JWT with a string `exp` became a 500.

A failed cast now keeps its value when it initializes a `const`/`let`
binding declared in a function or block and only the code after the cast
reads the binding. That code (the statements after the cast in each
enclosing statement list, up to the declaration) lowers twice: the
static copy, unchanged, runs while the value fits; a dynamic copy, in
which the binding and everything derived from it is typed `any`, runs
after a failure, so missing members read undefined, wrong-typed members
keep their values, extra members stay visible in source order, and
objects keep their identity. The cast sets the flag that selects the
copy: the fused JSON.parse helper's slow path and the plain check call
scr_dyn_check_recover, which takes back the TypeError and parks the
value for checkedCast.fallbackTaken/fallbackTake.

A failed value that flows into a typed slot outside the region is
checked there, as any `any` value is: it continues when it fits and
throws at that use when it does not. A statement of the dynamic copy
with no `any` lowering becomes a runtime fence that throws only when a
failed value reaches it. Module-level bindings, regions with hoisted
declarations or super() calls, union targets and targets holding class instances, and
--tier=static-only (and library mode) keep the throwing cast.

`a?.b.c` on an `any` value now short-circuits only on `a`, so `.c`
throws when `a.b` is undefined, as in Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:25:33 -07:00
Chris Tate 5b964e244c Jump out of WebAssembly traps without __builtin_setjmp on AArch64 sanitized builds
Clang has no __builtin_setjmp lowering for AArch64, so scr_wasm.c did not
compile under AddressSanitizer on macOS arm64. Sanitized AArch64 builds
now use an uninstrumented setjmp/longjmp pair that saves the
callee-preserved registers, sp and the frame and link registers; like the
builtin pair, no runtime intercepts it, and scr_wasm_trap still unpoisons
the abandoned frames.
2026-10-20 16:51:09 -05:00
Malte UblandClaude Opus 5.5 be9c18f0b3 Bind builtin static functions and read Buffer.alloc, fs.realpathSync and String methods as values
- bind/call/apply on builtin function values held as checked-dynamic
  functions (String.fromCharCode, JSON.stringify and the forms below).
- Function values for Buffer.byteLength (Node's runtime encoding ladder),
  Buffer.compare, Buffer.alloc (size check, then fill), JSON.parse,
  fs.realpathSync and fs.realpathSync.native (Node's options handling).
- A JavaScript string method read as a value answers String.prototype's
  function (smol-toml's `!!(''.isWellFormed)` probe).

The runtime entries live in a new unit, scr_builtin_values.c, linked only
by programs that read these values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:50:09 -07:00
Malte UblandClaude Opus 5.5 b723155820 Run buffer-crc32's and yazl's capability probes as Node does
- A JavaScript binding written with a value whose representation differs
  from its initializer's (buffer-crc32's Int32Array table) stores a
  checked-dynamic value.
- A top-level JavaScript function declaration its module reassigns
  (yazl's self-redefining bufferFrom) lives in a mutable module global,
  assigned when the module starts; reads, calls and writes use it.
- Buffer.from with a non-literal encoding follows Node's fromString rules
  at run time, and copies arrays crossing by reference; array and byte
  sources ignore the extra argument.
- `new Buffer(...)` in dependency sources, where Node never warns, is
  Node's construction.
- Fixed-width read/write methods on Buffers held as checked-dynamic
  values dispatch through a separate runtime unit (scr_bytes_dyn.c),
  linked only by programs that call them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:26:02 -07:00
Chris Tate 19091ebbda Pipe into constructor-function streams natively in checked-dynamic stream slots
With checked-dynamic stream slots, stream.dynNative answered only boxed
runtime streams, so a pipe into a constructor-function stream took the
JavaScript listener protocol, and src.unpipe(this) in its 'pipe' listener
had no runtime pipe to remove (6564-constructor-function-stream-late-hooks).
In programs that link the constructor-function stream unit,
stream.dynNative now answers such a stream's runtime stream, as
stream.unboxDyn does there. A stream that overrides write() or end()
still pipes through its JavaScript members.
2026-10-20 14:49:30 -05:00
Chris Tate a601cba268 Merge main 2026-10-20 14:27:36 -05:00
Chris Tate 7b510efacd Merge main 2026-10-20 14:13:21 -05:00
Chris Tate 87e51e6c71 Read non-ASCII scan receivers inline in worker executables and keep units for longer texts
Indexed reads of a non-ASCII string (charCodeAt, length) become loads
once the runtime holds its UTF-16 units, but emitted code read the
published scan receiver only where runtime state is ordinary global
storage, so worker executables called into the runtime for every read.
On ELF targets their thread-locals live in the executable's static TLS
block, so the receiver is now declared thread-local there and read with
one thread-pointer-relative load. The unit array was also limited to
receivers of 2^20 units; source texts of several megabytes, which
scanners read in full, now get one too.

Adds the unicode-lexer runtime benchmark workload: a lexer over a
2.7M-unit module whose comments and strings are in several languages.
2026-10-20 14:11:26 -05:00
Chris Tate 9758a56b7b Merge main 2026-10-20 14:10:57 -05:00
Chris Tate c90b321a5b Print an error's non-enumerable cause and an AggregateError's errors in its checked-dynamic rendering
An error rendered through its checked-dynamic node (a runtime error class,
or an error held as a subclass's `cause`) prints a non-enumerable `cause`
as `[cause]: value` and an AggregateError's `errors` as `[errors]: [...]`,
after its enumerable properties, as Node's formatError does. Before, a
subclass's cause that was an Error constructed with a `cause` option
printed without it: `{ cause: [Error: inner] }` where Node prints
`{ cause: [Error: inner] { [cause]: 'deep' } }`.
2026-10-20 13:59:33 -05:00
Chris Tate a0894cc488 Give constructor functions native stream state over the stream classes (#1533) 2026-10-20 13:21:25 -05:00
Chris Tate 5344367219 Read constructor-function stream hooks when they run
A constructor-function stream now reads `_final` and `_flush` when its
writable side finishes, and `_transform` for each chunk, as Node does. A hook
assigned after `Writable.call(this)` or `Transform.call(this)` runs, and an
override of a PassThrough's `_transform` replaces the pass-through. Without
`_final` or `_flush` the finish continues as Node's does.

A runtime stream passed to a constructor-function stream's listeners, such
as the source of a 'pipe' event, is the view whose pipe and unpipe accept
constructor-function streams, so `src.unpipe(this)` in a 'pipe' listener
unpipes instead of throwing.
2026-10-20 13:16:29 -05:00
Chris Tate 3751afa51f Merge main 2026-10-20 13:02:01 -05:00
Chris Tate e0d14fefaa Merge main 2026-10-20 12:55:55 -05:00
Chris Tate 97bf98cd89 Declare the WebAssembly W5 and stream-slot entry points in the runtime headers
The backend can emit scr_wasm_memory_construct, scr_wasm_table_construct,
scr_wasm_global_construct, scr_wasm_construct_without_new,
scr_wasm_module_imports, scr_wasm_module_exports,
scr_wasm_module_custom_sections and scr_wasm_value_of (#1410), and
scr_stream_dyn_native, scr_stream_pipe_dyn and scr_stream_unpipe_dyn
(#1524). Their prototypes were in scr_wasm.c or nowhere, so the runtime
ABI guard failed. They are now in scr_wasm.h and scr_stream.h, and
scr_wasm.c no longer repeats the header's declarations.

The speculative-hoisting test relied on `v instanceof Num` in a
JavaScript function failing, which constructor-function instanceof now
compiles (#1512). Its hoisted function reads the prototype through
Reflect.getPrototypeOf instead, which still fences.
2026-10-20 12:50:19 -05:00
Chris Tate 73a595692f Let NodeJS stream interface slots hold JavaScript stream objects (#1524) 2026-10-20 12:32:16 -05:00
Chris Tate 28f1646461 Unpipe JavaScript destinations on unpipe() without a destination
With checked-dynamic stream slots, readable.unpipe() also unpipes every
JavaScript destination stream.pipeDyn registered from the source, oldest
pipe first. Each destination hears 'unpipe' and the source pauses, as in
Node's unpipe(), which empties every pipe. Before, those pipes stayed
attached and later chunks still reached them.
2026-10-20 12:29:12 -05:00
Chris Tate 8ac2d854cf WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables (#1410) 2026-10-20 12:12:19 -05:00
Chris Tate ae3642d1bb Merge main 2026-10-20 12:08:37 -05:00
Chris Tate 76227c86c8 Merge main 2026-10-20 12:07:23 -05:00
Chris Tate cb8eb5972e Merge main 2026-10-20 12:04:40 -05:00
Chris Tate eea3bc5447 Specialize variadic generic wrappers to the wrapped function's signature (#1413) 2026-10-20 11:52:34 -05:00
Malte UblandClaude Opus 5.5 73e40f4196 Merge main
pipe() into an untyped destination now comes from main's pipeIntoDyn
(stream.unboxDyn); programs with constructor-function streams unbox
through scr_dyn_builtin_stream.c, which also takes those streams, so the
separate dyn.builtinStreamPipe entry point is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:49:43 -07:00
Malte UblandClaude Opus 5.5 507610b90d Give constructor functions native stream state over the stream classes
Bundled npm code (from2, bl, tar-stream, yazl, multistream) builds its
streams as ES5 constructor functions: util.inherits(Ctor, Readable) and
Readable.call(this, opts), with _read/_write/_transform on the
prototype. Readable.call(this) threw SC2020.

A new runtime unit, scr_dyn_builtin_stream.c (feature
dynBuiltinStream), gives the untyped stream constructors their stream
half. Base.call(this, opts) creates a runtime stream over the receiver
and keeps it in a hidden slot; the runtime calls the receiver's
underscore methods through its property lookup; the stream prototypes
carry Node's methods and accessors over the hidden stream; and
EventEmitter's methods register listeners with the runtime stream's
emitter, so runtime events and pipes reach JavaScript listeners with the
receiver as this. _readableState/_writableState are live views.

Stream class values now come from dyn.builtinStreamClass, so only
programs that use them link the stream half. Typed code sees these
objects as their runtime streams: checked casts to the stream classes
take them, readable.pipe() into an untyped destination pipes into them,
and runtime streams crossing into JavaScript answer the constructor
object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:46:05 -07:00
Chris Tate a43f0a71a6 Merge main 2026-10-20 11:36:33 -05:00
Chris Tate 8823556a84 Let constructor functions inherit from EventEmitter and the stream classes (#1512) 2026-10-20 11:32:22 -05:00
Chris Tate 99af5c878b Merge main and cover a block-local arguments alias that shadows an outer one
- Merge origin/main (e9fb01603); the import lines at the top of
  lowerer.ts conflicted and both are kept.
- Corpus 6385 gains a wrapper whose block-local `args` alias shadows an
  outer one in the same function; each call forwards its own arguments.
2026-10-20 11:30:11 -05:00
Chris Tate 447f56e5c3 Share defaultMaxListeners with the typed EventEmitter, link instanceof alone, and clean up a pipe once
- EventEmitter.defaultMaxListeners on the untyped constructor is Node's
  accessor over the default the typed EventEmitter reads: assignments
  validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and
  getMaxListeners() of emitters without their own limit answers the new
  value.
- dyn.builtinInstanceOf and dyn.functionInstanceOf link the
  constructor-function unit and the units it calls (dyn-invoke, inspect,
  symbol, emitter), so a program whose only use is `this instanceof F`
  links.
- The legacy pipe's cleanup runs once: when the destination emits
  'close' from end(), the cleanup still queued on the source's 'end'
  finds the listeners removed and emit('end') answers true.
2026-10-20 11:28:28 -05:00
Chris Tate e9fb01603f Compile NodeJS.ReadableStream and NodeJS.WritableStream as general streams when a program mixes stream producers (#1518) 2026-10-20 11:24:09 -05:00
Chris Tate 324f173f65 Merge main 2026-10-20 11:23:45 -05:00
Chris Tate 1818faef10 Keep a process stream open when JavaScript pipes into it
pipe() called from untyped JavaScript on a native stream went through
scr_stream_pipe directly, so a helper that piped into the converted
process.stdout or process.stderr ended it at the source's end, and later
writes through the program's NodeJS.WritableStream slots failed. The
stream view's pipe now calls scr_stream_pipe_general, which keeps end off
for those Writables, as Node's pipe() does for process.stdout and
process.stderr.

Adds corpus 6469.
2026-10-20 11:20:28 -05:00
Malte UblandClaude Opus 5.5 bc6a2fc767 Merge main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:06:59 -07:00
Chris Tate 25780d9231 Merge main 2026-10-20 11:03:09 -05:00
Chris Tate 8eb5ad28e5 Keep WebAssembly references and memory storage alive while they are in use; merge main
Externrefs a guest holds are borrowed pointers. An import's result was
kept only until the outermost call returned, which was before the export
call converted its results to JavaScript, so a result created by an
import was freed before conversion. A table or global slot released the
reference it replaced at once, although the guest may still hold it in a
local after reading the slot. The kept references are now released after
the outermost caller has converted its results (scr_wasm_leave), and a
table or global store during a call keeps the reference it replaces until
then.

Checked memories (explicit bounds checks: sanitized builds, Windows)
grew with realloc, which can move the storage that memory.buffer and its
views alias. They now reserve the address space for their largest size
(mmap on POSIX hosts, VirtualAlloc on Windows) and commit pages as they
grow, so the base never moves. Hosts without address-space reservation
grow in place only until a buffer has been handed out; after that,
growth that would move the storage fails.

Tests: webassembly-externref-lifetimes.mjs (import results through an
export, table and global references replaced by the guest and by
JavaScript), webassembly-memory-growth.test.ts (views written after
growth from JavaScript and from the guest), and translate.test.ts cases
for memargs naming memory 0 explicitly and for local.tee operand types,
which b3bcbaaf7 made consistent with validation. Under SCRIPTC_SAN=1 the
two runtime tests report heap-use-after-free without these changes.

Merges main; internal/compatibility/generated/node-v24-backlog.json is
regenerated with pnpm node-compat.
2026-10-20 10:58:34 -05:00
Malte UblandClaude Opus 5.5 c7b2620e5c Compile events.once with an AbortSignal option and over stream receivers
events.once(emitter, name, { signal }) lowers to events.onceSignal, served
by a new runtime unit (scr_events_once_signal.c, linked only through the
eventsOnceSignal feature). It follows Node 24's lib/events.js: an
undefined signal is the plain form, a value that is not an AbortSignal
rejects with ERR_INVALID_ARG_TYPE, an aborted signal rejects with an
AbortError whose cause is signal.reason, and otherwise a once,
resist-stop-propagation 'abort' listener removes the event and 'error'
listeners and rejects. The event or the error removes the abort listener.
Options other than an object literal with one signal stay refused.

events.once now accepts stream receivers: a stream's runtime-emitted
events use their fixed tuples, and its 'data' and 'keypress' events stay
refused. A NodeJS stream interface slot that holds a stdio handle requests
the general stream representation.

Node's once is an async function that returns the executor's promise, so
its result adopts that promise through a thenable job. The plain form
settled its result two turns early; both forms now settle on Node's turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:47:51 -07:00
Chris Tate 21c6505709 Match tuple rests and positional any[] parameters in function patterns, and merge main
- matchInferSignature reads a fixed-tuple rest as its positions, so
  (...args: [string, number]) needs two arguments, and a positional
  any[] pattern parameter matches only when the function's parameter
  there accepts an array, as TypeScript's assignability does
  (corpus 6398).
- Merge origin/main (200fe8d22); node-v24-backlog.json regenerated.
2026-10-20 10:37:43 -05:00