Commit Graph
146 Commits
Author SHA1 Message Date
Chris Tate fa02fcd8ae Report which packages and modules would run in a Node island (#1691) 2026-10-25 04:32:22 -05:00
Chris Tate 4050c0fb5c Merge main and skip package.json files without a scriptc key 2026-10-25 03:58:49 -05:00
Chris Tate 8b0921243f Compile large sanitized dev programs in shards 2026-10-25 01:59:32 -05:00
Chris Tate 1f26462fb7 Keep an unchanged executable without selecting the runtime pack (#2187) 2026-10-25 01:41:58 -05:00
Chris Tate 5d3e0c7a8b Support X509Certificate fields and methods, TLS certificates and tls.Server contexts (#2052) 2026-10-25 01:00:24 -05:00
Chris Tate 944cae84a8 Keep an unchanged executable without selecting the runtime pack
An unchanged rebuild that kept its output still selected the runtime pack's
artifacts first, reading the stored selection, and computed the identity of
every selected artifact to open the executable cache.

- The executable cache key names the pack by its package.json text, the
  manifest's identity (its text on darwin), and the link features and
  flavor. Together they fix which artifacts the selection names.
- The cache selects the artifacts only when it needs their identities, in
  restore() and trace(). keep() checks the recorded entry, which lists them.
- A build that does not keep or restore its executable selects the pack
  before code generation, as before.
2026-10-25 00:06:13 -05:00
Chris Tate 7464f12e6b Emit unwinding code for textual LLVM and assembly output on Linux 2026-10-24 20:01:49 -05:00
Chris Tate 9e55835014 Compile more npm and CommonJS JavaScript dependencies statically (#2122) 2026-10-24 18:30:56 -05:00
Chris Tate 6c18c58708 Merge main 2026-10-24 18:21:25 -05:00
Chris Tate aa9664bf8a Propagate exceptions by unwinding from directly called functions on Linux (#2115) 2026-10-24 17:15:36 -05:00
Chris Tate 6852b778c3 Verify runtime pack artifacts once per file identity, and store pack selections (#2106) 2026-10-24 16:36:34 -05:00
Chris Tate d388cdd9cf Merge main 2026-10-24 14:20:14 -05:00
Chris Tate 587443428c Link Linux executables with lld when the driver finds it
Linux executables linked through the clang driver with its default linker,
GNU ld. lld links the same objects in about half the time.

- Native and Node-hosted Linux exe links pass -fuse-ld=lld when ld.lld is
  beside the driver's executable (where clang looks first) or on PATH, and
  the default driver is in use (no SCRIPTC_LINKER, no sanitizer link).
- A link with lld that fails runs again with the driver's default linker,
  whose error is the one reported when that fails too.
2026-10-24 13:22:26 -05:00
Chris Tate 31d3ba07ca Reuse dev builds' frontend output after edits that keep token positions
A native build reuses its stale frontend cache entry when every changed
source is token-equivalent to the text the entry was built from, but dev
builds never did: their debug information records source lines and
columns, which a token-equivalent edit can move.

Builds with debug information (dev builds without --strip) now reuse the
entry when every changed source is token-equivalent and every token keeps
its line and column (tokenPositionEquivalentSource): edits to a comment's
text within its line, comments and whitespace after the last token, and
trailing whitespace. Edits that move a token still rebuild.

The token scanner also reads a `/` after a non-null assertion as division
(`data[i]! / 7`). It used to read it as the start of a regular expression
and give up on the file, so comment edits in files with that pattern
always rebuilt. A `!` after a line break, or after an operator or a
keyword such as `return`, still starts a negated regular expression, and
a `!` after `)`, `}`, `++` or `--` gives up.
2026-10-24 12:22:19 -05:00
Chris Tate 32752f768e Merge main 2026-10-24 12:19:43 -05:00
Chris Tate aa4e046046 Emit function bodies on up to eight workers (#2118) 2026-10-24 12:04:33 -05:00
Chris Tate 4f0975cbbe Compile more npm and CommonJS JavaScript dependencies statically 2026-10-24 12:01:26 -05:00
Chris Tate c09385c7ab Emit function bodies on up to eight workers
The native compiler forked four worker processes to emit the function
bodies of a large module. It now forks one per available core, up to
eight. SCRIPTC_EMIT_WORKERS still sets the count. The output is the same
text at any count.
2026-10-24 11:29:56 -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 205f099957 Keep an unchanged executable on its first Linux rebuild
On Linux the executable cache stored an executable only from a key's second
link on, so the first unchanged rebuild after a build linked again, and only
the build after that restored the executable.

- A key's first link on Linux is traced, in the same verbose link. Instead
  of storing the executable, the cache records the output's file identity
  together with the link's inputs once the output is in place.
- keep() answers an unchanged rebuild whose output still has that identity
  and whose inputs are unchanged. The build then neither links nor copies:
  the output stays as it is.
- A rebuild whose output changed or is missing links again; from that second
  link on, the executable is stored as before and restored where needed.
- The cache records the output after every Linux build that places one, so
  the next unchanged rebuild keeps it.

darwin keeps its existing behavior.
2026-10-24 10:26:38 -05:00
Chris Tate 48cbf3a89e Verify runtime pack artifacts once per file identity, and store pack selections
Every native exe build that the executable cache does not restore hashed
each selected runtime artifact before staging it, and every exe build
parsed and validated the whole runtime-pack.json manifest to select its
artifacts.

- Staging still copies each artifact. It skips the SHA-256 of an artifact
  when the file's identity (device, inode, size, mtime, ctime) is the one
  recorded the last time that path matched the same size and digest, and
  the identity does not change while the file is read. Other artifacts are
  hashed as before, and their identities are recorded after they match.
- A pack's selection for a target, compiler version, feature set, flavor
  and mode is stored, keyed by the file identities of package.json and
  runtime-pack.json. A stored selection skips parsing and validating the
  manifest; the manifest is parsed only when a caller needs it, through
  runtimePackManifest.
- The native compiler keeps both in its cache, in the runtime-pack family,
  which prune covers.
2026-10-24 09:20:05 -05:00
Chris Tate 76611d1151 Merge main 2026-10-24 06:49:23 -05:00
Chris Tate 39216602ab Merge main 2026-10-24 02:39:41 -05:00
Chris Tate dd4ae4e508 Merge main 2026-10-24 01:57:53 -05:00
Chris Tate 98e3ff52b3 Reuse linked executables on Linux (#2077) 2026-10-24 00:50:58 -05:00
Chris Tate 7bf50dd9cc zlib: Brotli compression and decompression over the vendored brotli 1.2.0 (#2053) 2026-10-24 00:30:21 -05:00
Chris Tate 0431240719 Reuse linked executables on Linux
The native compiler's executable cache restored a linked executable only
on darwin. On Linux every exe build linked again, even when the frontend
and object caches answered the rest of the build.

- The cache now opens on Linux when the linker is a clang driver. One link
  with `-v -Wl,-t` lists the driver's linker job and every file the linker
  loads; their identities are checked again before an entry is published
  and when it is restored.
- On Linux a key's first link only records that it was linked. A later
  link of the same key traces and publishes, so a program built once pays
  no tracing.
- The key names the runtime pack manifest by its file identity on Linux
  instead of by its text.
- The executable and object caches share one digest of the LLVM text.
- Content-addressed entries are hashed once, and identities are compared
  by field.

darwin keeps its own driver check, dry run and key.
2026-10-23 23:20:06 -05:00
Chris Tate 9031ddafe0 Merge main 2026-10-23 21:50:24 -05:00
Chris Tate 17f1351b6f Fix the CI failures 2026-10-23 20:12:40 -05:00
Chris Tate e9833e3260 Add --target-cpu for x86-64 microarchitecture levels (#2025) 2026-10-23 20:00:33 -05:00
Chris Tate 6b1a56a49a Check target CPU levels in the native CLI and honor them for assembly and object builds 2026-10-23 18:26:12 -05:00
Chris Tate a9da37b82e zlib: Brotli compression and decompression over the vendored brotli 1.2.0 2026-10-23 18:11:49 -05:00
Chris Tate 072543f5b9 Support X509Certificate fields and methods, TLS certificates and tls.Server contexts 2026-10-23 18:01:19 -05:00
Chris Tate 398930e7de Store the frontend cache's LLVM placeholder as an empty array
A native frontend cache entry stores the input's LLVM text after its
metadata, with an empty placeholder in the input itself. The placeholder
was an empty string where NativeExecutableInput declares an array of
parts. Under Node the reader's cast of the parsed entry is not checked,
but the native compiler checks it, so every read threw "expected array
at $.input.llvm, got string". The reader treated that as a miss: no
native build ever reused its frontend output, and an unchanged rebuild
ran the frontend, lowering and LLVM emission again.

The placeholder is now an empty array, and the cache test checks the
stored member types.
2026-10-23 17:48:22 -05:00
Chris Tate 96a5954940 Merge main 2026-10-23 14:09:13 -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
Malte UblandClaude Opus 5.5 f14997776b Box TextEncoder and TextDecoder records as checked-dynamic handles
A codec stored in untyped storage (hono's StreamingApi keeps
`this.encoder = new TextEncoder()` in a JavaScript class field) had no
dynamic representation and threw SC1101 at run time. The codec records
now cross 'unknown' slots as handles of two new tags that hold the record
itself, so identity and a decoder's streaming state survive the round
trip, and checked casts unbox the same record.

The handles answer encode, encodeInto and decode (with options objects),
the encoding, fatal and ignoreBOM properties, method values with Node's
ERR_INVALID_THIS, instanceof, String() tags and JSON's {}. The unit
scr_text_codec_dyn.c links only into programs that convert a codec to or
from an untyped value (the textCodecDyn runtime feature, found with the
dgram carrier walk, now shared).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:33:06 -07:00
Chris Tate ccd45c748f Address the Node island report review findings 2026-10-23 12:21:50 -05:00
Chris Tate d3c8155c85 Reuse the bundled declaration files' diagnostics across builds 2026-10-23 10:32:15 -05:00
Chris Tate 47490f0bca Revert "Reuse the bundled declaration files' diagnostics across builds (#1981)" 2026-10-23 05:16:33 -05:00
Chris Tate fb200c1240 Reuse the bundled declaration files' diagnostics across builds 2026-10-23 04:27:14 -05:00
Chris Tate ebefb8d208 Merge main 2026-10-22 23:23:00 -05:00
Chris Tate 4dce87dbcb Name the compiler's own shim files independently of the install path (#1941) 2026-10-22 22:51:57 -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 14966de563 Move adoption into its own runtime unit behind the declAdopt feature
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:35:40 -07:00
Chris Tate 835eecd6f6 Merge main 2026-10-22 21:26:13 -05:00
Malte UblandClaude Opus 5.5 94173bebdc Name the compiler's own shim files independently of the install path
Programs that compile against a package shim (fs-extra, chalk, undici,
hot-shots) produced IR and LLVM that depended on where the compiler was
installed: lifted helper and closure names hashed the shim file's path
relative to the entry, the shared-helper location pass ordered candidate
sites by absolute path, and IR artifacts spelled every shim location as
an absolute path. Two checkouts of the same sources therefore emitted
different artifacts, and the zero-cost gate always reported
fs-extra-package as differing.

Files scriptc ships are now named by their place inside scriptc
(scriptc:shims/...) in structural positions, in the shared-helper
ordering, and in serialized IR artifacts. In-memory locations keep the
real path, so diagnostics and source lookups are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:12:16 -07:00
Chris Tate 5e4303e62d Keep the native compiler's LLVM text as the emitter's parts
The LLVM emitter builds a module as parts and joined them into one
string: on a large program such as tsc-ts the parts and the 150 MB joined
text were live together at the end of emission. The native compiler only
writes, escapes and hashes the text, so it now takes the emitter's parts
(emitLlvmModuleParts) and never joins them. Consecutive small parts are
joined into batches of about 1 MiB as they are written, escaped or hashed
(joinedParts), so the many small parts cost few calls. A frontend cache
hit reads the text back as one part. The three emission entry points
share one module pipeline (moduleEmitter).
2026-10-22 20:52:22 -05:00
Chris Tate 5846dce309 Merge main 2026-10-22 19:05:56 -05:00
Chris Tate 067be15683 Write and hash the LLVM module text in slices instead of whole copies
The native compiler wrote the module's LLVM text, 150 MB on a large
program such as tsc-ts, by encoding the whole string at once; its frontend
cache entry by first escaping the whole text into a second string and then
encoding that; and it read the written file back whole to key the object
cache. Those copies were the build's peak.

Text parts are now encoded and written in slices of at most 1 MiB UTF-16
units that never split a surrogate pair. The frontend cache entry is
generated while it is written: the LLVM text is escaped slice by slice,
which is the same JSON as escaping it whole, the entry is hashed in the
same pass, and an entry past one native string abandons its write as it
was skipped before. The object cache key hashes the file in 1 MiB blocks.
The files and cache keys are unchanged.
2026-10-22 18:12:38 -05:00