/** * Tiny WASM-module builder used by in-package fixtures + the * conformance harness. Emits hand-encoded WebAssembly bytes per the * binary format spec (https://webassembly.github.io/spec/core/binary/). * * Only enough to construct test modules — one functype `() -> ()`, one * import, one export, an `_start` body. NOT a general-purpose WAT * compiler. Kept inside the package so the test suite has no external * build step (`wabt` / `wat2wasm`); the fixture wasm is produced * deterministically at runtime. */ const MAGIC = new Uint8Array([0x00, 0x61, 0x73, 0x6d]); const VERSION = new Uint8Array([0x01, 0x00, 0x00, 0x00]); interface BuilderOptions { /** WASI version module name to import (default `wasi_snapshot_preview1`). */ readonly wasiVersion?: string; /** Whether to import `wasi_snapshot_preview1.proc_exit`. */ readonly importProcExit?: boolean; /** Additional capability imports — emitted under `suaveplan_caps`. */ readonly capabilityImports?: readonly string[]; /** Export a `memory` of `initial` pages — exercises sandbox memory binding. */ readonly exportMemory?: boolean; } /** * Build a deterministic, validator-clean WASM module with the requested * imports + an exported `_start` whose body is just `end`. Adequate for * `WebAssembly.compile` + `WebAssembly.instantiate` against a matching * imports object. */ export function buildFixtureWasm(options: BuilderOptions = {}): Uint8Array { const wasiVersion = options.wasiVersion ?? "wasi_snapshot_preview1"; const importProcExit = options.importProcExit !== false; const capImports = options.capabilityImports ?? []; // Type section: index 0 = () -> () (for _start + capability imports); // index 1 = (i32) -> () (for proc_exit). const types: number[] = [ 0x02 /* count */, ...funcType([], []), ...funcType([0x7f], []), ]; const typeSection = section(0x01, types); // Import section. const importEntries: number[][] = []; if (importProcExit) { importEntries.push(importEntry(wasiVersion, "proc_exit", 0x00, 1)); } for (const cap of capImports) { importEntries.push(importEntry("suaveplan_caps", cap, 0x00, 0)); } const importSection = section(0x02, [ importEntries.length, ...flatten(importEntries), ]); // Function section: one local function, type 0. const funcSection = section(0x03, [0x01, 0x00]); // Optional memory section: 1 limits entry, flags=0 (no max), initial=1 page. const exportMemory = options.exportMemory === true; const memorySection = exportMemory ? section(0x05, [0x01, 0x00, 0x01]) : []; // Export section: "_start" + optional "memory". const localFnIndex = importEntries.length; const exportItems: number[][] = [exportEntry("_start", 0x00, localFnIndex)]; if (exportMemory) { exportItems.push(exportEntry("memory", 0x02, 0)); } const exportSection = section(0x07, [ exportItems.length, ...flatten(exportItems), ]); // Code section: one body, locals=0, end. const codeSection = section(0x0a, [0x01, 0x02, 0x00, 0x0b]); return concatBytes([ MAGIC, VERSION, new Uint8Array(typeSection), new Uint8Array(importSection), new Uint8Array(funcSection), new Uint8Array(memorySection), new Uint8Array(exportSection), new Uint8Array(codeSection), ]); } function funcType( params: readonly number[], results: readonly number[] ): number[] { return [0x60, params.length, ...params, results.length, ...results]; } function importEntry( module: string, field: string, kind: number, typeIndex: number ): number[] { return [...encodeName(module), ...encodeName(field), kind, typeIndex]; } function exportEntry(name: string, kind: number, index: number): number[] { return [...encodeName(name), kind, index]; } function encodeName(value: string): number[] { const bytes = new TextEncoder().encode(value); return [bytes.length, ...bytes]; } function section(id: number, payload: readonly number[]): number[] { return [id, payload.length, ...payload]; } function flatten(arrays: readonly number[][]): number[] { const out: number[] = []; for (const a of arrays) out.push(...a); return out; } function concatBytes(parts: readonly Uint8Array[]): Uint8Array { let total = 0; for (const p of parts) total += p.length; const out = new Uint8Array(total); let offset = 0; for (const p of parts) { out.set(p, offset); offset += p.length; } return out; }