velxio/frontend/src/simulation/customChips/WasiShim.ts

135 lines
3.9 KiB
TypeScript

/**
* WasiShim — minimal WASI implementation for Velxio custom chips.
*
* Implements just the syscalls wasi-libc invokes from the typical chip code
* path (printf, exit, clock_time_get). Everything else returns ENOSYS (28).
*/
const ENOSYS = 28;
export type SimNanosFn = () => bigint | number;
export type WriteStdoutFn = (text: string) => void;
export class WasiShim {
memory: WebAssembly.Memory | null = null;
simNanos: SimNanosFn;
writeStdout: WriteStdoutFn;
private _stdoutBuf = '';
private _randCounter = 0;
constructor(simNanos: SimNanosFn, writeStdout: WriteStdoutFn) {
this.simNanos = simNanos;
this.writeStdout = writeStdout;
}
setMemory(memory: WebAssembly.Memory): void {
this.memory = memory;
}
private _u8(): Uint8Array {
return new Uint8Array(this.memory!.buffer);
}
private _dv(): DataView {
return new DataView(this.memory!.buffer);
}
// ── Syscalls ─────────────────────────────────────────────────────────────
fd_write = (fd: number, iovsPtr: number, iovsLen: number, nwrittenPtr: number): number => {
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;
let nl: number;
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: number): never => {
throw new Error(`chip called proc_exit(${code})`);
};
clock_time_get = (_id: number, _precision: bigint, timePtr: number): number => {
const dv = this._dv();
const ns = BigInt(this.simNanos() as number | bigint);
dv.setBigUint64(timePtr, ns, true);
return 0;
};
environ_sizes_get = (countPtr: number, sizePtr: number): number => {
const dv = this._dv();
dv.setUint32(countPtr, 0, true);
dv.setUint32(sizePtr, 0, true);
return 0;
};
environ_get = (): number => 0;
args_sizes_get = (cPtr: number, sPtr: number): number => {
const dv = this._dv();
dv.setUint32(cPtr, 0, true);
dv.setUint32(sPtr, 0, true);
return 0;
};
args_get = (): number => 0;
random_get = (ptr: number, len: number): number => {
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 = (): number => 0;
fd_seek = (): number => ENOSYS;
fd_read = (): number => ENOSYS;
fd_fdstat_get = (): number => 0;
fd_prestat_get = (): number => 8;
fd_prestat_dir_name = (): number => ENOSYS;
imports(): Record<string, Record<string, (...args: any[]) => any>> {
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,
} as Record<string, (...args: any[]) => any>;
return {
wasi_snapshot_preview1: wasi,
wasi_unstable: wasi,
};
}
flush(): void {
if (this._stdoutBuf.length > 0) {
this.writeStdout(this._stdoutBuf);
this._stdoutBuf = '';
}
}
}