- A deferred class with static field initializers is reached where its
statement evaluates: its file's %init retries it there
(retryDeferredAtDeclaration), so its initializers lower in place. A failed
retry reports nothing, restores the types it degraded, and leaves the
class deferred; no later flush registers it, so it reports at its
reference instead of registering after its initializers' place. Before, a
class a later reference registered never ran its static initializers.
- A generic class that registers on retry registers the units of its
static methods. Before, a call to one was an undeclared function
(SC9001).
- A statement that lowers again after degrading a type keeps the
diagnostics of the function-local class bodies it lowered the first time:
the retry reuses those bodies instead of lowering them again.
- retriableDiags and failedTypes are a Set and a Map owned by the lowerer
instead of weak collections, and the degraded types of a mode are copied
without an empty-array fallback, which the self-hosted compiler could not
lower.
- The coverage test of a class rejected behind a receiver probe pins its
refusal under --tier=static-only, as the diagnostics corpus does; under
localized failure the class compiles.
The native toolchain manifest's lib_diagnostics path is relative to the
platform package's dist/bin. installNativeCli copies the manifest into the
scriptc package's bin and relocates each asset path, but lib_diagnostics
was not in the list, so it pointed at a missing file under the scriptc
package and npm installs did not use the prebuilt lib diagnostics. It is
now relocated with the other assets.
A class extending a program class whose own declaration was rejected
(a degradable field type) failed collection with an inheritance error
that carried no cause. The localized-failure report then counted the
derived class, and the uses of it, as remaining blockers instead of the
base field's cascade. The error now names the base's failure group as its
cause.
A shape union's tag helper switched on the record's shape number. Inlined
into a caller's branch on the arm, LLVM folded the two into one jump table
per site, spanning the arms' shape numbers. In the native compiler, whose
IR unions span about 112 shape numbers, that came to about 27,600 tables
and 9 MB of read-only data, most of which a cold build pages in.
A union whose arms span more than 32 shape numbers now maps the shape
number to its arm through one constant table, indexed by the shape number
minus the arms' lowest. An entry is a byte, or a 16-bit word for a union
of 255 arms or more; the all-ones entry names no arm. Callers branch on
the small arm tag. Narrower unions keep the switch, whose folded jump
tables stay small and dispatch in one jump.
- Index walks (slice, substring, substr, startsWith/endsWith with a
position, lastIndexOf results) on a non-ASCII receiver of 64 KiB or
more: once they would have paid for one pass over it, the receiver's
index entry keeps every unit's byte offset (a 32-bit base per 64 units
and one byte per unit), and indexes map with two loads. Before that, a
walk within 64 units of the hot cursor starts from it, without the index
search. The index entry stays 128 bytes on 64-bit targets.
- lastIndexOf on non-ASCII receivers tests the needle's first byte before
comparing, as the ASCII path does.
- normalize: a quick check (UAX #15) returns the receiver when every code
point is inert for the form: unchanged when normalized alone, combining
class 0, and, in the ranges checked, never the second element of a
composition. Other text normalizes segment by segment, each starting at
a starter, so runs of inert code points copy as they are and a lone
precomposed letter's result is kept per form. The per-code-point facts
come from unicode_normalize, 256 code points at a time.
A function expression's or arrow's parameter and return types commit at the
statement that holds it, and a generic instance's at the instance drain.
Both are now localized-failure commit points for declaration slots: the
failing type degrades and the statement or instance lowers again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flush inside a capture buffer cannot retry (a probe that later declines
must see today's answer), so its copies of the declaration's diagnostics
reach the build when the statement commits. That statement commit point
now reopens such a declaration: when its diagnostics are retriable, they
leave the build, the declaration retries, and the statement lowers again.
Probe copies of a declaration that already registered are dropped.
The 'any'-typed class field refusal stays a fence at collection and is
lifted by the flush retry, so only reached classes change; the coverage
remainder registers the declarations reachable emit registered, and maps
the degraded types alike.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage L2 of the localized-failure design.
A class whose field (or parameter property) type has no static lowering,
or a function or method whose parameter or return type has none, used to
be rejected, and every use of it failed on its own line. Its collection
deferred the diagnostics until a reachable reference flushed them into the
build. That flush is now a commit point: when every deferred diagnostic is
a degradable type failure at one of the declaration's slots, the failing
types degrade and the collection runs again, up to 16 rounds. The
declaration registers with checked dynamic slots (one SC6104 each), the
reference proceeds, and emitReachable (or run) lowers the bodies it had
skipped. A derived class whose base registers this way retries with it.
The 'any'-typed class field refusal is lifted: a TypeScript class field
whose type maps to the checked dynamic stores it, as JavaScript classes
already do.
A program that compiles today flushes no deferred diagnostics into its
build, so nothing degrades and its output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its programs pin today's refusals; localized failure turns several of them
into checked dynamic slots in the default mode. Under static-only every
snapshot stays byte-identical, which is the stage's proof that the switch
restores the refusals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
collectProgram and the runtime-optional array analysis walk every file of
the program before any function body is lowered. This change removes work
they repeated and moves single checker requests into existing batches. The
emitted IR is unchanged.
- classReferences reads a per-file index of identifiers by text, built in
one walk and kept on ProgramAnalysis, instead of walking every file once
per class.
- widenedType reads its memo before the syntactic position test, and
memoizes nodes without a wide position.
- isJsSourceFileName tests the extensions with endsWith.
- NanCodedReads groups the identifiers of a name by resolved symbol once,
instead of resolving every same-named identifier for every binding.
- Each function's return scan in the optional-array fixpoint is a
SiteDependencies site, so incremental passes rescan only the returns
whose facts changed.
- The fixpoint's site batch also types the receivers widenedType reads and
for-of iterables, and collectDynamicViews prefetches the values it types
in one batch before its walk.
Stage L1 of the localized-failure design.
A local, loop variable or module global whose declared TypeScript type has
no static lowering used to reject its declaration statement, and every
later use of the binding failed with it. Where that failure is about to
enter the build (the statement's poison in a TypeScript file, or the
module global's registration), the failing type now joins the lowerer's
degraded types and the unit lowers again: the type maps to the checked
dynamic wherever its strict mapping fails, so the slot holds values by
reference (typed capsules, live records and arrays, function thunks) and
typed reads out of it are checked. Each such slot is an SC6104
performance warning, listed under coverage's statement counts and in the
JSON envelope; coverage --fail-on=dynamic-slots fails on them, and
--tier=static-only keeps today's refusals.
A program that compiles today reaches neither commit point, so nothing
degrades and its output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A native record, array or class instance boxed as unknown or any is the same
object: every box of it is one value, untyped writes, optional fields,
deletes and expandos show through typed reads and other boxes, nested
records and arrays are the typed program's own, and the value keys Map, Set
and WeakMap entries as itself. Main already behaves this way since records
and arrays box as live references; this corpus program pins it for the
single object model's later stages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sum of products of integer typed-array reads that only reaches a
bitwise operator, such as `(p[j] * 77 + p[j + 1] * 150) >> 8`, now adds
in 32-bit integers like a sum of the reads themselves: a product whose
every result is a safe integer, and never -0, is an exact integer or NaN.
A NaN-coded read passes its validity bit up through those sums, so the
integer result selects 0 for a missed read without a floating compare,
and a loop over such sums vectorizes without converting to doubles.
With --cpu-dispatch, an x86-64 executable carries two copies of each
program function: the baseline code and a copy compiled for x86-64-v3.
main reads CPUID and XGETBV once. Calls between the program's functions
go to the caller's copy. A function that is also named outside the
copies (a function value, a call from main, a runtime callback or a
coroutine body) keeps a dispatcher under its own name, which tests the
level and tail-calls one copy. Functions without such a use get no
dispatcher, so their copies have the callers the original had and LLVM
inlines them the same way.
The executable still runs on any x86-64 CPU. SCRIPTC_CPU_LEVEL caps the
level at run time. The option is off by default, applies to executables
without a --target-cpu level, and is part of the executable cache key.
bench:runtime takes --baseline-cpu-dispatch and --candidate-cpu-dispatch,
and the LLVM differential suite runs each program at levels 0 and 3 with
SCRIPTC_CPU_DISPATCH=1.
Node for macOS on arm64 is compiled by clang, which contracts a multiply
and an add within one expression into a fused multiply-add. Its sin, cos,
tan, asin, acos, atan, sinh, asinh, acosh, atanh, exp, expm1, log, log1p
and log10 differ from the unfused results in the last bit for some inputs,
so corpus 7717 and 7718 failed on macOS. scr_ieee754.c now sets
FP_CONTRACT ON for Apple arm64 targets. Other targets keep it off and
compile to the same code as before.
A build with an empty cache type-checked scriptc's own declaration files,
the TypeScript libraries and scriptc's declarations, before reusing the
answer in later builds. That check was 18 ms of a 105 ms first build of
hello world.
- build-native-cli.mts has the built compiler build a module without
imports and a module with a type error, each in an empty cache, and
records the content keys of the declaration file sets the checker
reported no diagnostics in: the checker release, the compiler options,
and each file's name and sha256. They ship in
lib/declarations/lib-diagnostics.json, named by the toolchain manifest's
lib_diagnostics.
- A build whose cache has no answer computes the files' content key and,
when the shipped answers hold it, uses no diagnostics for those files
and stores that answer in the cache. The shipped answers apply only
while the checker is the distribution's own tsc-ts and its package
version is the release they were recorded with; a changed file has
another key.
- A compiler the build host cannot run ships without them.
declareTypedArguments appends the hidden positional slots of a
typed-arguments function expression to the parameter list that
declareParams returns. Its parameter was typed
`{ localId: string; name: string; type: IrType }[]`, while the list is an
`IrParam[]` (which also declares `owned?: true`). The native compiler
passes an array whose element type has another layout as a converted copy,
so the slots were appended to the copy and the lifted function kept no
parameters. Building tests/corpus/6380 to 6384 (variadic wrappers that
forward `arguments`) with the native compiler then stopped with SC9001
"closure ...: func type does not match target signature" in 6380, 6381,
6382 and 6384.
The parameter is now typed `IrParam[]`, the type of the list the caller
passes, and the native compiler builds all five programs with output that
matches Node.
The Linux helpers linked LLVM 22.1.8's release archive. The helper's code
generation is LLVM's code, so building that LLVM from its release sources
with ThinLTO and with a profile of the helper's own work makes code
generation 5-9% faster than the helper as released, with identical objects.
- native/llvm-codegen/llvm.profdata.gz is the profile, collected by
scripts/refresh-llvm-profile.mjs: it builds LLVM with IR instrumentation
and an instrumented helper, and runs that helper on tsc-ts, five Effect
fixtures, the runtime benchmark workloads and every 50th corpus program
(scripts/llvm-helper-inputs.mjs). It follows LLVM's sources, so it needs
a refresh when LLVM is bumped.
- scripts/build-llvm-dist.mjs builds LLVM with that profile and ThinLTO,
and packs it with a checksum. The llvm-dist workflow, run by hand, builds
it for x64 and arm64, requires a helper linked against it to give the
same objects as one linked against the release archive
(scripts/check-llvm-dist.mjs), prints both helpers' times and the
profile's mismatch count, and attaches the archive to the LLVM_DIST_TAG
release of this repository.
- The release workflow's Linux jobs, and the wasm32-wasi job, which also
uploads the x64 GNU helper, download that archive and build the helper
with SCRIPTC_LLVM_THINLTO=1: clang, lld and ThinLTO, and the profile for
the helper's own sources. Without the archive, or if that build fails,
the helper links the release archive as before.
- The four Linux helper packages share their build in
native/llvm-codegen/linux-helper.mjs. Without SCRIPTC_LLVM_THINLTO it
builds the same executable as before.