// 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();