three.js's Color constructor ends with `return this.set(r, g, b)`, and
every reachable `set` (Color's, and Color4's override returning
`super.set(...)`) returns `this`. The return analysis classified it as
returning another instance and refused `class Color4 extends Color`.
A return that is `this`, or a `this.m(...)`/`super.m(...)` call whose
every reachable implementation returns only `this`, a primitive or
nothing, now keeps the ordinary lowering, so such classes need no
result slot and subclasses construct through super() as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage S4 of the two-tier design: dynamic-tier code reads, writes and
calls properties through per-site inline caches keyed on hidden-class
shapes.
- Runtime: scr_js_object.c (dynTier) attaches shapes to the existing
checked objects: a shape is a node of the thread's transition tree
over key sequences, computed on an object's first cache miss, and
objects past 64 keys stay unshaped (dictionary mode). Attributes and
the prototype are tested live on every hit. The dyn-tier build of
scr_json.c (SCR_JS_TIER) resets a shape on every key-set change;
typed programs keep linking the ordinary build.
- Sites: ir/js-ic.ts recognizes the lowering's property helpers by
structure; calls to them from dynamic-tier functions, and method
dispatch outside module initializers, become sites with a zeroed
thread-local ScrJsIc. backend/llvm/js-ic.ts tests entry 0 inline
(own property or one prototype link), probes out of line, and keeps
the helper as the noinline cold slow path. Array length and dense
number-key element reads are inline.
- Numbers: jsval reads of number properties are immediates, and a jsval
number stored over an unshared number cell updates it in place; the
jsval pass feeds cache stores jsvals and stops boxing discarded
compound-assignment results. `%` and bitwise operators get integer
paths.
Static functions are untouched: typed programs compile to byte-identical
LLVM and executables, and every static function of programs with
dynamic code is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture builds and serializes an IR module by hand. Since IR version
17 serializes function tiers instead of deriving them on load, the loaded
module's main, which performs dynamic operations, validated as a static
function. Assign tiers as lowering does before serializing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch lowers toPrecision statically, but STATIC_NUMBER_METHODS still
lacked it, so the regenerated manifest kept stdlib.number.toPrecision as
unsupported/SC2012 and a determinism fence on it had no reach detector.
List it with its num.toPrecision witness and 0 to 1 arguments, like
toFixed, and regenerate the manifest; the compatibility outputs are
unchanged.
library-profile's remediation test used that id as its SC2012 fence;
it now names the SC2012 code family (diagnostic.sc2012), so the explicit
remediations key still competes with fence-supplied SC2012 text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
toPrecision lowers statically on this branch, so the two K14 tests that
needed a surface whose own refusal fires compiled instead. Use
zlib.brotliCompressSync: it has its own manifest id, the fallback types
declare it, and it refuses with SC2020 because the static runtime vendors
no Brotli codec. The id-keyed teaching and the fence teaching ride that
refusal as the note, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build now translates every WebAssembly module whose bytes it can see
(W2's discovery) into LLVM IR with a TypeScript translator
(packages/compiler/src/wasm/module.ts, translate.ts) and appends it to the
program module, so the existing LLVM helper optimizes, partitions and
caches it with the program. The program also emits scr_wasm_registry, a
portable table of module descriptors (scr_wasm_abi.h). At run time,
WebAssembly.compile, instantiate, validate, new Module and new Instance
find the translated module by the SHA-256 of its bytes; bytes the build did
not see reject with a CompileError. There is no interpreter and no JIT.
The runtime (scr_wasm.c, link-gated by the new `wasm` feature, handle tags
inside W0's headroom) implements the JavaScript API once, over
descriptors, with Node's semantics and messages: exports (functions,
memories, tables, globals), imports (functions, immutable globals, and
memories, tables and globals other instances export), every trap as
WebAssembly.RuntimeError, the CompileError, LinkError and RuntimeError
classes, compile and instantiate settling on a later turn, and
globalThis.WebAssembly as a value. On 64-bit Linux and macOS, memories sit
in 8 GiB guard reservations and a SIGSEGV/SIGBUS handler turns faults into
traps or stack exhaustion; async fibers get a guard page on first use.
Sanitizer builds and Windows check bounds and the stack explicitly. A
WebAssembly.Module crosses to workers through W0's transfer hook (W4).
W1's refusals become lowerings; the Memory, Table and Global constructors,
Module.imports/exports/customSections and the streaming functions still
refuse with SC2020. The globals: WebAssembly compatibility row moves to
partial.
Evidence: differential corpus programs for the API, TypeScript use and the
codemode worker shape, plus five programs executing about 9,300 commands
of the WebAssembly 2.0 spec suite; all match Node in the plain and
sanitized lanes. quickjs-wasi runs through its own glue, matching Node
(apart from a pre-existing non-wasm fence) and on par with or faster than
V8. The zero-cost gate leaves every non-wasm program byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frontend's node lists become node lists, { pos, end, nodes }, the
shape of tsc-ts's NodeList, so that the frontend can later read tsc-ts's
own nodes. AstNode's list getters return an AstNodeList, ts.NodeArray<T>
is that class, and the frontend reads a list's elements through nodes.
The lists already carried their positions on the wire.
The class is the type, not a structural interface, because native code
represents a class and a record differently.
A decoded file keeps each list's nodes array, as before, and each read
of a list field returns a new AstNodeList over it, so the file holds no
more objects than before. nodes keeps its identity; a list object is not
the same object on each read.
The change is mechanical: a type-aware rewrite of each list field read,
plus hand edits where lists are read through casts, generic accessors
or test mocks. The generated getters and types follow from
generate-ts7-ast-schema.mjs. AstFile and Ts7Session drop their unused
listMetadata hook.
node:test changes, matching Node 24.22:
- getTestContext() returns the context of the test, suite or hook whose
code is running: undefined at module top level, the root's context in
module-level hooks, the suite's in a suite body and in the suite's
hooks, and the test's in its body. In the afterEach and t.after hooks
of a subtest cancelled by its parent's timeout, it returns each hook's
owner.
- The context rides the AsyncLocalStorage snapshot under a reserved id,
so it follows a test across its awaits, into the async functions,
promise callbacks, timers and other callbacks it schedules, and into
concurrent subtests, which each see their own.
- The root's fullName is "<root>", as its name is.
- Optional chains over a test context, such as getTestContext()?.name,
lower.
AsyncLocalStorage enter and restore move into the always-linked async
core so the test runner can use them. The snapshot holds the node's
index in a table of live nodes, so a snapshot that outlives its node
answers undefined.
The get-test-context and get-test-context-cancelled fixtures compare
the contexts seen at each point with Node, plain and with SCRIPTC_SAN=1.
In a fused helper chain, take(n) sets the chain's done flag as it
delivers its nth value, so nothing is pulled after it. A flatMap later in
the chain tested the same flag in its inner walk and stopped after the
first flattened value: gen().take(2).flatMap((x) => [x, x + 10]) gave
[0, 10] where Node gives [0, 10, 1, 11].
A flatMap stage after a take now holds the flag while it walks its inner
iterable, and sets it again afterwards. A terminal that short-circuits or
a take later in the chain still stops the inner walk.
A compiled generator that reached an untyped value, as in
`function f(it) { return it.map(g) } f(gen())`, refused its iterator
helper calls with SC1071 at run time: the runtime's helpers could not
step a generator's capsule. Array destructuring of untyped values only
handled arrays, strings and bytes, so `const [a, b] = it` threw "is not
iterable" for generators, iterators, Maps and Sets.
Programs that box a synchronous generator and call a helper method or
destructure an untyped value now register, first thing in %main, the
checked getters that read a generator capsule's next and return methods
(scr_dyn_set_generator_members). With them:
- helper calls on a generator capsule run the runtime's helpers, which
step and close the generator through its own next() and return();
- reads of a helper name answer the runtime's helper function, so
`typeof it.map` is "function" and `it.reduce.call(it, f, init)` works;
- destructuring of any other iterable steps it through the iterator
protocol (scr_dyn_iter_take, and rest elements through
scr_dyn_iter_pack), closing an iterator that has values left.
A return() through an untyped value with no argument, or with undefined,
now completes with undefined as the typed .return() does, instead of
converting undefined to the generator's return type. This also fixes
for-of loops over untyped generators with a typed return value that exit
early.
Object.defineProperty with an object-literal descriptor that declares get
or set for a declared class field is refused at compile time with SC2020,
naming the field. JavaScript sources report it as a deferred fence, so
coverage lists the call. Other accessor descriptors still throw SC2020
when the call runs.
The native descriptor test now compiles a data descriptor change on a
declared field and compares the output with Node, and checks that an
accessor descriptor stays fenced.
`function make(C: typeof EventEmitter) { return new C(); }`,
`const kinds: ErrorConstructor[] = [TypeError, RangeError]` and
`(typeof Readable)[]` stopped at SC2020 (the type has no mapping) or
SC1090 (builtin classes as values). These classes are now class values:
the name in a TypeScript file (or a const alias of it) is the class
object, `typeof EventEmitter`, `typeof Readable` and ErrorConstructor-
style types map to class values, and parameters, arrays, Map values and
object fields hold them.
Construction through a value calls %C.construct, which builds the
instance with the runtime constructor the direct `new` uses (emitter.new,
a stream's newDyn without options, error.newOptions for Error and its
six standard subclasses). instanceof, typeof, .name and === answer as in
Node. A Duplex value widens into a `typeof Readable` slot, and stream
values widen into `typeof EventEmitter` slots, as the constructor ABIs
match; a union of such class values (`const kinds = [Readable,
Writable]`) is a value of their nearest common ancestor, and `new K()`
through it answers the checker's union of instances. `instanceof`
through a value that may be Writable keeps the Duplex rule at run time.
EventEmitter and stream values constructed with options, runtime class
values stored as untyped values or printed, statics read through a value,
and DOMException, AggregateError, AssertionError and SuppressedError
values report SC1090. The existing static lowerings
(`EventEmitter.setMaxListeners`, `Error.stackTraceLimit`, the events
module) are unchanged. JavaScript files keep the identity token.
Object.freeze, Object.seal and Object.preventExtensions accept a class
instance whose static type names the class, and Object.isFrozen,
Object.isSealed and Object.isExtensible answer for it as Node does.
Object.defineProperty with a string-literal key changes the value and
the writable, enumerable and configurable attributes of a declared
field, with Node's validation and errors.
Only hierarchies passed to these calls pay for them: they get a
per-instance integrity word and an attribute word per redefined field,
and each write to a declared field checks them first. A write to a
read-only field throws Node's TypeError in strict mode code and leaves
the field unchanged in sloppy mode code; a class field initializer on a
non-extensible instance throws "Cannot define property x, object is not
extensible". The instance's checked view carries the attributes, so
Object.keys, JSON.stringify, util.inspect and getOwnPropertyDescriptor
follow them. Readonly<C> over a class maps to the class.
Through an untyped value, freezing, sealing or preventing extensions of
a class instance throws SC2020 instead of doing nothing. The runtime's
"Cannot redefine property" and "Cannot add property" errors name the
property, as Node does.
console.log and util.inspect of a class instance list the properties
added to it after construction, after its declared fields. JavaScript
classes print their own properties in creation order: a field first
assigned in a method or under a condition appears only once it exists.
Such instances render through their checked-dynamic view when the class
has a property bag or is a JavaScript class; other classes keep the
generated inspect helper.
The runtime inspector detects cycles through the class instances it
reaches through views, so a cycle prints [Circular *N] instead of
expanding to the depth limit. A circular target's <ref *N> prefix counts
toward the 80-column line width, as in Node.
Capsules of a recursive class type carry the class's view operations,
so a nested instance prints with its class name.
A test name computed at run time took the function's name when it was
empty, which needs that name at compile time. For a function the
program received as a parameter, such as a helper that registers
`test(\`case ${label}\`, body)` for its callers, the call was refused,
where it compiled before. The name is now used as given in that case,
and the limitations page notes that an empty one stays empty, where
Node uses the function's name.
The test-name-forms fixture registers tests through such a helper and
compares them with Node, plain and with SCRIPTC_SAN=1. Without this
change it fails to compile.
Node 24's http.Agent installs a read guard on each socket it puts in
freeSockets: bytes that arrive while the socket is idle destroy it, so
they are never read as the response to the next request that reuses the
socket. scriptc discarded the bytes and kept the socket in the pool, so
the next request reused it (reusedSocket true, one connection) where
Node dials a new one.
The client connection's data hook now destroys the socket when it is in
an agent's free list. The socket leaves freeSockets and
totalSocketCount, and the next request through the agent opens a new
connection, as in Node.
Corpus 6200 covers an idle socket that receives a stray response.
console.log and util.inspect of a generator object, an iterator helper,
or an array or string iterator were refused, and printing a Map or Set
iterator threw "util.inspect of a dynamic Array Iterator is not
supported yet".
Compiled generators now carry the tag their object prints with:
"Generator" or "AsyncGenerator" by default, "Iterator Helper" for the
helpers, "Array Iterator" and "String Iterator" for the iterators the
compiler builds as generators. The spawn wrapper sets it through the new
scr_gen_set_tag; an adapter over another generator copies that
generator's tag (scr_gen_copy_tag), and generators that stand in for an
object of another class, such as a Set iterator adapted into an
iterator-typed slot, keep refusing to print. They print as Node's
Object [<tag>] {} at any depth, and Object.prototype.toString answers
[object <tag>].
The runtime's native iterators print as Node does: Object [Array
Iterator] {} and Object [String Iterator] {}, and for Map and Set
iterators the entries the next steps would produce, without advancing,
under [Set Iterator], [Set Entries], [Map Iterator] or [Map Entries],
as [Object [Set Iterator]] past the depth limit and { } when none are
left. Object.prototype.toString answers [object Set Iterator] and the
other tags.
node:test changes, matching Node 24.22:
- test, it, describe, suite, their skip/todo/only twins and t.test take
Node's (name?, options?, fn?) forms: test(fn), test(options, fn),
test(options) and test() compile, as does describe(fn) and
describe(options, fn). The fallback declarations add the overloads.
- A missing or empty name is the function's name, or "<anonymous>", as
Node's Test constructor completes it. The name is the function
expression's or declaration's own name, or the binding name of a
const arrow or anonymous function; a name that is empty at run time
takes the same default.
- With both skip and todo, the test is skipped, and the directive message
is the skip string, else the todo string. The skip and todo method
twins override the option of the same name, so test.skip("x",
{ skip: "msg" }) reports a bare SKIP and it.skip("x", { todo: "msg" })
reports "msg". Before, the last option set the mode.
The limitations page notes that a thrown object or array that is not an
Error reports [object Object] in the failing section.
The test-name-forms fixture compares the names, directives and counts
with Node, plain and with SCRIPTC_SAN=1.
A helper object over a Map or Set iterator that is stored and consumed
later (const it = set.values().map(f); for (const x of it) ...) stepped
the runtime's checked iterator through an adapter generator, boxing each
value and resuming two generators per element: 5.1x Node's time on a
100k-element Set.
A stage on m.keys(), m.values(), m.entries() (or a Set's), or on
Iterator.from(set) or Iterator.from(map), now walks the collection inside
the helper's own generator. The walk steps the runtime's native iterator
by entry index through the new scr_dyn_collection_iterator_index and
reads each key or value with the typed intrinsics. The native iterator
keeps the walk live and ends the collection's iteration when it is
exhausted or released, so an abandoned or returned helper leaves the
collection free to compact. Other helper sources over a Map or Set, and
Iterator.from over a Set or Map, use a typed generator over the
collection built the same way.
Speculative lowering lowers some function bodies several times: the
receiver of `(function () { ... }).call(this)` goes through four call
lowerings that each probe it with tryLowerExpression, and each probe lowered
and counted the body. A five-statement program with such a wrapper reported
14 statements, and a fenced statement inside it counted as four failures.
The Lowerer now records which statements and class field initializers are
in the totals and which are counted as failed, and counts each node once.
Corpus 6110's @deferred-fences directive stays 1, its one fenced statement
in a class base lowered once, and its header no longer ties the count to
how many times that body lowers.
AsyncLocalStorage changes, matching Node 24.22:
- setTimeout, setInterval, setImmediate, process.nextTick and
queueMicrotask callbacks run with the stores that were active when they
were scheduled. Before, getStore() returned undefined in them.
- An interval keeps that context on every tick, and a refreshed timer
keeps it too.
- enterWith inside one of these callbacks lasts until the callback
returns. An enterWith in top-level code ends when the main module's body
returns, and one inside a callback scheduled with no store active ends
when that callback returns, so the store does not reach callbacks that
were scheduled without it.
Programs that enter stores (store.run, exit, enterWith, or a
diagnostics_channel store binding) schedule through new *_als runtime
entry points, which wrap a callback scheduled with a store active in a
closure that installs the stores around the call. The emitter picks them
from the new asyncContext runtime feature; other programs keep the plain
entry points, so their scheduling is unchanged.
The limitations page notes that completion callbacks of callback-style
I/O APIs still run with no store.
5815-als-callback-context compares the stores each kind of callback sees
with Node, plain and with SCRIPTC_SAN=1.
String iterators (s[Symbol.iterator]()) and typed array values(),
keys() and entries() compile as iterator objects: generators over the
code points and over the indices. The iterator helpers apply to them,
and for...of, spreads and Array.from consume them.
Iterator.from over an array, string, typed array, Set, Map or iterator
object answers the value's own iterator, as Node does for iterators
that already inherit from Iterator.prototype. Objects with their own
next method keep the Iterator fence. As the root of a helper chain,
Iterator.from(x) walks x itself: an array by index, a Set or Map live,
a generator directly.
h.return!() and h.return?.() compile when h is a helper call or a const
initialized with one: helper objects always have return(), so both
spellings are the call. Other iterator objects keep the fence, since the
array, string, Map and Set iterators have no return().
flatMap callbacks may return Sets (their values) and Maps (their
entries), walked live; a string result throws Node's TypeError and
closes the source.
Every change that adds a corpus program also added its CI costs to
tests/harness/corpus-costs.json. The table is sorted by program name and
programs are numbered in sequence, so concurrent changes inserted lines at
the same place and conflicted. A change now writes
tests/harness/corpus-costs/<program>.json ({ "plain": ms, "san": ms }).
The shard loader applies these files over the table, and
scripts/ci-corpus-costs.mjs folds them into the table and deletes them when
it refreshes the costs from CI logs. With no per-program files the table and
the shard assignment are unchanged.
The error identity cache (scr_dyn_from_error) keeps one checked-dynamic
node per error so a crossing error compares reference-equal and keeps the
properties set on it. It retained both sides for the whole process, so
every error that crossed a checked-dynamic boundary (a caught error read
through a cast, an error delivered to an untyped callback, a dns failure, a
warning) stayed alive until exit: 100,000 caught AssertionErrors read
through a cast grew the resident set by about 210 MiB.
The cache now sweeps itself. An entry whose error and node are referenced
only by the cache is unreachable (a node holds no reference to its error;
a registered subclass box holds one, and a spawnSync error's `error`
property refers to its own node), so a sweep releases both sides. Sweeps
run when the cache doubles past the entries the last sweep kept, so they
cost amortized constant time per crossing. The cache is per thread, and
objects shared across threads are published (immortal), so a sweep never
races another thread's reference. Live errors and live nodes keep their
entries: identity, rethrowing the same instance, isNativeError and kind
checks are unchanged.
scr_error_set_prop sets an own property on an error's node; dns, assert,
cipher, crypto, util.callbackify and warning errors use it for the
properties they attach. util.inspect of a runtime error renders through
its node: Node's own properties follow the bracket form in key order (a
system error's errno, code, syscall and path, and the extra properties a
throw site attached), and an AssertionError renders at depth 0 with long
string actual/expected values shortened, as its custom inspect does. A
node that contains itself renders as Node's <ref *1>/[Circular *1].
child_process errors carry Node's own properties:
- execSync and execFileSync: status, signal, output, pid, stdout and
stderr (Buffers, or strings with an encoding option); a spawn failure
or timeout adds errno, syscall, path, spawnargs and error first.
- execFile's callback and the promisified execFile/exec: code (the exit
status, or null after a signal), killed, signal and cmd, with stdout and
stderr when promisified; a spawn failure has errno, code, syscall, path,
spawnargs and cmd.
The sync forms echo the captured stderr before a timeout throw too, as
Node's inheritStderr does. The cp.execSync libCall takes a flag for Buffer
outputs.
An error's own `stack` stays non-enumerable when assigned through its
node. A worker's record-source table is freed when the worker exits.
scr_dyn_invoke.c links into libraries that use dynamic calls, and its stored
timer functions call scr_set_timeout_handle, scr_set_interval,
scr_clear_interval, scr_set_immediate, scr_clear_immediate, scr_next_tick
and scr_queue_microtask_dyn, which live in scr_async.c. Libraries never link
scr_async.c (a library that schedules timers is refused at SC4005), so their
host links failed with undefined symbols. Under SCR_LIB the calls that
schedule are left out, and a call through a stored timer function throws the
SC4005 event loop error. Executables compile the same code as before.
- 6236-builtin-function-values.cjs counts the interval's runs apart from the
order of the other callbacks and reports once the interval has stopped, so
a slow machine prints the same line as Node.
- node24-util-types: Function now reads as a value, so the fixture's first
refusal is the SC1090 for extending Promise instead of the SC2009 for the
FunctionConstructor value.