Commit Graph
298 Commits
Author SHA1 Message Date
Chris Tate 18196d4df0 Lay out code by context-sensitive profiles 2026-10-27 09:36:18 -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 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 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 618243caa8 Generate code concurrently for optimized executables that stay one partition (#2387) 2026-10-27 01:10:09 -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 140990d257 Add --large-pages: move executable code onto transparent huge pages
A large program's hot code spans hundreds of 4 KiB pages, more than the
instruction TLB holds. tsc-ts has 12 MB of code, and 90% of its
main-thread samples at --checkers 1 fall in 274 distinct code pages.

`scriptc build --large-pages` links the new runtime unit
scr_text_pages.c, whose constructor moves the executable's code onto
2 MiB transparent huge pages before main. It copies the executable
segment, from its first to its last 2 MiB boundary, into anonymous memory
advised MADV_HUGEPAGE, makes the copy read-only and executable, and moves
it over the original range with one mremap, so addresses and instructions
stay the same. The move runs only on Linux, only when THP is "always" or
"madvise", and only in executables. If a step fails, the code stays
file-mapped. Like Node's --use-largepages, it is off by default: without
the flag the unit is not linked, and executables are unchanged.

The move costs about 3 ms per 10 MiB of code at startup, and all of the
moved code stays resident as private memory. The flag is part of the
executable cache key and the runtime-pack selection, and is refused for
non-executable output, like --detached-exit.
2026-10-26 23:41:46 -05:00
Chris Tate 7e225c5d87 Merge main 2026-10-26 17:33:06 -05:00
Chris Tate 3c83fcb89c Allocate object groups that die together in arenas (#2348) 2026-10-26 16:54:31 -05:00
Chris Tate 18755e0599 Allocate object groups that die together in arenas 2026-10-26 11:41:21 -05:00
Malte Ubl 572e7e3eac Merge remote-tracking branch 'origin/lf/l4-expressions' into lf/l5-polish
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-10-26 05:37:00 -07:00
Chris Tate fe244085d4 Run native async generators as stackless coroutines
An async generator whose body suspends only at its own awaits and yields
now compiles to an LLVM switched coroutine, as async functions of that
kind already do, instead of running on a fiber with its own stack. Its
steps run on the stack that resumes it (the loop's after an await), its
awaits suspend through scr_coro_await_prepare and scr_coro_hop_prepare,
and its yields store OUT through scr_coro_yield_* before suspending.

- scr_async_gen_new_stackless creates the generator. The stackless hop
  settles an async generator's state as the fiber hop does, and a step
  that would start below the stack guard throws a RangeError, which
  rejects its request.
- Calling an async generator's next, return or throw no longer counts as
  parking the caller: they queue a request and run the body to its next
  suspension, which returns to the caller. Bodies that consume async
  generators can now be stackless too.
- An untyped await (async.awaitDyn) has a native coroutine form,
  extracted by scr_coro_await_dyn_value, so async bodies that await
  untyped values can be stackless.
- An async generator's return through a finally region takes its await
  turn through the coroutine when the body is one.

Debug builds, libraries, worker programs and Windows executables keep
fibers, as for stackless async functions.
2026-10-26 07:28:50 -05:00
Chris Tate acf19e2652 Merge main 2026-10-26 02:53:26 -05:00
Chris Tate 21bce9e4fa Merge main 2026-10-26 00:21:24 -05:00
Chris Tate f591e8e6d7 Print how a --profile-use build's profile matched
`scriptc build --profile-use` prints one line on stderr with the number of
functions the profile annotated, the number whose control flow changed since
it was collected (mismatched), and the number it has no record of. When more
than 10% of the functions the profile describes are mismatched, it also prints
a warning to collect the profile again. The build still succeeds.

Both the compiler library's build and the native compiler's build report the
match. Cached builds report the match of the build that produced them: the
native codegen and object caches store it beside the cached objects, and the
executable cache stores it in its entry.
2026-10-25 22:14:25 -05:00
Malte UblandClaude Opus 5.5 1ae5f105ca Rank dynamic slots by their dynamic operations, name them in SC6101, and document them
Stage L5 of the localized-failure design.

- SC6101 names a dynamic slot as the cause of a likely-hot untyped function
  ('tool' is a dynamic slot, declared 'ToolDefinition', which does not
  compile).
- Coverage counts the dynamic operations each slot's values take part in,
  lists the costly slots first, and puts the count in the JSON envelope.
- A build whose only performance warnings are dynamic slots closes with a
  line about declared types instead of function types.
- Docs: a Dynamic slots section in the coverage guide, the limitations
  page bullet, and --fail-on=dynamic-slots in the CLI reference and usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:48:18 -07:00
Chris Tate cc2266ab1d Add --cpu-dispatch for x86-64 executables (#2288) 2026-10-25 21:36:00 -05:00
Chris Tate 07c5e79ffd Add --cpu-dispatch: x86-64-v3 copies of the program's code chosen at startup
With --cpu-dispatch, an x86-64 executable carries two copies of each
program function: the baseline code and a copy compiled for x86-64-v3.
main reads CPUID and XGETBV once. Calls between the program's functions
go to the caller's copy. A function that is also named outside the
copies (a function value, a call from main, a runtime callback or a
coroutine body) keeps a dispatcher under its own name, which tests the
level and tail-calls one copy. Functions without such a use get no
dispatcher, so their copies have the callers the original had and LLVM
inlines them the same way.

The executable still runs on any x86-64 CPU. SCRIPTC_CPU_LEVEL caps the
level at run time. The option is off by default, applies to executables
without a --target-cpu level, and is part of the executable cache key.
bench:runtime takes --baseline-cpu-dispatch and --candidate-cpu-dispatch,
and the LLVM differential suite runs each program at levels 0 and 3 with
SCRIPTC_CPU_DISPATCH=1.
2026-10-25 20:00:16 -05:00
Malte UblandClaude Opus 5.5 7675b7d653 Add scriptc types patch and scriptc types list
`scriptc types patch <package> <selector> <type> [--range] [--dry-run]`
finds the installed package from the working directory, shows every
declaration the selector matches (declaration files, JSDoc, @types
package) with its type before and after, and adds the patch to the
nearest package.json: into an entry whose range covers the installed
version, or under ^<version>. Nothing is written when the selector
matches nothing. `scriptc types list` shows each patch and what it
matches. Both CLI hosts run the same implementation with their own
parser. The TypeError note for a wrong declaration now names the command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:00:29 -07:00
Malte UblandClaude Opus 5.5 56e8417746 Name the package declaration and its patch when a declared-type check fails
A value that contradicts a type an npm package declares used to throw
"expected number at $, got undefined" and nothing else. Such a check now
names the declaration and the type patch that admits the value:

    profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)
    if that declaration is wrong, patch it in package.json: ...

Origins are found for values a package function returns under a written
return type, for properties of records typed by a package-declared type
(record projections into such a type are keyed by it), and for
declaration-boundary casts, whose origin the projection records from the
declaration-world checker. Generic declarations get none: their types
come from the program's own instantiation.

Checked conversions carry dynCheck.origin, an index into
IrModule.declOrigins; the site's existing cold exception block calls
scr_type_origin_note (new runtime unit behind the typeOrigins feature),
which appends the note to the pending TypeError and derives the selector
suffix and the admitted kind from the failure. Union extractions get the
note per arm at compile time. The hot path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:06:50 -07:00
Malte UblandClaude Opus 5.5 f4825e05a7 Replace a package's declaration files or supply its declarations with type patches
A scriptc.typePatches entry can now replace a declaration file of the
package (or of its @types twin, written @types/<name>/<path>) with a
local .d.ts ("files"), or give the package a declaration entry ("types").
A supplied entry answers the package's types file and the declarations
TypeScript probes beside its JavaScript entries, and exists for the
program host, the resolver and npm-static's untyped-package test, so a
package without declarations becomes a typed package. Declaration patches
still apply on top. A files target the installed copy does not ship is
SC8031.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:05:52 -07:00
Chris Tate 1b9a4e1361 Refuse SC9001 cases and merge main 2026-10-25 13:26:27 -05:00
Malte UblandClaude Opus 5.5 c46f70e451 Fix wrong npm declarations locally with type patches
A project can now patch a package's declarations in package.json:
"scriptc": { "typePatches": { "<package>@<range>": { "declarations":
{ "<selector>": "<type>" } } } }. A leading "|" widens the declared type,
any other type replaces it. Selectors name declarations by path
(Type.member, fn(), fn(param), list()[], Env[string], export=(), default,
file.js:local()) and apply to the package's .d.ts, its JSDoc and its
@types twin alike.

Patches edit file text where every consumer reads it: the program host's
source reads (both checker worlds) and the npm-static passes that read
package files themselves. Only type nodes and JSDoc type tags change, and
no edit adds or removes a line. Installed copies are selected by version;
an @types twin by the runtime package's installed version. Patches that do
not apply as written warn (SC8030-SC8033) and the warnings repeat on
executable cache hits. Without the setting nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:27:52 -07:00
Chris Tate 5da6d76085 Run lone array callbacks as loops with their bodies inlined (#1997) 2026-10-25 05:24:07 -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 d775fdee8c Merge main 2026-10-24 23:26:30 -05:00
Chris Tate fc076258e4 Merge main 2026-10-24 20:07:16 -05:00
Chris Tate 6c18c58708 Merge main 2026-10-24 18:21:25 -05:00
Chris Tate 8da01d3ab3 State that SCRIPTC_CHECKER_SERVER needs a tsc-ts that serves --listen
The tsc-ts 0.0.9 that scriptc includes rejects --listen, so with it every
build that sets SCRIPTC_CHECKER_SERVER=1 checks with its own tsc-ts
process. The variable's description now says so.
2026-10-24 18:02:25 -05:00
Chris Tate c0a4496900 Check with a tsc-ts server shared by later builds (opt-in)
With SCRIPTC_CHECKER_SERVER=1 on Linux and macOS, the native compiler's
checker connections go to a shared `tsc-ts --api --listen` server, which
keeps the files it parsed for the next build. When the variable is unset,
nothing changes.

checkerServerName names the server by a digest of the tsc-ts executable's
identity, the working directory, the file system callbacks, the timing
flag and the TSC_TS_* environment. scriptc_ts7_attach takes the server's
lock file with a non-blocking flock, connects to its socket in this
user's private server directory ($XDG_RUNTIME_DIR/scriptc or
/tmp/scriptc-<uid>), and starts the server when none answers: in its own
process group, with its standard streams on /dev/null and no other
descriptor of the build, with --idle-exit 300 and TSC_TS_LISTEN_MEMORY=2048
unless the environment sets it. A build that finds the server in use,
cannot start or reach it, or gets no answer to a ping checks with its own
tsc-ts process, as without the variable; so does a tsc-ts without
--listen, which exits as it starts. The syntax-only source parser, which
reads no libraries, always uses its own process and leaves the server to
the program's checker.
2026-10-24 17:50:45 -05:00
Chris Tate f23447dc99 Merge main 2026-10-24 06:50:53 -05:00
Chris Tate dd4ae4e508 Merge main 2026-10-24 01:57:53 -05:00
Chris Tate 6715609695 Place each source file's functions in the same partitions
Partitions were filled by a hash of each function's name, so most calls
crossed partitions and the thin link imported their small callees into
nearly every partition. The emitter now writes a "; scriptc-group" line
before the functions of each source file, and the text partitioner places
a group's functions together: a group larger than the mean partition, or
than 1 MiB, is divided into a power of two of chunks by name, and chunks
go, largest first, to the lighter of two partitions chosen by hash. The
1 MiB bound keeps the code a partition optimizes together, and the
inlining within it, from growing when a program has fewer partitions.
Modules with fewer than 32 partitions, and functions without a group,
keep their name-hash placement: with few partitions per core, the balance
of the largest partitions decides the build time more than the imports.
The placement still depends only on the module text and the partition
count.
2026-10-24 00:34:25 -05:00
Chris Tate 0e733f22cf Construct an Array subclass's species result before the first callback, and merge main 2026-10-23 21:25:55 -05:00
Chris Tate e9833e3260 Add --target-cpu for x86-64 microarchitecture levels (#2025) 2026-10-23 20:00:33 -05:00
Chris Tate f0da37fa42 Compile classes that extend Array
A class declared `extends Array` (or `extends Array<T>`) was refused with
"extending classes not declared in the program ('Array')", which blocked
lru-cache at module load. An instance is now an array of the class's
element type: its struct begins with the runtime's array header, whose
reference count it shares, and the class's own fields follow it. The
class's TypeScript type maps to its element array type, so every array
operation applies to an instance, and passing it where an array is
expected passes the same object. The class's own fields, methods,
accessors and statics read the array as the class object.

super() follows the Array constructor: one number argument sets the
length (a RangeError for an invalid one), other arguments become the
elements, and the implicit constructor forwards its arguments the same
way. The runtime finds the class through the array's ext (ScrArrClass:
name, destroy, the own fields, the species constructor), so instanceof
the class (or a binding that holds it) works on static, array-typed and
dyn values, util.inspect
prints `Name(length) [ ..., field: value ]`, Object.keys lists the own
fields, and a release through any array path reaches the class's
teardown. Instances whose elements or fields can form cycles carry the
collector header and are traced.

map, filter, slice, splice, concat, flat and flatMap construct their
result through the class as ArraySpeciesCreate does: `new C(length)` with
the specification's argument, then the elements, then the length where
the specification sets it. A map to another element type retypes the
new instance when its constructor added no elements. Array.of and
Array.from called on the class construct through it. Programs that
declare no Array subclass compile as before.

A generic subclass, a subclass of such a class and `static
[Symbol.species]` are refused at compile time; a species result the
class's constructor cannot construct throws a TypeError.

String() of an array with checked-dynamic elements now uses the
runtime's String() instead of being refused.

Corpus 7720-7726, diagnostics fixture array-subclass-fences.ts.
2026-10-23 19:52:48 -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 bc6359443a Divide mid-size executables into at least eight partitions
An executable's LLVM text was divided into one partition per 4 MiB, so a
program under 4 MiB was optimized and compiled on one thread, and one of
up to 32 MiB used fewer than eight cores. A module over 1 MiB now gets at
least eight partitions of 512 KiB or more: four up to 2 MiB, and eight up
to 32 MiB. Larger modules keep one partition per 4 MiB. The count still
depends only on the module's size.
2026-10-23 16:33: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 c7c8f5d45e Run lone array callbacks as loops with their bodies inlined 2026-10-23 09:25:07 -05:00
Chris Tate ba5d73e90b Recognize esbuild's __commonJS helper under a minified name
Refs #1777

An identifier-minified esbuild bundle renames the __commonJS helper
(var j=(u,t)=>()=>(t||u((t={exports:{}}).exports,t),t.exports)). The
recognizer already compared the helper's shape independently of its
parameter names, but only used a renamed match to refuse the package.
A stable top-level declaration with the helper's shape now splits the
bundle's chunks like the named helper, so lru-cache's CommonJS build is
no longer refused.
2026-10-23 06:20:02 -05:00
Chris Tate 967f2ec85d Compile minified npm entries and untyped regex arguments
Refs #1777

npm packages are no longer refused up front for minified JavaScript. A
bundle whose interop helpers lost their names is still refused by the
bundle recognizers, and a package whose attempt fails is still refused on
its own.

A `var` read in a later declarator of the statement that stores it
(`var a = {...}, b = {...a}`) runs after the store. A read in a function
body is now located at the body's own statement instead of the body, so
JavaScript `var` bindings read after their store in a function keep their
type instead of gaining undefined.

`replace` and `replaceAll` with an untyped search value and a callback
call the regex callback replacement for a RegExp and the string-search
helper otherwise. That helper now accepts untyped callback parameters,
passes undefined to parameters after the subject, and converts number,
boolean and untyped results with ToString.

In JavaScript, `||` and `&&` over primitives of different types whose
result tsc types `any` evaluate in the checked domain.
2026-10-23 05:55:39 -05:00
Chris Tate 63fd3cecb7 Build @hono/node-server 2.x into scriptc (#1886) 2026-10-23 05:25:14 -05:00
Chris Tate 952e01d2ed Stream request bodies in the Node server adapter 2026-10-23 05:03:12 -05:00
Chris Tate fc31dd5060 Enumerate each record in the key order its object literal wrote 2026-10-22 22:56:12 -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 9bf76af974 Build @hono/node-server 2.x into scriptc
@hono/node-server replaces the global Request and Response with
lightweight objects and reparents prototypes, tricks that only pay off
under V8 and that native compilation cannot follow. scriptc now compiles
the package from a built-in implementation (shims/hono-node-server/v2.ts)
over node:http and the native Request/Response/Headers: serve,
createAdaptorServer, getRequestListener and RequestError.

Responses are written the way the package writes them, byte for byte on
the wire: the general path (header record in Headers order, Set-Cookie
gathered, default content type, content length for bodies that finish
within the first reads, streamed bodies, error responses) and the
package's cached-response path for a Response that is still exactly its
constructor arguments. The runtime tracks that lightweight state on each
constructed Response (header form, raw plain-record headers, body length;
cleared by body and metadata reads, turned into Headers form by a headers
read) and exposes it to the shim through scriptcResponseLight.

The declaration entry of a shimmed package may now be a .d.mts/.d.cts
file. The npm case hono-node-server drives buffered, streamed, cookie,
header-form, HEAD, error-handler and URL cases against the real package
under Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:32:49 -07:00
Chris Tate 1660531c7b Support FinalizationRegistry with bookkeeping and no cleanup callbacks (#1854) 2026-10-22 20:03:31 -05:00
Chris Tate 636ffa21be Merge main 2026-10-22 19:59:51 -05:00
Chris Tate 3024e57eeb Revert "Enumerate each record in the key order its object literal wrote (#1802)" 2026-10-22 19:02:41 -05:00