Each npm-static fixpoint round loaded the program with new frontend
services, so every load started new tsc-ts processes and parsed the
libraries and unchanged package files again. The Node frontend now
keeps one FrontendServices per compile, as the native frontend does,
and program hosts serve up to four loads: a host its load closes goes
back to an idle list, and the next load rebinds it to its own file
system shadow and sends its first snapshot with invalidateAll, so the
server reads every file again and reuses only identical text. Loads
alive at once hold different hosts; timed hosts are never reused.
SCRIPTC_TS7_HOST_LOADS=1 restores a new host per load.
A rebound host answers as a new host for changed shadows, hidden
files, dropped in-memory files and on-disk edits (unit test), npm
builds emit identical IR with and without reuse (parity test), and
pi's loads after the scout take 1-3 s less each, with identical
coverage diagnostics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
assert.throws and assert.rejects with an expected object held in a
variable compare each key with the thrown error's own or inherited value,
as Node's `key in actual` check does. An error's name is on its
prototype, so { name, message, code } failed against every error.
Untyped code called a method of a compiled class instance through the
site's dispatch helper: the callback that resolves own function-valued
properties, then the helper, which normalizes the capsule, tests each
class key and unwraps the instance before calling the method.
A call of a method dispatch helper from a dynamic-tier function now
tests the receiver's capsule key against the helper's plain class
branches (a direct call of one method with the helper's arguments) and
calls that branch's method with the capsule's instance. A branch
qualifies when every subclass the normalization can rebox its key to
calls the same method, and when the callback has no branch for the key
or those subclasses, so it answers undefined for the capsule. Every
other receiver calls the callback before the arguments and then the
helper, as before. Module initializers call this way only inside loops.
An integer `+`, `-` or `*` whose operands and result are proven within
the uint32 range, and whose operand is a `>>>` result or arithmetic on
one, such as `((i + j) * (i + j + 1) >>> 1) + i + 1`, is now 32-bit
arithmetic without wrap, and its double converts from the 32 bits
(`uitofp i32`). Vectorized, that conversion takes three instructions on
x86-64 without AVX-512, where the 64-bit sum's unsigned conversion took
six. Sums of counters and literals, such as `i + 1`, keep the 64-bit
form, whose conversion LLVM folds with their uses as indexes.
An optimized program divided into partitions was simplified at O2 before
the thin link and optimized again at O2 after importing, so the work on
each partition's largest functions ran twice. The pre-link now runs the
O1 simplification, which still annotates the profile and inlines and
simplifies enough for the summaries the import decisions read; the
backend's O2 optimization is unchanged. A program optimized as one module
is unchanged.
The O1 simplification has no correlated value propagation. Without it,
SimplifyCFG folds a jammed loop's early exit and its trip-count exit into
one branch, the loop's latch no longer exits, and the backend's unroller
keeps a trip-count test in every unrolled iteration. The pre-link runs the
pass once per function after simplifying it.
A JavaScript child stream (`child.stdout`) reached through the child's
checked-dynamic box registers its listeners through the stream's own
`on`. The declarations type an unannotated 'data' listener's chunk as a
Buffer, so the listener body checked the chunk as one and threw
`TypeError: expected Uint8Array at $, got string` once `setEncoding` made
the stream pass strings, as in Node's test-child-process-default-options.
When the file calls `setEncoding` on the same access path, an inline
'data' listener registered with on, once, addListener, prependListener
or prependOnceListener now binds its unannotated chunk as a
checked-dynamic value, which holds the string or the Buffer Node passes.
Streams without a `setEncoding` call keep the Buffer-typed chunk.
Inside a nested function, a read of a captured handle `let` narrowed to
the handle whenever it was a member receiver. A function declared before
the first write reads the binding as `any`, so the narrowed handle reached
member reads typed `any`, and an optional read (`c?.remoteAddress`)
compiled to an unconditional SC1090 throw. js-dyn-server, whose listener
reads `server.listening` inside the callback passed to createServer, hit
the same path.
The read now narrows only when the checker types it as the handle. An
optional member read keeps the tagged value, so undefined short-circuits
and the handle arm narrows; other reads take the checked-dynamic value.
The lowerer's closureDynLets collection is a checked journal table.
Corpus evolving-let-socket-optional-reads.cjs reads and calls members
through `?.` on a captured socket before and after its first write.
Reflect.has over a target held as any, unknown or a union answered a
presence result for a primitive. It now checks the target at run time
after both arguments are evaluated and throws Node's TypeError
("Reflect.has called on non-object"). The stdlib-fence snapshot no
longer lists Reflect.has, which lowers.
process.stdin.isRaw read a flag that only the dynamic setRawMode path
set, so a direct process.stdin.setRawMode(true) on a TTY left it false.
The flag now lives with scr_process_stdin_set_raw_mode, which every
path calls, readline's raw mode included.
StringDecoder.end(chunk) with a dynamic chunk that is undefined threw
ERR_INVALID_ARG_TYPE. It now only flushes, as Node's end skips the
write for undefined. A dynamic ArrayBuffer or SharedArrayBuffer chunk
decoded its contents; write and end now throw Node's
ERR_INVALID_ARG_TYPE for it, as it is not an ArrayBufferView.
After socket.setEncoding(enc), socket.pipe(destination) wrote the raw
bytes it read to the destination. It now writes the decoded strings, as
Node's pipe writes each string chunk: the destination receives their
UTF-8 bytes, and a partial character the decoder holds at EOF is written
before the destination ends.
tests/node-parallel/run.mjs runs each job's files with its own
TEST_THREAD_ID, as Node's test runner does, so test/common/tmpdir gives
concurrent files different directories instead of one shared .tmp.0.
- listeners() and rawListeners() in TypeScript of an event that no literal
emit or listener gives a tuple (one registered only under computed names)
read the untyped array, so each returned listener takes every argument a
call passes. The array was checked into zero-argument functions, which
called the listener without its arguments.
- A typed 'newListener' or 'removeListener' listener in a program with a
checked-dynamic listener of the other meta event registers through the
adapter. The meta lowering's check looked only at the listener's own
event and refused it with SC2020.
The --layouts wrapper always forwarded to clang. A musl target links with
`zig cc`, and SCRIPTC_LINKER replaces the driver together with its `cc`
argument, so every layout build failed. The wrapper now forwards to
`zig cc` when SCRIPTC_TARGET names a musl triple.
The runtime pack shipped ICU's full icudt78l.dat (33.1 MB), and every
Intl executable gets a copy beside it. scr_intl.c answers its Intl forms
with ICU's break iterators, number and date formatters, calendars and
locale IDs, which never read the charset converters or the collation,
language, region, time zone name, unit, spell-out and transliterator
trees: 21.5 MB of the file.
- The pack build writes icudt78l.dat without those items
(ICU_DATA_DROPPED in icu-inputs.mjs). filterIcuData rewrites the data
file's table of contents to the remaining items, and the result is
checked against a pinned sha256 and size (11,653,984 B).
- tests/harness/icu-data-filter.test.ts runs every @requires-icu corpus
program under 47 locales and 3 time zones with the pack's data, with
the full data and in Node, and fails when the outputs differ, so an
Intl form that reads a dropped category fails there.
- intl-word-segmenter-dictionaries.ts segments Chinese, Japanese, Thai,
Lao, Khmer and Burmese text, which ICU breaks with the dictionaries in
brkitr/.
codec.generated.ts was not regenerated when partialView was added to
IR expressions, so IR read through the binary codec dropped the field
and the stale-codec check failed.
A generic declaration held at several signatures and read through
unknown was refused with SC1090 when compared with a string, number or
boolean. A function is unequal to such a value whichever instance it is,
so a comparison with an operand the checker types as a string, number,
boolean, bigint or symbol is not recorded for the identity check.
`IrFunction.plainLoops` was missing from codec.generated.ts, so IR read
back through the binary codec lost it. Regenerated with
scripts/generate-ir-codec.mjs.
probeNodeFileImportRefusal resolved a relative or absolute specifier as a
filesystem path, so a percent escape, a query or a fragment became part of
the path: "./a%20b.mjs" was looked up as "a%20b.mjs", and "./x.mjs?v=1" of
an existing file rejected. It now resolves the specifier as a URL against
the importer's file URL, as Node's ESM resolution does: the message names
the decoded path without the query or fragment, and the error's url is the
resolved URL with them. A path ending in "/", an encoded "/" or "\" and a
malformed escape, which Node refuses with other errors, keep the current
lowering.
An absolute specifier that CommonJS resolution finds (for example a file
named without its extension) gets Node's "Did you mean to import" hint.
Node resolves the hint of a relative specifier against the process's
working directory, so relative specifiers get none.
npm-file-import.test.ts compares the probe with Node's import() rejections
for 29 specifiers, and the dynamic-import-not-found corpus program covers
an escaped specifier with a query and fragment.