velxio/test/test_custom_chips/src/WasiShim.js

156 lines
4.6 KiB
JavaScript

/**
* WasiShim — minimal WASI implementation for Velxio custom chips.
*
* Only the syscalls that wasi-libc actually invokes from the typical chip
* code path are implemented:
* - fd_write (used by printf)
* - proc_exit (used by abort/exit)
* - clock_time_get (rarely used; returns simulated nanos)
* - environ_sizes_get / environ_get / args_sizes_get / args_get (stubs)
* - random_get (deterministic counter — chips shouldn't depend on randomness)
*
* Everything else returns 28 (ENOSYS).
*/
const ENOSYS = 28;
export class WasiShim {
/**
* @param {() => bigint | number} simNanos Function that returns simulation
* time in nanoseconds. Used by clock_time_get.
* @param {(text: string) => void} writeStdout Callback for printf output.
*/
constructor(simNanos, writeStdout) {
this.memory = null;
this.simNanos = simNanos ?? (() => 0n);
this.writeStdout = writeStdout ?? ((s) => process.stdout.write(s));
this._stdoutBuf = '';
this._randCounter = 0;
}
setMemory(memory) {
this.memory = memory;
}
// ── Helpers ────────────────────────────────────────────────────────────
_u8() {
return new Uint8Array(this.memory.buffer);
}
_dv() {
return new DataView(this.memory.buffer);
}
_readBytes(ptr, len) {
return this._u8().slice(ptr, ptr + len);
}
// ── Syscalls ───────────────────────────────────────────────────────────
/** fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) → errno */
fd_write = (fd, iovsPtr, iovsLen, nwrittenPtr) => {
const dv = this._dv();
const u8 = this._u8();
let total = 0;
let chunk = '';
for (let i = 0; i < iovsLen; i++) {
const buf = dv.getUint32(iovsPtr + i * 8, true);
const len = dv.getUint32(iovsPtr + i * 8 + 4, true);
chunk += new TextDecoder().decode(u8.subarray(buf, buf + len));
total += len;
}
dv.setUint32(nwrittenPtr, total, true);
if (fd === 1 || fd === 2) {
this._stdoutBuf += chunk;
// Emit complete lines so log() with newlines flushes immediately.
let nl;
while ((nl = this._stdoutBuf.indexOf('\n')) !== -1) {
this.writeStdout(this._stdoutBuf.slice(0, nl + 1));
this._stdoutBuf = this._stdoutBuf.slice(nl + 1);
}
}
return 0;
};
proc_exit = (_code) => {
throw new Error(`chip called proc_exit(${_code})`);
};
/**
* clock_time_get(clock_id, precision, time_ptr) → errno
* We always return our simulation clock regardless of clock_id.
*/
clock_time_get = (_id, _precision, timePtr) => {
const dv = this._dv();
const ns = BigInt(this.simNanos());
dv.setBigUint64(timePtr, ns, true);
return 0;
};
environ_sizes_get = (countPtr, sizePtr) => {
const dv = this._dv();
dv.setUint32(countPtr, 0, true);
dv.setUint32(sizePtr, 0, true);
return 0;
};
environ_get = () => 0;
args_sizes_get = (cPtr, sPtr) => {
const dv = this._dv();
dv.setUint32(cPtr, 0, true);
dv.setUint32(sPtr, 0, true);
return 0;
};
args_get = () => 0;
random_get = (ptr, len) => {
const u8 = this._u8();
for (let i = 0; i < len; i++) {
this._randCounter = (this._randCounter * 1664525 + 1013904223) >>> 0;
u8[ptr + i] = this._randCounter & 0xff;
}
return 0;
};
fd_close = () => 0;
fd_seek = () => ENOSYS;
fd_read = () => ENOSYS;
fd_fdstat_get = () => 0;
fd_prestat_get = () => 8; // EBADF
fd_prestat_dir_name = () => ENOSYS;
/** Returns the import object for `WebAssembly.instantiate`. */
imports() {
const wasi = {
fd_write: this.fd_write,
proc_exit: this.proc_exit,
clock_time_get: this.clock_time_get,
environ_sizes_get: this.environ_sizes_get,
environ_get: this.environ_get,
args_sizes_get: this.args_sizes_get,
args_get: this.args_get,
random_get: this.random_get,
fd_close: this.fd_close,
fd_seek: this.fd_seek,
fd_read: this.fd_read,
fd_fdstat_get: this.fd_fdstat_get,
fd_prestat_get: this.fd_prestat_get,
fd_prestat_dir_name: this.fd_prestat_dir_name,
};
// wasi-libc looks for either preview1 or unstable depending on version.
return {
wasi_snapshot_preview1: wasi,
wasi_unstable: wasi,
};
}
/** Flush any pending stdout that didn't end with a newline. */
flush() {
if (this._stdoutBuf.length > 0) {
this.writeStdout(this._stdoutBuf);
this._stdoutBuf = '';
}
}
}