A codec stored in untyped storage (hono's StreamingApi keeps
`this.encoder = new TextEncoder()` in a JavaScript class field) had no
dynamic representation and threw SC1101 at run time. The codec records
now cross 'unknown' slots as handles of two new tags that hold the record
itself, so identity and a decoder's streaming state survive the round
trip, and checked casts unbox the same record.
The handles answer encode, encodeInto and decode (with options objects),
the encoding, fatal and ignoreBOM properties, method values with Node's
ERR_INVALID_THIS, instanceof, String() tags and JSON's {}. The unit
scr_text_codec_dyn.c links only into programs that convert a codec to or
from an untyped value (the textCodecDyn runtime feature, found with the
dgram carrier walk, now shared).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A JavaScript binding referenced by its own initializer's inference
collapses to `any` in the checker while it still lowers to a Uint8Array:
hono's etag digest declares `const nextChunk = new Uint8Array(new
ArrayBuffer(f(chunk)))` beside `chunk = nextChunk`. The typed-array
lowerings key on the checker type and declined such a receiver, and the
dynamic paths required a dyn value, so `nextChunk.set(...)` and
`nextChunk.byteLength` threw SC1090/SC2020 at run time and multi-chunk
etag digests failed.
Method calls and property reads on such a receiver now box it by
reference (the box shares the native storage, so writes stay visible) and
dispatch on the runtime kind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A literal that starts with `...value` creates value's keys first, in
value's own order. When checked conversions build value's shape (records
JavaScript hands back through replaceable methods, field setters,
generator calls or callbacks), the key-order witness could not order the
literal statically and kept the declared table, so `{ ...draft, id }`
and `{ ...current, ...patch }` enumerated `id` first natively.
Every value of a record shape already enumerates either in its table or
in the order its record source holds: a checked conversion keeps the
source's order there whenever it differs from the table. Such a literal
(an ordered spread) now carries that order over: after it builds the
record, record.sourceSpread gives the record a source listing the keys
it declares in value's order, and the keys value lacks follow in the
table's order, which every literal sequence of the shape agrees with.
The literal then resolves as if value were in its table's order, and its
own shape counts as built at run time, since its values may carry a
source too. It qualifies only when every value of the source has all of
the source's keys, both shapes can keep a source, every later spread of
a shape whose values may carry a source only overwrites the source's
keys, and the source shape resolves (or is the literal's own shape).
Literals of shapes that do not resolve are left as they were.
Programs without such literals compile to identical IR.
Corpus 7750-key-order-javascript-spread-sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An element assignment whose value is an optional element read or call,
such as `out[n++] = src[n]!`, held the value in a temporary and then
evaluated the receiver and index inside the store, after the value. So
`n++` ran after `src[n]` was read, and `src[0]` was stored where Node
stores `src[1]`. The receiver and index are now evaluated first whenever
the value could change what they read: when it assigns a binding they
read, or runs code that can assign their `let` and `var` bindings or
fields. A literal, or a binding the value cannot change, is still read
at the store.
Packages such as pi-tui look for their prebuilt Node-API addons at run
time: a createRequire require call with a computed path, and the
loader's resolve passed as a value, each inside a try block whose catch
takes a fallback. Preflight refused these packages.
Preflight now classifies each use of a createRequire loader. Inside the
try block of a try statement in the same function, a call with a
computed specifier or another argument shape, and resolve or
resolve.paths read as a value, no longer refuse the package. Outside a
try block they keep it refused, with a reason that names the computed
specifier. The loader itself as a value, writes and optional calls stay
refused.
In JavaScript source, such a call evaluates its arguments and then
throws a runtime fence, and the resolve value is a function that throws
one when called, so `(options.resolve ?? require.resolve)(id)` still
calls the option. Both are listed in coverage as deferred to runtime.
TypeScript source keeps the compile-time refusals.
Refs #1777
scr_dyn_object_key_set, behind every untyped property write and every
member of an untyped object literal, looked the key up as many as five
times: a linear scan for an own accessor or read-only entry, a lookup
before the prototype walk, one before the non-extensible check, one to
know whether the key was created, and one more inside the store.
The own property is now looked up once. An own data property is written
in place (an own setter still runs; a read-only or getter-only property
still refuses), and an absent key walks the prototype chain as before
and is then added by scr_dyn_obj_set, which is unchanged.
zod 3, z.array(Order).parse over 500 orders: 199.8M -> 194.1M
instructions per round (-2.8%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tee learned a source error only through a pending read's error
steps, so a source that errored while both branches held buffered
chunks and no read was pending left the branches readable: the next read
returned a chunk where Node rejects. The tee now watches its source's
error, as the streams spec reacts to the source reader's closed promise,
and errors both branches in a microtask (resolving the cancel promise
unless both branches were canceled). A read's error steps only end the
read, and a clone of an already errored body errors both branches the
same way.
JSON.stringify walked a dense native array through its borrowed storage
and wrote record fields borrowed from the record. When an element's own
serialization can run user code (it reaches an array that may hold
foreign elements, whose getters and toJSON run, or a class instance or
checked-dynamic value), that code could grow the array, reallocating the
storage the walk still read, or replace the field it was writing.
In programs that store foreign elements, such arrays now take the
general walk, which reads each index afresh and holds its element, and
such record fields are held across their write. Programs without
foreign elements compile as before.
The differential and LLVM differential suites can run the same corpus
program's Node oracle at the same time in one shard. Programs whose
scratch directory or file was derived only from argv[1] collided, and
one oracle removed the paths under the other. Their names now include
the pid, as 1403 and 5567 already do. Printed output never carries the
name.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
'new TransformStream()' had no lowering, so hono's stream() and
streamSSE() helpers failed to compile their checked-dynamic paths. The
constructor without arguments now builds a native handle: its readable
side is a native ReadableStream with a zero high-water mark, and its
writable side runs the streams spec's WritableStream state machine
(getWriter, abort, close, locked; the writer's write, close, abort,
releaseLock, ready, closed and desiredSize). A write completes when the
readable side reads its chunk, close/abort/cancel propagate across the
pair, and deferred reactions take as many microtask turns as in Node.
Web ReadableStreams can also be weak collection keys (hono keys a
WeakMap by the response stream). A transformer, queuing strategies and
the WritableStream constructor keep the SC2020 refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response.clone() and Request.clone() on checked-dynamic handles answered
'res.clone is not a function' (hono's etag middleware hashes
res.clone().body). The runtime now implements the streams spec's
ReadableStreamDefaultTee over native streams and clones the body with it,
so the original and the clone each read every chunk. A used or locked body
throws undici's TypeErrors, the clone's headers are a copy with the same
guard, and a cloned Request's signal follows the original's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Headers and Response constructors combine duplicate entries (a: x,
a: "") into one value with the separator, which can end in its space
("x, "). They marked the pairs canonical anyway, so a copy took them
unchanged where Node trims the copied value ("x,"). The constructors
now mark the pairs canonical only when every value is already trimmed;
otherwise copies normalize them pair by pair.
The differential and LLVM differential suites can run the same corpus
program at the same time in one shard. Their Node oracles had the same
argv[1] and so the same scratch directory, and one removed it under the
other. The directory name now includes the pid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every untyped property read or write in a program with dynamic-tier
classes calls a generated helper (%dyn.class.property.N). For a value that
is not a class instance, the helper still called the normalization, the
instance-bag and the static-bag tables, each of which returns the value
itself, then compared the bag with the value and finally read the key.
When every class branch of a helper tests a capsule key or a class key,
its fallthrough is still the access it was created with, and every table
that fallthrough calls has its kind exit, a value that is neither a class
capsule nor a function now gets the same access on itself at the top of
the helper. Helpers for `constructor` and `toString`, which answer
primitives and errors specially, keep their full body.
zod 3, z.array(Order).parse over 500 orders: 235.8M -> 199.8M
instructions per round (-15.3%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A keyed read of a string-indexed record whose values are numbers or
booleans (`counts[key]` on a `Record<string, number>`, typed
`number | undefined`) allocated a heap union for every read, also when
the only consumer was `??`, `!` or a comparison with undefined. The
runtime already returns the entry's value and whether it is present;
the box came from wrapping that result.
Such a read is now a stack union source: under a projection, the read
writes the tag and bits of the declared field or index-signature entry
the key names, or the undefined arm for a missing key, into a private
stack box, and allocates nothing. This applies when every hit is a
number or boolean arm of the result union, or an inline field of the
same union, and also to an argument a callee only projects (the helper
that `x!` lowers to). Other consumers keep the heap union.
dictionary-walks allocates no union instead of 363,359 and runs 14.6%
faster; no other runtime benchmark's code changes.
Corpus 7390.
Appending an empty value to an existing header leaves the separator's
space at the end of the combined value ("x, "). Node keeps that in the
Headers object but trims it when the value is copied into another
Headers, Request or Response ("x,"). The copy fast path copied a
canonical object's pairs as they were, so the copy kept the space. Such
an append now marks the pairs as no longer canonical, and copies
normalize them pair by pair.
In JavaScript sources, Math, JSON or Reflect read as a value lowered to
an identity token, the string "[builtin Math]": typeof, String, `in`,
descriptors and member calls through an alias all had string semantics.
Each of these namespaces read as a value is now one object per program,
built on first use, with Node 24's own properties in Node's order:
function members are writable, configurable and non-enumerable with
Node's `length`, the Math constants are read-only, and
Symbol.toStringTag names the namespace. Members hold the same function
values the spelled member read gives; a member without one throws its
fence when called. Global reads, globalThis, destructuring and
shorthand properties all produce the same object, and an object with a
string Symbol.toStringTag coerces to "[object <tag>]".
Direct member calls keep their static lowerings, so replacing or
deleting a function member would not reach them: such changes throw
SC1090. New properties and attribute-only redefinitions behave as in
Node.