| // 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 | |
| // JS implementations of iree_syscall wasm imports. | |
| // | |
| // These functions are provided to the wasm module via the import object when | |
| // instantiating. They implement the minimal OS-level interface that the | |
| // freestanding libc needs: console output, monotonic clock, program exit, | |
| // and assertion failure reporting. | |
| // | |
| // Usage (standalone): | |
| // import { createImports } from './syscall_imports.js'; | |
| // const imports = { iree_syscall: createImports(context) }; | |
| // | |
| // Usage (via bundler — the typical path): | |
| // The wasm binary bundler inlines this file and wires it up automatically. | |
| // The entry point calls createWasmImports(context) which delegates here. | |
| // Creates the iree_syscall import implementations. | |
| // | |
| // |context.memory| must be a WebAssembly.Memory instance. The function reads | |
| // context.memory.buffer on each use (not cached) because memory.grow can | |
| // detach the underlying ArrayBuffer. | |
| export function createImports(context) { | |
| const getMemory = () => context.memory; | |
| const decoder = new TextDecoder(); | |
| return { | |
| // write(fd, buffer_ptr, length) -> bytes_written | |
| write(fd, pointer, length) { | |
| const bytes = new Uint8Array(getMemory().buffer, pointer, length); | |
| const text = decoder.decode(bytes); | |
| if (fd === 2) { | |
| console.error(text); | |
| } else { | |
| // fd 1 (stdout) or any other fd — write to console.log. | |
| // Avoid process.stdout.write to keep this browser-compatible. | |
| console.log(text); | |
| } | |
| return length; | |
| }, | |
| // clock_now_ns() -> int64 nanoseconds (monotonic) | |
| // | |
| // Returns a BigInt because wasm i64 values are represented as BigInt | |
| // in the JS-wasm boundary. | |
| clock_now_ns() { | |
| // performance.now() returns milliseconds as a float64. | |
| // Resolution: ~5us when cross-origin isolated, ~100us-1ms otherwise. | |
| return BigInt(Math.round(performance.now() * 1e6)); | |
| }, | |
| // exit(status) | |
| // | |
| // If context.exit is provided, it is called instead of process.exit. | |
| // This is necessary in Worker threads where process.exit would terminate | |
| // the entire Node.js process. The Worker entry point sets context.exit | |
| // to throw a sentinel that unwinds the wasm call stack. | |
| exit(status) { | |
| if (context.exit) { | |
| context.exit(status); | |
| } else if (typeof process !== 'undefined' && process.exit) { | |
| process.exit(status); | |
| } | |
| // In browser context (or if context.exit returns), throwing stops | |
| // execution. The host page can catch this to display an error. | |
| throw new Error(`IREE wasm module exited with status ${status}`); | |
| }, | |
| // assert_fail(expression_ptr, file_ptr, line, function_ptr) | |
| assert_fail(expressionPointer, filePointer, line, functionPointer) { | |
| const memory = getMemory(); | |
| const readString = (pointer) => { | |
| const bytes = new Uint8Array(memory.buffer, pointer); | |
| const end = bytes.indexOf(0); | |
| return decoder.decode(bytes.subarray(0, end === -1 ? 256 : end)); | |
| }; | |
| const expression = readString(expressionPointer); | |
| const file = readString(filePointer); | |
| const func = readString(functionPointer); | |
| console.error( | |
| `Assertion failed: ${expression}\n at ${func} (${file}:${line})`); | |
| }, | |
| }; | |
| } |