Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 1d6bd1ea04 Shrink the array header to 48 bytes
ScrArr stores len and cap as 32-bit fields and no longer stores the
state pointer. The dense states share the values' allocation and run
backward from its end: the state of slot i is at
data + cap * (slot width + 1) - 1 - i. Removing slots from the front
(shift, splice(0, n)) advances data and lowers cap, which leaves every
remaining state at its address; the head room size, needed only to free
or reallocate the block, is kept in the vacated slot before data. On
64-bit targets the header goes from 64 to 48 bytes, and from 36 to 32
bytes on wasm32. Array lengths were already limited to 2^32 - 1.

The emitted code loads len and cap as i32 and zero-extends them, and
computes state addresses from data and cap. Runtime loops compute the
end of the states once per call. Local array reads load data once for
both the state and value paths. A local or strict element read whose
index is a loop's induction variable reads slots above the hole-free
prefix through the runtime: an inline state check there makes LLVM keep
-1 - i as a second induction variable, which costs a register in the
loop.

Bulk operations do not read back states they just wrote: an append to
an array whose hole-free prefix reaches its length moves the prefix bound
to the new length, a copy from below the source's prefix writes value
states without reading the source's, and releasing a string array skips
the state checks below the prefix. Copies of up to eight slots and of
short state runs use overlapping word moves instead of a call. Emitted
element access computes the state address only on the state-checked
path. Without these, programs that build many small arrays with split or
slice ran 2-4% slower than with the separate state pointer.

Integer loops over an array's length compare the counter with the i32
length field in 32 bits; the counter never exceeds 2^32 - 1 there. The
array-length stability proof accepts Map and Set reads (get, has, size)
and checked union extractions (`x!`), which run no user code, so loops
that look up maps keep the dense fast copy, which tests the hole-free
prefix once before the loop.

Small constructions with the small allocator request 6 slots, so their
blocks stay 112 bytes, now with 7 inline slots (6 after a collector
header). With 96-byte blocks of 5 slots, programs that free and reuse
many small arrays ran 15-20% slower.

RUNTIME_ABI_VERSION goes from 22 to 23.
2026-10-20 00:21:45 -05:00
Chris Tate 12d0215c4d Link the child-process unit only in programs that spawn (#1403) 2026-10-20 00:18:21 -05:00
Chris Tate bae0570c2c Format checked-dynamic arguments under %d and %i as Node does
util.format's %d validated a checked-dynamic argument as a number and
threw "expected number" for anything else, where Node prints
Number(arg), and %i of such an argument had no lowering. Both now follow
Node: a bigint prints as `${n}n`, a symbol as NaN, and any other value
as Number(arg) under %d or parseInt(String(arg)) under %i.
2026-10-20 00:17:34 -05:00
Chris Tate 6d898dc0c7 Read T[keyof T] as unknown for T bound to an unknown-valued record (#1485) 2026-10-20 00:16:58 -05:00
Chris Tate b452a06425 Keep the class id of hierarchy instances in the rc word
On 64-bit targets, an instance of a class in a program-rooted hierarchy
has no vtable word. The rc word holds the class id (the class's preorder
number + 1) in its high 32 bits and the reference count in its low 32
bits. Retain and release update the count as an i32 at offset 0;
instanceof and the other preorder tests read the id at offset 4; virtual
calls and the last release through a base-typed reference index the
hierarchy's constant vtable array by the id. The runtime reads every
object's count through SCR_RC_COUNT.

Runtime-rooted hierarchies (Error, EventEmitter, EventTarget, Event and
the streams), standalone classes and 32-bit targets keep the vtable word.
2026-10-20 00:15:31 -05:00
Chris Tate 8df5e8d48a Accept readline's dispose, instanceof, dynamic streams and non-string prompts (#1401) 2026-10-20 00:13:26 -05:00
Chris Tate 89625ef91c Borrow typed-array locals read from fields and parameters (#1481) 2026-10-20 00:12:50 -05:00
Chris Tate ac0ae29b18 Construct CommonJS constructor functions imported through a member alias (#1453) 2026-10-20 00:10:40 -05:00
Chris Tate 3944a70017 Compile module exports that pass this on with apply or call (#1444) 2026-10-20 00:09:54 -05:00
Chris Tate 619c857fcd List an error's own name where it was created
Node lists an object's own properties in creation order. An Error's own
`name` is created by an assignment after construction defines `message`
and `cause`, or during construction by a `name` field or a constructor's
`this.name = ...`. Errors listed an own `name` before `message`, and
instances of Error subclasses listed it before every field, so
Object.keys, Object.getOwnPropertyNames and JSON.stringify answered a
different order than Node's: `{"name":"E","extra":1}` for `{"extra":1,
"name":"E"}`.

An error's own-property view now lists `name` after the properties its
construction defines. For an Error subclass, the class records which
field `name` follows: a `name` field takes its place among the fields,
parameter properties first, and `this.name = ...` in a constructor follows
the fields created before it, including the ones that constructor assigns
in JavaScript sources. When no class of the chain sets `name` during
construction, an own `name` follows every other own property.
2026-10-20 00:09:04 -05:00
Chris Tate 98a7d3b33d Merge main 2026-10-20 00:08:29 -05:00
Chris Tate 5cf51c46b0 Read x.constructor on EventEmitter and stream instances (#1442) 2026-10-20 00:06:32 -05:00
Chris Tate 226ba9672f Borrow typed-array locals read from fields and parameters
A local initialized from a typed-array field or parameter, such as
`const chars = this.chars`, took a reference and released it at the end
of its block, like any owned binding. Class instances, records, arrays
and strings already borrow such a projection when the rest of the block
writes none of the locations it reads, and walk and rebound parameters
use the same test. Typed arrays are one plain pointer as well, so they
now take the same paths: the field or the caller's binding keeps the
array alive while the local reads it, and a block that may replace the
field still retains its own reference.
2026-10-20 00:05:09 -05:00
Chris Tate 59dd6d1742 Read member aliases only while the binding holds the exported value
A member export resolves to its binding only when the binding is declared
before the write and never reassigned, so a consumer reads the value the
member stored. A module that re-exports another module's exports through
module.exports = require("./x") now counts its own writes after the
re-exported module's: a member it adds resolves, and a member both modules
write stays refused. The exports rebinding refusal and its test now cover
the form that stays refused, exports = value without module.exports.
2026-10-20 00:02:49 -05:00
Chris Tate 679227aeb1 Read an Error subclass's own code through NodeJS.ErrnoException (#1471) 2026-10-19 23:59:55 -05:00
Chris Tate 63cb11ff83 Keep DataView's own behavior through unknown (#1470) 2026-10-19 23:59:13 -05:00
Chris Tate b690f8f8f4 Count destructuring and for-of writes of a factory's parameter
A factory's parameter counts as the argument it was called with only
while the factory can't change it. The check saw assignments and updates
of the name, but not a write through a destructuring pattern
(`[body] = ...`, `({ body } = ...)`) or a for-in or for-of target, so a
factory could replace its callback with one that reads `this` and the
call still compiled without the module object.

The parameter now counts as changed by any assignment, update,
destructuring or for-in/for-of target that writes it, by a declaration
of the same name, by a write to an element of `arguments`, and by a call
of `eval`.
2026-10-19 23:56:22 -05:00
Chris Tate 6bf934d4cd Leave the error marker out of own property names (#1476) 2026-10-19 23:56:01 -05:00
Chris Tate d321ea5780 Leave the error marker out of own property names
Object.getOwnPropertyNames, Reflect.ownKeys and
Object.getOwnPropertyDescriptors on errors held as `any` or `unknown`
listed "%error", the key that marks the checked-dynamic error encoding.
Node lists only the error's properties. The marker is now left out of
own property names; the runtime's own checks still read it.
2026-10-19 23:52:46 -05:00
Chris Tate e134489edc Unroll the MD5 block function (#1465) 2026-10-19 23:51:35 -05:00
Chris Tate ee8d320fdb Widen an unjudged backlog threshold with its level's growth fraction (#1462) 2026-10-19 23:51:08 -05:00
Chris Tate 097b6b4c86 Cache zone offsets and UTC date fields, and return small integers' text without allocating
Local-time getters converted every new Date with a localtime call. The
runtime now keeps the zone's offset over an interval of instants: when
the offset and DST flag at an instant equal those 19 days later, every
instant between has them too (zones do not change offset twice within
19 days; V8's date cache relies on the same span), so consecutive Dates
convert without a localtime call each. The interval grows as nearby
instants confirm the same offset and restarts on a change of
process.env.TZ.

UTC getters keep the thread's last decomposition, like the local ones,
so reading several fields of one Date decomposes it once.

Converting an integer from 0 to 99 to a string returns an immortal
static string instead of formatting and allocating one.
2026-10-19 23:50:28 -05:00
Chris Tate e3f3dfd312 Copy repeated childless IR nodes one kind at a time (#1467) 2026-10-19 23:49:58 -05:00
Chris Tate b07a3bd5f2 Size encoded messages to their nodes and wake a channel's owner once per batch (#1468) 2026-10-19 23:48:19 -05:00
Chris Tate 21e5e2cd69 Answer in on typed arrays held as any (#1473) 2026-10-19 23:47:59 -05:00
Malte UblandClaude Opus 5.5 1a17bd3405 Merge branch 'opt/npm-polyfill-shims' into opt/npm-esm-class-hoist
Renumbers this branch's npm case 2572 to 2588, after the renumbered
polyfill cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:23 -07:00
Malte UblandClaude Opus 5.5 1b282f9c80 Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
Renumbers this branch's npm cases 2569-2571 to 2585-2587: #1411 now uses
2571.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:08 -07:00
Malte UblandClaude Opus 5.5 dde27c7e21 Merge branch 'opt/npm-lazy-loaders' into opt/npm-cjs-exports
Renumbers this branch's npm cases 2567-2568 to 2583-2584: #1422 now uses
2567.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:43:36 -07:00
Malte UblandClaude Opus 5.5 6fc85e848d Merge branch 'opt/npm-esm-wrappers' into opt/npm-lazy-loaders
Renumbers this branch's npm cases 2564-2566 to 2580-2582: #1411 now uses
2564-2566.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:42:59 -07:00
Malte Ubl a77c3edd47 Merge remote-tracking branch 'origin/opt/npm-bundler-interop' into opt/npm-esm-wrappers
# Conflicts:
#	packages/compiler/src/frontend/npm-static-bundled-chunks.ts
#	tests/harness/npm-cases.ts
#	tests/harness/npm-static.test.ts
2026-10-19 21:42:14 -07:00
Chris Tate 9fc483b5c6 Box asserted class bindings into class-backed views (#1469) 2026-10-19 23:42:07 -05:00
Chris Tate 9548ab3663 Give the undici npm cases a 600 s timeout
The undici cases compile the built-in undici implementation's module
graph; a sanitized build of it on a CI runner outlasted the default
120 s per case.
2026-10-19 23:38:15 -05:00
Chris Tate cdd78b4502 Answer in on typed arrays held as any
`in` on a typed array, Buffer or DataView held as `any` or `unknown`
answered false for every key: indices, `length`, prototype methods and
Object.prototype names.

On a typed array, a canonical numeric key (one that ToString(ToNumber(key))
reproduces, or "-0") now answers by bounds: an integral index, not -0,
below the length, so "1.5", "-0", "NaN" and "Infinity" answer false. Other
keys answer true for members of %TypedArray%.prototype, Buffer.prototype on
a Buffer, the constructors' BYTES_PER_ELEMENT and Object.prototype. A
DataView has no indexed elements and answers DataView.prototype's members
and Object.prototype's.
2026-10-19 23:36:14 -05:00
Chris Tate 135e36ba7a Merge main 2026-10-19 23:35:07 -05:00
Malte UblandClaude Opus 5.5 1a65f234c9 Narrow unions of records by intersection refinements, and keep members an assertion attaches
'k' in u answers per value on arms that declare k as an optional slot.
A predicate or 'in' narrowing that intersects record arms views the
value through the stored arms that can hold it. An assertion
'r as R & { m: T }' gives R's arms m as an optional member, keeps the
object, and a binding initialized by it keeps the stored type, so
writes through it reach the original. Late writes through a union
receiver count as writes through the binding it was read from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:32:43 -07:00
Chris Tate 2259da3cf7 Give interfaces extending class-backed protocols checked identity (#1461) 2026-10-19 23:29:44 -05:00
Chris Tate 0b98a8c713 Read an Error subclass's own code through NodeJS.ErrnoException
`(err as NodeJS.ErrnoException).code` read the runtime error's errno
slot, which only runtime errors set, so an Error subclass with its own
`code` field answered undefined where Node reads the own property. When
a class in the program under the receiver's class has its own `code`
field or a dynamic property bag, the read goes through the error's
dynamic view, which answers the own property and the errno code alike.
2026-10-19 23:28:36 -05:00
Chris Tate bc9ef21b90 Keep DataView's own behavior through unknown
A DataView reached as a checked-dynamic value behaved like a Uint8Array
over its bytes: it printed as `Uint8Array(2) [ 0, 0 ]`, its string and
tag were the typed array's, `.length` and index reads answered from the
bytes, and typed array methods ran on it. It now inspects as Node's
`DataView { [byteLength], [byteOffset], [buffer] }`, answers
"[object DataView]", has no length or indexed elements, and throws
"is not a function" for typed array methods.
2026-10-19 23:28:21 -05:00
Chris Tate c0048af88b Box asserted class bindings into class-backed views
`const node = stack.pop()!` keeps the value's undefined at run time, so
the binding holds `Base | undefined`. Asserting it to a class-backed
protocol view (`node as unknown as View`) reported SC1090, while the same
cast of a plain instance boxed the instance. A union of class instances
and undefined or null now boxes the same way: class arms give the live
instance, and undefined stays undefined until a property read throws
Node's TypeError.
2026-10-19 23:26:01 -05:00
Chris Tate 3d00b5b837 Size encoded messages to their nodes and wake a channel's owner once per batch
A message encoder started its node array at 16 nodes of 184 bytes and
kept the array, with its unused capacity, until the message was
received. A port that queued many messages held about 3 KB of zeroed
memory per message: 200,000 queued numbers took about 590 MB, and the
page faults made postMessage cost about 1.3 µs. The node array now
starts at 4 nodes and is trimmed to the message's node count when
encoding ends.

scr_channel_post posted a wake event, and with it a pipe write and a
condition broadcast, to the receiving thread's mailbox for every
message. The owner's loop counts a port with queued messages as ready,
so only a message that finds the queue empty needs to wake it.

bench/channel-messages, added here, posts 200,000 messages (numbers and
small objects) on a MessageChannel before the port delivers any.
2026-10-19 23:24:56 -05:00
Chris Tate b00dac886e Copy repeated childless IR nodes one kind at a time
unshareNodes copies a repeated childless node through reparseExpr and
reparseStmt, which stringify the node and parse the text and so copy its
type and location objects as well; on the three.js nodes fixture that
takes about 6 s. Spreading the whole IrExpr or IrStmt union instead does
not compile in the bootstrap seed build: the optional-read analysis lets
the callback of `fn.body.map(stmt)` receive undefined, and a union record
copy of a source that may be undefined is not supported (SC1090).

copyChildlessExpr and copyChildlessStmt switch over the node kind and
spread the narrowed node in each case, which is a plain record copy of
that kind. The cases are the kinds mapExprChildren and mapStmtChildren
return unchanged; any other kind keeps the encoding's copy. Each copy is
a new node object, which is what analyses key their facts by, and shares
the type and location objects, as copies of nodes with children already
do. The emitted IR is unchanged for every corpus program.

unshareNodes takes about 1.8 s on the three.js nodes fixture instead of
6.5 s, and compiling it to LLVM text takes 115 s instead of 123 s
(median of five interleaved runs).
2026-10-19 23:24:04 -05:00
Malte UblandClaude Opus 5.5 73bd0b53c3 Pass rest-parameter functions into slots with fixed parameters
A typed rest-parameter function passed where a function with fixed parameters is expected (`log(...m: string[])` into `(m: string) => void`) was refused. The function adapter now packs the slot's remaining arguments into the rest array, and the value check admits the function wherever that adapter fits. A nested function declaration with a typed rest parameter becomes a value with its own typed-rest signature, converted at each use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:23:54 -07:00
Malte UblandClaude Opus 5.5 d02c85a611 Use classes that extend EventEmitter or a stream class as values
Naming a class that extends EventEmitter, Readable, Writable, Duplex or Transform as a value was refused ("classes extending builtin bases as values"), and so was a class expression of that kind. The refusal also showed up as a deferred runtime fence at the class declaration, because the checked-dynamic tier reflects every class as a value. A user subclass always constructs through its own or synthesized constructor, whose super call runs the runtime initializer, so the class object's construct thunk serves it like any program class. EventTarget subclasses keep the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:23:53 -07:00
Chris Tate 95bdd7305c Unroll the MD5 block function
scr_md5_block ran its 64 steps as one loop that chose the round
function, message index and rotation per step, and the update path
called it once per block. scr_md5_blocks unrolls the steps with the
message word, constant and rotation inlined, processes every whole block
of an update in one call, and keeps each step's critical path to the
round function of b, an add, the rotate and the add of b: a + m + K is
summed first, G is written as (b & d) + (c & ~d) so that its c & ~d half
joins that sum, and an empty asm statement keeps the compiler from moving
the constant's add after the round function. MD5, HMAC-MD5 and md5-sha1
use it.

Corpus 5775 compares MD5, md5-sha1 and HMAC-MD5 digests over every
length from 0 to 520 bytes in five update sizes and over 1 MiB with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
2026-10-19 23:22:51 -05:00
Chris Tate 72e22ed0ed Run SHA-384 and SHA-512 blocks with AVX2, AVX-512VL or the arm64 SHA-512 instructions
SHA-384, SHA-512, SHA-512/224 and SHA-512/256 (createHash, createHmac,
crypto.hash, pbkdf2, hkdf) process their blocks with the fastest kernel
the CPU supports:

- x86-64 with AVX2 and BMI2: the message schedule is computed two words
  at a time in vector registers, interleaved with the scalar rounds,
  which rotate with RORX.
- x86-64 with AVX-512VL: the same, with vector rotates and three-input
  XOR in the schedule.
- arm64 with the SHA-512 extension: SHA512H, SHA512H2, SHA512SU0 and
  SHA512SU1.
- Otherwise the portable blocks, now unrolled over a 16-word schedule
  ring.

The CPU and OS checks run once and are cached. The update and final
paths pass all their whole blocks to the kernel in one call.

test_sha_kernels.c checks the FIPS 180-4 SHA-384 and SHA-512 vectors
through every kernel the CPU supports and compares each kernel with the
portable blocks over every length from 0 to 1100 bytes and 1 MiB, whole
and in 1-, 7-, 64- and 100-byte updates. Corpus 5779 compares SHA-384,
SHA-512 and HMAC-SHA512 digests over many lengths and update sizes with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
2026-10-19 23:22:23 -05:00
Chris Tate bbefa36394 Bound loop counters compared as i + c with a bound
The range analysis narrowed a counter only when the comparison read the
counter itself (`i < n`). A comparison of the counter plus or minus an
integer literal (`i + 1 < xs.length`, `1 + i < n`, `i - 1 >= 0`) now
narrows it as `i < n - c` does. Against a bound within 2^52, a sum that
rounds has a magnitude of at least 2^53, so every such comparison answers
as the exact sum would.

The counter of `for (let i = 0; i + 1 < xs.length; i++)` then lands in
an i32 slot like the one of `i < xs.length - 1`, and helper calls that
index arrays with it read the elements without the double index checks.
2026-10-19 23:19:46 -05:00
Chris Tate 7e0106722b Widen an unjudged backlog threshold with its level's growth fraction
The backlog threshold of a level backs off by doublings as backlog passes
find little to free, but only passes that had candidates count. A program
whose mature passes have no candidates while its heap grows keeps the base
threshold, a quarter of the live heap, while those same passes widen the
level's growth fraction. When it then reads the structure it built, the
mature buffer fills with live objects and the first backlog pass walks a
quarter of the live heap to find nothing. In tsc-ts's program build on
Sentry, that pass ran inside the loader walk and took 9 to 18 ms.

Until a pass at a level has had candidates, the backlog threshold now uses
the level's growth fraction doublings. The first pass with candidates
judges the level, and the threshold follows its own yield from then on.
2026-10-19 23:18:55 -05:00
Malte UblandClaude Opus 5.5 fe322ae901 Keep the JSON-shape diagnostics fixture refused now that null fields bake
A null-valued JSON field bakes as null, so the fixture's document now
compiles. It keeps its purpose with a value no literal spells (1e400 parses
to Infinity), and the mixed-table corpus program covers null elements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:16:25 -07:00
Chris Tate 783870dec3 Compile TLSSocket.isSessionReused, keyed short-circuit assignment and Object.assign into class instances (#1460) 2026-10-19 23:10:10 -05:00
Chris Tate 751d22ae6e Pass this on to module exports only where every function that gets it ignores it
The exemption for `f.apply(this, ...)` and `f.call(this, ...)` dropped
SC1090 for every module export that passes `this` on. When the function
it reaches reads `this`, Node gives it the module object, and the direct
module call has none, so the result differed from Node.

A `this` that is passed on now counts as unread only when the function
it goes to provably ignores its `this`: an arrow, a function that uses
`this` only to pass it on to such functions, or what a known function
returns, with its parameters bound to the call's arguments and never
assigned. The analysis follows at most six calls and functions, and
refuses otherwise. Effect 4's provideService still compiles: it passes
`this` to what dual(3, body) returns, which passes it on only to the
arrow it was given.

Passing `this` on to a function that reads it now refuses, directly,
through such a factory, or when the factory reassigns its parameter.
2026-10-19 23:09:08 -05:00