Overrides whose parameters widen and whose results convert into the
inherited ones keep the inherited slot, which converts around the
override's own body; Error values take code writes, `in` tests and
Object.assign; and a few gaps behind generic wrappers close:
- Method overrides: each inherited argument converts into the
override's parameter (equal, wider, or a checked extraction from an
`any` slot) and the override's result converts into the inherited one
(narrower, a convertible union, a widening Promise payload, anything
convertible over an `any` base). The slot %C.m keeps the inherited
ABI and forwards to %C.m$own; TypeScript callers typed with the
subclass call $own directly when nothing overrides below. Async
overrides of sync and async bases, and JavaScript overrides of
builtin or TypeScript bases (zod's ZodError.toString) are included;
a toString slot converts its result with ToString.
- `err.code = value` on Error values in TypeScript: strings set the
runtime code slot (and the error's checked-dynamic node, if any); other
values clear it and keep the dynamic own property.
- `'key' in err` with any receiver: declared members answer true;
otherwise a class test over the declaring subclasses, a runtime lookup
of the error's own properties, or an Object.prototype member.
- `Object.assign(err, ...sources)` onto an Error target copies onto the
error, re-reads name, message, stack, cause and code into the typed
slots, and returns the error.
- `Error & { ... }` intersections whose extra members have checkable
types are Error values; those members read and write the error's own
properties, each read checked at its declared type.
- Catch bindings typed `any` read as checked-dynamic values (member
reads and writes, assertions); a multi-statement type guard with an
`unknown` parameter takes the caught value as an ordinary argument;
Error members of a binding narrowed to an Error-extending interface
read through the same value.
- Lambda parameters typed `any` by their default are checked-dynamic
slots; forwarded-only rest tuples with optional positions forward
their slots; `value as never` from an untyped value into a typed
parameter takes the checked extraction to the parameter's type.
- Lexical `this` stops at generic instance frames, so an instance no
longer sees the receiver of the caller that triggered it. The fence
for `this` captured through a plain function now points at the
`this` keyword.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading Object, Date, RegExp, Map, Set, Promise, WeakMap and WeakSet as
interned function values made every use of those globals a dynamic value,
including the receivers of member reads. The interned function models only
name, length and constructor, so `Map.prototype`, `Date.prototype.getYear`,
`Object.isFrozen` as a value, `Map["prototype"]` and `"prototype" in Map`
stopped reaching their SC2020/SC1090 fences and read undefined through the
dynamic paths instead (769 Test262 variants went from a refusal to a wrong
result).
A builtin global read for its members now keeps its member lowerings and
fences; only name, length and constructor read through the value. A member
read through an alias typed as the constructor's static side (`const D =
Date; D.prototype`) reports SC2020 before the generic field read. Passing
the value to Object or Reflect statics that reflect on or redefine its
members (`Object.hasOwn(Object, ...)`, `Object.defineProperty(Promise,
...)`, `hasOwnProperty.call(Map, ...)`) is refused; identity and the empty
enumerable key lists still compile. The interned functions answer Node's
lengths (Date 7, RegExp 2, Promise 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record type whose declared members are all optional and typed as its
index signature's slot (ProcessEnv's TZ?, '{ CI?: string } &
Dictionary<string>') interns as the pure index-signature record, its
slot admitting undefined: spread, delete and enumeration then follow one
insertion order. 'value as never' erases, so the consuming slot checks
an unknown value like any other. 'Object.fromEntries(...) as T' for a
declared record T goes through the checked-dynamic bridge, which keeps
the entries' keys and order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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.
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.
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.
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.
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.
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`.
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.
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.
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.
`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.
'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>
`(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.
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.
`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.
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.
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).
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>
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>
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.
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.
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.