Readable.from accepts any iterable or async iterable of strings or
Buffers, following from.js: the iterator is taken when the stream is
built, before the options are read, next() is looked up and called on
each pull, a sync iterator's promise values are awaited, and destroy
calls the iterator's throw() and return() only when it has them, reading
each method when close() runs. A close() that fails with a second error
passes an AggregateError of both to the destroy callback, as
aggregateTwoErrors does.
In typed code this covers Sets of strings, Map and Set iterators,
classes whose [Symbol.iterator]() or [Symbol.asyncIterator]() is a
generator method, iterator classes, and async iterator classes, whose
return() runs at the end of the stream and on destroy. In JavaScript it
covers every iterable; an object whose iterator method is a generator
method throws a not-supported error when the stream is built.
When a 'newListener' listener threw while events.once registered its
listeners, the exception escaped the call, the 'error' listener's closure
was never registered or released, and the shared state and its promise
leaked with it. Node registers both listeners inside the promise's
executor: the throw rejects the returned promise, and the listener whose
announcement threw is not added (the event's listener stays when only the
'error' announcement throws).
scr_events_once now takes the pending exception, takes back the listener
whose announcement threw without a 'removeListener' event, releases the
'error' closure when it was never registered, and rejects the promise
with the exception.
InternTable keys can be arrays of numbers or of strings, compared by their
elements: numbers by SameValueZero, strings by content, and holes equal to
each other only. The runtime stores an array key as one byte string that
encodes its elements; the Node.js implementation encodes it into a Map key
the same way.
Replaced table indexes are now freed. Every thread that uses a table
records the epoch it read at the start of each lookup and insert, and an
index retired in an earlier epoch than every recorded one is freed. Worker
threads stop holding retirements back when they exit.
Number indexes keep a tag byte per slot ahead of the key and value pairs,
so a lookup of an absent key reads the dense tag bytes instead of pairs.
getOrInsertComputed lowers an arrow callback with an expression body in
place, with its parameter bound to the key, so interning through
`(key) => new T(...)` creates no closure.
A timers/promises timer that nothing awaited did not keep the process
alive: the loop only ran when the program used the global timers, async
functions or generators. Node keeps a pending ref'd timer's process open,
and the timer heap's exit teardown releases an unref'd one only when the
loop runs, so a pending { ref: false } timer stayed live at exit. Any
timers/promises call now runs the loop.
The node-types fixture no longer attaches .then to the never-firing
unref'd timer, whose suspended continuation the sanitizer's RC audit
reports at exit. Corpus 5620 no longer orders a 1ms timeout against an
immediate, which Node itself does not order from the main module.
A return whose value always throws, such as a call of a function
returning never, ends its block before the union result pair is built.
The payload retain then opened new blocks that referred to values the
ended block never defined, and a conditional's join took an edge from
an arm that never reaches it. The retain now emits nothing in an ended
block, and the join takes edges only from arms that reach it.
Exact constructions (literals, slice, concat results) round their request
to 16 bytes before the size class, so arrays of 2, 4 and 6 slots get one
spare slot and an append right after a copy no longer reallocates. Arrays
of 8-byte slots with their own storage and no sparse entries grow below
the dense cutoff on a path with a constant slot size, and dense copies of
8-byte slots use a constant size.
@scriptc/experimental exports InternTable<K, V>: a named table that every
thread of a scriptc program shares and that each thread has its own copy
of under Node.js. Keys are strings or numbers with Map equality; values are
published (@scriptc/threads) when stored, and the first stored value of a
key is kept. Members are get, has, size, getOrInsert and
getOrInsertComputed.
The runtime table (scr_itable.c) splits each table into 64 segments.
Lookups take no lock and do not write; inserts take a segment's spin lock
for a probe and a few stores, and callbacks run without a lock. Number
tables keep keys and values in the index; string tables keep entries in
segment-owned chunks behind tagged slots. Replaced indexes are kept.
The compiler lowers the class to a synthetic class with one f64 handle
field and five intrinsics, with interned helpers per member and type and
an in-place lowering of getOrInsertComputed that creates a function
expression callback only on a miss. Stored values go through the publish
intrinsic, so their types get the write guard. scriptc refuses names that
are not string literals, one name opened with different type arguments,
and key or value types a table cannot store.
scr_publish walks small graphs in inline buffers and reads
SCRIPTC_PUBLISH_TRACE once, instead of allocating and calling getenv on
every call.
The differential and llvm-differential corpus split their programs across the ten Linux test shards by a hash of
the program's name, so a shard's share of the measured work varied from 565 to 913 s (plain) and 868 to 1358 s
(sanitized). corpusSelect assigns programs longest first to the lightest shard by their measured cost per flavor
(tests/harness/corpus-costs.json: the differential plus llvm-differential milliseconds on a CI runner, medians of
four green main runs), giving 740-741 s and 1098-1099 s. Both harnesses still select identically, so a shard's
compile cache serves both. Programs without a measurement weigh as much as the median one.
scripts/ci-corpus-costs.mjs refreshes the table from downloaded CI shard logs.
Each section of 5615 now starts from the previous section's last
callback. Before, the sections started at fixed delays (20, 100 and 200
ms), so a slow first callback could delay the timers section's report
past the start of the I/O section and print its readFile callbacks on
the timers line. The program's output is unchanged.
An empty string message follows Node 24.22's rules for each assertion:
- equal, notEqual, strictEqual, notStrictEqual, deepStrictEqual,
notDeepStrictEqual and fail pass it to the AssertionError constructor,
so the message stays empty, or the diff header is the default one, and
generatedMessage is true (`!message`).
- match, doesNotMatch, and throws with a regex, a class or an object shape
treat it as no message: the generated message is built and
generatedMessage is true.
- throws with nothing thrown and doesNotReject end their message with "."
instead of ": ", and keep generatedMessage false.
- ok and assert() keep the empty message with generatedMessage false.
assert.fail computes generatedMessage from the message value at run time.
Corpus 5521 covers each method with a literal and a computed empty
message.
Directly called functions returning a union of numbers, booleans, unit
values and class instances, strings, records or arrays now return its tag
and payload in registers, as scalar unions already did. A reference
payload carries the reference the heap box held. A wrapped return value
moves its reference into the pair, and a returned call with the same
convention passes its pair through.
Callers that only project the result keep it in a private stack box whose
payload the statement frame releases by tag. A discarded result releases
its payload from the registers. Locals bound to such results keep the
tag and payload: with one reference arm through the existing owner slot,
and with several through the box itself, released by tag on rebinding and
scope exit. Other callers, and the boxed entry used by closures, dispatch
and the runtime, box the pair. Functions with a finally block keep the
boxed result.
createCipheriv, createDecipheriv, update(), setAAD() and setAuthTag()
now read the value a typed-ref box holds when it is a string, view,
ArrayBuffer or KeyObject. ERR_INVALID_ARG_TYPE names the class of a
class instance that arrives through an untyped value, as Node does,
instead of Object. An unknown output encoding that is not a string is
rendered as Node's %s formats it (util.inspect at depth 0, or the
object's own toString); a function, or an instance of a class that
defines toString, throws a coded SC2020 error. Cipher programs link the
inspect unit for that rendering.
5250 passes each argument through `any`, including class instances,
records and arrays that Node rejects, and 5254 covers output encodings
that are not strings from JavaScript.
pipeline() accepts a run of async generator function stages between
streams, following pipeline.js: each stage is called during setup with the
previous stream (a Readable is async iterable) or the previous stage's
generator, and the last generator is pumped into the next stream with
pumpToNode's write, drain and end waits. An error in the pump loop returns
the generator before the pipeline finishes, so the stage's finally runs.
A stage's source parameter is typed as what the pipeline passes it and may
only head for await loops. Function sources and destinations, stages that
take the signal argument, and stages separated by streams are refused.
A named property read on a value typed unknown goes through the property
helper the class dispatch generates. Its read compared the property bag
with the receiver and then read from it, and the bag expression was
evaluated at each use: three calls that each retained and released the
receiver. Releasing a record capsule whose fields can form cycles also
buffers it as a cycle candidate, so a single `u.id` paid that three times.
The helper now evaluates the bag once into a local.
Typed records seen as unknown also find a field by comparing the key
pointer with the interned field names first, since compiled reads pass
the same literal, before comparing bytes, and records whose fields are all
required skip the presence check.
A chunked response whose trailers ran past the header limit failed the
request with "Parse Error: Header overflow" and marked the exchange done
while the parser still held the response. The socket-close cleanup then
skipped it, so the response, its socket and the parser kept one another
alive, and every such response leaked. The overflow path now settles the
response ('aborted' and 'close', as a premature close does) and drops the
parser's reference before the exchange completes.
Corpus 5652 repeats response trailer and head overflows and request
trailer overflows; under SCRIPTC_SAN=1 the RC audit fails it without this
change.
A candidate release now also runs a pass once the live cycle-headered
count has grown by 4096 objects since the last pass. A dead graph is
usually reachable from one candidate (its root), so the candidate count
alone let a few hundred dead graphs pile up before a pass, which then
walked them after they had left the cache.
markGray records the objects it grays and counts the ones still
externally referenced after the trial decrements. When none is, every
gray object is garbage, so the pass whitens the recorded set and skips
scan. markGray also records each edge it skips into an older generation,
so a restricted pass pins those targets without tracing the white set a
second time.
Adds the cyclic-graphs runtime benchmark and corpus programs 5700 and
5701.
events.once(emitter, name) was refused. The emitter dispatches statically
emitted events to typed listeners through invokers built per argument
tuple, so the lowering takes the event's tuple from the program-wide event
table and builds a capture-free function that boxes that tuple into the
any[] Node fulfills with, and one for the 'error' tuple.
scr_events_once registers both as one-shot typed listeners behind emitted
adapters (emitterOnceAdapter). The first to fire settles the promise
(fulfilled with the arguments, or rejected with the error) and removes
the other, as in Node; awaiting 'error' itself registers only the one
listener. The shared state holds the emitter and the listener closures
weakly, so a never-fired once creates no cycle.
Options, computed or meta event names, EventTarget and stream receivers,
and events emitted with computed names or checked-dynamic listeners stay
refused. The fallback declarations type the result Promise<unknown[]>;
under @types/node (Promise<any[]>) TypeScript code narrows it to
unknown[].
%f was refused because parseFloat had no static lowering; the global
parseFloat lowers now. Node formats parseFloat(String(arg)): a number
keeps its value except -0 (String prints "0"), a string parses with the
global parseFloat, a checked-dynamic value converts with String first,
and booleans, undefined and null format as NaN. Other argument types are
refused with their type named.
A number-key write that follows a get or has of the same collection and
key, inside its own arguments (m.set(k, (m.get(k) ?? 0) + 1)) or in the
statement just before it, now looks the key up through the read-only
sc_map_entry_f64 helper. A found entry takes the new scalar value in its
slot (a Set entry already holds its unit value); a new key appends
through the runtime set. LLVM merges that lookup with the read's when no
store intervenes, so the write does not hash and probe the key again.
Other writes, including those guarded by an absent key, keep the single
runtime call, which finds the insertion bucket in the same probe.
Reference values keep the runtime call, which releases the old value.
timers/promises setTimeout(delay, value) and setImmediate(value) were
refused: only the void forms lowered. The value now travels as an
already-resolved promise of its own type (the Promise.resolve lowering),
and scr_tp_set_timeout_value / scr_tp_set_immediate_value copy its
settlement into the result when the timer or immediate fires, so the
result keeps the value's type. An options object literal takes `ref`;
`ref: false` unrefs the timer, so it no longer keeps the process alive.
AbortSignal options and other option forms stay refused, with their own
messages.
@types/node types setInterval as NodeJS.AsyncIterator<T>, which maps to
no IR type, so a for await loop over setInterval(delay, value) was refused
in projects with @types/node. The interval's async generator type now
comes from the iterator's type argument, and a for await loop whose head
is that call consumes it directly.
The fallback declarations type the new forms.
Readable.from accepts sync and async generators of strings and Buffers,
following from.js: values are pushed while push() accepts, an async
generator's next() promise is awaited once per value, and destroy runs
the generator's throw() and return() before the destroy callback.
for await over a stream follows createAsyncIterator: the loop registers
its 'readable' listener, reads through read(), and a parked loop wakes
through the 'readable' tick. push(null) before the first read ends the
stream after the next read, finished() answers an already settled stream
with its result on a nextTick, read(0) on an object-mode stream reads
nothing, and write(string, 'buffer') throws ERR_UNKNOWN_ENCODING.
When appending one more entry would grow a hashed number-key map's
index, Map.set and Set.add first look the key up to decide whether the
map should move to the direct index. A key that lookup finds was then
looked up again by the generic set. The found entry now takes the new
value directly, so overwriting a present key in a map at its growth
boundary probes the table once.
Number-key reads of a map without the direct index called
scr_map_entry_f64 for every lookup. The internal sc_map_entry_f64 helper
now calls sc_map_entry_f64_hashed for such maps instead: it hashes the
key as scr_map_find_f64 does (SameValueZero key word, scr_key_mix, the
absent-key filter), scans the entries of a small map or probes a compact
bucket table, and calls the runtime lookup only for a table wider than
the compact limit. It stays out of line so the direct-index path stays
small at every call site.
The runtime fallback is called as a read-only call, so both helpers only
read memory and LLVM merges lookups of one key when nothing is stored in
between, such as a has() followed by get().
The runtime benchmark suite gains number-maps: a node table indexed by
dense ids, scope tables keyed by sparse ids, and get/has/set mixes.
- getProtocol() answers the negotiated version ("TLSv1.2" or
"TLSv1.3"), the highest version on offer before the handshake as
OpenSSL does, and null after close.
- getCipher() answers { name, standardName, version }: OpenSSL's name
for the negotiated suite, its IANA name, and the suite's minimum
protocol version. A suite without a known OpenSSL name throws the
"not supported yet" error.
- alpnProtocol is null before the handshake, then the negotiated
protocol or false.
- tls.DEFAULT_MIN_VERSION and DEFAULT_MAX_VERSION are 'TLSv1.2' and
'TLSv1.3', and the bundled declarations type the server's
secureConnection socket as a TLSSocket.
- The limitations page notes that a typed getCipher() read before the
handshake or after close throws where Node answers undefined or null.
Corpus 5651 covers each case.