mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
A build now translates every WebAssembly module whose bytes it can see (W2's discovery) into LLVM IR with a TypeScript translator (packages/compiler/src/wasm/module.ts, translate.ts) and appends it to the program module, so the existing LLVM helper optimizes, partitions and caches it with the program. The program also emits scr_wasm_registry, a portable table of module descriptors (scr_wasm_abi.h). At run time, WebAssembly.compile, instantiate, validate, new Module and new Instance find the translated module by the SHA-256 of its bytes; bytes the build did not see reject with a CompileError. There is no interpreter and no JIT. The runtime (scr_wasm.c, link-gated by the new `wasm` feature, handle tags inside W0's headroom) implements the JavaScript API once, over descriptors, with Node's semantics and messages: exports (functions, memories, tables, globals), imports (functions, immutable globals, and memories, tables and globals other instances export), every trap as WebAssembly.RuntimeError, the CompileError, LinkError and RuntimeError classes, compile and instantiate settling on a later turn, and globalThis.WebAssembly as a value. On 64-bit Linux and macOS, memories sit in 8 GiB guard reservations and a SIGSEGV/SIGBUS handler turns faults into traps or stack exhaustion; async fibers get a guard page on first use. Sanitizer builds and Windows check bounds and the stack explicitly. A WebAssembly.Module crosses to workers through W0's transfer hook (W4). W1's refusals become lowerings; the Memory, Table and Global constructors, Module.imports/exports/customSections and the streaming functions still refuse with SC2020. The globals: WebAssembly compatibility row moves to partial. Evidence: differential corpus programs for the API, TypeScript use and the codemode worker shape, plus five programs executing about 9,300 commands of the WebAssembly 2.0 spec suite; all match Node in the plain and sanitized lanes. quickjs-wasi runs through its own glue, matching Node (apart from a pre-existing non-wasm fence) and on par with or faster than V8. The zero-cost gate leaves every non-wasm program byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
3.5 KiB
TypeScript
69 lines
3.5 KiB
TypeScript
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
|
|
// The codemode shape: the main thread compiles a module once and sends the
|
|
// WebAssembly.Module to a fresh Worker per job, through workerData and
|
|
// through postMessage; each worker instantiates it with its own imports,
|
|
// runs it, and reports back. Traps in a worker (out-of-bounds memory and
|
|
// stack exhaustion, both caught by the per-thread fault handling) stay in
|
|
// that worker. The module text is tests/wasm-spec/corpus/api.wat.
|
|
const bytes = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64");
|
|
|
|
interface Job {
|
|
module: WebAssembly.Module;
|
|
input: number;
|
|
}
|
|
|
|
function run(module: WebAssembly.Module, input: number): string {
|
|
const seen: number[] = [];
|
|
const imports = {
|
|
env: {
|
|
log: (v: number): void => {
|
|
seen.push(v);
|
|
},
|
|
fail: (v: number): number => v * 2,
|
|
base: input,
|
|
big: BigInt(input),
|
|
},
|
|
} as unknown as WebAssembly.Imports;
|
|
const instance = new WebAssembly.Instance(module, imports);
|
|
const exports = instance.exports;
|
|
const apply = exports.apply as (fn: number, v: number) => number;
|
|
const callFail = exports.callFail as (v: number) => number;
|
|
const trap = exports.trap as (kind: number) => number;
|
|
const recurse = exports.recurse as (v: number) => number;
|
|
const traps: string[] = [];
|
|
for (const attempt of [() => trap(2), () => recurse(0), () => trap(1)]) {
|
|
try {
|
|
attempt();
|
|
} catch (e) {
|
|
traps.push(e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
}
|
|
}
|
|
return `job ${input}: start ${seen.join(",")} apply ${apply(1, input + 3)} callFail ${callFail(input)} traps ${traps.join(" | ")}`;
|
|
}
|
|
|
|
if (isMainThread) {
|
|
const module = await WebAssembly.compile(bytes);
|
|
console.log(run(module, 0));
|
|
for (let input = 1; input <= 3; input++) {
|
|
const line = await new Promise<string>((resolve, reject) => {
|
|
const worker = new Worker(new URL(import.meta.url), { workerData: { module, input } satisfies Job });
|
|
worker.once("message", (message: string) => resolve(message));
|
|
worker.once("error", reject);
|
|
});
|
|
console.log(line);
|
|
}
|
|
const worker = new Worker(new URL(import.meta.url), { workerData: null });
|
|
const reply = new Promise<string>((resolve) => worker.once("message", (message: string) => resolve(message)));
|
|
worker.postMessage({ module, input: 9 } satisfies Job);
|
|
console.log(await reply);
|
|
console.log("main still works:", run(module, 4).slice(0, 18));
|
|
} else if (workerData !== null) {
|
|
const job = workerData as Job;
|
|
parentPort!.postMessage(`${job.module instanceof WebAssembly.Module} ${run(job.module, job.input)}`);
|
|
} else {
|
|
parentPort!.once("message", (job: Job) => {
|
|
parentPort!.postMessage(run(job.module, job.input));
|
|
});
|
|
}
|