Malte Ubl e7eef59ec1 Fast paths for number<->string conversion (Number/parseFloat, toFixed, two-decimal String)
String to number: Number(str), unary +, and parseFloat now convert short
decimal spellings with Clinger's exact fast path (scr_decimal_scan in
scr_number.c): significant digits m <= 2^53 and a decimal exponent within
+-22 (plus the folded range up to 22 + 15) need one correctly rounded
multiply or divide, bit-identical to strtod. ToNumber validates and
accumulates in the same pass; everything else still goes through the copied
span and strtod. Whitespace trimming in Number(), parseInt, and parseFloat
skips the UTF-8 decode for ASCII non-whitespace.

toFixed: the exact-binary-value digit generation moves from scr_lib.c into
scr_number.c (scr_f64_to_fixed). For f <= 22, mantissa * 5^f fits a
64x64->128 multiply, so the spec's n is one shift plus the dropped-half bit
whenever it fits 64 bits. Digits render two at a time instead of through
snprintf. The 16-limb bignum remains the fallback.

Number to string (H44): a non-integral x below 2^46 that is the double
nearest to m/100 prints as m/100 directly instead of running Ryu. Below 2^46
ulp(x) < 0.01, so m/100 is the unique shortest round-tripping decimal. The
correctly rounded division (double)m / 100 == x is the proof. This covers
JSON.stringify, templates, String(), join, and console output.

Tests: Node oracles for every path. number-cases.txt gains money-like values,
the 2^46 cutoff, and one-ulp neighbors. tonumber-cases.txt gains fast-path
boundaries plus 20k seeded short decimals. The new parsefloat-cases.txt pins
scr_parse_float, and the new fixed-cases.txt/test_fixed.c pin toFixed,
including the 2^64 fast-path limit and f up to 100. Fuzz modes cover all four
paths. Existing case lines are unchanged. Corpus 3170 pins the paths
end-to-end against Node.

Linux x64 sandbox, A/B against perf/stack/06-h23-layout-noise,
--layouts=4 --runs=16 (run 3 of 3; geomean -6.0% / -6.7% / -6.3%):

| workload | baseline ms | candidate ms | change | 95% CI | verdict | size | node ms | candidate/node |
| --- | ---: | ---: | ---: | --- | --- | ---: | ---: | ---: |
| json-records | 236.2 | 204.2 | -13.5% | -19.8% .. +2.1% | neutral | +0.0% | 488.62 | 0.42x |
| regex-logs | 176.2 | 168.8 | -4.2% | -10.1% .. +0.3% | neutral | +0.0% | 281.23 | 0.60x |
| ast-interp | 280.0 | 273.1 | -2.5% | -5.6% .. +1.4% | neutral | +2.8% | 273.05 | 1.00x |
| records-sort | 952.4 | 939.4 | -1.4% | -5.0% .. +5.6% | neutral | +0.0% | 289.87 | 3.24x |
| template-render | 78.9 | 77.3 | -2.1% | -6.2% .. +1.2% | neutral | +0.0% | 246.71 | 0.31x |
| functional-pipeline | 338.7 | 348.8 | +3.0% | -6.5% .. +24.0% | neutral | +0.1% | 339.84 | 1.03x |
| async-pipeline | 207.3 | 207.8 | +0.2% | -5.3% .. +3.8% | neutral | +0.0% | 105.57 | 1.97x |
| word-graph | 302.0 | 300.1 | -0.6% | -5.1% .. +12.9% | neutral | +3.0% | 600.48 | 0.50x |
| alloc-trees | 336.3 | 332.4 | -1.1% | -6.2% .. +2.6% | neutral | +0.1% | 144.73 | 2.30x |
| numeric-kernels | 49.7 | 49.8 | +0.1% | -3.4% .. +6.5% | neutral | +4.0% | 143.73 | 0.35x |
| validate-errors | 179.1 | 182.9 | +2.1% | -5.8% .. +9.1% | neutral | +0.0% | 727.78 | 0.25x |
| csv-numbers | 349.0 | 233.8 | -33.0% | -37.5% .. -26.5% | faster | +0.1% | 541.77 | 0.43x |
| log-summary | 171.6 | 135.7 | -20.9% | -23.3% .. -17.7% | faster | +1.8% | 313.2 | 0.43x |
| inventory-report | 411.9 | 384.7 | -6.6% | -18.1% .. +5.5% | neutral | +2.7% | 521.27 | 0.74x |

Size: +64..+136 bytes per executable. The +1.8..+4.0% rows are one 4 KiB
page crossing (two for inventory-report).
2026-10-08 15:49:53 -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
153 MiB
Languages
TypeScript 67.8%
C 24.5%
JavaScript 7.3%
C++ 0.3%