Commit Graph
5651 Commits
Author SHA1 Message Date
Chris Tate 82cf32d1cd Convert number | undefined timer delays with ToNumber (#2427) 2026-10-27 12:42:02 -05:00
Chris Tate fa660eddf9 Carry interned unions in unit records (#2425) 2026-10-27 12:25:38 -05:00
Chris Tate 6d0cb98d7b Carry interned unions in unit records
A unit's record carries the unions it interned, as their definitions. Applying one interns the union under the key its arms give and checks that its name matches. Recursive unions still leave the unit without a record, and the arm-tag index is a memo.
2026-10-27 11:39:51 -05:00
Chris Tate a3ae97c097 Specialize calls that omit a constant default and drop unreached originals (#2423) 2026-10-27 11:37:19 -05:00
Chris Tate 096bf9a364 Map a failing type exactly from its canonical form where localized failure would degrade it (#2338) 2026-10-27 11:32:18 -05:00
Chris Tate 3e2bd16a36 Reserve fewer worker arenas before smaller class slices on hosts that limit address space (#2422) 2026-10-27 11:25:09 -05:00
Malte UblandClaude Opus 5.5 cdd121405b Convert number | undefined timer delays with ToNumber
setTimeout and setInterval passed their delay to the F64 timer slot
unconverted, so a delay typed number | undefined failed with SC2003. Such
a delay is an optional `ms?: number` passed on, or a number[] element
read, whose storage keeps the out-of-range undefined. Node converts the
delay with ToNumber, so undefined is NaN, the omitted delay. A union
delay now goes through lowerOptionalNumber, as Math arguments already
do. Other unions still reach the lib boundary's refusal, and number
delays are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:11:51 -07:00
Chris Tate fa65a3b385 Specialize calls that omit a constant default and drop unreached originals 2026-10-27 10:49:22 -05:00
Chris Tate 8de39fdd57 Borrow typed-array pages and literal-ending getter returns (#2421) 2026-10-27 10:48:04 -05:00
Chris Tate b11bc54a2b Expect the collapsed canonical forms as dynamic slots in the coverage tests
The tools field (Tool[]) and the all global ((number | Callbacks)[]) hold unions that canonical mapping maps to the checked dynamic, so the canonical fallback now degrades them: they are dynamic slots again, with SC6104 warnings and attributed operations, as these tests expected before the fallback classified them as exact.
2026-10-27 10:46:54 -05:00
Chris Tate 0c954ebfd6 Reserve fewer worker arenas before smaller class slices on hosts that limit address space
Worker runtimes reserve one region for 32 arenas of 40 class slices. When
the host refused the full region (32 arenas of 1 GiB slices, 1.28 TiB of
address space), the allocator kept 32 arenas and shrank every slice. Under
valgrind, which maps at most 56 GiB, slices dropped to 16 MiB, and every
class whose live blocks outgrew 16 MiB took the system allocator.
Callgrind profiles then counted system allocations that a native run
never makes: tsc-ts checking VS Code made 10.3 M small calloc calls under
valgrind and 0.06 M natively.

The reservation now tries arena counts from 32 down to 4 at each slice
size, from 1 GiB down, before smaller slices, and 2 and 1 arena only when
no slice size fits 4. Arenas that are not reserved stay claimed, so
threads beyond them use the system allocator, and the region bounds of
foreign frees use the arenas reserved. Hosts that map the full region keep
32 arenas of 1 GiB slices.

A runtime test limits RLIMIT_AS to 60 GiB and checks that the first thread
gets 256 MiB slices for 400,000 blocks of 56 bytes, that three more
threads get arenas and a fourth uses the system allocator, and that blocks
freed across threads keep their contents.
2026-10-27 10:43:05 -05:00
Chris Tate 06e9672d90 Degrade a canonical form whose union mapped to the checked dynamic
Localized failure's canonical fallback classified a failing type as an exact mapping whenever its canonical form mapped to something other than the checked dynamic. A record or array holding a union that mapCanonical collapses to the checked dynamic (an arm such as {} or unknown) then counted as exact, so its slot got no SC6104 warning and its dynamic operations no attribution.

mapCanonical now notes such a collapse (CanonMapNotes), and canonicalType refuses the form when it happened, so the type degrades through the pushed-down form like any other.
2026-10-27 10:32:32 -05:00
Chris Tate 84f0368f5f Lay out code by context-sensitive profiles (#2420) 2026-10-27 10:31:49 -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
Chris Tate aed3e84ff5 Borrow typed-array pages and literal-ending getter returns 2026-10-27 10:04:40 -05:00
Malte UblandClaude Opus 5.5 0927bfd295 Expect exact canonical mappings in the dynamic-slot coverage tests
With main's canonical-mapping gap fixes (#2340), stage N2 maps the
Tool[] field and the (Callbacks | number)[] global exactly, so they are no
longer dynamic slots. The hot-slot fixture's Hook gets an intersection arm,
which has no canonical form, so it stays a dynamic piece of Plugin[].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:42:33 -07:00
Chris Tate d4be651952 Prove module-level number state int32 through every write (#2416) 2026-10-27 09:37:35 -05:00
Chris Tate 18196d4df0 Lay out code by context-sensitive profiles 2026-10-27 09:36:18 -05:00
Chris Tate ddea35322b Check with the shared tsc-ts server by default on Linux and macOS (#2417) 2026-10-27 09:20:35 -05:00
Chris Tate df912c4b4b Reuse the supplied files' diagnostics across Node module sets (#2418) 2026-10-27 09:20:16 -05:00
Chris Tate 80bfc5fada Version for loops on the positions they advance (#2415) 2026-10-27 09:16:42 -05:00
Chris Tate aed7a20709 Let units without a record change the helpers they name in place
The serial mode's wave-end check threw when a unit changed a held function in place, even when the unit had no record and its change was its own lowering's: two functions that define computed-key properties on one class both rebuild the class's descriptor guard. Each unit's in-place changes are now checked against the names its IR and new entries quote: a unit with a record that changed such an entry has no record, and a unit without one keeps its changes. The wave-end check throws only for a change to an entry no unit names.
2026-10-27 09:03:39 -05:00
Chris Tate 660ddfd90e Keep the view-header test's loops speculative
Module number globals are now proven int32 through their writes, so the test's generator state starts from a value no analysis bounds to keep its loops on the speculative path. Format int32-slots.ts.
2026-10-27 08:41:05 -05:00
Chris Tate 5d3a234d07 Reuse the supplied files' diagnostics across Node module sets
The stored diagnostics of the supplied declaration files are keyed by the
whole supplied set, which includes the fallback declarations of each Node
module a program imports. A program that imports a different set of modules
checked every supplied file again, even when the cache held an answer for
the same libs, scriptc declaration files and options without module files.

The module fallback files declare only ambient modules, and the TypeScript
libs declare only globals and name no module, so adding module files cannot
change what checking the libs reports. Nor can it change what checking
scriptc's declaration files reports when the set without module files
reports nothing in them and no module file declares a module they declare.
On a miss, a program of modules now reuses the answer of the set without
module files, stored or prebuilt:

- for every file of that set when both conditions hold, and checks only its
  own files and its module files;
- for the libs alone otherwise, and checks its own files, scriptc's
  declaration files and its module files.

The answer for the whole set is then stored as before. Programs with
scripts, and sets whose module files declare a global, check in full. The
content digests behind prebuilt answers are kept per file identity, so a
miss hashes each supplied file once.
2026-10-27 08:34:10 -05:00
Chris Tate f69ff780e5 Give a stop command that matches the shared checker server
The server's command line puts --cwd and --callbacks between --api and
--listen, so `pkill -f 'tsc-ts --api --listen'` matched no server that a
build started. The docs now give `pkill -f 'tsc-ts --api .*--listen'`, and
name the flags instead of writing them as one command.
2026-10-27 08:33:04 -05:00
Chris Tate 1a20467889 Find the counter loop's fast copy inside its entry version
The counter loop's remainder by n now versions the loop on n's entry range, so the int32 slots copy ends where the copy for other entry values starts.
2026-10-27 08:31:01 -05:00
Chris Tate 3b374f3201 Check with the shared tsc-ts server by default on Linux and macOS
Builds on Linux and macOS now connect to a shared `tsc-ts --api --listen`
server by default, which keeps the files it parsed and bound, so a later
build in the same working directory does not parse the TypeScript libs and
scriptc's declaration files again. SCRIPTC_CHECKER_SERVER=0 turns it off.
When CI is set to a value other than 0 or false, builds use their own tsc-ts
process unless SCRIPTC_CHECKER_SERVER=1.

- The server exits after 600 seconds without a build (was 300).
- Its name also digests the compiler executable's identity, so each scriptc
  installation and build connects only to servers it started.
- The server process runs in the root directory, so it does not keep the
  first build's directory in use; its --cwd argument is unchanged.
- When the connection to the server fails after it answered, through a
  failed transfer or an ended stream, the native CLI runs the command again
  in a new process with SCRIPTC_CHECKER_SERVER=0 and exits with its status.
- SCRIPTC_CHECKER_SERVER no longer changes the frontend cache key: the
  server's answers are the build's own checker's.

The CLI docs describe the background process, its socket directory, when it
exits and how to stop it.
2026-10-27 08:21:48 -05:00
Chris Tate d9eb38a54d Split the slowest sanitized suites by case and raise the sanitized bootstrap's stack (#2414) 2026-10-27 08:21:31 -05:00
Chris Tate c54c4e1d2a Prove module-level number state int32 through every write
- Module globals of type number join the whole-program int32 fixpoint as
  `global:` candidates, starting non-negative. A candidate needs its module
  initializer to assign it at the top level (its declaration), and no
  write by `++`/`--`, for-of or catch; every assignment must evaluate to
  an int32 under the current facts (an integer literal counts in any
  function), and a write that may be negative keeps only the int32 fact.
- Reads take the proven range through IntegerSlotFacts.locals, so a
  generator state reduced by a remainder (`seed = (seed * a + c) % m`)
  takes unsigned integer remainders without the -0 select.
2026-10-27 07:57:22 -05:00
Chris Tate c554942816 Link AbortController without the fetch unit (#2412) 2026-10-27 07:57:03 -05:00
Chris Tate f553e5790d Check that recorded units leave held functions and IR data unchanged
Function tables and IR data maps hold objects a unit could change in place, which a record does not carry. The serial mode now keeps the encoded content of their entries from the start of each wave and throws an InternalCompilerError naming an entry whose content changed by the wave's end.
2026-10-27 07:54:56 -05:00
Chris Tate adbf74802f Free a worker's group-arena pool when the worker exits (#2413) 2026-10-27 07:54:31 -05:00
Chris Tate fb58a34ca1 Version for loops on the positions they advance
- Loop-entry versioning covers innermost for loops as well as while loops,
  and a typed array index that the assumed entry ranges prove qualifies a
  loop as a remainder does. In `for (let j = 0; j < line; j++)
  out[o++] = f(src[i++])` the test checks `line`, `o` and `i` once per
  entry, and the fast copy bounds both positions by the loop's passes.
- Proven integer loops step their shadowed bindings in expression position
  too (`out[o++] = v`), so those positions run as i64 counters in the
  fast copy and the typed array accesses use them without a round trip.
- An increment of a binding the enclosing for loop bounds takes the loop
  range where a surrounding statement has discarded its fact.
2026-10-27 07:44:34 -05:00
Malte Ubl 26557112f1 Merge remote-tracking branch 'origin/main' into lf/n2-fallback
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
#	packages/compiler/test/canon-normalize.test.ts
2026-10-27 05:40:00 -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 635f732a34 Free a worker's group-arena pool when the worker exits
Each thread keeps up to 8 MB of arena chunks and up to 64 dropped arenas
for reuse (scr_arena.c). Nothing freed them when a worker thread finished,
so every worker that used a group arena left its pool behind: 40 workers
run one after another, each dropping a 12 MB arena, kept 328 MB resident.

scr_arena_thread_exit frees the calling thread's pooled arenas and chunks.
The worker thread calls it after its context cleanup, before it posts the
exit event. The new harness test runs 24 such workers in sequence and
checks that the resident set grows by less than 32 MiB over the last 20
(160 MiB before this change, 1 MiB after).
2026-10-27 07:17:08 -05:00
Chris Tate e84d332264 Link AbortController without the fetch unit
AbortController and AbortSignal lowered to fetch.abort* entry points, so
a program that only used them linked the fetch unit with its socket,
HTTP, TLS and zlib dependencies. They now lower to the abort.* entry
points on every target, a new abortSignal feature links scr_abort.c (the
fetch unit's signal implementation built without its transport, which
WASI already ships) and the EventTarget unit, and programs that also
fetch keep the fetch unit's copy.
2026-10-27 07:05:47 -05: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 f73722c2ef Add an opt-in --static flag for Linux executables (#2398) 2026-10-27 04:31:35 -05:00
Chris Tate 3335cc8ba4 Leave the dispose list out of arena constructors when every arena is closed (#2393) 2026-10-27 04:26:03 -05:00
Chris Tate 549bec79f8 Name the source file in empty split codegen parts (#2397) 2026-10-27 04:12:57 -05:00
Chris Tate 7bd12ea89f Add a representation report and a heap census for ranking value-layout work (#2390) 2026-10-27 04:01:41 -05:00
Chris Tate 8cdf783023 Find glibc's NSS loader in the --static link map
The NSS check scanned the linked executable for the `libnss_` literal, so
a program with that text in a string of its own was refused. A --static
glibc link now writes a link map, and the executable is refused when the
map places code from libc.a's nss_module.o or nsswitch.o in it (lld's
kept input sections, or GNU ld's memory map with a nonzero size).
2026-10-27 04:00:21 -05:00
Chris Tate 2739da5fc0 Skip the helper version probe and unchanged files' digests on rebuilds (#2396) 2026-10-27 03:59:01 -05:00
Chris Tate d4185bcd8e Add an opt-in --static flag for Linux executables
- `scriptc build --static` (and the `staticLink` compile option) links glibc
  executables as static PIE on the build host's own glibc target; musl
  executables are already static and accept the flag.
- An executable that keeps glibc's NSS module loader (host, user and group
  name lookups) is refused with SC3002, since a static glibc executable
  loads those modules from the run host.
- Other targets, cross-compiled glibc targets, --sanitize and non-executable
  output are refused; static and dynamic builds use separate cache entries.
2026-10-27 03:34:16 -05:00
Chris Tate 7c770d0887 Name the source file in empty split codegen parts
When --split codegen keeps the module whole, the extra outputs are new
empty modules, and their source file name defaulted to the module
identifier: the path of the private copy of the LLVM input, which has a
random name. That name reached each part's STT_FILE symbol, so two
release builds of one program differed in .strtab. The empty parts now
take the module's source file name, as the first part has it.
2026-10-27 03:30:15 -05:00
Chris Tate 60372c09a4 Declare the heap census writer in scr_lib.h
The LLVM backend can emit scr_census_write, which a census program
defines and the runtime calls through a weak reference, so the header
declares it as it does scr_detached_exit.
2026-10-27 03:28:32 -05:00
Chris Tate 9179a881a4 Skip the helper version probe and unchanged files' digests on rebuilds
Every native build started the LLVM helper once for `version` before
emitting, and hashed every file the frontend read, including the supplied
declaration files that do not change between builds.

- verifyNativeHelper takes an optional record of helpers that already
  answered `version` as expected. The native compiler keeps it in its cache
  (family "helper"), keyed by the helper's and the compiler's file identity,
  the compiler version and the target. The probe runs again when the helper
  file changes. The package name and version are still checked every time.
- FrontendInputTracker takes the snapshot of the cache entry a build
  replaces. A file whose settled identity has not changed since that
  snapshot keeps the digest recorded there, which is the trust
  frontendInputsStillMatch already gives an unchanged identity.

Builds produce the same executables.
2026-10-27 03:24:16 -05:00
Chris Tate ed54f4f0c8 Leave the dispose list out of arena constructors when every arena is closed
An arena-aware constructor of a record shape or class with reference
fields tests whether the current arena is closed on every allocation, and
appends a dispose entry when it is not. When every root and call region in
the program opens a closed arena, the current arena is closed or absent,
so planArenas sets ArenaPlan.allClosed and those constructors leave out
the test and the dispose list.
2026-10-27 02:59:35 -05:00
Chris Tate 429aa8273d Keep the heap census hook out of library archives
Libraries never build the census (heapCensusOf), but the runtime's weak
scr_census_write reference made library archives list an external
symbol outside their declared set. Declare and call it only in programs.
2026-10-27 02:49:05 -05:00