Commit Graph
12 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 640001b260 Generate code concurrently for optimized executables that stay one partition
Optimized executables with at most 1 MiB of LLVM text were compiled as one
module on one thread. With more than 128 KiB of LLVM text they now pass
several outputs and `--split codegen` to the LLVM helper. The helper
optimizes the module whole, as for a single output, then divides its
functions in module order into runs of about the same instruction count and
generates code for each run on its own thread.

- The output count is 4 or 8 and depends only on the module's size
  (nativeCodegenParts), so artifacts never vary with the host. Node and
  native compiler builds pass the same outputs.
- The helper writes the optimized module to bitcode once with its use-list
  order. Every output after the first parses it and turns the other
  outputs' definitions into declarations; the first output does the same to
  the optimized module in place. Each function's machine code matches a
  single-output build except for jumps to a definition that another output
  also uses.
- Only local definitions that another output uses become hidden. Dead
  constant users are dropped before unused declarations are removed, so no
  output names a thread-local variable it does not use.
- When one run would hold more than three quarters of the instructions, the
  first output is generated from the optimized module as for a single
  output and the others are empty objects.
- Dev builds, partitioned builds and program-object validation are
  unchanged.
2026-10-27 00:22:17 -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 4c3a1d9c98 Link the Linux release helpers against LLVM built with ThinLTO and a helper profile
The Linux helpers linked LLVM 22.1.8's release archive. The helper's code
generation is LLVM's code, so building that LLVM from its release sources
with ThinLTO and with a profile of the helper's own work makes code
generation 5-9% faster than the helper as released, with identical objects.

- native/llvm-codegen/llvm.profdata.gz is the profile, collected by
  scripts/refresh-llvm-profile.mjs: it builds LLVM with IR instrumentation
  and an instrumented helper, and runs that helper on tsc-ts, five Effect
  fixtures, the runtime benchmark workloads and every 50th corpus program
  (scripts/llvm-helper-inputs.mjs). It follows LLVM's sources, so it needs
  a refresh when LLVM is bumped.
- scripts/build-llvm-dist.mjs builds LLVM with that profile and ThinLTO,
  and packs it with a checksum. The llvm-dist workflow, run by hand, builds
  it for x64 and arm64, requires a helper linked against it to give the
  same objects as one linked against the release archive
  (scripts/check-llvm-dist.mjs), prints both helpers' times and the
  profile's mismatch count, and attaches the archive to the LLVM_DIST_TAG
  release of this repository.
- The release workflow's Linux jobs, and the wasm32-wasi job, which also
  uploads the x64 GNU helper, download that archive and build the helper
  with SCRIPTC_LLVM_THINLTO=1: clang, lld and ThinLTO, and the profile for
  the helper's own sources. Without the archive, or if that build fails,
  the helper links the release archive as before.
- The four Linux helper packages share their build in
  native/llvm-codegen/linux-helper.mjs. Without SCRIPTC_LLVM_THINLTO it
  builds the same executable as before.
2026-10-25 18:53:25 -05:00
Chris Tate 65555dace3 Drop struct type names before the thin link instead of renaming promoted locals
The partitions of a program build have no locals for ThinLTO to promote:
symbols that other partitions refer to are already hidden. What changed
the modules after importing, and with them the object cache keys, of
partitions an edit did not touch was the numbering of struct types. Every
partition declares the program's types under the same names, and the thin
link loads the importing partition and the partitions it imports from into
one context, which tells same-named types apart by appending a counter
that runs across the whole link. An edit that adds a type anywhere shifted
the counter.

Partitions now drop the names of their struct types before their summary
is written; importing matches unnamed types by their bodies. The
promoted-locals option, the per-partition source file name and the
partition index in the summary key are removed.
2026-10-24 17:47:58 -05:00
Chris Tate 9e4f1253c6 Keep unchanged partitions cached across edits 2026-10-24 10:11:29 -05:00
Chris Tate fc38e62791 Stop importing runtime bitcode into program builds 2026-10-21 14:10:23 -05:00
Chris Tate 588e16d8c7 Skip WebAssembly irreducible control flow analysis on reducible functions
LLVM's WebAssembly pass that gives multiple-entry loops a single entry
first records, for every block, every block it can reach. Its time and
memory grow with the square of a function's block count even when the
function has no such loop. In the three.js nodes fixture a module
initializer of 40,000 blocks spent four minutes and 22 GB in it, which
set the helper's peak and most of its wall time.

The helper now runs the pass only on functions whose control flow graph
is irreducible, and WebAssembly object emission leaves out the module
pass that collects external symbols for assembly declarations, so each
function's machine code is freed once it is printed. Optimized
partitions are compiled through the thin backend's pre-codegen hook to
use the same pipeline. Objects are unchanged.
2026-10-18 01:30:29 -05:00
Malte Ubl f4ce6910c7 Inline small runtime functions into release executables
Release executables now import small runtime functions from runtime
bitcode, as speed executables do, so LLVM can inline them into program
code. Release units keep the runtime compiler's own objects, unchanged,
and additionally ship the bitcode of the same compilation reduced by the
helper's prune-import. Their unit-local symbols stay local, so only
bodies that need none of them can be imported, which keeps the import
small: about 1.3 MB per program.

Only unsanitized programs small enough for one codegen partition import.
Every partition reads the bitcode again; on the Effect fixtures (64
partitions) import cost +2..18% compile time and +4.3..4.9% size, so
partitioned release builds stay exactly as before. Speed is unchanged.

Measured against main in Linux Vercel Sandboxes, same-VM interleaved
medians (bench-runtime --layouts=4 --runs=16), on two VMs:

- bench-runtime (31 workloads): -1.3% (8 faster, 0 slower) and -1.0%
  (1 slower, typed-array-scan-workers +3.2%); size +0.0%
- js-compiled suite (22 programs): -2.2% and -1.8%; size -0.1%
- cold builds of all 53 programs, interleaved: +3.1%; helper time per
  build +40..235 ms
- Effect fixtures: byte-identical to main
2026-10-16 14:03:52 -07: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
Malte Ubl 60c30d6ebd Import runtime bitcode into speed programs (H02)
Re-lands perf/h02-runtime-lto (6d373317, b62448f3) behind the speed
posture.

- Runtime packs gain an executable `speed` flavor whenever the host LLVM
  helper can run. Its static units are emitted through the helper's
  `runtime-unit` command (promoted unit-local symbols, object and import
  bitcode from one module); its SCR_DYNAMIC variants reference the release
  objects. Release, dev, and library flavors and the vendor archives are
  built exactly as before (the rebuilt macOS pack's flavors and archives
  are identical to the baseline's).
- Speed program builds load the speed flavor's bitcode and pass it to the
  helper (`--import-bitcode`); release builds never do.
  loadRuntimeBitcode only reads a real speed flavor, never release.
- The helper drops the emitter's inert sanitize_address attributes only
  when it imports, so release modules reach the pipeline unchanged.
- Tests: runtime-pack.test.ts covers speed-variant bitcode selection,
  verification, and that release-flavor bitcode is never imported;
  native-codegen.test.ts covers helper arguments and cache keys.

Release executables stay byte-identical to the stack tip with the rebuilt
helper (14/14 workloads, macOS).
2026-10-08 15:46:34 -05:00
Chris Tate 401338f437 feat(compiler): add native LLVM object emission (#242)
* feat(compiler): add native LLVM object emission

- ship the pinned macOS arm64 LLVM helper and platform package
- add assembly/object CLI outputs, caching, diagnostics, and runtime ABI checks
- validate helper artifacts through CI, packaging, docs, and differential coverage

* fix(compiler): harden native LLVM emission

* fix(compiler): harden LLVM helper packaging

* fix(compiler): stabilize LLVM helper package size

* fix(compiler): harden native helper validation

* fix(compiler): harden LLVM helper verification

* fix(compiler): harden native object emission
2026-08-27 10:30:40 -05:00