Commit Graph
304 Commits
Author SHA1 Message Date
Chris Tate c12f2d64f3 Keep the runtime pack's verified artifacts in a record beside the pack
Each build stages the runtime artifacts it links from the runtime pack and
hashes each one with SHA-256 unless the build cache records that it was
verified with the file identity it still has. A build with an empty cache
therefore hashed every artifact again: on Linux x64, 40 objects and 4.3 MB
for a Benchmarks Game program, about 25 ms of a 300-1100 ms build.

Staging now also reads and writes runtime-pack.verified.json beside the
pack. It lists each verified artifact with its digest and file identity
(device, inode, size, mtime and ctime), as the cache's record does:

- An artifact is trusted only when one of the two records lists it with
  the manifest's digest and size and the identity its file has during the
  read. Any change to the file means hashing it again.
- A missing, unreadable or malformed record, or a malformed entry, is
  treated as absent.
- The record is written to a new file and renamed over the old one, so a
  reader sees a whole record. A write merges the record as it is then.
- When the pack directory cannot be written, the cache's record is the only
  one, as before.

The runtime pack packages do not publish the file. The native CLI build
removes the record its probe builds leave in the bundled pack, and
verify-native-cli.mjs rejects a distribution that has one.
2026-10-29 11:11:35 -05:00
Chris Tate 10479ce2e0 Link the scriptc CLI without huge text
An executable with at least 4 MiB of program code is linked with 2 MiB
segment alignment and published with one write, so the page cache holds
its text in 2 MiB folios and the runtime advises huge pages for it. A run
then maps every 2 MiB text window it touches whole. Most runs of the
compiler are short: `scriptc --version` peaked at 76 MB and an unchanged
rebuild at 78 MB, against 20 MB and 26 MB with 4 KiB alignment, and cold
and edited builds of tsc-ts took the same time either way.

compile() takes an internal `hugeText: false`, which links with 4 KiB
alignment whatever the code size and is part of the executable cache key.
scripts/build-native-cli.mts passes it. Executables the compiler builds
keep huge text.
2026-10-29 01:01:21 -05:00
Chris Tate 269ef9f0bc Read each layout training profile when its build finishes
refresh-cli-layout.mjs kept the raw profile of every cold training build
until the end of the run, about 23 MB per build and 8.7 GB for the 399
builds. When the disk filled, the profile runtime wrote partial files, the
compiler still exited with 0, and reading a partial file threw a RangeError
past the end of its counters.

Each cold profile is now read and deleted when its build finishes, so the
work directory stays under 200 MB. The script throws when a training run
reports `LLVM Profile Error`, naming the build and the write error, and
when a profile is shorter than its header describes.
2026-10-28 22:43:27 -05:00
Chris Tate a484390e07 Merge main 2026-10-28 09:50:05 -05:00
Chris Tate d9481023c3 Annotate functions a stale profile misses from its block counts
A profile annotates a function only while its control flow hashes as it did
when the profile was collected, so compiler and source changes leave the
changed functions without counts. A --profile-use build now reads a
block-count file beside the profile (app.blocks beside app.profdata). Right
after the annotation before inlining, the helper aligns each uncounted
function's blocks with its recorded blocks by label kind and successor
count. Paired blocks take their recorded counts and their edges the recorded
edge counts, by target or, between targets whose labels changed, by
position. LLVM's flow inference fills in the rest, avoiding the edges the
function's own llvm.expect weights make unlikely. The helper then sets the
entry count, branch weights and hot or cold attribute as the profile
annotation does.

SCRIPTC_RECORD_PROFILE_BLOCKS names the file a --profile-use build writes
for the functions its profile annotates; such a build reuses no cached
artifacts. The block file keys every cache, the build report counts the
functions it annotated, and the staleness warning counts only the functions
left without counts. A single-module build with a context-sensitive profile
no longer counts annotated, mismatched and missing functions a second time
after inlining. scripts/refresh-cli-profile.mjs records the file in the
second phase and build-native-cli.mts reads it beside the profile.
2026-10-28 04:50:01 -05:00
Chris Tate 0325b921aa Compile pre-module constructor functions as classes and namespace functions as direct functions (#2175) 2026-10-28 03:57:06 -05:00
Chris Tate c9c8224aa2 Merge main 2026-10-28 02:45:59 -05:00
Chris Tate d5ac31f812 Raise through cached unwind plans on x86-64 Linux
scr_unwind_raise walks the stack with plans cached per return address.
The first raise through a return address finds the function's FDE
through the .eh_frame_hdr of the object that contains it, runs the CFI
up to the call, and finds the call site's landing pad in the LSDA; the
plan records the frame's CFA rule, where its callee-saved registers are
saved, and the landing pad. Later raises look the plans up, restore the
registers, and jump to the first landing pad. Plans are inserted under
a lock and read without one.

Landing pads continue the unwind with scr_unwind_resume instead of
`resume`, so the walk goes on from their frame. A frame whose CFI,
pointer encodings or personality the plans do not express, or a return
address without an FDE, leaves the unwind to libgcc's forced unwind
from the frame the walk started at, as before. Other Linux targets keep
libgcc's unwinder; their scr_unwind_resume calls _Unwind_Resume.

bench:runtime gains layered-errors, a workload whose errors are thrown
below small functions that hold references and caught per entry in a
loop, and the corpus gains unwind-landing-pad-chains, which pins landing
pads that continue the unwind, including in frames LLVM inlines into a
caller whose handler takes the exception.
2026-10-28 00:22:04 -05:00
Chris Tate 1278729267 Merge main 2026-10-27 23:21:09 -05:00
Chris Tate 93081ea679 Host the runtime's event loop on Node's libuv once Node loads (NI-3) (#1696) 2026-10-27 22:19:38 -05:00
Chris Tate 14e109a34f Merge main 2026-10-27 21:42:17 -05:00
Malte Ubl ad45f05ca8 Merge branch 'ni/i4-deferred' into ni/i5-shared-modules 2026-10-27 19:14:39 -07:00
Malte Ubl 8c4eb85122 Merge branch 'ni/i3-exports' into ni/i4-deferred 2026-10-27 19:14:34 -07:00
Chris Tate ca4a162e35 Merge main 2026-10-27 20:40:14 -05:00
Chris Tate 59a08406ac Merge main 2026-10-27 20:19:47 -05:00
Chris Tate 95e715febd Optimize the speed runtime units with a committed runtime profile
The LLVM helper's runtime-unit now optimizes a unit itself (--opt-level),
with IR instrumentation (--profile-generate) or a profile (--profile-use),
through the same pipeline as program code. The Linux x64 pack's speed
units are compiled to unoptimized bitcode and optimized there with
packages/runtime-pack-common/profiles/linux-x64-gnu.profdata.gz, so one
LLVM writes and reads every profile. Release and library flavors are
unchanged. The pack manifest records how the profile matched the units.

Code the runtime profile finds cold is optimized for speed, not size: a
runtime function the training programs rarely call can be hot in another
program. scr_message.c is optimized without the profile.

scripts/refresh-runtime-profile.mjs trains the profile on the runtime
benchmark workloads and, optionally, a tsc-ts checkout, and keeps only the
records of functions the instrumented runtime units define
(merge-profiles --keep-functions). Tests check that the profile still
matches the runtime and names only runtime functions.
2026-10-27 20:11:44 -05:00
Chris Tate 248030cdd8 Merge main 2026-10-27 19:47:24 -05:00
Chris Tate b16c739d34 Classify zero-cost gate differences instead of failing every runtime change (#2271) 2026-10-27 19:27:25 -05:00
Chris Tate d2595a4a70 Merge main 2026-10-27 18:49:24 -05:00
Chris Tate 8104795c17 Merge main 2026-10-27 18:15:25 -05:00
Chris Tate 08fbecb7e7 Excuse only reproduced differences as nondeterministic, and read vendor archive symbols
A program is nondeterministic only when every artifact that differs also
differs between two baseline builds; a deterministic difference in another
artifact still fails the gate. The runtime-only check now reads the symbols
of changed vendor archives as well as runtime objects.
2026-10-27 17:55:44 -05:00
Chris Tate 1a86f94239 Merge main 2026-10-27 16:45:14 -05:00
Chris Tate bb0513c03c Collect the native compiler's profile in two phases
`scripts/refresh-cli-profile.mjs --context-sensitive` runs the training twice. The second run uses a compiler that the first profile optimizes and that is instrumented again after inlining, and it merges those counts into the first profile. Builds with the result also order each function's blocks by the counts and split cold blocks off (context-sensitive profiles). `--base-profile` runs only the second phase. `scripts/build-native-cli.mts` builds such a compiler when `SCRIPTC_NATIVE_PROFILE_GENERATE` and `SCRIPTC_NATIVE_PROFILE_USE` are both set. The release workflow's cli-profile job collects the profile this way, with a 120-minute timeout.
2026-10-27 11:39:28 -05:00
Chris Tate 3f81117a2d Record each reachable-emit unit's lowering as plain data, checked by a serial mode (#2410) 2026-10-27 10:07:09 -05:00
Malte Ubl 195ed79f00 Merge remote-tracking branch 'origin/main' into perf/static-objects
# Conflicts:
#	tests/harness/npm.test.ts
2026-10-27 07:21:50 -07:00
Malte UblandClaude Opus 5.5 3a8aee738a Merge the restacked base into ni/i5-shared-modules
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:41:22 -07:00
Malte UblandClaude Opus 5.5 603fec49b9 Merge the restacked base into ni/i4-deferred
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:41:03 -07:00
Malte Ubl f9419b5d47 Merge remote-tracking branch 'origin/ni/1-libnode-loader' into HEAD
# Conflicts:
#	packages/compiler/src/index.ts
2026-10-27 05:40:18 -07:00
Malte UblandClaude Opus 5.5 4d5127944e Merge main into the zero-cost triage branch
Main moved the per-program report lines into zero-cost-compare.mjs and
added sharding and a baseline cache. The runtime-only and
nondeterministic verdicts now print through programLines, count as
compared in merged shard reports, and the runtime pack diff is skipped
when only recording a baseline cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:38:08 -07:00
Malte Ubl 7f0488a411 Merge remote-tracking branch 'origin/main' into perf/static-objects 2026-10-27 05:35:56 -07:00
Malte UblandClaude Opus 5.5 041565d7f2 Split the slowest sanitized suites by case and raise the sanitized bootstrap's stack
Under ASan, self-hosting-llvm-emitter, three-library, npm-static and npm
each take 20 to 40 minutes as one Vitest file, so a sharded run cannot
finish before them.

- self-hosting-llvm-emitter splits into its emitter-cases-and-programs
  and fixed-point halves; a shard builds the seed once for the halves it
  owns. three-library skips the optimization its shard does not own.
  Both put their tests in a describe block, which skipUnownedTest
  partitions.
- CI runs both in every shard's case-sharded side queue
  (scripts/ci-test-plan.mjs); pnpm test:sandbox case-shards them with npm
  and npm-static.
- npm.test.ts keeps a skipped placeholder in a shard that owns no
  program, so an empty shard does not fail the file.

The sanitized native-driver bootstrap ran its instrumented compiler with
the default 8 MiB stack. Its frontend phase overflows it (SC3004) on main
as well, outside CI's ulimit for the emitter phase. The test now raises
the soft stack limit to the hard one for every sanitized compiler process
it starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:32:27 -07:00
Chris Tate f65c5518bf Record each reachable-emit unit's lowering as plain data, checked by a serial mode
A unit's record holds its IR in the compact encoding, the entries it added
to the helper, lifted-function and other appendable lowerer tables, and the
reachability, generic demand, statement and flag events it made. A unit has
no record when it grew a table no record carries, changed a field, replaced
an entry it did not add, or emitted a diagnostic. Every lowerer collection
and scalar field is classified, and a test fails on one that is not.

SCRIPTC_LOWER_JOURNAL=serial records every unit and replaces its results
with the ones its record carries. scripts/recorded-lowering.mjs
(pnpm test:recorded-lowering) compares corpus programs' LLVM text with and
without the mode. SCRIPTC_LOWER_JOURNAL_DUMP=<dir> also turns the mode on
and appends each unit's record to <dir>/records.jsonl. Without either,
lowering is unchanged.
2026-10-27 06:43:19 -05:00
Chris Tate df77671e33 Merge main 2026-10-26 23:13:34 -05:00
Chris Tate 80b851d65f Write untyped stores into live records and keep one object across prefix casts (#2366) 2026-10-26 22:56:27 -05:00
Chris Tate 55cf42e9c0 Install ld.lld in the Sandbox toolchain so Sandbox links match CI (#2375) 2026-10-26 22:14:48 -05:00
Chris Tate da7aa5377a Key the gate's baseline cache on the linker clang finds (#2376) 2026-10-26 22:13:58 -05:00
Chris Tate ad29d598f5 Merge main into the untyped-store branch 2026-10-26 21:44:47 -05:00
Malte UblandClaude Opus 5.5 b4ba4d38ac Key the gate's baseline cache on the linker clang finds
Linux links use ld.lld when clang finds it and the system linker
otherwise, and the two produce different executables. A baseline record
made with one must not settle candidates linked with the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:43:02 -07:00
Malte UblandClaude Opus 5.5 9c42c8d74b Install ld.lld in the Sandbox toolchain so Sandbox links match CI
CI's pinned LLVM ships ld.lld, and the compiler links Linux executables
with it whenever clang finds it. The Sandbox image and the managed-image
bootstrap installed clang-22 without lld-22, so Sandbox builds linked
with GNU ld: static hello came out at 99 KB instead of 65 KB, and
runtime-tree-shaking failed only in Sandboxes. Install lld-22 in both
and check that clang finds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:40:54 -07:00
Chris Tate 2c09b499b2 Check a nested record of scalar fields by its own fields
The generator listed a field as a scalar when its walk read nothing, so
a nested record holding only numbers and booleans got a ValuesNotRead
check on the record itself, which fails because the record is an object,
even after regenerating. A field is now listed as a scalar only when its
type holds no string and no object; such a record is checked through its
own fields, so a field added to it or a scalar in it that starts to hold a
string still fails type checking with its name.

The generator exports generateIrFields(irText) for the new test, which
generates the readers for an ir.ts with such a record and type-checks
them. The generated file for the current ir.ts is unchanged.
2026-10-26 21:18:38 -05:00
Chris Tate 07f159accd Fail type checking when the generated IR field readers miss a field
fields.generated.ts reads IR nodes' fields by name, so a field ir.ts gains
after the last `pnpm generate:ir-fields` is skipped by the walkers until
someone regenerates the file; `--check` in CI reports it, but only on the
run that looks.

The generator now ends the file with type-level checks, one per node kind
and per nested record the walkers enumerate: a field outside the list the
file was generated from fails type checking with the field's name, and so
does a field read as a scalar (no string, no object) that can now hold a
string or an object. Fields the file reads already fail type checking when
they are removed or change type. The checks are types only and emit no
code.
2026-10-26 21:06:07 -05:00
Chris Tate e58def0299 Update tests for the record-ops store hook and the untyped-writes benchmark
- forin-keys.test.ts matches the record-ops table with its eighth field, the
  store hook, which is null for a record no untyped code writes.
- bench/record-untyped-writes writes typed records through `unknown` and
  `Object.assign` views, so it links dynamic-tier symbols; DYNAMIC_WORKLOADS
  lists it.
- The sanitized self-hosting emission test gets a 600 s timeout. It takes
  about half of the default 300 s on an idle 16-core host, with or without
  these changes, and exceeded the default on a loaded CI shard.
2026-10-26 21:04:31 -05:00
Chris Tate cd606e96ff Key baseline cache entries by their program's inputs
A recorded baseline entry now holds a hash of its program's inputs: the
entry file and its relative imports (paths and contents) and the
optimization level. --baseline-cache uses an entry only when the program
still hashes the same, so a program or an import that changed since the
record builds its baseline, instead of a recorded build error or artifact
hash of the old sources settling the comparison as both-fail or identical.
Records take schema 2; older records are ignored.
2026-10-26 19:53:49 -05:00
Malte UblandClaude Opus 5.5 d0047c813f Shard the zero-cost gate and reuse baseline work across runs
The full gate takes 30 to 75 minutes on one Sandbox, which made it the
longest step of a validation round. It can now be split and merged:

- --shard=i/n splits the bench and corpus programs (the self-hosted
  compiler runs in shard 1, or alone with --set=self), and
  scripts/zero-cost-merge.mjs merges the shards' --json reports into one
  verdict, recomputing the compile-time intervals over every shard.
- --record-baseline-cache records the baseline's artifact hashes once;
  --baseline-cache skips the baseline build of every program whose
  candidate artifacts hash the same. The record is tied to the built
  baseline checkout, toolchain and paths.
- The candidate-source self build overlaps the comparison builds on
  hosts with room for it.

pnpm test:sandbox can reuse a prepared snapshot's LLVM helper and runtime
pack (SCRIPTC_SANDBOX_NATIVE_REFERENCE) when their inputs are identical,
and retries rate-limited Sandbox creation with backoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:43:44 -07:00
Chris Tate 2b8873bb92 Keep the native compiler's profile matching its functions (#2357) 2026-10-26 18:07:49 -05:00
Malte Ubl 60ed8cb2ba Merge remote-tracking branch 'origin/ni/i4-deferred' into HEAD 2026-10-26 16:05:36 -07:00
Malte Ubl 30c01c4ba9 Merge remote-tracking branch 'origin/ni/i3-exports' into HEAD 2026-10-26 16:05:09 -07:00
Malte Ubl d245796462 Merge remote-tracking branch 'origin/ni/3-hosted-loop' into HEAD 2026-10-26 15:58:16 -07:00
Malte Ubl e99d3445bf Merge remote-tracking branch 'origin/ni/1-libnode-loader' into HEAD 2026-10-26 15:57:24 -07:00
Chris Tate 5ab76a4d54 Keep the native compiler's profile matching its functions 2026-10-26 15:31:14 -05:00