getCipherInfo(nameOrNid[, options]) runs in a new runtime unit,
scr_cipher_info.c, over a table that scripts/gen-cipher-info.mjs generates
from Node 24.22's getCipherInfo. The table covers every cipher OpenSSL
names, including the ones without a native implementation. Lookups match
names case-insensitively and accept OpenSSL's nids. The unit applies
cipher.js's argument checks and GetCipherInfo's keyLength and ivLength
rules, and returns the info object in Node's key order. CipherInfo maps
to a checked-dynamic value, so TypeScript and JavaScript code receive
Node's object. Reading the nid Node leaves out for the SIV and SM4 GCM,
CCM and XTS ciphers, where the declared type gives number, throws a
TypeError.
createCipheriv and createDecipheriv support aes-128/192/256-ccm (and the
id-aesN-ccm names) over OpenSSL's ccm128 steps. They follow Node's tag and
IV lengths, the plaintextLength requirement, the message-length caps and
the one-update rule, and report a failed authentication from final().
They also support the RFC 3394 and RFC 5649 key wrap ciphers (aes-N-wrap,
aesN-wrap, id-aesN-wrap, the -pad forms and the -inv ciphers) over
wrap128.c's routines, with Node's lengths, integrity failures and
one-update rule.
The new names follow the existing ones in the cipher table, so the lookup
cost of the existing ciphers is unchanged.
An array or typed-array index that is an exact integer or NaN, such as an
element read that may miss (`t[i]!`), a `charCodeAt`, or a sum of those, is
a valid index when it is ordered and below the length. The index check now
tests that with one ordered compare and the integer, taken from the value's
32-bit view or from one conversion, instead of converting the index back to a
double and comparing it.
Number locals that are declared once with an initializer, are not captured
or boxed, and are only written such values carry the same fact, so
`const c = t[i]!; table[c]` and `(h * 31 + c) | 0` take the integer paths
that the inline read already takes.
Stream events and callbacks ran with no store. Node's streams run each
step in the context of the code that triggers it: a step deferred with
process.nextTick (resume, emitReadable, endReadable, afterWrite, the
destroy and finish steps) in the context that scheduled it, and other
events in the context of the push(), write(), end(), destroy() or pipe()
call that emits them. Native-backed streams run their events in the
context of their handle.
The runtime now captures the active context where it defers a stream
step and enters it around the step's dispatch:
- the stream tick queue keeps each tick's scheduling context;
- the runtime's own process.nextTick jobs (zlib completions, pipeline and
finished steps, readline) run in the context they were queued in, as
user ticks already did;
- fs read and write stream requests keep the context of the call that
started them, so a stream's open, reads, writes and close follow its
creation;
- zlib streams keep their creation context and run each engine step's
completion in it.
In programs that use AsyncLocalStorage, stream.finished and
stream.pipeline callbacks are bound to the context of their call, like
the other completion callbacks. Programs that do not use
AsyncLocalStorage compile to the same IR.
child_process and http2 events are unchanged.
Corpus 7383 and 7384.
ToInt32 and ToUint32 of a double that no range proves an integer took the
i64 conversion only up to 2^53 in magnitude, and decoded the double's bits
above that. Every double between 2^53 and 2^63 is an integer, so the i64
conversion is exact there too, and its low 32 bits are the result. The bit
decoding now handles only NaN, the infinities and magnitudes from 2^63.
Multiplicative hashes such as `((h ^ b) * 16777619) >>> 0` and
`(h * 1000003 + b) | 0`, whose products usually pass 2^53, convert on the
fast path.
`const kinds = [Dog, Cat, Animal]; new kinds[0]()` now works as in Node:
- TypeScript: the checker types the element as the common base
(`typeof Animal`), and an element read keeps the `undefined` an index
past the end reads. The construction now narrows the class value out of
that union, and an index past the end throws Node's TypeError
("kinds[5] is not a constructor"). Before, it refused with SC2001.
- JavaScript: such a literal is a checked-dynamic array stored in a slot
of the base's class values, and the check accepted only the base's own
class value, so storing it threw "expected class constructor".
A class value of a subclass now checks into its base class's slot, as a
subclass instance already checks into a base-class instance slot.
Classes without subclasses keep the exact check.
The backend gives every function that returns a complete record literal a
result-slot entry, `%into.<fn>(...params, dst)`, which stores the literal's
fields into caller frame storage. The cycle analysis counted those stores
as writes after construction, so a record type that refers to itself and
is never written after construction carried the cycle collector's header,
its live count and its candidate checks in release builds, even when
nothing called the entry. Dev builds, which add no entries, left it
acyclic.
The destination is frame storage of a caller that only projects the value
(stack-objects.ts): no object can point to it, so its fields cannot lie on
a cycle. computeCycleMutability now skips those stores, under the same
condition emit-statements.ts already uses to emit them as initializations
(resultSlotStore in the new result-slots.ts). Every other store still
makes its field mutable. The same facts mark such fields as never
replaced, so reads of them borrow instead of retaining.
A program that rebuilds a tree of 2^21 - 1 object literals 12 times runs
in 1.19 s instead of 1.35 s and peaks at 68 MB instead of 84 MB: each node
is 32 bytes instead of 40.
g01 (twenty fs.stat round trips against a 2 ms timer), p10 (two 0 ms
timers armed on either side of an island call) and s06 (fs callbacks and
timers pending when Node loads) each failed one CI run in a different
lane: their order depends on how long the island call or the I/O takes,
which differs between the sides and grows under ASan. Their native runs
now have to produce the lines of one of Node's outputs, like the other
race programs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tsgo rejects a call of `resolve()` without an argument when the
`new Promise()` that produced it has no type, and asks for a JSDoc hint;
tsc-ts accepts it. The two programs compiled under tsc-ts only, so the
checker-identity check failed on them. Each scenario function now
declares `@returns {Promise<void>}`, as corpus 1594 does. Their output is
unchanged.
A checked-dynamic method call looked its name up in the method table on
every call: a strlen, a hash and a byte comparison of the name.
The compiler now gives each method-name constant an i32 slot that starts
at -1 and passes it with the call (scr_dyn_invoke_prepared_id, and the
Buffer method entries, which forward it). The first call stores the
name's id in the slot, and later calls read it. Ids stay the runtime's
own, so the slot holds whatever this runtime's table says. Names are
still matched by content on the first call, and the slot is written with
a relaxed atomic store, since every thread writes the same id.
The runtime ABI is now 28.
The sanitized seed compiles the compiler's 700 MB LLVM module with one clang
process under AddressSanitizer. The compile peaks near 14 GB while the Node
process that emitted the module still holds about 6 GB, which exceeds the
runner's 16 GB of memory. The job now enlarges swap to 24 GB before it builds
the seed, as the sanitized emitter job does.
for-in over a pure index-signature record (`Record<string, number>`) skips
keys deleted during the walk by testing each snapshotted key for presence
before visiting it. The test built the overflow map's whole key list and
searched it with includes() on every visit, so a walk over n keys took
O(n^2) time: 200 walks over 2000 keys took 8.7 s, against 31 ms under
Node. The test is now recordOvfHas, the overflow map's hashed presence
check, which answers the same question in constant time.
The new benchmark workload dictionary-walks walks, prunes and merges
Record<string, number> dictionaries with for-in and Object.keys/values/
entries. It takes 7.1 s before this change and 23 ms after (Node 116 ms).
Corpus program 7011 pins for-in's visit order and its handling of keys
deleted, re-added and added during the walk.
`a[i * n + k]` with int32 operands kept the index in doubles whenever the
product's range could pass 2^53: integer arithmetic needs a proven safe
range, and an int32 product can reach 2^62. Every access then converted
the double back with an exactness test.
Such arithmetic now also carries a wide view: the exact integer as an
i64, the magnitude it is bounded by, and a threshold below which the
double equals it. A product of two exact integers equals its double below
2^53 in magnitude, and a sum or difference of a wide view exact below T
and an exact integer of magnitude at most B equals its double below
min(T - B, 2^53). Every i64 result stays within 2^62 + 2^53, so none
wraps.
An array index below a length or capacity is below 2^32, under every
threshold kept, so the dense array guards and local array reads compare
the wide view, unsigned, with the array's counts and skip the round trip.
Any index that fails those comparisons, including every index whose double
rounds, takes the double's path as before. Typed arrays and strings are
unchanged.
`new Set(x)`, `new Map(x)`, `new WeakSet(x)`, `new WeakMap(x)` and
`Object.fromEntries(x)` now accept any checked-dynamic iterable, as in
Node: a native iterator (`map.keys()`, `set.values()`), a compiled
generator, an object with a Symbol.iterator method, or a Set. Before, a
Set seed that was not an array or a string threw "is not iterable", and a
Map, weak collection or Object.fromEntries seed that was a generator or a
collection other than a Map refused with SC2020.
The runtime drains native iterators, packs arrays, strings and
collections, and steps every other iterable through its iterator
(scr_dyn_seed_values). A program that builds these from an untyped seed
registers the compiled generators' next and return methods with the
runtime, as destructuring rest already did, so a generator seed can be
stepped.
The seed's values are all taken before the first one is added, so when an
entry of a Map seed is not an object the TypeError comes after the
iterable is exhausted; Node closes the iterator at that entry. The
limitations page says so.
wasm-bindgen output constructs a FinalizationRegistry per exported class
when its module loads, so every wasm-bindgen package stopped at load with
SC2020. `new FinalizationRegistry(cleanup)` now creates a native registry:
- register(target, held, token) and unregister(token) check their
arguments and answer as Node does, with Node's TypeError messages;
- the unregister tokens are keys of an internal weak map, so the registry
keeps no token alive;
- the cleanup callback never runs, which ECMAScript allows, so the
registry keeps neither the callback nor the held values.
The global answers typeof "function", instanceof, Object.prototype.toString
and util.inspect as Node does. Symbol targets and tokens are refused with
SC2020.
`"encodeInto" in encoder` on a TextEncoder, which wasm-bindgen's loaders
test at load, answered false because the encoder is a brand record without
fields for its methods. `in` over such a builtin brand record now answers
from the members the standard library declares, plus Object.prototype's.
The limitations page says that memory a package frees only from a cleanup
callback, such as the WebAssembly memory behind wasm-bindgen wrappers the
program drops without free(), is not reclaimed until the program exits.
Fixes#1678.
An async generator kept the context of its creation for its whole life,
and a store its body entered carried into every later step. Node runs
each step in the execution that resumes it: a step starts with the
context of the next(), return() or throw() call, a store it enters
before its first await is the caller's after the call, and after an
await the step continues in its own copy.
Async generators now start each new step from the resumer's context and
hand the prefix's context back at the step's first suspension, as
synchronous generator steps and async function prefixes already do; the
continuation after an await keeps the generator's own. Two requests that
start from the settlement job, which runs from the loop, take Node's
contexts too: a request queued behind a running step starts in that
step's context, and a return() at a yield, whose unwind waits a turn,
runs in the return() call's context.
Corpus 7381 and 7382.
A number parameter that every call site proves an int32 reaches the
function's i32 entry. One that some caller passes as an unproven number,
such as a value read from a worker message or parsed from text, kept its
double, so every loop it bounds compared an integer counter with a double
limit and the values derived from it stayed doubles.
When such a parameter bounds a numeric loop (arithmetic, element and
field reads and writes, Math calls, no allocation or other calls), the
function now gets a copy of its body. Its own body first tests
each such parameter for an int32 that is not -0 and, when all pass, calls
the copy with `p | 0`, the same double. The copy's only caller proves
those parameters int32s, so it gets the i32 entry and integer loops.
Other values run the body as before.
Only plain synchronous functions of at most 2048 IR nodes are versioned:
not class members or generated helpers, not capturing, not the entry,
not dynamic-tier, not calling themselves, and at most 16 per module. A
function holding the only call site of another function is kept whole
unless that callee only computes a number or boolean from scalar reads:
a second call site can stop LLVM from inlining the callee and leave the
records it builds in memory. Library builds and modules that observe
call stacks are left as they are.