Commit Graph
3758 Commits
Author SHA1 Message Date
Chris Tate acb2d23df5 Attribute slot causes to the owner only for types without their own slot 2026-10-26 07:59:53 -05:00
Malte Ubl 572e7e3eac Merge remote-tracking branch 'origin/lf/l4-expressions' into lf/l5-polish
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-10-26 05:37:00 -07:00
Malte UblandClaude Opus 5.5 ec9624997a Attribute the dynamic pieces of pushed-down types in SC6101 and the slot operation counts
A pushed-down slot's dynamic operations act on its failing components,
whose checker types are not degraded themselves. The lenient mapping
records those components with the declared type they belong to; SC6101
names them ('hook' is a dynamic piece of 'Plugin[]'), and the operation
counts go to the owning slot. Causes read a binding's declared type, not
its narrowed flow type. The coverage test uses its own fixture, so it does
not depend on which interface members main can map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 05:35:58 -07:00
Chris Tate 75e48ddec3 Merge lf/l3-push-down 2026-10-26 06:55:57 -05:00
Chris Tate 92e42f7c6f Merge lf/l2-declarations and main, and fix self-hosting 2026-10-26 06:24:09 -05:00
Chris Tate 2ad699c98a Compile classes that extend Array (#2059) 2026-10-26 03:57:16 -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 7183a76422 Address review: missing strings in bindings and calls, evaluated recursive flags
- A string held in a binding or returned by a call can be a missing element
  read (undefined), so `head + PAD[j]` and `at(i) + PAD[j]` still add to NaN
  when both are missing. Only string literals, templates and sums with such
  an operand now count as always a string.
- `readdirSync(path, { recursive })` folds only the `true` and `false`
  keywords. Any other flag expression, including one typed as a literal,
  evaluates at run time for its effects and exceptions.
2026-10-26 02:42:04 -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 035d618517 Store a table entry for if/else chains that store literals to one element (#2317) 2026-10-26 02:21:51 -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 e03afc25f3 Add range-proof regression cases for constant-choice tables 2026-10-26 01:51:53 -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 60bd974744 Wait for the lookup connection's accept in 7515 before changing the server's mode
7515-net-socket-backpressure's server picks each connection's handling from
`mode` when it accepts it. The lookup client's 70000 bytes fit in the
kernel's buffers, so its 'drain', and the step that sets `mode` to "pause",
can run before the server accepts that connection. On macOS the accept can
come in a later kqueue batch: the server then paused the lookup socket,
never read its EOF, and the open connection kept the program running after
server.close(). The program now waits for that accept before it changes
`mode`. Its output is unchanged.
2026-10-26 01:10:20 -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 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 d60698695f Read an ignored element before its callback runs
The element-independent callback paths read the slot after the callback:
- filter appended the slot as it was after the predicate;
- find and findLast read the element only once the predicate held.
A predicate that overwrote or removed the current slot therefore kept or
returned the new value, where JavaScript keeps the element it read before
calling the predicate. An unfused filter over proven-present elements also
stored the placeholder it passed to a predicate with an unused element
parameter.

- filter, find and findLast keep a snapshot of the slot, read before the
  callback runs (arraySlotSnapshot): the value, or the dynamic value of an
  element outside the element type. filter appends that snapshot as it
  was stored, and find returns it, with the checked conversion for such an
  element. Fused loops take the same snapshot before a stage whose
  callback ignores the element.
- Leaving an ignored element unread now applies only to arrays that can
  hold elements outside their element type (elementMayBeForeign) and are
  not proven present. Every other array reads the element as before, so
  its filter stores the element it read.
2026-10-25 23:56:15 -05:00
Chris Tate 4a39f8e1b7 Address review feedback 2026-10-25 23:55:18 -05:00
Chris Tate 6817a24581 Merge lf/l0-report and main 2026-10-25 23:49:52 -05:00
Chris Tate a8080733df Build the bootstrap test's own seed without the function layout
The bootstrap test builds its own seed when CI shares none, and that build
still applied the committed function layout, so its LLVM listed functions in
a different order from the native compiler's own emission. Both seed builds
now share one environment, which leaves the layout out.
2026-10-25 23:42:51 -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 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 766a732f8e Initialize a forward-captured binding's box when its statement lowers again
A binding that a function declared earlier captures is pre-declared as a
TDZ box, and its declaration's assign initializes the box. The declaration
dropped the pre-declaration before lowering its initializer. When the
initializer failed with a degradable type (a cast whose type does not
compile) and the statement commit point lowered the statement again, the
retry no longer saw the box: it declared a new local and initialized that,
and a later call of the function threw ReferenceError.

The pre-declaration is now dropped after the initializer lowers, for
variable, class and generic-function declarations.
2026-10-25 23:21:20 -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 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 5679a06caa Report which type failures localized failure would turn into dynamic slots (#2284) 2026-10-25 22:31:15 -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
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 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 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 59e78f298a Name the package declaration and its patch when a declared-type check fails (#2268) 2026-10-25 21:20:34 -05:00