/** * Optional WebAssembly compile / instantiate for feature-gated kernel extensions. * Uses global WebAssembly when present (Bare/V8); no Node built-ins. */ const DEFAULT_MAX = 512 * 1024 /** * @param {Uint8Array} source * @param {{ maxBytes?: number }} [opts] */ export async function bareOsWasmKernelCompile(source, opts = {}) { const maxB = Math.min( DEFAULT_MAX, Math.max(1024, Number(opts.maxBytes) || DEFAULT_MAX) ) if (!(source instanceof Uint8Array) || source.byteLength === 0) { return { ok: false, reason: 'invalid_source' } } if (source.byteLength > maxB) { return { ok: false, reason: 'source_too_large' } } const W = globalThis.WebAssembly if (!W || typeof W.compile !== 'function') { return { ok: false, reason: 'webassembly_unavailable' } } try { const mod = await W.compile(source) /** @type {{ name: string }[]} */ let names = [] if (typeof W.Module.exports === 'function') { names = W.Module.exports(mod).map((e) => String(e.name || '')) } return { ok: true, exportNames: names } } catch (e) { return { ok: false, reason: String(e && e.message ? e.message : e) } } } /** * Read bounded UTF-8 from Wasm linear memory (host view). * @param {WebAssembly.Memory} memory * @param {number} ptr * @param {number} len * @param {number} [maxLen] */ function readUtf8FromMemory(memory, ptr, len, maxLen = 4096) { const buf = memory.buffer const n = Math.min(len >>> 0, maxLen) const p = ptr >>> 0 if (p + n > buf.byteLength) return '' return new TextDecoder().decode(new Uint8Array(buf, p, n)) } /** * Write NUL-terminated UTF-8 into Wasm memory; returns bytes written (excl. NUL) or -1. * @param {WebAssembly.Memory} memory * @param {number} ptr * @param {number} outCap * @param {string} text */ function writeCStrToMemory(memory, ptr, outCap, text) { const enc = new TextEncoder().encode(String(text || '')) const cap = Math.max(0, outCap >>> 0) if (cap < 2) return -1 const max = cap - 1 const n = Math.min(enc.length, max) const p = ptr >>> 0 const buf = memory.buffer if (p + cap > buf.byteLength) return -1 const u8 = new Uint8Array(buf, p, cap) u8.set(enc.subarray(0, n)) u8[n] = 0 return n } /** * Bounded `instantiate` with an isolated `Memory` and minimal `env` imports. * Optional sync imports **`env.bare_os_pathconf`** / **`env.bare_os_umask_get`** when * **`BARE_OS_WASM_KERNEL_SYSCALL=1`** (or **`opts.wasmSyscallImports: true`**) and **`ctx.bareOsPathconf`** exists. * Also **`env.bare_os_wall_time_ms32`**: low 32 bits of wall time (`Date.now()`, signed i32 wrap) for bounded guest timing. * * @param {Uint8Array} source * @param {Record} ctx * @param {{ * maxBytes?: number, * memoryInitialPages?: number, * memoryMaxPages?: number, * timeoutMs?: number, * wasmSyscallImports?: boolean, * shellEnv?: Record * }} [opts] */ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) { const maxB = Math.min( DEFAULT_MAX, Math.max(1024, Number(opts.maxBytes) || DEFAULT_MAX) ) if (!(source instanceof Uint8Array) || source.byteLength === 0) { return { ok: false, reason: 'invalid_source' } } if (source.byteLength > maxB) { return { ok: false, reason: 'source_too_large' } } const W = globalThis.WebAssembly if (!W || typeof W.instantiate !== 'function' || typeof W.Memory !== 'function') { return { ok: false, reason: 'webassembly_unavailable' } } const memInitial = Math.min( 256, Math.max(1, Number(opts.memoryInitialPages) || 1) ) const memMax = Math.min( 256, Math.max(memInitial, Number(opts.memoryMaxPages) || 8) ) const timeoutMs = Math.min( 60000, Math.max(50, Number(opts.timeoutMs) || 5000) ) try { const mod = await W.compile(source) /** @type {{ name: string }[]} */ let exportNames = [] if (typeof W.Module.exports === 'function') { exportNames = W.Module.exports(mod).map((e) => String(e.name || '')) } const memory = new W.Memory({ initial: memInitial, maximum: memMax }) const shellEnv = opts.shellEnv && typeof opts.shellEnv === 'object' ? /** @type {Record} */ (opts.shellEnv) : {} const wantSyscall = opts.wasmSyscallImports === true || shellEnv.BARE_OS_WASM_KERNEL_SYSCALL === '1' || shellEnv.BARE_OS_WASM_KERNEL_SYSCALL === 'true' const pathconfFn = ctx && typeof ctx.bareOsPathconf === 'function' ? ctx.bareOsPathconf.bind(ctx) : null /** @type {Record} */ const envImports = { memory, bare_os_nop: () => 0 } if (wantSyscall) { envImports.bare_os_wall_time_ms32 = () => Date.now() | 0 } if (wantSyscall && pathconfFn) { envImports.bare_os_pathconf = ( pathPtr, pathLen, namePtr, nameLen, outPtr, outCap ) => { try { const path = readUtf8FromMemory( memory, Number(pathPtr), Number(pathLen) ).trim() const name = readUtf8FromMemory( memory, Number(namePtr), Number(nameLen) ).trim() if (!path.startsWith('/') || !name) return -1 const v = pathconfFn(path, name) if (v == null || v === undefined) return -2 return writeCStrToMemory( memory, Number(outPtr), Number(outCap), String(v) ) } catch { return -3 } } envImports.bare_os_umask_get = () => { const m = parseInt(String(shellEnv.UMASK || '022'), 8) return Number.isFinite(m) ? m & 0o777 : 0o22 } } const importObject = { env: envImports } const instP = W.instantiate(mod, importObject) const done = await Promise.race([ instP, new Promise((_, rej) => setTimeout( () => rej(new Error('wasm_instantiate_timeout')), timeoutMs ) ) ]) const instance = /** @type {{ instance: WebAssembly.Instance }} */ (done) .instance return { ok: true, exportNames, instance, memory, memoryInitialPages: memInitial, memoryMaxPages: memMax, wasmSyscallImports: !!wantSyscall, wasmPathconfImport: !!(wantSyscall && pathconfFn), wasmWallClockMs32Import: !!wantSyscall } } catch (e) { return { ok: false, reason: String(e && e.message ? e.message : e) } } }