- A module namespace converted to an untyped object, and a computed
namespace read, use the module's resolved exports, which include the
members of `export *` declarations.
- `super(message, options)` through a runtime Error parent installs the
cause as Error does: an inherited `cause` counts, the value is read
through its getter, and the getter's exception propagates.
- `value instanceof Error` on an instance of a class whose heritage is
evaluated at run time tests that class's prototype chain for
Error.prototype. A program with such a class checks prototype chains
for `instanceof Error` on untyped values.
- The cache of names written on the global object is a Lowerer field
instead of a WeakMap keyed by the Lowerer.
- errors.test.ts: `"prototype" in Map` answers from the builtin
constructor's keys now, so it leaves the refused reads and joins a
check of `in` probes against Node's answers.
Math.min(...src) and Math.max(...src) over an untyped source now take its
values the way a spread does: arrays, strings, bytes and collections pack,
a native iterator (a Map's keys()) drains, and other iterables (a generator,
an object with a Symbol.iterator method) step their iterator to the end.
Before, only the packing kinds were accepted and every other iterable threw
the spread TypeError.
- An enumerable getter or setter on an array printed as undefined: the
property loop rendered the accessor's unused data slot. It now prints
[Getter], [Setter] or [Getter/Setter], as util.inspect does.
- Object.defineProperty on a live native array with a number key that is
not an array index (-3, 1.5) skipped the numeric-property fence and
lost the property on commit. A number key now takes the same fence as
its string spelling.
A local number[] candidate whose stores are exact integers but not all
proven int32s (a distance array updated with dist[v] = dist[u]! + 1), and
that is only ever allocated by new Array(n).fill(v), now holds SCR_ELEM_I32
slots when n is at least 4096. A shorter fill creates doubles, which fit a
typical L1 data cache. The first store of a value that is not an int32
widens the array to doubles in place (scr_arr_widen_i32).
An innermost loop that accesses such arrays tests their kinds once on
entry (backend/llvm/int32-kind-loops.ts). Its fast copy reads and writes
them as int32 arrays; a store that would widen one leaves for a copy that
re-executes it with kind tests and finishes the loop on doubles. When the
arrays already hold doubles, a copy accesses them as double arrays. Other
accesses test the kind each time.
Only these arrays take kind tests: every other array, and every program
without one, emits the same code as before.
An element index the checker types as a number can hold undefined at run
time (an element read from a holey array). JavaScript then names the
array's "undefined" property, but the index converted with ToNumber, so
such reads and writes used the "NaN" property: Node prints `10 0,7,0,7`
for a program that sets `values[NaN] = 10` and then writes through two
holes, the compiled program printed `7 0,7,0,7`.
The runtime's noncanonical property table, which holds the properties
named by non-index numbers, now spells one NaN payload
(scr_arr_undefined_key) "undefined". At array element reads, writes,
compound and update writes, destructuring targets and writes in value
position, an index that can be undefined is that key when it is
undefined. The property stays a typed entry of the array, so the Int32[]
contract checks cover it like any element store.
A typed array cannot hold the property: a write through an undefined
index throws the catchable SC1090 error where Node adds it.
Fixes#1640.
flush() on a zlib stream writes a zero-length chunk that _transform
recognizes by identity and compresses with that flush kind. Since
writes of a typed array that is not a Buffer reach _write as a Buffer
over the same bytes (as Node's Stream._uint8ArrayToBuffer does), the
sentinel, created as a plain Uint8Array, reached _transform as a new
Buffer. _transform then used the default NO_FLUSH, so flush() emitted
nothing: corpus 5374-zlib-stream-flush-params decoded an empty prefix
where Node decodes "hello ".
The sentinels are Buffers, as Node's kFlushBuffers are (Buffer.alloc(0)),
so write() passes them through unchanged.
The npm fixture stream-shims, whose test expected a deferred SC1090, now
runs to a later deferred refusal, SC2020 (write completion callbacks
taking the error argument); its expectation names that code.
zod 4 saves and restores the limit through an alias,
`const E = "captureStackTrace" in Error ? Error : null`. The interned
Error constructor value now answers `stackTraceLimit` from the same limit
the static member and stack capture use, and numeric writes set it, so
`E.stackTraceLimit` reads 10 instead of undefined and `E.stackTraceLimit = 0`
reaches `Error.stackTraceLimit`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local number[] that only compiled element operations reach, and whose
every stored element the int32 slot analysis proves an int32 (never -0),
is now stored as SCR_ELEM_I32: 4-byte int32 slots, with state bytes once
it holds a hole or undefined, as for every array. Element reads give
consumers an exact int32, so indexing with an element skips the double
round trip, and the slots take half the memory.
The runtime's dense slot accessors convert between the int32 slot and the
double, so its double-valued entry points serve int32 arrays unchanged;
growth, copies, fill and length changes use the 4-byte width.
ir/int32-arrays.ts proposes the candidates and inlines their element-reader
helper calls; the int32 slot analysis keeps the proven ones, and
selectInt32Arrays rewrites the module for exactly those, so a program
without an int32 array emits the same LLVM IR as before.
Every evaluation of `globalThis` as a value built an array literal of
every name the global type declares (hundreds of strings) and registered
each one in the known-name table again. A server that reads a singleton
through `globalThis` per request spent about a tenth of its time there.
Emit the name table as one immortal static array per program (the
tagged-template site array, keyed by its contents), and return the
global object at once when the runtime sees a table it has already
registered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The poller test recorded every initial deadline as the same start time
plus the delay, while the poller reads the clock at each arm call; when
arming the 2,000 timers took over 1 ms, the recorded order could differ
from the delivered one by more than the assertion's slack. Each deadline
now counts from the time read just before its scrp_timer_arm call, as
the re-arm loop's do, and a 3 ms pause halfway through arming keeps the
test exercising that case.
zod 4 builds every schema through `$constructor`, a factory of JavaScript
patterns the dynamic tier refused or answered wrongly. Each is fixed
generally:
- `"key" in BuiltinCtor` (`"captureStackTrace" in Error`) folds to Node's
answer from a table of the builtin constructor globals' keys.
- A class whose heritage evaluates to Object or Error at runtime
(`class D extends (params?.Parent ?? Object)`): Object means no
heritage; Error links Error.prototype and super() installs message,
cause and stack.
- `Object.defineProperty` on a class value boxes to its constructor, and
a JavaScript class targeted by defineProperty gets a live static table.
- A Symbol.hasInstance defined on a function's own table answers
untyped `instanceof`; an error constructor held as a value answers by
error kind.
- A builtin error constructor stored in a slot of its class value type
(`{ Parent: Error }`) is the class value, not a checked cast that fails.
- A function hoisted above a local class it references captures the
class binding as a TDZ box the class statement initializes.
- Names a program stores on the global object read back from it instead
of folding to undefined.
- A module namespace whose exports never change converts to an untyped
object (`{ ...ns }`).
- An untyped call of a String-only method name (`x.trim()`) dispatches on
the receiver's runtime kind, so objects defining that member work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the last reference to an array dropped, the runtime ran the generic
teardown: scr_rc_destroy's depth bookkeeping, an indirect call to the array
destroy, and a loop over the element slots. Arrays of numbers, booleans and
compact ProgramLifetime references release nothing, and neither do arrays of
other ProgramLifetime instances, whose release does nothing.
- scr_arr_release frees an acyclic array without an ext directly when its
elements are numbers, booleans or compact references, or its descriptor
has SCR_ARR_OPS_RELEASE_NOTHING. Every other array keeps the full
teardown.
- The backend sets SCR_ARR_OPS_RELEASE_NOTHING in the descriptor's flags
word on 64-bit targets for ProgramLifetime element types that are not
compact, plain or as the pointer arm of a nullable union, in programs
without lifetime regions. Registration copies it into a table indexed by
the descriptor's registry id before the id is published. Descriptors
without the flag read 0 and keep the full teardown.
Corpus 7401 drops arrays of ProgramLifetime instances, numbers and booleans
through every array operation, with holes and far indices, next to arrays of
ordinary objects, and matches Node.
JSON.stringify of a value reached through `unknown` walks native records
and arrays through their live capsules. The protocol walk built a
key-string array for every record and formatted every array index with
snprintf into a fresh string, only to look the member up again by that
key. A JSON API response of a few records spent most of its
serialization time there.
Without a replacer, walk a live record by field index over a snapshot of
its present fields (its keys are the immortal field names) and a live
array by element index, forming an index string only for a toJSON call
that receives it. Members are still read as scr_json_member reads them,
so callbacks that mutate or materialize the container see the same
results. Index keys elsewhere use the integer formatter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The epoll poller gave every armed timer its own timerfd. A keep-alive
HTTP server re-arms and cancels the socket idle timer on every request,
which cost six system calls per request (timerfd_create,
timerfd_settime twice, epoll_ctl twice, close) on top of the read and
the send; Node makes two.
Armed keys now live in a deadline min-heap with a pointer hash, and one
timerfd wakes the loop at the earliest deadline. Arming a later
deadline, re-arming and cancelling make no system call; the timerfd is
reprogrammed only when an earlier deadline arrives or when it fires.
The fd table is indexed by fd number instead of scanned. Delivery is
unchanged: each expired timer reports once (one-shot), now in deadline
order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
request.end(body) on a request whose head was not sent yet wrote the
head and then the body. On a socket that was already connected (a
reused keep-alive connection), each write is sent at once, so the two
went out as separate segments, and the server could handle the head and
answer before the body arrived. Node sends the head together with the
body. When the client and the server run in one program, the server's
'end' and 'close' for the request and the client's reading of the
response then depended on which socket the poller reported first: corpus
5409-http-held-request-body printed the client's line before the
request's 'end' and 'close' in a macOS dev build.
The head and a body known at end() now go out as one write, as Node
sends them (the server's response already did).