Malte Ubl 363fb69f07 Run await-free async functions without a fiber
An async function whose body can never park its execution context
completes before its call returns, exactly like a fiber that never
suspends. The LLVM emitter now proves that from the emitted IR: a body
qualifies when neither its own text nor that of any module function it
mentions (transitively) contains a suspension entry point (scr_await_*,
scr_module_await, scr_promise_await_settled, scr_test_sub, generator
yields, async-generator operations, fiber accessors). wasm32, programs
embedding the dynamic engine, module-evaluation bodies, dyn results, and
programs using node:test subtests (t.test awaits on the current fiber
even from sync code) keep fibers. Qualifying spawn wrappers call the body
on the caller's stack between scr_async_inline_enter/leave, which save
and restore what a fresh fiber isolates (the Error.stack frame chain and
the AsyncLocalStorage context), then settle a fresh promise from the
outcome through the same fulfill/reject paths the fiber trampoline uses.
A park or hop while a fiberless frame is on a fiber's stack traps as an
internal error rather than reordering.

In async-pipeline, loadUser and authorize become fiberless: one fiber
per request instead of five. New corpus programs pin microtask ordering,
rejections (handled and unhandled), finally, nested suspending spawns,
closures, methods, Error.stack, AsyncLocalStorage, and a fiber-switch
stress test.

Final benchmarks for the branch (three commits) vs perf/harness, runs=15.

macOS arm64 (local):
| workload | baseline ms | candidate ms | change | 95% CI | verdict | size | node ms | candidate/node |
| --- | ---: | ---: | ---: | --- | --- | ---: | ---: | ---: |
| json-records | 279.2 | 280.7 | +0.5% | -1.8% .. +3.0% | neutral | +0.0% | 230.93 | 1.22x |
| regex-logs | 305.8 | 306.0 | +0.1% | -0.8% .. +2.0% | neutral | +0.0% | 132.42 | 2.31x |
| ast-interp | 235.3 | 235.1 | -0.1% | -0.6% .. +0.4% | neutral | +0.0% | 133.55 | 1.76x |
| records-sort | 621.6 | 619.0 | -0.4% | -1.1% .. +0.6% | neutral | +0.0% | 131.56 | 4.71x |
| template-render | 202.8 | 201.9 | -0.4% | -1.7% .. +2.6% | neutral | +0.0% | 99.28 | 2.03x |
| functional-pipeline | 226.0 | 226.4 | +0.2% | -0.4% .. +0.6% | neutral | +0.0% | 120.24 | 1.88x |
| async-pipeline | 267.0 | 21.6 | -91.9% | -92.2% .. -91.4% | faster | +0.0% | 58.58 | 0.37x |
| word-graph | 417.6 | 566.8 | +35.7% | -49.6% .. +171.5% | neutral | +0.0% | 944.76 | 0.60x |
| alloc-trees | 311.1 | 311.3 | +0.1% | -4.8% .. +2.3% | neutral | +0.0% | 87.37 | 3.56x |
| numeric-kernels | 79.0 | 80.5 | +1.9% | -4.1% .. +8.4% | neutral | +0.0% | 94.05 | 0.86x |
| validate-errors | 169.9 | 169.4 | -0.3% | -12.0% .. +12.3% | neutral | +0.0% | 517.13 | 0.33x |
| csv-numbers | 379.3 | 372.8 | -1.7% | -27.8% .. +12.5% | neutral | +0.0% | 360.69 | 1.03x |
| log-summary | 180.7 | 184.0 | +1.8% | -14.4% .. +9.1% | neutral | +0.0% | 181.51 | 1.01x |
| inventory-report | 231.7 | 239.0 | +3.2% | -11.1% .. +10.9% | neutral | +0.0% | 245.91 | 0.97x |
(word-graph was host noise from concurrent agents; the immediate recheck
measured -2.0%, neutral.)

Linux x86_64 (sandbox):
| workload | baseline ms | candidate ms | change | 95% CI | verdict | size | node ms | candidate/node |
| --- | ---: | ---: | ---: | --- | --- | ---: | ---: | ---: |
| json-records | 1031.8 | 1019.7 | -1.2% | -5.5% .. +3.2% | neutral | +0.0% | 627.39 | 1.63x |
| regex-logs | 534.1 | 533.6 | -0.1% | -5.5% .. +1.9% | neutral | +0.0% | 336.44 | 1.59x |
| ast-interp | 538.1 | 536.8 | -0.2% | -1.7% .. +2.3% | neutral | +0.0% | 324.66 | 1.65x |
| records-sort | 1798.6 | 1749.9 | -2.7% | -4.8% .. +0.5% | neutral | +0.0% | 326.49 | 5.36x |
| template-render | 379.2 | 371.0 | -2.1% | -9.3% .. +1.0% | neutral | +0.0% | 287.51 | 1.29x |
| functional-pipeline | 608.5 | 596.7 | -1.9% | -3.9% .. +3.2% | neutral | +0.0% | 390.16 | 1.53x |
| async-pipeline | 250.3 | 35.9 | -85.7% | -86.2% .. -85.3% | faster | -0.0% | 123.43 | 0.29x |
| word-graph | 462.1 | 461.7 | -0.1% | -3.3% .. +2.4% | neutral | +0.0% | 690.81 | 0.67x |
| alloc-trees | 690.4 | 699.1 | +1.3% | -0.9% .. +4.1% | neutral | +0.0% | 174.67 | 4.00x |
| numeric-kernels | 176.0 | 172.8 | -1.8% | -3.4% .. +0.6% | neutral | +0.0% | 176.9 | 0.98x |
| validate-errors | 304.4 | 301.0 | -1.1% | -4.9% .. +1.6% | neutral | +0.0% | 945.13 | 0.32x |
| csv-numbers | 602.8 | 605.0 | +0.4% | -4.7% .. +1.9% | neutral | +0.0% | 696.66 | 0.87x |
| log-summary | 332.9 | 332.6 | -0.1% | -4.0% .. +1.4% | neutral | +0.0% | 384.79 | 0.86x |
| inventory-report | 601.0 | 583.5 | -2.9% | -6.5% .. +5.6% | neutral | +0.0% | 585.31 | 1.00x |
2026-10-08 15:22:27 -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
180 MiB
Languages
TypeScript 68%
C 23.4%
JavaScript 7.9%
C++ 0.6%