156 lines
4.6 KiB
JavaScript
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 = '';
|
|
}
|
|
}
|
|
}
|