~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-code-mode

0
0
main
47 lines · 2.9 KB Raw History
// One compile, in a worker (a Web Worker in pages, a worker_thread in Node): asc calls process.exit(1)
// when it crashes (a stack overflow on deep generics does), which here ends only this worker, and a
// compile that runs long is terminated by index.js. Gets { code, urls? }, posts { binary } or { error }.
import { ABI, ENTRY } from "./abi.js";

const FLAGS = ["--runtime", "stub", "--use", "abort=", "--optimizeLevel", "1", "--noAssert", "--maximumMemory", "1024"];

// A page's worker sees no import map: the page resolves asc and its dependencies (index.js `urls`) and
// asc's own bare imports are rewritten to them, loaded as blob modules. Node resolves them itself.
async function loadAsc(urls) {
	if (!urls) return import("assemblyscript/asc");
	const rewrite = async (url, to) => {
		const source = (await (await fetch(url)).text()).replace(/from"(assemblyscript|binaryen|long)"/g, (all, name) => (to[name] ? `from"${to[name]}"` : all));
		return URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
	};
	const deps = { binaryen: urls.binaryen, long: urls.long };
	return import(await rewrite(urls["assemblyscript/asc"], { ...deps, assemblyscript: await rewrite(urls.assemblyscript, deps) }));
}

async function compile(code, urls) {
	// the prelude's names without a line of their own, so the compiler's line numbers are the agent's
	const files = { "abi.ts": ABI, "module.ts": ENTRY, "code.ts": (code.includes('"./abi"') ? "" : 'import { Json, Kind, Run, print } from "./abi"; ') + code };
	const asc = await loadAsc(urls);
	let binary;
	let log = "";
	const stream = { write: (chunk) => ((log += typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk)), true) };
	const { error } = await asc.main(["module.ts", "--outFile", "module.wasm", ...FLAGS], {
		stdout: stream,
		stderr: stream,
		// only these files and asc's own standard library: an import of anything else is a compile error
		readFile: (name) => (Object.hasOwn(files, name.replace(/^\.\//, "")) ? files[name.replace(/^\.\//, "")] : null),
		writeFile: (name, contents) => {
			if (name.endsWith("module.wasm") && typeof contents !== "string") binary = new Uint8Array(contents);
		},
		listFiles: () => [],
	});
	if (error || !binary) return { error: (log || error?.message || "no output").trim() };
	// a module that declares imports compiles; refuse it here, with its imports named (the host would refuse it too)
	const imports = WebAssembly.Module.imports(await WebAssembly.compile(binary));
	if (imports.length) return { error: `imports are not allowed: ${imports.map((i) => `${i.module}.${i.name}`).join(", ")}` };
	return { binary };
}

const port = typeof self === "undefined" ? (await import("node:worker_threads")).parentPort : self;
const reply = async ({ code, urls }) => port.postMessage(await compile(code, urls).catch((error) => ({ error: String(error?.message ?? error) })));
if (typeof self === "undefined") port.once("message", reply);
else self.onmessage = ({ data }) => reply(data);