/** * Minimal opcode helpers for hand-encoded WASM function bodies. Only the * subset the bridge's fixture WASM needs is implemented — enough to * model a bump allocator, a memcpy loop, packed i64 returns, an * `unreachable` trap, and load/store of static byte ranges out of the * data section. */ import { concat, encodeI32, encodeU32 } from "./leb128.js"; // Value types. export const VT_I32 = 0x7f; export const VT_I64 = 0x7e; // Instructions. export const OP_UNREACHABLE = 0x00; export const OP_NOP = 0x01; export const OP_BLOCK = 0x02; export const OP_LOOP = 0x03; export const OP_BR = 0x0c; export const OP_BR_IF = 0x0d; export const OP_RETURN = 0x0f; export const OP_CALL = 0x10; export const OP_DROP = 0x1a; export const OP_LOCAL_GET = 0x20; export const OP_LOCAL_SET = 0x21; export const OP_LOCAL_TEE = 0x22; export const OP_GLOBAL_GET = 0x23; export const OP_GLOBAL_SET = 0x24; export const OP_I32_LOAD = 0x28; export const OP_I32_LOAD8_U = 0x2d; export const OP_I32_STORE = 0x36; export const OP_I32_STORE8 = 0x3a; export const OP_I32_CONST = 0x41; export const OP_I64_CONST = 0x42; export const OP_I32_EQZ = 0x45; export const OP_I32_LT_U = 0x49; export const OP_I32_ADD = 0x6a; export const OP_I32_SUB = 0x6b; export const OP_I64_OR = 0x84; export const OP_I64_SHL = 0x86; export const OP_I64_EXTEND_I32_U = 0xad; export const OP_END = 0x0b; export const BLOCK_TYPE_EMPTY = 0x40; export function opI32Const(v: number): Uint8Array { return concat(new Uint8Array([OP_I32_CONST]), encodeI32(v)); } export function opLocalGet(idx: number): Uint8Array { return concat(new Uint8Array([OP_LOCAL_GET]), encodeU32(idx)); } export function opLocalSet(idx: number): Uint8Array { return concat(new Uint8Array([OP_LOCAL_SET]), encodeU32(idx)); } export function opLocalTee(idx: number): Uint8Array { return concat(new Uint8Array([OP_LOCAL_TEE]), encodeU32(idx)); } export function opGlobalGet(idx: number): Uint8Array { return concat(new Uint8Array([OP_GLOBAL_GET]), encodeU32(idx)); } export function opGlobalSet(idx: number): Uint8Array { return concat(new Uint8Array([OP_GLOBAL_SET]), encodeU32(idx)); } /** memarg = align (u32) + offset (u32). */ function memarg(align = 0, offset = 0): Uint8Array { return concat(encodeU32(align), encodeU32(offset)); } export function opI32Load8U(): Uint8Array { return concat(new Uint8Array([OP_I32_LOAD8_U]), memarg(0, 0)); } export function opI32Store8(): Uint8Array { return concat(new Uint8Array([OP_I32_STORE8]), memarg(0, 0)); } export function opBr(depth: number): Uint8Array { return concat(new Uint8Array([OP_BR]), encodeU32(depth)); } export function opBrIf(depth: number): Uint8Array { return concat(new Uint8Array([OP_BR_IF]), encodeU32(depth)); } export function opCall(funcIdx: number): Uint8Array { return concat(new Uint8Array([OP_CALL]), encodeU32(funcIdx)); } /** Wrap body bytes as a function body: locals + body + end. */ export function funcBody( locals: ReadonlyArray<{ readonly count: number; readonly type: number }>, body: Uint8Array ): Uint8Array { const localGroups: Uint8Array[] = []; for (const group of locals) { localGroups.push( concat(encodeU32(group.count), new Uint8Array([group.type])) ); } const localsHeader = concat( encodeU32(locals.length), concat(...localGroups) ); const inner = concat(localsHeader, body, new Uint8Array([OP_END])); return concat(encodeU32(inner.length), inner); }