Initial Commit
This commit is contained in:
138
packages/api-stack/api/api-sandbox-wasm/tests/fixtures/build-wasm.ts
vendored
Normal file
138
packages/api-stack/api/api-sandbox-wasm/tests/fixtures/build-wasm.ts
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user