Malte Ubl d5e42b049e Skip cycle collection for shapes whose self references are constructor-only
A reference edge that can only be stored while its owner is still under
construction points to an object that existed before the owner, so a heap
cycle made only of such edges is impossible. computeTraced now builds the
shape/union graph with per-edge mutability, finds strongly connected
components, and marks a component cycle-capable only if it holds an
intrinsic reference (closure, dyn, class value, caught, promise) or a
mutable internal edge; units that reach such a component stay traced. With
every edge mutable this is exactly the previous fixpoint.

Mutability comes from the final IR (cycle-mutability.ts), not from the TS
readonly modifier: every fieldSet outside a constructor's own this,
fieldIncDec, recordSet, recordKeySet and recordKeyDelete counts; a
constructor where this escapes (anything but a field store target, a field
read, or the receiver of the direct base constructor call, including
closure/class captures) makes its hierarchy mutable, as does any other call
of a constructor function; every type that can cross into checked-dynamic
code (dynFrom operands including boxed closure signatures, thrown values,
library/intrinsic/JSON/FFI arguments) is mutable with its whole type
closure, because typed-ref capsules commit dynamic writes into native
fields; runtime-rooted hierarchies stay mutable; library builds and
dynamic-island programs disable the refinement. Union arms are immutable
(boxes are), collection edges are always mutable.

New corpus programs pin the acyclic case (DAG sharing, a 200k-node chain,
derived constructors, records) and try to defeat the proof (constructor
escapes, readonly bypass through casts/mapped types/any/Reflect/aliases,
subclass rewrites, readonly arrays, untyped writes through capsules).
2026-10-08 15:26:31 -05:00
2026-10-08 11:00:13 -05:00
2026-07-22 18:08:04 -05:00
2026-10-07 19:07:02 -05:00
2026-10-07 19:15:55 -05:00
2026-07-22 18:08:04 -05:00
2026-07-22 18:08:04 -05:00

scriptc

Vercel Labs Experiment npm version: scriptc License: Apache-2.0 npm downloads per month: scriptc

scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code to native instructions. Static builds run without Node.js or a JavaScript engine.

For npm dependencies and any-typed code, enable --dynamic to include the embedded quickjs-ng engine. Dependency JavaScript is included at build time.

scriptc is experimental and supports a subset of JavaScript, TypeScript, and Node.js APIs. See the limitations and Node.js compatibility reference before using it for an existing project.

Installation

Install with Node.js 24 or later and npm:

$ npm install -g scriptc

The installed compiler runs natively on supported macOS, Linux, and Windows hosts. Native compilation and execution do not require Node.js. Standalone compiler archives are also available from GitHub Releases.

Executable builds require a platform linker and SDK/sysroot. See the quickstart for prerequisites and platform support for host requirements, cross-compilation, and WebAssembly.

Build a program

Create hello.ts:

const who = process.argv.length > 2 ? process.argv[2] : "world";
console.log(`hello, ${who}`);

Compile and run it in one step:

$ scriptc run hello.ts
hello, world

Or write a standalone executable:

$ scriptc build hello.ts -o hello
$ ./hello scriptc
hello, scriptc

These examples use POSIX shell syntax. On Windows, build hello.exe and run it with .\hello.exe.

Use scriptc coverage hello.ts to check which operations compile statically, require dynamic execution, or are unsupported. See coverage reports for details.

Use Node APIs

Supported Node APIs compile to the native runtime. For example, server.ts:

import { createServer } from "node:http";

const server = createServer((req, res) => {
  res.setHeader("content-type", "application/json");
  res.end(JSON.stringify({ path: req.url }));
});

server.listen(8080, () => {
  console.log("listening on http://localhost:8080");
});
$ scriptc build server.ts -o server
$ ./server
listening on http://localhost:8080

Use npm packages

Pass --dynamic to embed an npm package's JavaScript in the executable. The result does not read node_modules at runtime.

Create cli.ts:

import pc from "picocolors";

console.log(pc.green("hello from scriptc"));
$ npm install picocolors
$ scriptc build cli.ts --dynamic -o cli
$ ./cli
hello from scriptc

Enabling --dynamic does not add support for unsupported statically typed calls. See npm dependencies for package resolution and the boundary between static and dynamic code.

Documentation

Contributing

See CONTRIBUTING.md for source setup, testing, native artifact builds, and docs development.

S
Description
GitHub Trending: vercel-labs/scriptc
Readme Apache-2.0
208 MiB
Languages
TypeScript 67.9%
C 23.5%
JavaScript 7.9%
C++ 0.6%