diff --git a/cli/index.js b/cli/index.js index 7e202e4f61..7a4e5672ae 100644 --- a/cli/index.js +++ b/cli/index.js @@ -33,18 +33,54 @@ import { utf8 } from "../util/text.js"; import * as optionsUtil from "../util/options.js"; import * as generated from "./index.generated.js"; -import binaryen from "../lib/binaryen.js"; -import * as assemblyscriptJS from "assemblyscript"; - -// Use the TS->JS variant by default -let assemblyscript = assemblyscriptJS; - -// Use the AS->Wasm variant as an option (experimental) -const wasmPos = process.argv.indexOf("--wasm"); -if (~wasmPos) { - const wasmPath = String(process.argv[wasmPos + 1]); - process.argv.splice(wasmPos, 2); - assemblyscript = await import(new URL(wasmPath, url.pathToFileURL(process.cwd() + "/"))); +let assemblyscript = null; +let binaryen = null; +let binaryenModule = "../lib/binaryen.js"; + +async function loadAssemblyScript(wasmPath) { + if (assemblyscript) return assemblyscript; + return assemblyscript = wasmPath == null + ? await importWithInstantiateStreamingFallback("assemblyscript") + : await importWithInstantiateStreamingFallback(new URL(wasmPath, url.pathToFileURL(process.cwd() + "/"))); +} + +async function loadBinaryen() { + if (binaryen) return binaryen; + return binaryen = (await importWithInstantiateStreamingFallback(binaryenModule)).default; +} + +async function importWithInstantiateStreamingFallback(specifier) { + const restoreInstantiateStreaming = installInstantiateStreamingFallback(); + try { + return await import(specifier); + } finally { + if (restoreInstantiateStreaming) restoreInstantiateStreaming(); + } +} + +function installInstantiateStreamingFallback() { + const instantiateStreaming = WebAssembly.instantiateStreaming; + if (typeof instantiateStreaming !== "function") return null; + async function instantiateStreamingFallback(source, imports) { + const response = await source; + try { + const streamingSource = response && typeof response.clone === "function" + ? response.clone() + : response; + return await instantiateStreaming.call(WebAssembly, streamingSource, imports); + } catch (error) { + if (response && typeof response.arrayBuffer === "function") { + return WebAssembly.instantiate(await response.arrayBuffer(), imports); + } + throw error; + } + } + WebAssembly.instantiateStreaming = instantiateStreamingFallback; + return () => { + if (WebAssembly.instantiateStreaming === instantiateStreamingFallback) { + WebAssembly.instantiateStreaming = instantiateStreaming; + } + }; } const require = module.createRequire(import.meta.url); @@ -134,6 +170,15 @@ export async function main(argv, options) { if (!Array.isArray(argv)) argv = configToArguments(argv); if (!options) options = {}; + // Use the AS->Wasm compiler variant as an option (experimental) + let wasmPath = null; + const wasmPos = argv.indexOf("--wasm"); + if (~wasmPos) { + wasmPath = String(argv[wasmPos + 1]); + argv = argv.slice(); + argv.splice(wasmPos, 2); + } + const stats = options.stats || new Stats(); const statsBegin = stats.begin(); @@ -289,6 +334,8 @@ export async function main(argv, options) { return prepareResult(null); } + const assemblyscript = await loadAssemblyScript(wasmPath); + // create a unique set of values function unique(values) { return [...new Set(values)]; @@ -446,6 +493,7 @@ export async function main(argv, options) { // Fix up the prototype of the transforms’ constructors and instantiate them. try { + const binaryen = transforms.length ? await loadBinaryen() : null; transforms = transforms.map(transform => { if (typeof transform === "function") { Object.assign(transform.prototype, { @@ -736,6 +784,7 @@ export async function main(argv, options) { stats.compileTime += stats.end(begin); } // From here on we are going to use Binaryen.js + const binaryen = await loadBinaryen(); binaryenModule = binaryen.wrapModule( typeof module === "number" || module instanceof Number ? assemblyscript.getBinaryenModuleRef(module) diff --git a/tests/browser.js b/tests/browser.js index 0661990ef2..d91860126b 100644 --- a/tests/browser.js +++ b/tests/browser.js @@ -1,4 +1,21 @@ -import * as asc from "../dist/asc.js"; +import fs from "node:fs"; + +const ascSource = fs.readFileSync(new URL("../dist/asc.js", import.meta.url), "utf8"); +if (/\bfrom\s*["']binaryen["']/.test(ascSource)) { + throw Error("dist/asc.js must not statically import binaryen"); +} + +const wasmCompile = WebAssembly.compile; +const wasmCompileStreaming = WebAssembly.compileStreaming; + +WebAssembly.compile = () => { + throw Error("unexpected WebAssembly.compile"); +}; +WebAssembly.compileStreaming = () => { + throw Error("unexpected WebAssembly.compileStreaming"); +}; + +const asc = await import("../dist/asc.js"); if (typeof asc.definitionFiles.assembly !== "string") throw Error("missing bundled assembly.d.ts"); if (typeof asc.definitionFiles.portable !== "string") throw Error("missing bundled portable.d.ts"); @@ -15,6 +32,9 @@ console.log("# asc --version"); process.stdout.write(stderr.toString()); } +WebAssembly.compile = wasmCompile; +WebAssembly.compileStreaming = wasmCompileStreaming; + console.log("\n# asc --help"); { const { stdout, stderr } = await asc.main([ "--help" ]); @@ -25,6 +45,27 @@ console.log("\n# asc --help"); process.stdout.write(stderr.toString()); } +console.log("\n# asc.compileString with broken instantiateStreaming"); +{ + const wasmInstantiateStreaming = WebAssembly.instantiateStreaming; + WebAssembly.instantiateStreaming = () => { + throw Error("unexpected WebAssembly.instantiateStreaming"); + }; + try { + const { error, stdout, stderr, text, binary } = await asc.compileString(`export function test(): void {}`, { optimizeLevel: 3, exportTable: true, stats: true }); + if (error) throw error; + console.log(">>> .stdout >>>"); + process.stdout.write(stdout.toString()); + console.log(">>> .stderr >>>"); + process.stdout.write(stderr.toString()); + console.log(">>> .text >>>"); + process.stdout.write(text); + console.log(">>> .binary >>> " + binary.length + " bytes"); + } finally { + WebAssembly.instantiateStreaming = wasmInstantiateStreaming; + } +} + console.log("\n# asc index.ts --textFile"); { const { error, stdout, stderr } = await asc.main([ "index.ts", "--textFile" ], {