139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|