Commit Graph
2932 Commits
Author SHA1 Message Date
Chris Tate a59df23c71 Merge main 2026-10-26 07:21:12 -05:00
Chris Tate 636c7631b3 Keep program partitions local to the code an edit changes (#2328) 2026-10-26 06:35:05 -05:00
Chris Tate 067f146e87 Read builtin globals through the global object, and support Node's navigator (#2242) 2026-10-26 06:16:08 -05:00
Chris Tate 66b6ca53ce Load a Node module's fallback declarations only when the program names it (#2256) 2026-10-26 05:37:36 -05:00
Chris Tate b8aaa5985a Merge main 2026-10-26 05:23:00 -05:00
Chris Tate 2d2343e36f Keep program partitions local to the code an edit changes 2026-10-26 05:03:24 -05:00
Chris Tate 4553941d84 Merge main 2026-10-26 04:25:15 -05:00
Chris Tate ddf5b1f8e0 Keep the integers of typed-array reads in locals for indices and loop guards
A local that only holds exact integers or NaN, such as one assigned from
an integer typed-array read, now keeps whether it is NaN beside its
integer view. Its reads, and sums, differences and products of them,
carry an i64 that holds the integer whenever the number is not NaN, so
an index check or a counted loop's guard tests that integer and the
ordered bit instead of converting the double back and checking that it
is an exact integer.
2026-10-26 04:08:25 -05:00
Chris Tate c66ce24a73 Fix Array.prototype calls on native arrays, typeof on ambient names, and net connections to Unix sockets
Fixes #2116
Co-authored-by: 4ndual <44326858+4ndual@users.noreply.github.com>
Fixes #2134
Co-authored-by: dolphin278 <1130473+dolphin278@users.noreply.github.com>
Fixes #2142
2026-10-26 03:57:37 -05:00
Chris Tate 2ad699c98a Compile classes that extend Array (#2059) 2026-10-26 03:57:16 -05:00
Chris Tate ae3d388869 Merge main 2026-10-26 03:53:02 -05:00
Chris Tate e289b03ddc Keep int32 fields in programs with adopting casts (#2324) 2026-10-26 03:27:40 -05:00
Chris Tate eec525dc6a Fix eight compiler and type bug reports (#2319) 2026-10-26 03:10:55 -05:00
Chris Tate 6c1ddbf390 Keep int32 fields in programs with adopting casts 2026-10-26 02:57:52 -05:00
Chris Tate acf19e2652 Merge main 2026-10-26 02:53:26 -05:00
Chris Tate 7a8ec3f3d2 Hold locals and module globals whose declared type does not compile as checked dynamic slots (#2292) 2026-10-26 02:49:06 -05:00
Chris Tate f52806988d Read typed-array header fields once at a speculative loop's entry (#2321) 2026-10-26 02:33:23 -05:00
Chris Tate eaa8a44cdf Read typed-array header fields once at a speculative loop's entry
A speculative integer loop's fast copy exits to a copy that resumes the
failed iteration, and both read the header fields of the views they
access. GVN's load PRE moved the fast copy's read of a view's data
pointer ahead of the exit branch, to make the resume copy's read
redundant, and put a copy on the exit edge. The loop then kept its first
iteration in the loop, so the overflow checks of a value whose range is
known after one iteration stayed in every iteration, with a reload of the
data pointer.

A view held by a const local or a parameter that the loop does not rebind
is the same view in every iteration, and its length, data pointer and
shared storage never change. The loop's entry now reads them once, and the
element accesses of every copy use those values.
2026-10-26 02:02:46 -05:00
Chris Tate 055d1f9682 Fix eight compiler and type bug reports
Co-authored-by: dolphin278 <1130473+dolphin278@users.noreply.github.com>
Fixes #2139
Fixes #2131
Fixes #2132
Fixes #2133
Fixes #2140
Fixes #2141
Fixes #2159
2026-10-26 01:50:59 -05:00
Chris Tate 5d62d5a5f1 Read a constant choice's index as on every path when it is the compared local
The store a constant choice emits for its arms used the first arm's
index expression, with the ranges proven inside that arm. When the index
is the compared local, `j === 0` narrows it to [0, 0] there, so the one
store, which also runs on the final else's path, converted the index to
an int32 from that range. For `j` = 4294967297 that conversion has no
defined result, and the bounds check that follows could accept it: an
out-of-bounds write could reach a valid element.

The store now reads such an index through the first comparison's
operand: the same local, read before the chain branches, so its range
holds on every path. An index that is another local is not narrowed by
the comparisons and is unchanged.
2026-10-26 01:47:16 -05:00
Chris Tate 70eac263f7 Merge lf/l1-whole-slot 2026-10-26 01:43:44 -05:00
Chris Tate 33be2a80ee Store a table entry for if/else chains that store literals to one element
An if/else-if chain that compares one number local with integer literals
and stores a number literal to the same typed-array element in every arm,
including a final else, such as

    if (x === 97) a[i] = 0;
    else if (x === 116) a[i] = 1;
    else if (x === 99) a[i] = 2;
    else a[i] = 3;

is now emitted as one store of an entry of a constant table indexed by
the local's value (constant-choices.ts). On input whose cases do not
repeat in a pattern, such as the bases of a DNA sequence, the branches
mispredict; LLVM keeps them, because cases this far apart are too sparse
for its own switch tables.

The chain needs at least three distinct values within 256 of each other.
The table covers the cases' span, or the local's proven integer range
when that range has at most 256 values, in which case no test is
emitted. Otherwise a value that is not an exact integer in the table's
span (NaN, a fraction, or a value outside it) stores the final else's
literal, chosen with a select. The first arm wins for a repeated
value, and -0 selects the arm for 0, as strict equality does. The store
is the arms' own (the same receiver and index), converted to the
element's type as each arm's would be. Debug builds keep the arms.

k-nucleotide's transform of the input sequence uses this form: its
median wall time drops from 2.71 s to 2.21 s.
2026-10-26 01:04:42 -05:00
Chris Tate 2a0d115629 Keep a candidate's generation when a limited cycle pass reaches it (#2315) 2026-10-26 00:55:26 -05:00
Chris Tate 2d24d6ad80 Merge main 2026-10-26 00:41:23 -05:00
Chris Tate 77f65a7264 Skip the element read for array callbacks that never use it (#2286) 2026-10-26 00:29:20 -05:00
Chris Tate 7e3d10fa13 Lay out the native compiler's functions by the code each kind of run executes (#2265) 2026-10-26 00:29:02 -05:00
Chris Tate feb4501013 Keep a candidate's generation when a limited cycle pass reaches it
A limited pass (scr_cyc_collect_set, which freeing a LifetimeRegion runs
over the objects the region owned) takes only the candidates in its set.
Its walk can still reach other candidates, and one that is referenced from
outside is blackened by scan and promoted to the next generation while it
keeps its slot in its old generation's buffer. scr_cyc_on_dead finds a
candidate's buffer by its generation, so when that object died it removed
another buffer's entry and left its own slot pointing at the freed block,
which the next pass then read.

Such a candidate is no longer promoted: it keeps the generation of the
buffer it is in until a pass takes it.
2026-10-26 00:24:05 -05:00
Chris Tate e7b60a5302 Read and write shared typed-array elements at a counted loop's counter with plain accesses (#2309) 2026-10-26 00:23:15 -05:00
Chris Tate 21bce9e4fa Merge main 2026-10-26 00:21:24 -05:00
Chris Tate b090950ffe Read undeclared literal keys as undefined and keep filled-in JavaScript vars dynamic (#2311) 2026-10-26 00:08:06 -05:00
Chris Tate ef6f8838d0 Address review feedback 2026-10-25 23:53:54 -05:00
Chris Tate 6817a24581 Merge lf/l0-report and main 2026-10-25 23:49:52 -05:00
Chris Tate 801444fc30 Keep optional chains out of the undeclared-key absence probe
A presence test of a literal key that a record shape does not declare
read the receiver as the record and answered undefined. In an optional
chain that conversion runs before the chain can short-circuit, so
`missing?.["a;b"] === undefined` threw a TypeError for a missing value
stored dynamically, where Node answers true. An optional read keeps the
optional chain's lowering, which reads a nullish receiver as undefined
and the undeclared key of a present one as undefined.
2026-10-25 23:37:53 -05:00
Chris Tate 13757c7da3 Add the canonical type normalizer, not yet wired into mapping (#2305) 2026-10-25 23:36:57 -05:00
Chris Tate 9847375966 Run registered classes' static initializers in place and keep retried statements' diagnostics
- 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.
2026-10-25 23:22:35 -05:00
Chris Tate 2859472f3a Withdraw failed assumptions and compare type arguments in canonEqual
- A comparison records the pairs it assumes equal in order, and when it
  fails, withdraws every pair assumed since it began. Before, a failed
  attempt (one union arm against a non-matching arm) stayed assumed, and a
  later comparison of the same pair answered true.
- Nominal objects carry their type arguments, normalized lazily, and
  compare them; canonKey prints them. Before, instantiations of one
  generic class compared equal by name, so a union of arrays of two
  instantiations lost an arm.
2026-10-25 23:06:10 -05:00
Chris Tate 42e5d28684 Read undeclared literal keys as undefined and keep filled-in JavaScript vars dynamic 2026-10-25 23:05:10 -05:00
Chris Tate 141d612907 Keep every field of a record converted into a union arm (#2304) 2026-10-25 22:54:30 -05:00
Chris Tate 43d207ce38 Compare canonical types structurally and keep tuple flags in the normalizer
- The intern key names bound type parameters by identity, not by name, so
  environments that bind two parameters called T to swapped types no
  longer share a node.
- Type equality (intersection constituents, union arms) is a structural
  comparison, canonEqual, that compares signature parameters, index and
  construct signatures at any depth and closes on recursive types.
  canonKey no longer serves as equality, and now prints parameter types,
  construct and index signatures.
- An object with index or construct signatures is not the empty object of
  a narrowed parameter, so a dictionary intersected with named properties
  keeps its index signature.
- Arrays and tuples enter the intern table before their elements are
  normalized, so recursive array and tuple aliases close on themselves.
- Tuple elements read their optional and rest flags from the tuple's
  element flags.
2026-10-25 22:52:33 -05:00
Chris Tate feb9877f1a Read and write shared typed-array elements at a counted loop's counter with plain accesses
In a program with workers, every element of a view that may be backed
by a SharedArrayBuffer was read and written with relaxed atomics, so that
a loop which reads one element again sees another thread's write. LLVM
does not vectorize loops with atomic accesses: a byte loop over a shared
Uint8Array ran at 8.5 ns per element in a program with workers and 0.14
ns in one without.

In a counted loop that polls for cancellation between blocks of
iterations, an element read or written at the loop's counter, outside any
loop nested in the body, is now a plain load or store, like a
thread-owned view's. The counter steps by a nonzero constant and the body
does not assign it, so every iteration reaches a different element and
no read is merged across iterations or moved out of the loop. Within one
iteration, a racing write from another thread can be seen or missed, the
documented gap of non-atomic shared accesses. Other accesses, Float16
elements and Atomics keep their atomic paths.

Corpus 8332 runs these loops over shared views of each element type on a
worker and the main thread, and matches Node's output.
2026-10-25 22:44:42 -05:00
Chris Tate b01a86f557 Keep one identity through checked casts of dynamic values (#2296) 2026-10-25 22:41:00 -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 89db5d47f7 Merge main 2026-10-25 22:02:35 -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 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
Malte UblandClaude Opus 5.5 53ebf05ff5 Add the canonical type normalizer, not yet wired into mapping
Stage N0 of the localized-failure design (part 2, canonical type
normalization), report-only.

canon(type, env) resolves a checker type under generic bindings to a small
set of canonical shapes: primitive, literal, unit, top, object (nominal for
classes and standard-library interfaces, structural otherwise, with
members resolved lazily through the checker), array, tuple, flat union,
unbound parameter, and an opaque leaf. Intersections whose constituents
agree, or that are all object-like, resolve to one object; a branded
primitive is the primitive; T & {} drops T's unit arms. Type parameters
substitute from the environment, so a generic alias spelled over a generic
body's own parameters resolves without asking the checker to instantiate,
and the alias reference keeps the original checker type. Nodes intern by
type and bindings, so recursion closes on the node under construction.

Nothing calls it yet: N1 runs it in shadow mode beside mapType.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:35:12 -07: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 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