Commit Graph
4679 Commits
Author SHA1 Message Date
Chris Tate d48e060076 Merge main 2026-10-25 22:34:40 -05:00
Chris Tate 5679a06caa Report which type failures localized failure would turn into dynamic slots (#2284) 2026-10-25 22:31:15 -05:00
Chris Tate efca230de9 Merge ni/i4-deferred and apply parity and security fixes 2026-10-25 22:26:54 -05:00
Chris Tate 6f373b983b Keep every field of a record converted into a union arm
A record that converts into several arms of a union by a width conversion
preferred the unique arm whose fields the record has or that are
undefined-only, even when that arm holds the record only by dropping fields
and another arm keeps every field but declares an optional field the record
lacks (`adapts?: true`). The record was wrapped as the dropping arm:

- In a discriminated union the value kept its discriminant but had the other
  arm's tag, so narrowing on the discriminant stopped the program with
  "a narrowed value does not have the type its narrowing proved".
- Without a discriminant the dropped fields were lost: `"fnName" in value`
  was false and JSON.stringify left them out.

The native compiler hit the first case on
tests/corpus/2910-overloaded-callable-alias.ts: the closure that a value read
of an overloaded function alias builds is a record that is not typed by
IrExpr, and it converted into the `selfRef` arm.

The undefined-only preference no longer selects an arm that drops fields
when another candidate keeps every field, so a discriminated union selects
the arm by the record's discriminant. The lossless-arm preference still
prefers the unique arm with exactly the record's fields; when there is none,
it accepts the unique arm that keeps every field and declares optional
fields the record lacks, which complete as absent.
2026-10-25 22:26:16 -05:00
Chris Tate 14836e0408 Close nsbench's gap to its typed twin: store folds, receiver reads, exhaustive table dispatch (#2255) 2026-10-25 22:18:04 -05:00
Chris Tate ec6db73f6b Merge ni/i3-exports 2026-10-25 22:15:54 -05:00
Chris Tate 89db5d47f7 Merge main 2026-10-25 22:02:35 -05:00
Chris Tate 8b1deff6e9 Step untyped loops over the program's generators on the typed path
A for…of or for await loop over an untyped iterable now tests, once it has
its iterator and next method, whether the iterator is one of the
program's own generators with its own next method. Each step of such a
loop resumes the generator directly and boxes only the value, where the
dynamic protocol boxed the result record and its promise and read them
back. Every other iterator keeps the dynamic protocol, and early exits
still close the iterator through it. The awaits are the protocol's: an
async generator's next() promise is awaited, and a sync generator under
for await awaits the result and then the value.
2026-10-25 22:01:42 -05:00
Chris Tate 00b58e5c7a Map UTF-16 indexes of long non-ASCII strings in constant time, and quick-check normalize (#2294) 2026-10-25 21:59:27 -05:00
Chris Tate 35c6717694 Merge perf/shape-analysis-budget 2026-10-25 21:52:58 -05:00
Malte UblandClaude Opus 5.5 1ae5f105ca Rank dynamic slots by their dynamic operations, name them in SC6101, and document them
Stage L5 of the localized-failure design.

- SC6101 names a dynamic slot as the cause of a likely-hot untyped function
  ('tool' is a dynamic slot, declared 'ToolDefinition', which does not
  compile).
- Coverage counts the dynamic operations each slot's values take part in,
  lists the costly slots first, and puts the count in the JSON envelope.
- A build whose only performance warnings are dynamic slots closes with a
  line about declared types instead of function types.
- Docs: a Dynamic slots section in the coverage guide, the limitations
  page bullet, and --fail-on=dynamic-slots in the CLI reference and usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:48:18 -07:00
Malte UblandClaude Opus 5.5 b6af8bc0a0 Degrade the types of a statement's own expressions where the statement commits
Stage L4 of the localized-failure design.

A cast to a declared type that does not compile, a literal whose contextual
type does not compile, a spread clone of such a value or a temporary of
that type used to reject the statement. The statement commit point now
degrades those expression types too (with push-down), so the cast is a
checked conversion to the type's compilable shape and the literal builds
in it. The SC6104 of an expression names the expression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:48:17 -07:00
Chris Tate 8a71a20e32 Merge ni/i2-handles 2026-10-25 21:39:21 -05:00
Chris Tate a76b7e77b4 Address review feedback 2026-10-25 21:37:55 -05:00
Chris Tate cc2266ab1d Add --cpu-dispatch for x86-64 executables (#2288) 2026-10-25 21:36:00 -05:00
Chris Tate 0abe5063ff Wait for every released package to resolve on npm before the GitHub release (#2281) 2026-10-25 21:28:51 -05:00
Chris Tate 835303d23a Merge perf/shape-table-dispatch 2026-10-25 21:26:46 -05:00
Chris Tate 018dbb766c Compute Math functions as V8 does and link libm only when a program needs it (#2287) 2026-10-25 21:26:20 -05:00
Chris Tate 7373d3c776 Attribute a derived class's rejection to its rejected base
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.
2026-10-25 21:25:51 -05:00
Chris Tate 80ae660be6 Merge main 2026-10-25 21:22:05 -05:00
Chris Tate 59e78f298a Name the package declaration and its patch when a declared-type check fails (#2268) 2026-10-25 21:20:34 -05:00
Chris Tate 43a8c6d8cc Link the Linux release helpers against LLVM built with ThinLTO and a helper profile (#2282) 2026-10-25 21:09:17 -05:00
Chris Tate 28b76f7b25 Merge ni/i1-stack-bound 2026-10-25 21:08:37 -05:00
Malte UblandClaude Opus 5.5 bb33ec50fe Push localized failure down to the failing component of a declared type
Stage L3 of the localized-failure design.

A degraded type used to become the checked dynamic as a whole slot. Its
lenient mapping now keeps the compilable shape: the type maps again with
every nested component whose mapping fails as the checked dynamic, so a
record keeps its static members with one dynamic member, an array of
records keeps its records, and a Map, Promise or function keeps its static
parts. When even that shape does not map, or a unit still fails with it,
the type falls back to the whole-slot checked dynamic (one more round).
Lenient forms are IR types of one lowerer's shape tables, so the coverage
remainder re-derives them. A pushed-down type is one SC6104, at the slot
where it degraded, marked as keeping its shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:07:57 -07:00
Chris Tate 0b483e8650 Map UTF-16 indexes of long non-ASCII strings in constant time and quick-check normalize
- 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.
2026-10-25 21:04:29 -05:00
Chris Tate d21f0d7c8b Pin the identity of native values seen as untyped values (#2290) 2026-10-25 21:04:04 -05:00
Chris Tate 794a8cf8a8 Compute products of typed-array reads in 32 bits under bitwise operators (#2289) 2026-10-25 20:58:49 -05:00
Malte UblandClaude Opus 5.5 4da07089c9 Degrade parameters and returns of function expressions and generic instances where they commit
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>
2026-10-25 18:57:23 -07:00
Malte UblandClaude Opus 5.5 f0eb427152 Retry declarations a probe flushed at the statement that commits them; keep the dyn field policy outside reached classes
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>
2026-10-25 18:57:22 -07:00
Malte UblandClaude Opus 5.5 8b07790325 Register classes and functions with dynamic slots when a reference flushes their type failures
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>
2026-10-25 18:57:22 -07:00
Malte UblandClaude Opus 5.5 cd5607493e Pin the diagnostics corpus to --tier=static-only
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>
2026-10-25 18:57:21 -07:00
Malte Ubl 92a4b39ef6 Merge base branch 2026-10-25 18:38:31 -07:00
Malte Ubl f661d0f520 Merge base branch 2026-10-25 18:38:31 -07:00
Malte Ubl 8cde53d7cf Merge base branch 2026-10-25 18:38:30 -07:00
Malte Ubl 68eda2ec95 Merge base branch 2026-10-25 18:38:30 -07:00
Malte UblandClaude Opus 5.5 7687cf2d26 Type island packages from their declarations in failed builds and generics
When a native build failed and the packages it compiled with runtime fences
then failed as islands too, the native build's diagnostics were reported.
That build types those packages from their JavaScript, where declared-only
types (interfaces, `Static<T>`-style aliases) are `any`, so typed user code
reported cascades the island build does not have (a schema library's `TSchema`).
The island build's errors are reported instead.

In a generic function instance over island types (`<T extends TSchema>`),
a `T` value bound to dyn now crosses into the island the way `any` does,
instead of being refused as a native object.

Corpus 6829 types user code with an island package's interfaces, generic
aliases and a generic function; node-island.test.ts pins the reported
diagnostics of a failed build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:38:29 -07:00
Malte UblandClaude Opus 5.5 ac31a15a62 Hold locals and module globals whose declared type does not compile as checked dynamic slots
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>
2026-10-25 18:35:38 -07:00
Malte UblandClaude Opus 5.5 1493088594 Pin the identity of native values seen as untyped values
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>
2026-10-25 18:33:21 -07:00
Chris Tate 8981b5c0a2 Resolve computed createRequire specifiers at run time (#2079) 2026-10-25 20:29:51 -05:00
Chris Tate c820a6c57f Compute products of typed-array reads in 32 bits under bitwise operators
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.
2026-10-25 20:29:18 -05:00
Malte UblandClaude Opus 5.5 744585944a Keep the localized-failure fixture's field failing after interface any parameters map
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:25:27 -07:00
Chris Tate c9116749c1 Merge ni/3-hosted-loop 2026-10-25 20:18:43 -05:00
Chris Tate 8e4837f94a Run untyped pipeline function stages and later iterable stages (#2276) 2026-10-25 20:17:26 -05:00
Chris Tate 07c5e79ffd Add --cpu-dispatch: x86-64-v3 copies of the program's code chosen at startup
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.
2026-10-25 20:00:16 -05:00
Chris Tate 1a2eb52016 Start nested Node entries below the frames a fiber switch left live
A host callback marks where it runs on Node's stack, and a call into
Node.js from a fiber starts below that mark. Runtime code the callback
runs may go deeper on Node's stack before it starts or resumes a fiber,
and those frames stay live while the fiber runs: a call into Node.js
from the fiber overwrote them. scr_switch now reports where it leaves
Node's stack, and the mark moves down to it for the rest of the
callback.

6823 runs a timer callback 200 frames deep that starts an async
function calling into Node.js before its first await.
2026-10-25 19:54:03 -05:00
Chris Tate dd418b067a Compute Math functions as V8 does and link libm only when a program needs it 2026-10-25 19:48:38 -05:00
Chris Tate 89e2daa636 Keep borrowed fallbacks alive until their consumer and address review feedback 2026-10-25 19:42:59 -05:00
Chris Tate 1ea8a6c467 Skip the element read for array callbacks that never use it
A callback that declares no element parameter, or binds it to a name
that appears nowhere else in the function (`filter(() => true)`,
`map((_, i) => i)`, `reduce((s, v) => s + 1, 0)`), no longer gets the
element read and converted for each visit. The helpers and the fused
loops pass a constant of the parameter's type in its place (NaN, "",
false, or the undefined arm of a union that has one); other parameter
types keep the read.

An array holding an element its type cannot hold, written through an
unknown[] view, threw a TypeError at that element for every callback
method. With such callbacks it now behaves as in Node:

- map, flatMap, forEach, some, every, findIndex, findLastIndex, reduce
  and reduceRight visit it without reading it;
- filter appends each kept element as it is stored, so the written value
  stays in the result as the view's push would store it;
- find and findLast read the element only once the predicate holds.

Callbacks that read the element keep the TypeError for a value their
parameter type cannot hold.
2026-10-25 19:36:55 -05:00
Chris Tate c46faa93b2 Merge base branch 2026-10-25 19:32:26 -05:00
Chris Tate 58a64ffb36 Discard the result of void generic methods called through class-backed interfaces (#2275) 2026-10-25 18:56:01 -05:00