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`.
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>
The private copy of the LLVM text had no extension. Builds that compile it
with the C driver (sanitize builds, targets without the LLVM helper) passed
it to clang directly when the native cache was off, and clang read it as a
linker input, so the build failed. The copy is now named with `.ll`.
Those builds also record the output directory in their native cache proof.
Renaming the private copies to the shared names after the native build
changed that directory, so every unchanged repeat missed the proof and
relinked the executable. The shared names are now installed before the
native build, which compiles and caches copies in a private directory
outside the output directory.
The kept LLVM text and IR are named after the entry (`<stem>.ll`,
`<stem>.ir.json`) in the output directory, so two programs whose entries
share a name and are built into one directory share those files. The
executable cache published the translation unit by copying the shared
`.ll` after the native build, so when the other program wrote it in
between, the stamp recorded the other program's text, and a later cache
hit relinked the wrong program without any error.
A build now writes, compiles and caches private copies beside the shared
names and renames them into place when it ends. A cache hit that relinks
compiles a private copy of the cached translation unit, and leaves a
shared file that already holds those bytes as it is. The native build
reads the private copy and keeps the shared name as the spelling clang
sees, so native cache keys and debug names do not change.
crypto.createDiffieHellman, crypto.getDiffieHellman and
crypto.createDiffieHellmanGroup return native DiffieHellman and
DiffieHellmanGroup handles backed by mbedTLS bignums. generateKeys,
computeSecret, getPrime, getGenerator, getPublicKey, getPrivateKey,
setPublicKey, setPrivateKey and verifyError follow Node 24.22:
- the modp1, modp2, modp5 and modp14 through modp18 groups, with the
private key widths of Node's OpenSSL 3 path (200 to 400 bits for
modp5 and up, [2, p - 2] for modp1 and modp2);
- explicit primes and generators in every argument form and encoding,
with keys of bits(p) - 2 bits, or below min(2^N, q) when the
parameters equal an OpenSSL named group (RFC 3526, RFC 7919,
RFC 5114);
- generated safe primes with OpenSSL's congruences for the generator;
- verifyError's DH_check bits, computed when it is first read;
- Node's argument, generator, modulus, key range, state and encoding
errors, including the OpenSSL errors' library and reason properties.
Safe-prime generation sieves candidates with the odd primes below a
bound that grows with the prime's length and tests them with
Miller-Rabin rounds over a variable-time Montgomery exponentiation that
only reads public values; key and secret exponentiations stay on
mbedTLS's constant-time path. Primes longer than 8192 bits, property
reads other than verifyError, property writes, Object.prototype methods,
and calls whose output encoding is typed any or matches no @types/node
overload are refused.
The runtime unit scr_dh.c links, with the mbedTLS archive, only into
programs that use DiffieHellman. wasm32-wasi builds refuse it. The ECDH
unit's argument and encoding helpers move to scr_crypto_args.h, which
both units include.
The frontend, lowering and LLVM emission of an optimized executable build run in a child
process when no earlier build of the same invocation emitted its native program object.
The parent passes the LLVM text to the native helper as the file the child wrote, so the
helper runs after the pages the frontend used are released. Later builds of the
invocation find most native partitions cached and peak in the frontend, so they, along
with development builds and builds with SCRIPTC_FRONTEND_PROCESS=0, prepare their input
in-process. The child runs with the parent's Node options without --eval, --print and
their code, or inspector, watch and test runner options. With SCRIPTC_TIMING=1, it
reports its phases on the parent's clock.
crypto.createECDH returns a native ECDH handle backed by mbedTLS for
prime192v1, secp224r1, prime256v1, secp384r1, secp521r1, secp192k1,
secp224k1, secp256k1, brainpoolP256r1, brainpoolP384r1 and
brainpoolP512r1. generateKeys, computeSecret, getPublicKey,
getPrivateKey, setPrivateKey, setPublicKey and ECDH.convertKey follow
Node 24: compressed, uncompressed and hybrid points, input and output
encodings, the private key range, key pair validation, and Node's error
codes and messages.
Curve names follow OpenSSL's short-name lookup: names of other curves
from crypto.getCurves() are refused at compile time when literal and
throw a coded SC2020 error when computed, other OpenSSL short names
throw ERR_CRYPTO_OPERATION_FAILED, and unknown names throw
ERR_CRYPTO_INVALID_CURVE. Calls whose arguments no @types/node overload
accepts, and computeSecret(view, encoding), are refused at compile time.
The runtime unit scr_ecdh.c links, with the mbedTLS archive, only into
programs that use ECDH. wasm32-wasi builds refuse it.
hot-shots, a StatsD client compiled from its JavaScript, failed when a
program constructed a client. Each gap on its UDP path is closed
generically:
- Sloppy-mode functions with named parameters may read `arguments`
where the aliasing of their parameters cannot show: only
`arguments.length`, or element reads without parameter or element
writes.
- An `any` slot admits any boxable function value, including functions
with optional parameters (`C.prototype.m = function (...)` through an
untyped receiver).
- In a JavaScript file TypeScript does not check, a prototype method
takes its documented parameters as checked-dynamic values, as
constructors already did.
- JavaScript dgram sends run completion callbacks, buffer lists,
connected sends and the six-argument offset/length form.
- A dgram socket crosses into the checked-dynamic tree as a handle
(SCR_DYNH_DGRAM_SOCKET) whose methods are function values, with
untyped listeners that survive 'close'. The handle lives in its own
runtime unit (scr_dgram_dyn.c, the dgramDyn feature), linked only
when a program converts a socket to or from an untyped value.
- `String.prototype.concat` on strings, and `parseInt` over a possibly
missing or untyped value in JavaScript.
Corpus programs 6590-6596 and the hot-shots-udp npm
case (hot-shots 8.5.0 vendored) match Node in both lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pipe() into an untyped destination now comes from main's pipeIntoDyn
(stream.unboxDyn); programs with constructor-function streams unbox
through scr_dyn_builtin_stream.c, which also takes those streams, so the
separate dyn.builtinStreamPipe entry point is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundled npm code (from2, bl, tar-stream, yazl, multistream) builds its
streams as ES5 constructor functions: util.inherits(Ctor, Readable) and
Readable.call(this, opts), with _read/_write/_transform on the
prototype. Readable.call(this) threw SC2020.
A new runtime unit, scr_dyn_builtin_stream.c (feature
dynBuiltinStream), gives the untyped stream constructors their stream
half. Base.call(this, opts) creates a runtime stream over the receiver
and keeps it in a hidden slot; the runtime calls the receiver's
underscore methods through its property lookup; the stream prototypes
carry Node's methods and accessors over the hidden stream; and
EventEmitter's methods register listeners with the runtime stream's
emitter, so runtime events and pipes reach JavaScript listeners with the
receiver as this. _readableState/_writableState are live views.
Stream class values now come from dyn.builtinStreamClass, so only
programs that use them link the stream half. Typed code sees these
objects as their runtime streams: checked casts to the stream classes
take them, readable.pipe() into an untyped destination pipes into them,
and runtime streams crossing into JavaScript answer the constructor
object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- matchInferSignature reads a fixed-tuple rest as its positions, so
(...args: [string, number]) needs two arguments, and a positional
any[] pattern parameter matches only when the function's parameter
there accepts an array, as TypeScript's assignability does
(corpus 6398).
- Merge origin/main (200fe8d22); node-v24-backlog.json regenerated.
Bundled npm code (glob, yazl, from2, multistream) subclasses Node's
classes with constructor functions: util.inherits(Ctor, EventEmitter) and
EventEmitter.call(this). util.inherits received an identity token and
threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and
EventEmitter.prototype were refused, and `this instanceof Ctor` against
a function was refused.
A new runtime unit (scr_dyn_builtin_class.c, linked by the new
dynBuiltinClass feature) provides EventEmitter, the legacy Stream and
the stream classes as untyped constructor functions with Node's
prototype chain. EventEmitter's prototype ports lib/events.js over the
object's own _events, _eventsCount and _maxListeners properties; Stream
adds the legacy pipe. util.inherits, Base.call/apply(this, ...),
Base.prototype, instanceof on untyped values and JavaScript value reads
of these classes use them. Constructing over a stream class throws
SC2020 where it runs. instanceof against a compiled constructor function
compares its prototype with the value's prototype chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main replaced the runtime-feature scan with a rule table, the executable
unit list with a unit table and the module emission with emit-module.ts:
perfLoop becomes a rule (perf.idleTime, perf.loop*, perf.eld*), a unit
(scr_perf_loop.c) and emit-module's loop-start mark (readsLoopMetrics is
now public). The feature lists take both perfLoop and main's webcrypto.
This branch's lower-exprs.ts hunks move to expressions/property-access.ts
(histogram readings), expressions/binary.ts (`??` with a function
default) and expressions/relational.ts (`in` over class members); the
caughtRead fallback stays in lower-exprs.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
util.promisify(fn) over a function typed any (an npm package method typed
by its JavaScript, a bound method of an untyped value) was refused,
because the promisified signature is only known at run time. A native
wrapper now calls the function with the call's receiver, its arguments
and a callback, and answers a promise: a truthy error rejects, otherwise
the first value fulfills, the first settlement wins, and a synchronous
throw rejects. util.promisify.custom functions are not consulted.
The wrapper lives in its own runtime unit (scr_util_promisify.c), linked
by the new utilPromisify feature, so other programs are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once generic wrappers compile (#1413), the code behind them is reachable.
This closes the general gaps found there:
- performance.eventLoopUtilization() and monitorEventLoopDelay()'s
IntervalHistogram (enable, disable, reset, percentile and the min, max,
mean, stddev, count and exceeds readings), under the fallback
declarations and @types/node. A new runtime unit, scr_perf_loop.c, links
only for programs that read them (the perfLoop feature): an unref'd
sampling interval records into HdrHistogram-shaped buckets, and
utilization measures active time as the main thread's CPU time since
the loop started.
- Catch bindings narrowed by a user type predicate or an assertion read
their members through the checked-dynamic value.
- `??` with a function default of a different signature takes the
optional callback's own function type and adapts the default.
- Optional calls on interface-typed receivers dispatch like the plain
protocol call.
- `any` parameters of function declarations and spelled function types
are checked-dynamic slots, and a generic instantiated at `any` takes
`unknown` there.
- `'key' in err` on Error receivers answers from the declaring subclasses.
- Object.entries over a generic record resolves
`keyof T extends never ? unknown : T[keyof T]` through the binding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crypto.subtle.importKey('raw', ...) for HMAC keys, HMAC sign and verify,
and the SHA-1/SHA-2 digests compile through the crypto global, with
CryptoKey values as native handles. scr_webcrypto.c is only the WebCrypto
layer (Web IDL conversion, algorithm normalization, the CryptoKey handle,
promise wrapping and Node's TypeErrors and DOMExceptions) over node:crypto's
digest core and deferred promise queue. It links only into programs that
call these methods (the new webcrypto link feature). Other algorithms, the
jwk format and the other SubtleCrypto methods are SC2020 refusals.
The real jose@6.2.12 HS256 path (SignJWT, jwtVerify) now runs natively. It
also needed four compiler fixes: object patterns of positions and length
over arrays, the Uint8Array base64/hex feature probes, a compile-time
String.fromCharCode.apply, and structuredClone of a parameter whose default
is {}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scriptc's frontend talks to a TypeScript 7 API server. tsc-ts serves the
same API as tsgo, and it is now the default checker; SCRIPTC_CHECKER=tsgo
selects tsgo, which scriptc still includes.
- @scriptc/compiler depends on tsc-ts 0.0.3. The native distribution
ships its platform package as lib/tsc-ts, named by a new tsc_ts field
of the toolchain manifest; a manifest without it takes the executable
of an installed tsc-ts package.
- The executable cache key includes the tsc-ts executable's identity, in
the compiler and in the CLI's startup route.
- tsc-ts keeps the syntax trees it builds for the life of its process,
so the source parser starts a new server after 256 KB of parsed
source instead of letting one server grow with every file.
- A CI job compiles every corpus program with both checkers, in release
and dev, and requires the same LLVM IR and diagnostics
(scripts/checker-identity.mjs).
- Tests that compare with the TypeScript 7 SDK, the checker-panic
diagnostics, and the protocol tests run against tsgo.
T10 and T12 of the zero-cost compatibility plan, plus the maintainer
decisions:
- Programs that name Temporal type-check against TS 7's temporal lib
(added only for them) and lower to Node 24's answers: typeof is
"undefined", globalThis.Temporal is undefined, other uses throw
ReferenceError at the root read.
- node:v8 is importable; getHeapStatistics answers allocator-derived
figures in Node's shape; other members fence with the by-design reason.
vm's refusal says why.
- node:stream/web exports the web stream globals themselves.
- node:dns/promises and dns.promises: lookup and lookupService as
promises, in the link-gated scr_dns_promises.c unit.
- eval and the Function constructor throw Node's EvalError under
--disallow-code-generation-from-strings; the corpus harness runs the
Node oracle with that flag for `// @disallow-code-generation`.
- graceful-fs is served as node:fs.
Every new lowering replaces a refusal, and the new runtime code links only
when used, so other programs compile to identical bytes (zero-cost gate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`scriptc build|run --detached-exit` opts an executable into handing the address-space teardown after process.exit to a helper process on Linux. The helper shares the address space (CLONE_VM), blocks signals, closes every descriptor but the read end of a pipe and waits for EOF on it. The kernel releases an exiting process's memory (exit_mm) before its files (exit_files), so EOF means the helper holds the last reference; it then exits and the kernel frees the pages there instead of before the parent sees the exit (30-40 ms per GB of 4 KiB pages; tsc-ts 20-110 ms). The same latency mold hides with --fork.
It is a build-time choice, not a run-time knob, because the helper keeps the executable mapped (overwriting the binary right after it exits can fail with ETXTBSY) and the memory charged to the cgroup for up to ~100 ms, and the teardown CPU time leaves the parent's rusage. The program object defines `scr_detached_exit = 1`, overriding the runtime's weak default of 0, so no runtime flavor or linker flag is added. The option is part of the early executable cache and route keys; executables only, no effect on other platforms.
Speed is a third native optimization posture next to release (default,
-O2) and dev (-O0). It is optimized exactly like release at the C/LLVM
level and is the switch for optimizations that trade executable size and
build time for run-time speed, so the default release build stays inside
the binary-size budget.
- backend/optimization.ts: the NativeOptimization type, its -O class, and
cache-key helpers. Release keeps its historical key shapes (absent).
- CLI: build/run/cache warm accept speed; usage text names the trade.
Bootstrap's routed executable cache mirrors the posture.
- Early executable cache keys and native-feature validation include speed,
so release, dev, and speed entries never satisfy one another.
- Runtime packs: an optional executable `speed` flavor (-O2, only beside
release). Speed links it when present and the release objects otherwise;
library modes link their release flavor. The sanitizer lane follows the
same flavor key.
- Library profiles accept "speed".
- WASI release-only linker flags apply to speed too.
- bench-runtime: --baseline-optimization/--candidate-optimization and a
per-contender cold_build_ms.
Release executables are byte-identical to the stack tip for all 14
benchmark workloads (macOS arm64, sha256 of executables built from both
trees).
- Add native worker execution with isolated module state, structured messages, and lifecycle controls.
- Support shared buffers and integer atomics with coordinated runtime ownership and cancellation.
- Extend compiler, runtime-pack, differential, and compatibility coverage for native concurrency.
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
- Remove C emission, debugging options, and automatic backend fallback.
- Complete LLVM lowering and native bootstrap coverage across supported targets.
- Link executables and libraries with precompiled C runtime packs, including iOS and Android.
- Emit source locations for native TypeScript and JavaScript debugging across C and LLVM backends.
- Preserve macOS debug symbols through linking and cache reuse, with regression coverage and Xcode guidance.
Fixes#461
Co-authored-by: timokosonen <325538238+timokosonen@users.noreply.github.com>