// Minimal browser WASI shim. Loaded by index.html, which sets // window.CRAFTER_WASM_URL before this script runs so a single runtime.js // can serve any output name. Most syscalls return 0 — enough to host a // hello-world that uses fd_write (stdout) and the args/environ probes // libc invokes during startup. Extend as needed. const textEncoder = new TextEncoder(); class Wasi { #encodedStdin; #envEncodedStrings; #argEncodedStrings; instance; constructor({ env, stdin, args }) { this.#encodedStdin = textEncoder.encode(stdin); const envStrings = Object.entries(env).map(([k, v]) => `${k}=${v}`); this.#envEncodedStrings = envStrings.map(s => textEncoder.encode(s + "\0")); this.#argEncodedStrings = args.map(s => textEncoder.encode(s + "\0")); this.bind(); } bind() { this.args_get = this.args_get.bind(this); this.args_sizes_get = this.args_sizes_get.bind(this); this.environ_get = this.environ_get.bind(this); this.environ_sizes_get = this.environ_sizes_get.bind(this); this.fd_read = this.fd_read.bind(this); this.fd_write = this.fd_write.bind(this); } args_sizes_get(argCountPtr, argBufferSizePtr) { const argByteLength = this.#argEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0); const countPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argCountPtr, 1); const sizePointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argBufferSizePtr, 1); countPointerBuffer[0] = this.#argEncodedStrings.length; sizePointerBuffer[0] = argByteLength; return 0; } args_get(argsPtr, argBufferPtr) { const argsByteLength = this.#argEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0); const argsPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argsPtr, this.#argEncodedStrings.length); const argsBuffer = new Uint8Array(this.instance.exports.memory.buffer, argBufferPtr, argsByteLength); let pointerOffset = 0; for (let i = 0; i < this.#argEncodedStrings.length; i++) { argsPointerBuffer[i] = argBufferPtr + pointerOffset; argsBuffer.set(this.#argEncodedStrings[i], pointerOffset); pointerOffset += this.#argEncodedStrings[i].byteLength; } return 0; } fd_write(fd, iovsPtr, iovsLength, bytesWrittenPtr) { const iovs = new Uint32Array(this.instance.exports.memory.buffer, iovsPtr, iovsLength * 2); if (fd === 1 || fd === 2) { let text = ""; let totalBytesWritten = 0; const decoder = new TextDecoder(); for (let i = 0; i < iovsLength * 2; i += 2) { const offset = iovs[i]; const length = iovs[i + 1]; text += decoder.decode(new Int8Array(this.instance.exports.memory.buffer, offset, length)); totalBytesWritten += length; } const dataView = new DataView(this.instance.exports.memory.buffer); dataView.setInt32(bytesWrittenPtr, totalBytesWritten, true); (fd === 2 ? console.error : console.log)(text); } return 0; } fd_read(fd, iovsPtr, iovsLength, bytesReadPtr) { const memory = new Uint8Array(this.instance.exports.memory.buffer); const iovs = new Uint32Array(this.instance.exports.memory.buffer, iovsPtr, iovsLength * 2); let totalBytesRead = 0; if (fd === 0) { for (let i = 0; i < iovsLength * 2; i += 2) { const offset = iovs[i]; const length = iovs[i + 1]; const chunk = this.#encodedStdin.slice(0, length); this.#encodedStdin = this.#encodedStdin.slice(length); memory.set(chunk, offset); totalBytesRead += chunk.byteLength; if (this.#encodedStdin.length === 0) break; } const dataView = new DataView(this.instance.exports.memory.buffer); dataView.setInt32(bytesReadPtr, totalBytesRead, true); } return 0; } fd_advise() { return 0; } fd_close() { return 0; } fd_fdstat_get() { return 0; } fd_prestat_get() { return 0; } fd_prestat_dir_name() { return 0; } clock_res_get() { return 0; } clock_time_get() { return 0; } fd_seek() { return 0; } fd_allocate() { return 0; } fd_datasync() { return 0; } fd_fdstat_set_flags() { return 0; } fd_fdstat_set_rights() { return 0; } fd_filestat_get() { return 0; } fd_filestat_set_size() { return 0; } fd_filestat_set_times() { return 0; } fd_pread() { return 0; } fd_pwrite() { return 0; } fd_readdir() { return 0; } fd_renumber() { return 0; } fd_sync() { return 0; } fd_tell() { return 0; } path_create_directory() { return 0; } path_filestat_get() { return 0; } path_filestat_set_times() { return 0; } path_link() { return 0; } path_open() { return 0; } path_readlink() { return 0; } path_remove_directory() { return 0; } path_rename() { return 0; } path_symlink() { return 0; } path_unlink_file() { return 0; } poll_oneoff() { return 0; } sched_yield() { return 0; } random_get() { return 0; } sock_accept() { return 0; } sock_recv() { return 0; } sock_send() { return 0; } sock_shutdown() { return 0; } environ_get(environPtr, environBufferPtr) { const envByteLength = this.#envEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0); const environsPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environPtr, this.#envEncodedStrings.length); const environsBuffer = new Uint8Array(this.instance.exports.memory.buffer, environBufferPtr, envByteLength); let pointerOffset = 0; for (let i = 0; i < this.#envEncodedStrings.length; i++) { environsPointerBuffer[i] = environBufferPtr + pointerOffset; environsBuffer.set(this.#envEncodedStrings[i], pointerOffset); pointerOffset += this.#envEncodedStrings[i].byteLength; } return 0; } environ_sizes_get(environCountPtr, environBufferSizePtr) { const envByteLength = this.#envEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0); const countPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environCountPtr, 1); const sizePointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environBufferSizePtr, 1); countPointerBuffer[0] = this.#envEncodedStrings.length; sizePointerBuffer[0] = envByteLength; return 0; } proc_exit(code) { console.log(`[wasi] proc_exit(${code})`); } } const wasmUrl = window.CRAFTER_WASM_URL; if (!wasmUrl) { throw new Error("runtime.js: window.CRAFTER_WASM_URL is not set (set it in index.html before loading runtime.js)"); } const wasi = new Wasi({ stdin: "", env: {}, args: [] }); const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), { wasi_snapshot_preview1: wasi, }); wasi.instance = instance; window.crafter_wasi = wasi; instance.exports._start();