Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,455 @@
/**
* Hand-encoded WASM fixture builder for the wasm host bridge's tests +
* conformance harness. Produces deterministic, validator-clean WASM
* modules implementing the `__suaveplan_*` ABI surface with configurable
* behaviour per export so the test suite can exercise every bridge
* production path WITHOUT pulling in `wabt` or any other external WASM
* build tool.
*
* Memory layout (single 1-page memory):
* [ 0, 1024) — reserved scratch (cleared on init).
* [1024, 1024 + routesBytes.length) — routes JSON.
* [2048, 2048 + responseBytes.length) — canned response JSON.
* [3072, 3072 + WITNESS_CAPACITY) — request-byte witness.
* [4096, 65536) — bump allocator arena.
*
* Global 0: mut i32 = 4096 (bump pointer).
*/
import {
concat,
encodeI32,
encodeName,
encodeU32,
section,
vec,
} from "./leb128.js";
import {
BLOCK_TYPE_EMPTY,
funcBody,
OP_BLOCK,
OP_END,
OP_I32_ADD,
OP_I32_CONST,
OP_I32_EQZ,
OP_I32_LT_U,
OP_I64_CONST,
OP_LOOP,
OP_UNREACHABLE,
opBr,
opBrIf,
opCall,
opGlobalGet,
opGlobalSet,
opI32Const,
opI32Load8U,
opI32Store8,
opLocalGet,
opLocalSet,
VT_I32,
VT_I64,
} from "./wasm-opcodes.js";
const ROUTES_OFFSET = 1024;
const RESPONSE_OFFSET = 2048;
export const WITNESS_OFFSET = 3072;
export const WITNESS_CAPACITY = 1024;
const HEAP_START = 4096;
const MAGIC = new Uint8Array([0x00, 0x61, 0x73, 0x6d]);
const VERSION = new Uint8Array([0x01, 0x00, 0x00, 0x00]);
/** Configurable behaviour per ABI export. */
export interface BuildFixtureWasmOptions {
/**
* The JSON-encoded `SerializedRoute[]` the guest's
* `__suaveplan_routes` returns. Defaults to a single-route catalog.
*/
readonly routesJson?: string;
/**
* The JSON-encoded `SerializedResponse` the guest's
* `__suaveplan_handle_request` returns. Defaults to a 200 with an
* empty body.
*/
readonly responseJson?: string;
/** Force `__suaveplan_alloc` to return 0 (OOM). */
readonly allocAlwaysZero?: boolean;
/** Force `__suaveplan_handle_request` to execute `unreachable`. */
readonly handlerTraps?: boolean;
/** Force `__suaveplan_start` to execute `unreachable`. */
readonly startTraps?: boolean;
/** Force `__suaveplan_dispose` to execute `unreachable`. */
readonly disposeTraps?: boolean;
/** Force `__suaveplan_routes` to execute `unreachable`. */
readonly routesTraps?: boolean;
/** Force `__suaveplan_routes` to return `(0n, 0n)` packed. */
readonly routesEmpty?: boolean;
/** Force `__suaveplan_handle_request` to return `(0n, 0n)` packed. */
readonly handlerEmptyResponse?: boolean;
/**
* Force `__suaveplan_handle_request` to return `(ptr, 0)` packed
* with the supplied non-zero pointer. Used to test the
* `(nonzero, 0)` empty-response branch where the bridge must free
* the dangling pointer.
*/
readonly handlerZeroLengthResponse?: number;
/** Drop one or more required exports — exercises the ABI validator. */
readonly dropExports?: readonly (
| "memory"
| "__suaveplan_alloc"
| "__suaveplan_free"
| "__suaveplan_handle_request"
| "__suaveplan_routes"
| "__suaveplan_start"
| "__suaveplan_dispose"
)[];
}
const ENCODER = new TextEncoder();
const DEFAULT_ROUTES_JSON = JSON.stringify([{ method: "GET", path: "/hello" }]);
const DEFAULT_RESPONSE_JSON = JSON.stringify({
status: 200,
headers: [["content-type", "text/plain"]],
bodyBase64: btoa("ok")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, ""),
});
/**
* Build a validator-clean WASM module. The returned bytes are accepted
* by `WebAssembly.compile` and instantiate against an empty imports
* object (no WASI, no capability imports — the fixture is a pure
* compute module).
*/
export function buildFixtureWasm(
options: BuildFixtureWasmOptions = {}
): Uint8Array {
const routesBytes = ENCODER.encode(
options.routesJson ?? DEFAULT_ROUTES_JSON
);
const responseBytes = ENCODER.encode(
options.responseJson ?? DEFAULT_RESPONSE_JSON
);
if (routesBytes.length > RESPONSE_OFFSET - ROUTES_OFFSET) {
throw new RangeError("routesJson too large for fixture memory layout");
}
if (responseBytes.length > WITNESS_OFFSET - RESPONSE_OFFSET) {
throw new RangeError(
"responseJson too large for fixture memory layout"
);
}
const dropExports = new Set(options.dropExports ?? []);
// --- Type section ---------------------------------------------------
// type 0: () -> ()
// type 1: (i32) -> i32
// type 2: (i32) -> ()
// type 3: (i32, i32) -> i64
// type 4: () -> i64
// type 5: (i32, i32, i32) -> () [memcpy helper]
const typeSection = section(
0x01,
vec([
new Uint8Array([0x60, 0x00, 0x00]),
new Uint8Array([0x60, 0x01, VT_I32, 0x01, VT_I32]),
new Uint8Array([0x60, 0x01, VT_I32, 0x00]),
new Uint8Array([0x60, 0x02, VT_I32, VT_I32, 0x01, VT_I64]),
new Uint8Array([0x60, 0x00, 0x01, VT_I64]),
new Uint8Array([0x60, 0x03, VT_I32, VT_I32, VT_I32, 0x00]),
])
);
// --- Function section (declares the type index of each defined fn) -
// Function indices (no imports):
// 0: __suaveplan_alloc — type 1
// 1: __suaveplan_free — type 2
// 2: __suaveplan_handle_request — type 3
// 3: __suaveplan_routes — type 4
// 4: __suaveplan_start — type 0
// 5: __suaveplan_dispose — type 0
// 6: memcpy_internal — type 5
const funcSection = section(
0x03,
vec([
encodeU32(1),
encodeU32(2),
encodeU32(3),
encodeU32(4),
encodeU32(0),
encodeU32(0),
encodeU32(5),
])
);
// --- Memory section -------------------------------------------------
// One memory with initial=1 page, no max.
const memorySection = section(0x05, vec([new Uint8Array([0x00, 0x01])]));
// --- Global section -------------------------------------------------
// Global 0: (mut i32) = HEAP_START
const globalSection = section(
0x06,
vec([
concat(
new Uint8Array([VT_I32, 0x01]),
new Uint8Array([OP_I32_CONST]),
encodeI32(HEAP_START),
new Uint8Array([OP_END])
),
])
);
// --- Export section -------------------------------------------------
const exportEntries: Uint8Array[] = [];
const pushExport = (name: string, kind: number, index: number): void => {
if (dropExports.has(name as never)) return;
exportEntries.push(
concat(encodeName(name), new Uint8Array([kind, index]))
);
};
// 0x00 = function, 0x02 = memory.
pushExport("memory", 0x02, 0);
pushExport("__suaveplan_alloc", 0x00, 0);
pushExport("__suaveplan_free", 0x00, 1);
pushExport("__suaveplan_handle_request", 0x00, 2);
pushExport("__suaveplan_routes", 0x00, 3);
pushExport("__suaveplan_start", 0x00, 4);
pushExport("__suaveplan_dispose", 0x00, 5);
const exportSection = section(0x07, vec(exportEntries));
// --- Code section ---------------------------------------------------
const codeSection = section(
0x0a,
vec([
buildAllocBody(options.allocAlwaysZero === true),
buildFreeBody(),
buildHandleRequestBody({
trap: options.handlerTraps === true,
emptyResponse: options.handlerEmptyResponse === true,
zeroLengthPtr: options.handlerZeroLengthResponse,
responseBytesLen: responseBytes.length,
}),
buildRoutesBody({
trap: options.routesTraps === true,
empty: options.routesEmpty === true,
routesLen: routesBytes.length,
}),
buildLifecycleBody(options.startTraps === true),
buildLifecycleBody(options.disposeTraps === true),
buildMemcpyBody(),
])
);
// --- Data section ---------------------------------------------------
const dataSection = section(
0x0b,
vec([
buildDataSegment(ROUTES_OFFSET, routesBytes),
buildDataSegment(RESPONSE_OFFSET, responseBytes),
])
);
return concat(
MAGIC,
VERSION,
typeSection,
funcSection,
memorySection,
globalSection,
exportSection,
codeSection,
dataSection
);
}
function buildDataSegment(offset: number, payload: Uint8Array): Uint8Array {
// Active data segment, memory index 0, init expr (i32.const offset, end), bytes.
return concat(
encodeU32(0), // memory index
new Uint8Array([OP_I32_CONST]),
encodeI32(offset),
new Uint8Array([OP_END]),
encodeU32(payload.length),
payload
);
}
function buildAllocBody(zero: boolean): Uint8Array {
// (param i32) -> i32
// local 0 = len
if (zero) {
return funcBody([], concat(opI32Const(0)));
}
// ptr = global; global = ptr + len; return ptr
// locals: ptr (local 1)
const body = concat(
opGlobalGet(0),
opLocalSet(1),
opLocalGet(1),
opLocalGet(0),
new Uint8Array([OP_I32_ADD]),
opGlobalSet(0),
opLocalGet(1)
);
return funcBody([{ count: 1, type: VT_I32 }], body);
}
function buildFreeBody(): Uint8Array {
// (param i32) -> ()
// No-op — emit just end (handled by funcBody).
return funcBody([], new Uint8Array());
}
function buildHandleRequestBody(options: {
readonly trap: boolean;
readonly emptyResponse: boolean;
readonly zeroLengthPtr: number | undefined;
readonly responseBytesLen: number;
}): Uint8Array {
// (param i32 i32) -> i64
// local 0 = reqPtr, local 1 = reqLen
if (options.trap) {
return funcBody([], new Uint8Array([OP_UNREACHABLE]));
}
if (options.emptyResponse) {
return funcBody(
[],
concat(new Uint8Array([OP_I64_CONST]), encodeI32(0))
);
}
if (options.zeroLengthPtr !== undefined) {
const packed = BigInt(options.zeroLengthPtr) << 32n;
return funcBody(
[],
concat(new Uint8Array([OP_I64_CONST]), encodeI64(packed))
);
}
// 1. Clamp reqLen to WITNESS_CAPACITY.
// 2. Call memcpy_internal(WITNESS, reqPtr, reqLen).
// 3. Return packed (RESPONSE_OFFSET << 32) | responseBytesLen as i64.
const clipBlock = concat(
new Uint8Array([OP_BLOCK, BLOCK_TYPE_EMPTY]),
opLocalGet(1),
opI32Const(WITNESS_CAPACITY),
new Uint8Array([OP_I32_LT_U]),
opBrIf(0),
opI32Const(WITNESS_CAPACITY),
opLocalSet(1),
new Uint8Array([OP_END])
);
const callMemcpy = concat(
opI32Const(WITNESS_OFFSET), // dst
opLocalGet(0), // src = reqPtr
opLocalGet(1), // len (possibly clamped)
opCall(6) // memcpy_internal
);
const packed =
(BigInt(RESPONSE_OFFSET) << 32n) | BigInt(options.responseBytesLen);
const returnPacked = concat(
new Uint8Array([OP_I64_CONST]),
encodeI64(packed)
);
return funcBody([], concat(clipBlock, callMemcpy, returnPacked));
}
function buildRoutesBody(options: {
readonly trap: boolean;
readonly empty: boolean;
readonly routesLen: number;
}): Uint8Array {
// () -> i64
if (options.trap) {
return funcBody([], new Uint8Array([OP_UNREACHABLE]));
}
if (options.empty) {
return funcBody(
[],
concat(new Uint8Array([OP_I64_CONST]), encodeI64(0n))
);
}
const packed = (BigInt(ROUTES_OFFSET) << 32n) | BigInt(options.routesLen);
return funcBody(
[],
concat(new Uint8Array([OP_I64_CONST]), encodeI64(packed))
);
}
function buildLifecycleBody(trap: boolean): Uint8Array {
if (trap) {
return funcBody([], new Uint8Array([OP_UNREACHABLE]));
}
return funcBody([], new Uint8Array());
}
function buildMemcpyBody(): Uint8Array {
// (param dst i32, src i32, len i32) -> ()
// local 3 = i
// block
// loop
// local.get i
// local.get len
// i32.lt_u
// i32.eqz
// br_if 1
// ;; memory[dst+i] = memory[src+i]
// local.get dst
// local.get i
// i32.add
// local.get src
// local.get i
// i32.add
// i32.load8_u
// i32.store8
// local.get i
// i32.const 1
// i32.add
// local.set i
// br 0
// end
// end
const body = concat(
opI32Const(0),
opLocalSet(3),
new Uint8Array([OP_BLOCK, BLOCK_TYPE_EMPTY]),
new Uint8Array([OP_LOOP, BLOCK_TYPE_EMPTY]),
opLocalGet(3),
opLocalGet(2),
new Uint8Array([OP_I32_LT_U]),
new Uint8Array([OP_I32_EQZ]),
opBrIf(1),
opLocalGet(0),
opLocalGet(3),
new Uint8Array([OP_I32_ADD]),
opLocalGet(1),
opLocalGet(3),
new Uint8Array([OP_I32_ADD]),
opI32Load8U(),
opI32Store8(),
opLocalGet(3),
opI32Const(1),
new Uint8Array([OP_I32_ADD]),
opLocalSet(3),
opBr(0),
new Uint8Array([OP_END]),
new Uint8Array([OP_END])
);
return funcBody([{ count: 1, type: VT_I32 }], body);
}
/** Encode a signed LEB128 i64 literal. */
function encodeI64(value: bigint): Uint8Array {
const out: number[] = [];
let v = value;
while (true) {
const byte = Number(v & 0x7fn);
v >>= 7n;
const signBit = (byte & 0x40) !== 0;
if ((v === 0n && !signBit) || (v === -1n && signBit)) {
out.push(byte);
break;
}
out.push(byte | 0x80);
}
return new Uint8Array(out);
}