Commit Graph
11 Commits
Author SHA1 Message Date
Chris Tate 82f5aa9608 Keep x86-64-v3 code from running slower than the baseline
- Worker programs run strip-mined typed-array loops in blocks of 128
  iterations when the program's code is compiled for x86-64-v3 or above,
  or with --cpu-dispatch. At that level the vectorized loop over bytes
  takes 128 iterations a step, so in blocks of 64 its vector code never
  ran.
- In --cpu-dispatch builds, the LLVM helper gives an x86-64-v3 copy the
  optimized code of its baseline copy when the v3 code has vectors of at
  most 128 bits, none wider, and no rounding or bit-count intrinsic in a
  loop. Its direct calls go to the callees' x86-64-v3 copies. The text
  partitioner places each x86-64-v3 copy with its baseline copy, so the
  two are optimized in one partition.
2026-10-29 20:21:41 -05:00
Chris Tate f3ed70667e Count the small roots a partition takes toward its cap
A root under one 4 KiB unit went to the first open partition from its
name's whose load from the larger roots was below the cap. When only one
open partition was below the cap, every such root went there, and that
partition could hold most of the module, which then was optimized whole
and lost the per-partition cache. The small roots a partition takes now
count toward its cap for the small roots after them, and a small root
that finds no partition below the cap goes to its name's. The larger
roots' placement still ignores the small roots, so a helper that an edit
adds or removes moves no larger root.
2026-10-29 14:57:26 -05:00
Chris Tate da6665a98a Keep partition placement stable when an edit adds a small helper
Roots under one 4 KiB unit, mostly runtime helpers, no longer count in the
partition loads that balancing compares. They are placed after the larger
roots, each in the first open partition from its name's partition whose
load from the larger roots is within the cap, so where a helper goes
depends only on the larger roots. A helper that an edit adds or removes
used to change the total and the cap and move several other roots, and
every partition they left or joined was compiled again.

A root's size also leaves out the digits of metadata references, which an
edit elsewhere in the module can renumber, so an unchanged function no
longer crosses a unit boundary because its references were renumbered.
2026-10-29 09:20:26 -05:00
Chris Tate 31a9be23a4 Give the entry module's top-level code a partition of its own
The emitter writes "; scriptc-entry" before the entry module's top-level
function (its %init). In modules of fewer than 32 partitions, the helper
places the roots that hold a marked function first, and their partitions
take no other root. A root that finds no partition with room goes to the
first open partition from its own.

An edit of the program's top level then recompiles only the top-level code
and the chain that follows it, not the other roots that balancing placed
beside it.
2026-10-28 13:57:13 -05:00
Chris Tate 007a144cb6 Balance program partitions by size 2026-10-28 05:12:02 -05:00
Chris Tate 05810305cf Compile small release and speed programs in partitions 2026-10-27 20:49:11 -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 6715609695 Place each source file's functions in the same partitions
Partitions were filled by a hash of each function's name, so most calls
crossed partitions and the thin link imported their small callees into
nearly every partition. The emitter now writes a "; scriptc-group" line
before the functions of each source file, and the text partitioner places
a group's functions together: a group larger than the mean partition, or
than 1 MiB, is divided into a power of two of chunks by name, and chunks
go, largest first, to the lighter of two partitions chosen by hash. The
1 MiB bound keeps the code a partition optimizes together, and the
inlining within it, from growing when a program has fewer partitions.
Modules with fewer than 32 partitions, and functions without a group,
keep their name-hash placement: with few partitions per core, the balance
of the largest partitions decides the build time more than the imports.
The placement still depends only on the module text and the partition
count.
2026-10-24 00:34:25 -05:00
Chris Tate 267b77c994 Partition programs with multi-line dispatch tables as text
The emitter writes interface dispatch tables one element per line. The
helper's text partitioner accepted only single-line globals, so any
program with such a table (every partitioned program with class
interfaces) fell back to parsing the whole module before splitting it
with LLVM. The text partitioner now reads a global whose aggregate
initializer opens at the end of its line and closes on a line that holds
only the closing bracket.

Those programs now parse their partitions concurrently and keep the text
partitioner's placement, so an edit to one function changes only that
function's partition again.
2026-10-21 01:08:13 -05:00
Chris Tate 0e67cd1e3b Place functions too large to inline by their own name
The helper keeps an internal function that exactly one other function
references in its referrer's partition, so the partition can inline it.
A program's module initializers are each called only from the entry
point, so all of them, with every single-use helper they call, shared
the entry point's partition. In the three.js nodes fixture that
partition held 1.1 million lines of LLVM IR, almost five times the
average, and set the native build's longest compile.

A function whose body is larger than 256 KiB of LLVM text is past
LLVM's inline cost limit even for the only call to a local function, so
it is now placed by the hash of its own name. Its own single-use
callees still stay with it.
2026-10-18 08:02:34 -05:00
Chris Tate 3ea231d70d Optimize and generate native code in parallel partitions
Large executables were simplified and inlined as one module before the
optimizer split them, so most of a release build ran on a single core,
and any edit recompiled the whole program.

The LLVM helper now divides the program before optimization. It splits
the emitter's assembly text directly, so partitions also parse
concurrently, and falls back to LLVM's module splitter for any input
outside the emitter's grammar. Internal functions referenced by a single
function stay with that function, and globals stay with their first user.
Optimized partitions follow the ThinLTO model: each is simplified and
summarized on its own thread, a thin link imports small functions across
partitions for inlining and returns unexported hidden definitions to
internal linkage, then each partition is optimized and compiled on its
own thread. Unoptimized partitions compile independently, so dev builds
of large programs are parallel too.

A private per-helper cache directory stores simplified partitions under
the hash of their source and objects under the hash of the module after
importing, so a rebuild recompiles only the partitions an edit can
affect. To keep unchanged partitions identical across edits, each
partition numbers its metadata in first-use order, lists only the debug
compile units it uses, and orders declarations by name, and interned
string constants are named by their content rather than first-use order.

Partition counts are powers of two that depend only on the module size,
up to 64, so output stays the same on every host and functions keep
their partition across ordinary edits. LLVM text that arrives in parts is
written in large batches and handed to the helper in place instead of
being written a second time.
2026-10-09 19:51:47 -05:00