A pipeline stage that arrives as an untyped value now follows Node's
stage ladder for more shapes. As the first stage, a function is a source
function: the pipeline calls it with { signal } while it is set up and
pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE
otherwise), and a { readable, writable } pair pumps its readable side.
After the first stage, the pipeline call is lowered once per combination
of its first three untyped stages being functions or not, and a typeof
test picks the branch at run time. A function stage is called with the
previous stage and { signal }; before the last stage its result must be
async iterable, and as the last stage a promise resolves the pipeline's
value and an async iterable feeds the PassThrough pipeline() returns
(SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes
Duplex.from of it, which the next stage reads from without the previous
stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for
the values it rejects.
An untyped stage followed by an untyped function stage hands its value to
that stage through a cell created per pipeline call, so the function
receives the previous stage's own iterator, as Node's makeAsyncIterable
answers it, instead of an async generator over it.
Untyped web streams, pairs after the first stage, and function stages
that return a stream throw the not-supported error.
RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports.
Local setters and the field constructor convert a local time value to
UTC by bracketing it with the zone offset a day before and after, then
checking the candidate instants: three to five localtime(3) calls per
conversion. In glibc each call takes the zone lock and, for dates past
the zone file's last transition, parses the zone's rule string again.
The offsets now come from a per-thread cache of offsets at UTC midnights:
a day whose two midnights agree has that offset throughout, given at
most one transition per day, which the conversion already assumes. A
day with a transition still asks localtime for the instant. A change of
process.env.TZ clears the cache.
Parallel changes kept taking the same next corpus number (6992 twice, the
Node island programs renumbered several times). The harness never needed
the numbers: it discovers every entry by glob, and 818 programs already
have descriptive names. AGENTS.md now makes a name, without a number, the
convention for new programs and keeps the numbered ones as they are.
The two scripts that sampled programs by number skipped named programs.
They now key a named program by a stable hash of its name, so they sample
it at the same rate; the numbered programs they pick are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lambda with no type parameters of its own, contextually typed by a
generic signature (`send: (m) => …` for an interface's `send<T>(m:
Message<T>): string`), takes that signature's type parameters: its
parameter is `Message<T>`. A generic lambda written with its own type
parameters already lowers once, erased, with them bound to `unknown`, but
this one tried to map `Message<T>` with `T` unbound and was refused
(SC2009).
It now lowers the same way, erased, binding the contextual signature's
type parameters to `unknown`. Parameters bound in the enclosing scope
keep their bindings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal)
maps to the checked-dynamic value when every other arm is one the
checked-dynamic tree holds faithfully. Function arms were excluded, so
`Progress | AbortSignal | undefined` was refused (SC2009).
The tree boxes a function whose parameters check in and whose result
converts out by reference (canBoxFuncIntoDyn), and `typeof x ===
"function"` narrows it back out to the typed function. Such arms now
join the collapse. The corpus program checks calls, identity (`===`
against the original), and the other arm's members against Node.
pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress
| AbortSignal`. The refusal rejected the whole SessionManager class.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel
that composes with nothing, so for the composing forms the code points
between occurrences of it normalize on their own and it is kept as it
is. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.
8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
The range analysis caps a counter that steps by 1 at 2^53, where
`j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2;
j < 2 ** 53 + 10; j++)` has a range of three values at its condition and
was taken for a loop of three runs. It never ends, and without its poll
worker.terminate() could not stop it. A loop's runs now count as bounded
only when a step from every value in its counter's range gives a
different value.
A number-key Map or Set read whose key has an integer view
(sc_map_entry_f64_int) fell back to the same hashed probe as any double
key, which tests for NaN and -0 before hashing. The fallback is now
sc_map_entry_f64_hashed_int, which takes the integer and converts it to
the key word directly: an integer is never NaN, and zero converts to +0.
Both hashed probes test the absent-key filter only before scanning a map
that keeps no bucket table. The filter keeps two of its 64 bits per key,
so it rejects few absent keys once a map has a bucket table, and the
probe's first bucket read answers those.
The map layout and the runtime lookup are unchanged. Corpus 8331 covers
integers the compiler tracks as integers on hashed maps and absent keys on
small bucket tables, and matches Node's output.
A promise converted into a union's promise arm is a new promise, so two
optional slots set to one promise each hold their own conversion, and a
value narrowed out of such a slot may be one too. Union equality compared
those arm payloads by pointer and answered false where Node answers true.
Any comparison between a union with a promise arm and a promise or another
such union now compares both sides as untyped values, where a converted
promise answers with its origin's identity.
JavaScript sources hold node:util's MIMEType and MIMEParams as identity
tokens, so a read such as MIMEType.prototype or MIMEType.name answered
undefined where Node returns the class's member. Those reads now report
SC2020, as the TypeScript spelling does at compile time; in JavaScript
the refusal throws when the read runs.
The node24-mime-errors fixture reads MIMEType.prototype to take the
getters' descriptors, so its expected refusal is now SC2020.
The tsc gate (preflight's errorsOf, and the original program of a JSX or
declaration-projection build) asked for every file's semantic
diagnostics, then dropped all of those in an opted-in package's files, in
unselected package JavaScript and in package shims. With --npm-static
that is nearly the whole check: on three.js's nodes fixture the 82
package files took 1.17 s and reported 13,022 diagnostics, the program's
own files 0.1 s.
getPreEmitDiagnostics takes the files whose diagnostics its caller keeps.
When some file is left out, the server checks only the kept files
(getDiagnosticsOfCheckedFiles), and their semantic diagnostics come in
the full check's order. When a stored answer for the supplied
declaration files applies to the program, it stands in for checking
those files, as in the full check; the storage rule is unchanged. A
global diagnostic can come from checking any file, so when the partial
check reports one, the full check answers as before. At load time,
before workspace packages are known, the gate leaves out only package
and shim files.
The kept diagnostics are unchanged.
The test emits each corpus program from its serialized IR with its own
emitter options and compares the text with compile()'s. compile() now
marks functions sanitize_address only in sanitized builds, so the test
passes the same setting as objectAudit.
In a worker program, a loop inside another loop of the same function
needs less than its own budget, because the enclosing loop polls at
least once every 64 of its iterations:
- A loop whose counters' proven ranges bound its runs, and those of the
loops in its body, to 64 x 64 emits no poll. This covers loops with
constant bounds, such as mandelbrot's lane and iteration loops. The
bound uses the counted and two-counter loop ranges, or the range of a
counter that the update alone steps by a constant where the condition
reads it. Loops whose counted form needs a run-time guard don't count.
- An innermost counted loop is strip-mined like a counted loop over
typed arrays, whatever its body. A run of at most 64 iterations never
polls, and the budget's countdown leaves the body. A run of at most
16384 iterations is one block and polls once as it leaves. A longer
run polls between blocks of 64, but not after its last block.
Between two polls there are then at most 64 iterations of the enclosing
loop, and in each of them at most 16384 unpolled iterations of each
strip-mined inner loop and 64 x 64 runs of each bounded nest. A loop
that no other loop of its function encloses polls as before.
New corpus program: native-worker-terminate-bounded-loops.ts.
A Linux executable link runs `clang -fuse-ld=lld ...`, and the driver
only starts ld.lld with a job it derives from its arguments, its
environment and the installed toolchain. Starting the driver costs
about 10 ms of a small program's 29 ms link.
LinkerJobs (native/linker-job.ts) records that job in the native cache,
with the link's own inputs and output as positions. A traced link (the
Linux executable cache's `-v -Wl,-t` link) records it from the job line
the driver prints with -v; a link the executable cache does not trace
lists it with -v -### first. The entry also keeps the identities of the
files and directories the job and the driver's -v lines name (ld.lld,
startup objects, library directories, the GCC installation, the
driver's directory): the set the Linux executable cache records from its
traced link. It is keyed by the driver's identity, the link's
arguments, the toolchain environment, PATH and the working directory. A
later link with the same key whose recorded paths still have those
identities runs ld.lld with the job directly, and the executable cache
traces it with the job's -t output and the recorded identities.
Every other link runs through the driver as before: cross-target links,
links without lld, a driver that is not clang, SCRIPTC_LINKER, sanitizer
links, a toolchain environment that names mutable inputs, a listing
without exactly one ld.lld job naming each of the link's paths once, a
recorded path whose identity changed, and a job that fails (the driver
then reports the error). The linker gets the same arguments, so the
executables are identical.
The sanitized lane's -fsanitize=address link needs the attribute to
instrument the emitted functions. Without a sanitizer pass it is not
inert: LLVM does not shrink-wrap functions that carry it, so every call
saved and restored all of a function's callee-saved registers on entry,
including on paths that return before any call, and it blocks
speculative loads. The LLVM helper already dropped it for builds that
import runtime bitcode (speed); release builds kept it.
- The new sanitizeAttributes option (default true for direct emission)
is set from --sanitize wherever objectAudit is.
- Without it and without a target CPU level, attribute group #0 holds a
string attribute LLVM ignores, since a group cannot be empty.
The compiler compiles itself, and a Map keyed by another Map is outside
the key domains it compiles (SC2009 in the self-hosted frontend). The
Lowerer's table is now keyed by the declared alias reference: each entry
lists the instances that instantiate it with their ts-level bindings maps,
and the resolver picks the current instance's by identity.
The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a
-0 start into +0, and a term can tell the two apart (1 / i on the first
row). The bounds test now requires 1 / b > 0, which -0 fails, so such a
nest runs as written. Corpus 8355 adds row and column starts of -0 with
terms that see the sign.
The LLVM emitter's module passes walk every function, and on large programs
most of them find nothing to change in most functions. Each pass now
avoids work whose result it does not use, with the same output:
- scalarizeLiteralResults built, for every function, a set of all locals
and all module globals before looking for a producer call. It now
collects the names a fresh binding must avoid when it makes the first
one.
- specializeCallbackCalls walked every body five times: rebindings, node
counts and definitions, call keys, untouched bodies, and callers. One
walk now records each body's node count, rebindings, written values and
helper calls in walk order; the later steps read those records and walk
only the bodies the rewrite changed and the copies it made.
- holdCharLocalsAsUnits skips its two object walks in functions without a
candidate local, and its mention count where no candidate is declared
from a charAt or an optional read (the only ones it can hold).
- resolveSelfCaptures only asks CaptureFlow whether bindings are
unchanged, so its flow collects the facts of the bodies those questions
reach instead of scanning every body.
- copyStableCaptures reads the function references of the bodies it kept
from its CaptureFlow facts instead of walking them again, and makes the
set of local ids a copy must avoid only when it makes a copy.
- versionLoopBoundParameters counts call sites only when a function has
loop-bound parameters.
- pairRowSums and inlineExpressionFunctions look for a row sum or an
inlinable call before rebuilding a body.
The lowerer's optional-union cache relies on withUnitArm answering for a
placeholder before it completes and recomputing after the revision moves
(lowerer.test.ts). Only the optional-parameter site needs to keep the
placeholder, so the refusal goes back out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>