A `for` loop's initializer and a `for...of` loop's iterable run once, but
SC6101 counted any dynamic operation inside a loop statement's span, so
untyped setup there produced the warning and failed --fail-on=dynamic.
Loop work is now found by walking the IR: a loop's condition, update and
body repeat, its initializer and iterable do not. Locations could not
tell them apart, because lowering gives the index loop of a `for...of`
the whole statement's span.
A build now names the functions of the program's own files that compile
to the dynamic tier and likely run often, so a slow program with a little
untyped code says where the time goes:
warning SC6101: 3 functions that likely run often use untyped values, which runs slower than typed code:
advance (nbody.js:22): untyped JavaScript file, in a loop
...
- The functions come from the tier assignment lowering already makes
(Lowerer.dynamicFunctions). Without a profile, a function counts as
likely hot when a dynamic operation runs inside one of its loops, when
a loop calls it, or when it calls itself (lowering/dynamic-tier.ts).
- A function is named only when its source says why its values are
untyped: an `any` (for a for...of variable, the value it iterates), an
untyped JSON.parse result, or an untyped JavaScript file. Generated
%dyn.* helpers, module top levels and functions that are dynamic for an
operation without a static lowering are left out.
- At most five functions are named, most likely hot first, with the
total; dynamic-tier code in npm packages is counted per package on one
line. All of the warning's text is in diagnostics/dynamic-tier.ts.
- SC6101 opens the SC61xx band, performance notes, with a new
"performance" category in the diagnostics envelope; SC60xx stay the
divergences.
- scriptc build prints it after the divergence warnings, and the
executable cache stores it with the entry, so a cached build (including
the CLI's routed fast path) repeats it. scriptc coverage reports it, and
a comment-only edit no longer replays a verdict that carries it, since
its text holds line numbers.
- --no-dynamic-tier-warning leaves it out of build and coverage;
coverage --fail-on=dynamic fails on it.
- Docs: coverage.mdx (Dynamic tier, Gating builds and CI, the envelope
categories) and cli.mdx.
How It Works gains a Build parallelism section: the frontend runs on one thread, executables divide into
partitions by the size of their LLVM text, the LLVM helper compiles partitions on a thread per hardware thread,
what dev and optimized builds run in parallel and in order, what a rebuild compiles again, and what
profile-guided optimization adds to build time. The CLI reference links to it from --emit, --optimization,
--profile-use, SCRIPTC_CACHE_DIR, and Profile-guided optimization.
Fixes#1340
Co-authored-by: Quino Terrasa <26749475+espetro@users.noreply.github.com>
A thread's event loop delivered one message from its workers or its
parent per loop turn, so timers and setImmediate callbacks ran between
messages that were already queued. Node's MessagePort delivers, in one
callback, the messages queued when it starts and those that arrive
meanwhile until it has taken 1000, with a microtask checkpoint after
each, before the loop goes on to the check phase.
- scr_worker_dispatch starts a batch when it delivers a message: the
messages queued then, and at least 1000 in all.
- scr_workers_more continues the loop pass while the next event in the
thread's mailbox is a message and the batch has room; other events
(online, exit, error, stdio) keep their order and their turns.
- scr_mailbox_head_kind and scr_mailbox_count_kind read the mailbox's
first event kind and the number of queued events of a kind.
- Corpus 6211 checks the order of messages, nextTick, microtasks and
setImmediate for worker-to-parent and parent-to-worker messages
against Node.
Revert 3a27a92cb ("Forward at most four emit arguments through
EventEmitter method values") and 0cf62ebec ("Answer EventEmitter methods
read as values off compiled emitters"). Under the scope-B plan,
EventEmitter methods read as values, `EventEmitter.prototype.on` in
JavaScript and constructor-function emitters belong to Node islands, not
the native compiler. The typed/untyped listener sharing and the
catchable throw in place of the trap stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tls.connect and tls.createServer accept ALPNProtocols as an array of
strings, a Buffer or a Uint8Array in the wire format. The server answers
with the first of its own protocols that the client offers, and both
sockets report it as alpnProtocol. A client that offers none of the
server's protocols fails with ERR_SSL_TLSV1_ALERT_NO_APPLICATION_PROTOCOL.
minVersion and maxVersion bound the handshake, and getProtocol() reports
the version it picked. Ranges that do not overlap fail with Node's
ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION or ERR_SSL_NO_PROTOCOLS_AVAILABLE,
and invalid versions throw Node's TypeError. tls.connect's timeout option
arms the socket's inactivity timeout before it connects.
Refs #1438
Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
A WebAssembly module the build finds but does not translate (one that uses
a proposal outside the translated feature set, an invalid one, or one the
translator rejects) now rejects with a CompileError that says the module
was found and names the reason, for example "it uses simd, outside the
translated WebAssembly feature set". Bytes the build did not see keep the
existing message.
The build emits the hash, length and reason of each such module in a
second table, scr_wasm_untranslated, next to scr_wasm_registry; the runtime
reads it when a lookup misses. WebAssembly.validate still answers false for
these modules, and the error stays a catchable CompileError.
Fixes#1617
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
A plain function's own properties (name, length, prototype, and the ones a
program assigns, as in `hash.outputLen = 32`) live in its property table.
Object.freeze, Object.seal and Object.preventExtensions of a function now
act on that table and answer the function, and Object.isFrozen,
Object.isSealed and Object.isExtensible answer from it, as in Node:
- Object.freeze of a function was refused as a possibly-aliased value,
and Object.isFrozen of one threw SC2020;
- Object.isExtensible answered false and Object.isSealed true for every
function.
Adding a property to a frozen or non-extensible function throws Node's
TypeError; assigning a read-only one throws the runtime's existing
read-only TypeError. Class constructors keep their fences. This lets
@noble/hashes' createHasher return its frozen hasher, so the sha256
program in the issue prints the digest Node prints.
Fixes#1437
Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
readFileSync, writeFileSync, appendFileSync, statSync, lstatSync,
readdirSync, mkdirSync, rmSync, rmdirSync, mkdtempSync, accessSync,
unlinkSync, chmodSync, chownSync, lchownSync, truncateSync, copyFileSync,
renameSync, realpathSync, realpathSync.native, openSync and opendirSync
accept a file: URL wherever they accept a path. The URL converts as
fileURLToPath converts it, with Node's errors for other schemes, hosts,
encoded separators and null bytes.
Arguments after a URL path that have side effects are evaluated into
hidden locals before the conversion, so they run even when it throws. A
URL path after a string path, or with options that are not literals,
fails with SC2020, because Node checks those arguments before it converts
the URL. existsSync and the fs.promises functions also fail with SC2020
for URL paths: existsSync answers false for a URL it cannot convert, and
fs.promises reports conversion errors as rejections.
Fixes#1616
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex, reduce and reduceRight compile on every typed-array kind
through one shared helper per method shape. The receiver, the callback and
reduce's initial value evaluate in order before the helper runs; the
helper reads the length once and each element when it visits it, so the
callback's writes show in later elements. Callbacks receive the element,
its index and the typed array (reduce's receive the accumulator first).
map and filter return a new typed array of the receiver's kind, map
storing each result with that kind's element conversion, and a Buffer's
map and filter return a Buffer. reduce without an initial value throws
Node's TypeError on an empty typed array. A thisArg keeps the fence, as
for arrays.
Fixes#1620
Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
The ledger and the public artifact held a block of status counts for
every chapter. Nothing reads them (the docs page uses a chapter's slug
and title), and every classification change rewrote its chapter's block,
so concurrent compatibility changes conflicted there. The rows they were
counted from are unchanged.
The dispatch instantiates one bridged emit per argument count and emitter
class; capping it at four keeps programs that only read emitter members
through computed keys from growing by the full table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading an EventEmitter.prototype method off a compiled emitter (a program
class extending EventEmitter, or new EventEmitter()) through an untyped
value answered undefined (`typeof o.on`, `const f = o.on; f.call(o, ...)`,
`o.emit.bind(o)`), and EventEmitter.prototype.on in JavaScript was a fence.
Such reads, typed value reads, computed keys, `in`, and
EventEmitter.prototype.<method> now answer the runtime's
EventEmitter.prototype functions, the ones constructor-function emitters
inherit, so `o.on === o.addListener === EventEmitter.prototype.on` and
names and lengths match Node. The first read installs a program dispatch
that those functions call for a compiled emitter receiver; it invokes the
method through the existing untyped dispatch with the call's argument
count (emit forwards up to 16 arguments after the name, then fences).
An untyped emit on, or read of emit from, a class that overrides emit now
fences instead of bypassing the override for the native emit. A subclass
visited under the boxed EventEmitter capsule keeps its own dispatch plan
for values boxed as itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crypto.createECDH returns a native ECDH handle backed by mbedTLS for
prime192v1, secp224r1, prime256v1, secp384r1, secp521r1, secp192k1,
secp224k1, secp256k1, brainpoolP256r1, brainpoolP384r1 and
brainpoolP512r1. generateKeys, computeSecret, getPublicKey,
getPrivateKey, setPrivateKey, setPublicKey and ECDH.convertKey follow
Node 24: compressed, uncompressed and hybrid points, input and output
encodings, the private key range, key pair validation, and Node's error
codes and messages.
Curve names follow OpenSSL's short-name lookup: names of other curves
from crypto.getCurves() are refused at compile time when literal and
throw a coded SC2020 error when computed, other OpenSSL short names
throw ERR_CRYPTO_OPERATION_FAILED, and unknown names throw
ERR_CRYPTO_INVALID_CURVE. Calls whose arguments no @types/node overload
accepts, and computeSecret(view, encoding), are refused at compile time.
The runtime unit scr_ecdh.c links, with the mbedTLS archive, only into
programs that use ECDH. wasm32-wasi builds refuse it.
The Limitations page partials keep one sentence per line so edits to
different sentences merge, but AGENTS.md's general Markdown rule asks
for paragraphs on one line, and changes that followed it conflicted with
the split text. AGENTS.md now names the partials as the exception, and
limitations-partials.mjs gains a check, run by pnpm lint, that fails on a
partial line holding two sentences, and a fix mode that splits such lines.
The one partial that held several sentences per line is split; Markdown
joins the lines, so the page renders the same.
hot-shots, a StatsD client compiled from its JavaScript, failed when a
program constructed a client. Each gap on its UDP path is closed
generically:
- Sloppy-mode functions with named parameters may read `arguments`
where the aliasing of their parameters cannot show: only
`arguments.length`, or element reads without parameter or element
writes.
- An `any` slot admits any boxable function value, including functions
with optional parameters (`C.prototype.m = function (...)` through an
untyped receiver).
- In a JavaScript file TypeScript does not check, a prototype method
takes its documented parameters as checked-dynamic values, as
constructors already did.
- JavaScript dgram sends run completion callbacks, buffer lists,
connected sends and the six-argument offset/length form.
- A dgram socket crosses into the checked-dynamic tree as a handle
(SCR_DYNH_DGRAM_SOCKET) whose methods are function values, with
untyped listeners that survive 'close'. The handle lives in its own
runtime unit (scr_dgram_dyn.c, the dgramDyn feature), linked only
when a program converts a socket to or from an untyped value.
- `String.prototype.concat` on strings, and `parseInt` over a possibly
missing or untyped value in JavaScript.
Corpus programs 6590-6596 and the hot-shots-udp npm
case (hot-shots 8.5.0 vendored) match Node in both lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1360 moved the content hash and row count into next.config.mjs and
deleted node-v24-compatibility-meta.json, but a later merge kept a
branch's copy. Nothing reads it, and its hash line conflicts on every
compatibility change. The local compatibility check now fails while the
file exists, so a merge can't bring it back unnoticed.
`JSON.parse(text) as T`, `const x: T = anyValue` and `await res.json()
as T` threw `TypeError: expected X at $.path, got Y` when the value did
not fit `T`. Node never checks an assertion and continues with the value
it has, so the build-container listener rejected an order Node accepts
and a JWT with a string `exp` became a 500.
A failed cast now keeps its value when it initializes a `const`/`let`
binding declared in a function or block and only the code after the cast
reads the binding. That code (the statements after the cast in each
enclosing statement list, up to the declaration) lowers twice: the
static copy, unchanged, runs while the value fits; a dynamic copy, in
which the binding and everything derived from it is typed `any`, runs
after a failure, so missing members read undefined, wrong-typed members
keep their values, extra members stay visible in source order, and
objects keep their identity. The cast sets the flag that selects the
copy: the fused JSON.parse helper's slow path and the plain check call
scr_dyn_check_recover, which takes back the TypeError and parks the
value for checkedCast.fallbackTaken/fallbackTake.
A failed value that flows into a typed slot outside the region is
checked there, as any `any` value is: it continues when it fits and
throws at that use when it does not. A statement of the dynamic copy
with no `any` lowering becomes a runtime fence that throws only when a
failed value reaches it. Module-level bindings, regions with hoisted
declarations or super() calls, union targets and targets holding class instances, and
--tier=static-only (and library mode) keep the throwing cast.
`a?.b.c` on an `any` value now short-circuits only on `a`, so `.c`
throws when `a.b` is undefined, as in Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An emitter used both through its static type and through an untyped
reference aborted: a typed `on` followed by an untyped `emit` (or a typed
`emit` from a class body reaching a listener added through `any`) hit
"computed event collided with a fixed event", and an untyped
`emit('error', ...)` trapped outright. Listener entries held either a
typed adapter or a checked-dynamic thunk, and the emit core trapped when
an entry lacked the form the emit carried.
Entries may now carry both forms. Listeners registered through
scr_emitter_on_dyn keep their thunk beside the typed adapter; typed
emits call the adapter and untyped emits call the thunk with the
arguments as given. The class dispatch of compiled emitters registers
untyped listeners under an event typed code fixes with a typed adapter
for its tuple, and converts the arguments of an untyped emit of such an
event to the tuple for listeners with typed parameters
(scr_emitter_emit_mixed; a mismatch throws the checked conversion's
TypeError). Registrations and emits through untyped values otherwise go
through scr_emitter_on_any/emit_any: runtime adapters under 'error' and
the meta events, and Node's unhandled-'error' contract. The emit core
throws instead of trapping when an entry has no usable form. The
dynamic half of the registry sits behind a hook only on_any installs,
and on_flex/emit_flex keep their plain behavior, so programs without
untyped emitter calls link none of it.
listenerCount, eventNames, removeAllListeners, listeners, rawListeners,
setMaxListeners and getMaxListeners through an untyped value on a
compiled emitter answered "is not a function"; they now dispatch to the
native registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In JavaScript, `ReleaseEmitter = EventEmitter` over `const EventEmitter =
require("events")` was refused with SC1090 ("a runtime-provided class value
as an untyped value"), and `class Channel extends require("events")` failed
on the require call as a value. Bundled npm code does both, for example a
small semaphore that aliases EventEmitter and constructs its release channel
through the alias.
- The events module binding and the EventEmitter or stream class itself
turn untyped as the runtime's untyped constructor that constructor
functions already use (scr_dyn_builtin_class.c). `new` through it builds
an emitter object with Node's listener API, and `===` against the class
compares identity.
- `x instanceof EventEmitter` on an untyped value also recognizes a boxed
compiled emitter (an instance of a program class extending EventEmitter),
which the prototype test alone answered false for. The same holds through
an untyped binding narrowed to the class.
- `class X extends require("events")` extends the runtime emitter, as
through a require binding.
- A typed array of the class built from such values (`[EventEmitter]`) is
refused with SC1090 rather than failing its class check at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>