Commit Graph
5332 Commits
Author SHA1 Message Date
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 771706cfbe Wait for the lookup connection's accept in 7515 before changing the server's mode (#2318) 2026-10-26 01:38:08 -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 a28afa0261 Find bridgePlainUnwindCalls' calls and mentions with indexOf (#2316) 2026-10-26 01:08:35 -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 2e142d6121 Find bridgePlainUnwindCalls' calls and mentions with indexOf
bridgePlainUnwindCalls rewrites the module text after emission on Linux.
It split the text with a lookahead regular expression, ran a regular
expression over every line to find mentions of raising functions, and
split every function that has calls into lines to test each one.

- The text splits before each `define` line with indexOf.
- Mentions of raising functions are found by scanning for `@` and reading
  the name that follows it.
- A function without a plain call of a raising function rewrites only the
  lines that hold `call `, marking them nounwind. A function with such a
  call takes the line-by-line path as before.

The emitted text is unchanged.
2026-10-26 00:40:44 -05:00
Chris Tate e2a22aa1bc Localize merged program shards in process on Linux (#2310) 2026-10-26 00:31:57 -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 11aa86d571 Cache named child nodes and text on AST nodes (#2314) 2026-10-26 00:26:48 -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 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 04c0e5c66e Cache named child nodes and text on AST nodes
The frontend reads the same children of the same nodes many times: on a
tsc-ts build, `expression` is read 29.6M times over 253K nodes, `name`
16.4M times over 176K and `text` 26.8M times over 456K. Each read went
through AstFile.namedChildAt (the file check, the child mask, the packed
child table) and AstFile.node (the node table), and `text` through the
wire's kind switch and string table.

- AstNode.childNodeAt keeps the child nodes it has read, by wire slot, in
  a per-node array sized to the node's highest named child.
- AstNode.text keeps the text it has read.
- AstFile's packed child table is a Uint32Array from the start (empty
  until the first named child is read), so namedChildAt no longer
  retains and releases an optional value on every call.

Cold tsc-ts build with the native compiler, 16 interleaved rounds: lower
31.68 -> 30.46 s, wall 78.81 -> 76.56 s (faster in every round); peak RSS
+105 MB, which the frontend-dispose heap trim keeps below main's peak.
2026-10-25 23:57:36 -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 ef6f8838d0 Address review feedback 2026-10-25 23:53:54 -05:00
Chris Tate e30700f9e0 Guard every x86 intrinsic header in the toolchain probe, and declare mkdtemp on macOS (#2313) 2026-10-25 23:44:47 -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 41957ec668 Update tsc-ts to 0.0.10 (#2297) 2026-10-25 23:39:19 -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 f2c53ff2f8 Build the bootstrap seed without the function layout
The native bootstrap compares the seed's LLVM, which the Node-hosted
compiler emits, with the LLVM the native compiler emits for itself. The
seed's build applied the committed function layout and the native
compiler has none, so the two listed the same functions in different
orders. The layout only orders functions, so the seed is built without it.
2026-10-25 23:32:41 -05:00
Chris Tate fc88e5c474 Return the freed heap pages to the system after the frontend is disposed (#2306) 2026-10-25 23:24:57 -05:00
Chris Tate 55c43d9f27 Guard every x86 intrinsic header in the toolchain probe, and declare mkdtemp on macOS
The implicit-toolchain probe includes every <...> header the runtime and
vendored sources name, each behind __has_include. clang ships its x86
intrinsic headers for every target, so on arm64 macOS __has_include finds
<emmintrin.h> (included by scr_bytes.c and scr_string.c), and including it
there is an #error. The probe guarded only <cpuid.h> and <immintrin.h> by
target, so on arm64 macOS it failed, native cache warming reported that it
could not validate the compiler toolchain, and builds ran without the
persistent cache. The probe now guards every x86 intrinsic header family
(*mmintrin.h, <x86intrin.h>, <x86gprintrin.h>) and <cpuid.h> by target.

ts7-process.test.c defines _POSIX_C_SOURCE, which hides mkdtemp on macOS;
it now also defines _DARWIN_C_SOURCE there, as host.c does.
2026-10-25 23:15:32 -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 a21f4f6823 Localize merged program shards in process on Linux
A dev build splits a large program into LLVM shards, compiles them in
parallel and merges the objects with ld -r. On a native Linux host the
merge then ran objcopy --keep-global-symbols to turn the shards' promoted
symbols back into locals. A sanitized build of three.js's node materials
merges into a 660 MB object with 278k sections, and objcopy spent 425 s
of a 616 s build on it. localizeElfObject, which cross-ELF builds already
use, does the same demotion in about 2 s.

- Program-shard merges for executables, library archives and packed
  libraries use localizeElfObject after ld -r on native Linux. Library
  runtime localization keeps objcopy.
- localizeElfObject reads and writes extended section numbering: with
  SHN_LORESERVE or more sections the counts go in section 0, and symbol
  section indices from SHN_LORESERVE up go in .symtab_shndx. Before, it
  truncated e_shnum and read such indices as reserved values.
- The Linux shard-merge cache identity drops objcopy.
2026-10-25 22:55:22 -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 307f9e0009 Keep one heap-trim test block per four pages of any size
The test kept one live 48-byte block every 1,000 allocations, which on a
system with 64 KiB pages leaves a live block on every page, so no page held
only free blocks and the resident-memory check failed. It now keeps one
block per four pages of blocks, from the system's page size.
2026-10-25 22:47:07 -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 fc66af1842 Print how a --profile-use build's profile matched (#2303) 2026-10-25 22:42:24 -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 5b672087c6 Return the freed heap pages to the system after the frontend is disposed
The native compiler's frontend-dispose sweep frees about 450 MB on a cold
tsc-ts build, but the size-class allocator keeps the freed blocks on their
classes' free lists, which only later blocks of the same size reuse. Code
generation allocates other sizes, so its pages stacked on top of the freed
heap and set the build's peak resident memory.

- scr_heap_trim (scr_alloc.c) returns the pages of the calling thread's
  size-class slices that hold only free blocks (MADV_DONTNEED) and the
  system allocator's free pages (malloc_trim). In the single-threaded
  allocator the blocks of a returned page leave the free list and the
  class carves past its frontier instead; the remaining free blocks are
  relinked in address order. Worker runtimes return the calling thread's
  arena pages as a parking thread does.
- scr_collect_cycles_phase, the full sweep after a phase's data died,
  calls it; the native compiler calls that sweep when it disposes the
  frontend.
- test_heap_trim.c checks that the pages go back, that the blocks still
  allocated keep their contents, and that new blocks overlap none of
  them, with and without worker arenas (another thread allocating and
  freeing remotely throughout).

Cold tsc-ts build, 12 interleaved rounds: peak RSS 1407 -> 1247 MB
(-160 MB, -11.4%, every round); wall 87.1 -> 85.9 s (median).
2026-10-25 22:36:11 -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 f591e8e6d7 Print how a --profile-use build's profile matched
`scriptc build --profile-use` prints one line on stderr with the number of
functions the profile annotated, the number whose control flow changed since
it was collected (mismatched), and the number it has no record of. When more
than 10% of the functions the profile describes are mismatched, it also prints
a warning to collect the profile again. The build still succeeds.

Both the compiler library's build and the native compiler's build report the
match. Cached builds report the match of the build that produced them: the
native codegen and object caches store it beside the cached objects, and the
executable cache stores it in its entry.
2026-10-25 22:14:25 -05:00
Chris Tate 828ea84dcc Relocate the prebuilt lib diagnostics path when installing the native command (#2300) 2026-10-25 22:12:41 -05:00
Chris Tate 7741f24cc8 Reserve the rc word's high half for layout ids (#2299) 2026-10-25 22:10:20 -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 161d15538e Map wide shape-union records to their arm through one table per union (#2298) 2026-10-25 21:53:56 -05:00
Chris Tate 23deb595ac Relocate the prebuilt lib diagnostics path when installing the native command
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.
2026-10-25 21:41:36 -05:00
Malte UblandClaude Opus 5.5 19f51465f3 Format the class id bound check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:37:58 -07: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 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 a67f7d13ea Map wide shape-union records to their arm through one table per union
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.
2026-10-25 21:23:47 -05:00