| // Copyright 2026 The IREE Authors | |
| // | |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // Generic test entry point for gtest/gbenchmark wasm binaries under WASI. | |
| // | |
| // The bundler appends this file after inlined companion code and the generated | |
| // createWasmImports() function, so createWasmImports is available in module | |
| // scope. The bundler also defines __IREE_WASM_BINARY with the .wasm filename. | |
| // | |
| // Provides WASI preview1 syscalls via node:wasi, merges companion imports on | |
| // top, instantiates the wasm binary, and propagates the exit code. | |
| import {readFileSync} from 'node:fs'; | |
| import {dirname, resolve} from 'node:path'; | |
| import {fileURLToPath} from 'node:url'; | |
| import {WASI} from 'node:wasi'; | |
| const scriptDirectory = dirname(fileURLToPath(import.meta.url)); | |
| const wasmPath = resolve(scriptDirectory, __IREE_WASM_BINARY); | |
| // Build preopens for directories the test needs to access. Bazel sets | |
| // TEST_TMPDIR for scratch space and XML_OUTPUT_FILE for gtest XML output. | |
| // Without preopening these, WASI's sandbox blocks stat/open calls and gtest | |
| // crashes during teardown. | |
| const preopens = {}; | |
| if (process.env.TEST_TMPDIR) { | |
| preopens[process.env.TEST_TMPDIR] = process.env.TEST_TMPDIR; | |
| } | |
| if (process.env.XML_OUTPUT_FILE) { | |
| const xmlDirectory = dirname(process.env.XML_OUTPUT_FILE); | |
| preopens[xmlDirectory] = xmlDirectory; | |
| } | |
| // WASI provides the C runtime environment: stdio, filesystem, clocks. | |
| const wasi = new WASI({ | |
| version: 'preview1', | |
| args: [__IREE_WASM_BINARY, ...process.argv.slice(2)], | |
| env: process.env, | |
| preopens, | |
| }); | |
| // Start with WASI imports and merge companion imports on top. | |
| // createWasmImports is generated by the bundler — it returns an object keyed | |
| // by wasm import module name. For tests with no companions, it returns {}. | |
| const imports = wasi.getImportObject(); | |
| const context = { | |
| memory: null | |
| }; | |
| const companionImports = createWasmImports(context); | |
| for (const [moduleName, moduleImports] of Object.entries(companionImports)) { | |
| if (imports[moduleName]) { | |
| Object.assign(imports[moduleName], moduleImports); | |
| } else { | |
| imports[moduleName] = moduleImports; | |
| } | |
| } | |
| // Compile and instantiate. | |
| const wasmBytes = readFileSync(wasmPath); | |
| const {instance} = await WebAssembly.instantiate(wasmBytes, imports); | |
| // Wire up exported memory for companions that need it (e.g., reading | |
| // wasm linear memory for ring drain operations). | |
| context.memory = instance.exports.memory; | |
| // WASI.start() calls _start() on the wasm instance. The WASI runtime handles | |
| // proc_exit internally and sets process.exitCode. | |
| try { | |
| wasi.start(instance); | |
| } catch (error) { | |
| // WASI proc_exit throws to unwind the wasm call stack. process.exitCode is | |
| // already set by the WASI runtime. If it was not set, this is a genuine | |
| // error (not a proc_exit). | |
| if (!process.exitCode) { | |
| console.error(error); | |
| process.exitCode = 1; | |
| } | |
| } |