Commit Graph
315 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 0d27008899 Classify zero-cost gate differences instead of failing every runtime change
Any change to the runtime's C sources makes every executable differ, so
the gate failed even when every program's IR and LLVM were byte-identical.
The gate now classifies each difference outside --allow:

- runtime-only: IR and LLVM identical, the same LLVM helper, runtime pack
  objects that differ between the checkouts (bitcode alone records the
  checkout path, so it does not count), and every symbol the executable
  gains, loses or resizes defined by a changed runtime object. It passes
  with a note that lists the changed runtime functions the program links
  (from the release and speed bitcode, through llvm-dis) and the size
  change.
- nondeterministic: the baseline builds the program again, and its two
  builds differ in the same artifacts. It passes with a note.
- differs: anything else; it fails as before.

--strict fails runtime-only and nondeterministic programs too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:26:18 -07:00
Malte UblandClaude Opus 5.5 1733a4c84b Name new corpus programs instead of numbering them
Parallel changes kept taking the same next corpus number (6992 twice, the
Node island programs renumbered several times). The harness never needed
the numbers: it discovers every entry by glob, and 818 programs already
have descriptive names. AGENTS.md now makes a name, without a number, the
convention for new programs and keeps the numbered ones as they are.

The two scripts that sampled programs by number skipped named programs.
They now key a named program by a stable hash of its name, so they sample
it at the same rate; the numbered programs they pick are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:17:09 -07:00
Malte UblandClaude Opus 5.5 7bc082da29 Give every numbered registry one source of truth and a check
Parallel branches kept picking the same numbers: dyn handle tags, SC
diagnostic codes, and the values the LLVM backend copies from runtime
enums.

- Diagnostic codes: codes.ts lists every active, retired and reserved code.
  ScrDiagnostic.code is typed by it, and codes.test.ts fails on a code
  without a row, a retired code in use, an unused row, or rows out of
  order. The Node island codes open PRs already use are reserved.
- Dyn handle tags: a new tag is added by name to SCR_DYNH_NEW_TAGS and the
  enum numbers it after the largest pinned value, so branches never pick
  a number. The pinned values are unchanged; value 63, never assigned, is
  listed as unassigned, and the test fails on any other gap.
- The backend's copies of ScrDynKind, ScrMessageKind, ScrBytesElem, the
  closure function_kind bits (now named SCR_CLOSURE_KIND_* in C) and the
  %ScrArr spare flags are checked against their C declarations.
- scripts/registry-claims.mjs lists the codes and pinned tags open PRs
  claim and reports collisions; it finds handle tag 81 claimed by three
  open PRs today.

Every numeric value is unchanged, so IR, LLVM and executables are
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:14:59 -07:00
Chris Tate 3c057b501f Lay out the native compiler's functions by the code each kind of run executes
A warm rebuild ran about 3.5 MB of the native compiler's code, but the code
was spread over its 69 MB text, so 64 KB fault-around mapped about 20 MB of
it. The emitter can now take a function layout: a list of functions in
tiers. It writes the listed definitions, generated helpers included, first
and by tier, each in a `layout-<tier>` partition group. The LLVM helper
compiles each partition's definitions in text order, so the listed ones
come first in their partitions, and a helper that places functions by
group keeps a tier's definitions in the same partitions. The rest keep
their order and their source-file groups.

- compile() takes a `functionLayout` file (`<tier> <symbol>` lines,
  gzip-compressed when the name ends in .gz). Keys leave out module
  numbers.
- build-native-cli.mts uses the committed packages/compiler/native/cli.layout.gz,
  or the file SCRIPTC_NATIVE_LAYOUT names; an empty value and instrumented
  builds use none.
- scripts/refresh-cli-layout.mjs builds an instrumented compiler, builds
  the runtime workloads and a corpus slice cold and then unchanged, and
  reads the functions each run executed from the raw profiles. Tier 1 is
  `--version` and rebuilds that hit both caches; tier 2 is the other builds.
- The release workflow's cli-profile job also collects a layout on the
  release commit, and the CLI builds use it. Names of closures and
  generated helpers change with nearby edits, so a committed layout
  matches fewer functions as main moves on.
- Forked lowering and emission workers reset the profile counters after
  fork and write their profile before _exit, so instrumented builds also
  count the work done in workers. This applies to refresh-cli-profile.mjs
  too.

Executables the compiler produces are unchanged.
2026-10-25 16:44:46 -05:00
Chris Tate 27954618c7 Merge base branch 2026-10-25 16:31:56 -05:00
Malte Ubl cbede2e5fd Merge remote-tracking branch 'origin/perf/static-objects' into HEAD
# Conflicts:
#	packages/compiler/src/frontend/pipeline.ts
#	packages/compiler/src/frontend/shape-inference/overlay.ts
#	tests/harness/npm.test.ts
2026-10-25 13:38:56 -07:00
Chris Tate f0f91ac1ff Load a Node module's fallback declarations only when the program names it
Without @types/node, every program loaded scriptc-node-fallback.d.ts whole:
the console and process globals and 47 Node modules, about 8,100 lines,
parsed, bound and checked in every cold build whether or not the program
imported them.

- The fallback file keeps the globals and the modules they name (console,
  readline, readline/promises, perf_hooks, tty). Every other module, under
  both spellings, has its own file in ambient/node-fallback/, which
  references the files of the modules its declarations name.
- A module's file joins the program when one of the program's files names
  the module in a string literal: imports, requires, import types and JSDoc
  types all name modules that way. The entry is read before the first
  program; the program-root discovery passes add modules that other files
  name. Files are read as tsgo reads them, and the TypeScript libraries and
  scriptc's own declarations are skipped.
- The checks that recognized the fallback file by its path recognize every
  fallback file.
2026-10-25 15:36:08 -05:00
Chris Tate 7b24b410aa Merge main 2026-10-25 11:30:45 -05:00
Chris Tate 18fd46be1c Merge main 2026-10-25 10:43:36 -05:00
Malte UblandClaude Opus 5.5 0d4954f40c Never let the shape overlay break compilation
With the overlay on, a lowering refusal no longer fails the build. The
annotations the refusal depends on are dropped and the program is loaded
and lowered again, so those declarations compile untyped, as they do
without the overlay:

- Each overlay annotation carries an id (`@scriptcShape #<pos>`).
- shape-inference/fallback.ts blames the annotations of the declarations
  enclosing a refusal and of the declarations its expression names,
  following unannotated functions' returns.
- Rounds that stop converging drop whole files' overlays, then the
  overlay for the build. SC6103 reports what was dropped.

Also:

- The tsc gate's project world is built over the files as written; it
  used to see the overlay's files, which failed the typecheck of three
  npm cases.
- Arrays a builtin creates (match results, `new Array(n)`) are no longer
  typed by the overlay: they may have holes, and the library types them.
- compile() accepts `shapeOverlay: true`.

tests/harness/npm-shape-overlay.test.ts runs the whole npm suite with the
overlay on (npm.test.ts's body moves to npm-suite.ts): every case builds
and runs as in the default build, and a refused build that compiles with
the overlay matches Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 07:34:03 -07:00
Chris Tate 5247d2dfdd List bench/regex-replacers among the dynamic-tier workloads
Its replace callbacks receive checked-dynamic values where the lib types
their parameters any: captures a match can leave unset, and results the
checker types any.
2026-10-25 07:28:48 -05:00
Chris Tate 6157fa93ab Merge base branch and fix 2026-10-25 07:02:48 -05:00
Chris Tate c2e6c962e8 Build the sanitized three.js nodes fixture in its own CI job
On the san 1/20 shard the sanitized nodes build shares the runner with the
corpus's other two workers and the side queue. It took 944 s and 860 s on
#2194's runs and passed 1200 s on main's 7fa88083, failing the test timeout.

The Linux san corpus now skips that one case
(SCRIPTC_THREE_SANITIZED_NODES_JOB), and a three_nodes_san job runs it alone
with the corpus's LeakSanitizer settings. The required test check waits for
the new job. Every other three.js case, the plain nodes build and the macOS
suites are unchanged.
2026-10-25 06:49:46 -05:00
Chris Tate ad26bed418 List line-reader among the benchmark workloads that link the dynamic tier
The workload reads its file through readline over fs.createReadStream.
The stream and readline runtime units define dynamic-tier symbols on
their own: a program that only reads a file with createReadStream links
the same 32, as log-summary and inventory-report do.
2026-10-25 02:29:47 -05:00
Chris Tate 0f7fd1bdb6 Merge base and fix 2026-10-25 02:27:42 -05:00
Chris Tate db2cb5c99e Split readline input in one scan and copy ASCII strings into buffers whole
readline split redirected input byte by byte, twice per chunk (once to
find whether the chunk ends a line, once to split it). Before splitting
it copied each chunk out of its Buffer and into a scratch buffer, decoded
it into a new string, and copied the unfinished line and the whole chunk
into a joined buffer. Now:

- one scan per chunk: memchr for a CR once, then LF to LF with memchr
  when the chunk has none, and the LF, CR and CRLF split otherwise;
- the unfinished line joins only the first line of the chunk;
- an ASCII chunk with no partial UTF-8 sequence held from the previous
  one is split where it is, and a chunk that is not shared memory is
  read from its own storage. Lines are still copied into their strings
  before the first is delivered.

Buffer.from(string, 'latin1' | 'ascii') encoded character by character;
an ASCII prefix, all of an ASCII string, is now copied with memcpy.

Line events, their order and the CRLF delay are unchanged. Corpus 8344
covers lines joined across chunks, a line longer than a chunk, empty
lines, mixed line endings and UTF-8 split across chunks. The new
line-reader workload reads a 67 MB log through readline over a file
stream.
2026-10-25 01:35:28 -05:00
Chris Tate e6790def72 Merge main 2026-10-24 20:06:31 -05:00
Chris Tate e0f1de0a2b Merge main 2026-10-24 20:00:43 -05:00
Chris Tate 8b4f83ac02 Collect the native compiler profile on the release commit (#2138) 2026-10-24 18:25:03 -05:00
Chris Tate a5c7a4e983 Report how well the native compiler's profile matches its functions (#2117) 2026-10-24 17:39:30 -05:00
Chris Tate 40faf6a9f1 Collect the native compiler profile on the release commit
The release workflow's new cli-profile job runs
scripts/refresh-cli-profile.mjs on Linux x64 and uploads the profile.
Every platform's native CLI build uses it through
SCRIPTC_NATIVE_PROFILE_USE, and the GitHub release attaches it as
scriptc-<version>-cli.profdata.gz. When the job fails, the CLI builds
use the committed profile.

build-native-cli.mts accepts a gzip-compressed profile in
SCRIPTC_NATIVE_PROFILE_USE when the name ends in .gz.
refresh-cli-profile.mjs creates the directory of --output before it
starts.
2026-10-24 14:16:32 -05:00
Chris Tate f8f3194e0c Merge main 2026-10-24 13:13:52 -05:00
Chris Tate 29a78af70b Merge main 2026-10-24 13:13:07 -05:00
Chris Tate 396c6944ec Report how well the native compiler's profile matches its functions
The native compiler is built with a committed optimization profile. A
function whose control flow changed since the profile was collected is
optimized as without one, so the profile loses its gain as compiler code
changes, with nothing to show it.

With --profile-use, the helper now counts what the profile annotation
reports while it applies the profile: functions that took the profile's
counts, functions whose control flow no longer matches (hash mismatch),
and functions the profile has no record of. It sums the counts over
partitions, keeps each partition's counts beside its cached summary, and
prints them as one line of JSON after a successful emit. The compiler
reads that line (an older helper prints none) and returns it as the
executable artifact's profileMatch.

build-native-cli prints the share of profiled functions that match, and
warns below 90% that the profile should be refreshed. On main aa1c26845, a
profile refreshed on that commit matches 92.7% of the compiler's profiled
functions, and the one committed about 200 commits earlier 87.2%; that one
kept about half of the fresh profile's gain on a cold tsc-ts build.
2026-10-24 11:20:47 -05:00
Chris Tate 2967a1b8c0 Propagate exceptions by unwinding from directly called functions on Linux
Emitted functions report an exception by returning with the thread's
pending flag set, and every caller tests the flag after each call that
may throw. On Linux executables (glibc and musl), functions reached only
by direct calls now propagate exceptions by unwinding the native stack
instead:

- unwindAbiFunctions selects the may-throw functions that are not async
  or generators, constructors, virtually called or overridden methods,
  closure targets, the entry, library exports, module initializers or
  callees of coroutine bodies.
- A native unwind costs about 2 µs a raise and 0.1 µs a frame, so functions that
  exceptions are likely to cross keep the flag: those with a `throw` of
  their own, those up to three calls above one, and recursive functions,
  counting calls through methods, closures and callbacks.
- Their propagating exits call scr_unwind_raise (scr_unwind.c), a forced
  unwind through _Unwind_ForcedUnwind with the exception still in the
  active cell.
- Direct calls to them are `invoke`s without a pending check. The
  landing pad runs the frame's usual unwind path: it releases the frame's
  values and then enters the enclosing try handler or resumes the unwind.
- Remaining plain calls to them (wrappers, adapters, runtime-facing entry
  points) become invokes whose landing pad continues on the pending flag,
  or resumes when the wrapper is itself invoked. Every other call is
  marked `nounwind`, so inlining a raising function at an invoke keeps its
  calls plain.
- When emitted text breaks these rules (a raising function's address is
  taken, or an invoked function returns with the flag pending), the
  module is emitted again with those functions on the pending flag.

Emitted code does not declare scr_unwind_raise `noreturn`, so sanitized
builds do not run the sanitizer's no-return hook, which needs stack
bounds that fiber stacks do not report. Instead, scr_unwind_raise clears
the shadow of the frames it unwinds.

Executables link a static unwinder: libgcc_eh with the host toolchain and
the glibc runtime packs, and Zig's libunwind with Zig and the musl packs.
macOS, Windows, Android, WASI, libraries and debug builds keep the
pending flag everywhere and emit the same code as before.

The throw-chains runtime workload measures exceptions caught 2, 5 and 20
recursive calls above the throw, through direct calls and a callback,
and a parser that backtracks by throwing.
2026-10-24 11:03:34 -05:00
Chris Tate 427e383b5e Cache untyped property access in programs that declare classes (reland)
Reapplies #2017, which #2038 reverted after effect's observability-otlp
fixture crashed in plain builds. The crash was the candidate-buffer
corruption that the previous commit fixes: the cached property sites
change when the collector runs, and with enough environment variables a
pass buffered a string box that was later pooled while still buffered.
2026-10-24 08:22:30 -05:00
Chris Tate 7278c3c467 Read a union's discriminant once in checked casts (#2073) 2026-10-24 04:51:36 -05:00
Chris Tate 1559fb1d22 Merge main 2026-10-24 04:03:19 -05:00
Chris Tate bf2f1c45d7 Merge main 2026-10-24 01:52:57 -05:00
Chris Tate e3fb35cc68 Cap the Linux test step's memory and move memory-heavy side files apart (#2030) 2026-10-23 23:48:45 -05:00
Chris Tate 36daee3f61 Fix the CI failure 2026-10-23 23:16:58 -05:00
Chris Tate 4a48f466ee Merge the Node island runtime branch and stamp the node packs with 0.4.3 2026-10-23 15:32:55 -05:00
Chris Tate b6a3756e8f Merge main into the Node island runtime 2026-10-23 15:25:48 -05:00
Chris Tate 7cdfe16c9b Cap the Linux test step's memory and spread memory-heavy side files
The Linux test shards run their suites in a transient systemd scope with
MemoryMax=13G and no swap, below the runner's 16 GB. A process tree that
grows past the cap loses its largest process to the kernel, and the job
log names the failing test, where the runner itself ran out of memory
and left no log. Hosts without systemd run the suites directly.

The side-queue balancer also takes a table of memory-heavy files, with
their measured peak. They run whole in the cost-balanced task, and each
goes to the shard with the least memory-heavy work so far, after the
time-balanced files. decl-boundary.test.ts, which peaks at about 1.8 GB
alone and had no time measurement, so vitest's file sharding put it in
shard 11, is the first entry. It now runs in shard 1 (plain) and 6 (san).
2026-10-23 14:24:38 -05:00
Chris Tate e3b42a311d Add --target-cpu to compile x86-64 programs for a microarchitecture level
Release builds compile for the x86-64 baseline, so LLVM vectorizes with
SSE2 only. `--target-cpu=<baseline|x86-64-v2|x86-64-v3|x86-64-v4|native>`
compiles the program's code for a higher level: every emitted function
carries the level as its `target-cpu`, so LLVM can use AVX2, BMI2, FMA or
AVX-512. The executable then runs only on CPUs of that level.

- `native` resolves to the highest level the build machine supports, read
  from /proc/cpuinfo on Linux and `sysctl machdep.cpu` on macOS.
- The level is part of the executable cache key; `baseline` and builds
  without the option produce the same output and keys as before.
- The runtime pack keeps its baseline code.
- Targets other than x86-64 refuse a level (SC3002). The CLI's cached
  bootstrap route leaves builds with the option to the full compiler.
- `bench:runtime` takes `--baseline-target-cpu` and `--candidate-target-cpu`.
2026-10-23 13:10:16 -05:00
Chris Tate 4dcb83c418 Free a typed teardown's blocks in the order it met them (#1849) 2026-10-23 12:09:37 -05:00
Chris Tate a10c183b13 Revert "Cache untyped property access in programs that declare classes (#1995)" 2026-10-23 09:05:55 -05:00
Chris Tate 8eedab9fde Cache untyped property access in programs that declare classes
A program that declares a class reads and writes untyped properties
through helpers that first select the receiver's property bag. The bag
helpers return jsvals, so the helpers read the bag through js.toDyn;
keyed helpers test each class's key table between the key's coercion and
the read; and a program with subclasses normalizes the receiver
(value = %dyn.class.normalize(value)) at the start of each helper. The
inline-cache recognizer accepted none of these forms, so every property
site in such a program called its helper. It now looks through dyn/jsval
conversions of the receiver, accepts capsule branches before a keyed
helper's final read, and accepts a rebinding of the receiver to a bag of
itself. Plain objects and arrays then read and write through the site's
cache, as they do in programs without classes.

bench/dynamic-properties (new) exercises these sites.
2026-10-23 08:41:40 -05:00
Malte Ubl 4bbbf3e079 Merge remote-tracking branch 'origin/ni/i4-deferred' into HEAD 2026-10-23 06:33:28 -07:00
Malte Ubl 67a0656781 Merge remote-tracking branch 'origin/ni/i3-exports' into HEAD
# Conflicts:
#	packages/runtime-pack-common/runtime-pack-matrix.mjs
2026-10-23 06:32:59 -07:00
Malte Ubl 2acfd9e723 Merge remote-tracking branch 'origin/ni/3-hosted-loop' into HEAD
# Conflicts:
#	packages/compiler/src/diagnostics/diagnostic.ts
#	packages/compiler/src/frontend/pipeline.ts
2026-10-23 05:57:22 -07:00
Malte Ubl 9b3c7bb520 Merge remote-tracking branch 'origin/ni/1-libnode-loader' into HEAD 2026-10-23 05:56:23 -07:00
Malte Ubl 2494e7c1dc Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/src/backend/native/executable.ts
#	packages/compiler/src/frontend/lowering/lower-calls.ts
#	packages/compiler/src/frontend/lowering/lowerer.ts
#	packages/runtime-pack-common/runtime-pack-matrix.mjs
2026-10-23 05:53:42 -07:00
Chris Tate 19e0fe8bcd Merge main and read cipher options through getters and prototypes 2026-10-23 05:33:14 -05:00
Chris Tate 0314d9db3e Dispatch checked-dynamic method calls on method ids 2026-10-22 23:52:25 -05:00
Chris Tate f030e5ad44 Check with tsc-ts 0.0.8 on every platform
tsc-ts 0.0.8's API server serves on the named pipe the Node host
connects over on Windows (tsc-ts --api --pipe), and the native compiler
already starts it over its standard streams there, so tsc-ts is the
default checker on every platform. SCRIPTC_CHECKER=tsgo still selects
tsgo.

The compiler depends on tsc-ts 0.0.8, which the release-age exclusions
admit with its platform packages and @scriptc/threads 0.4.2.

The Windows CLI smoke test runs with both checkers, and
verify-native-cli.mjs --run checks that a distribution's tsc-ts runs on
its platform (tsc-ts.exe on Windows).
2026-10-22 22:36:11 -05:00
Malte UblandClaude Opus 5.5 5fa44d9243 Share modules between a program and its Node islands through loader hooks (NI-6)
Node islands stack, part 5 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:53 -07:00
Malte UblandClaude Opus 5.5 2f8e8b2b92 Defer evaluating Node island modules until the program uses them (NI-7)
Node islands stack, part 4 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:53 -07:00
Chris Tate dc2b238df7 Revert "Dispatch checked-dynamic method calls on method ids (#1851)" 2026-10-22 18:02:51 -05:00
Chris Tate 289a6f619d Merge main 2026-10-22 17:39:40 -05:00