Files
2026-05-27 23:29:53 +01:00

113 lines
3.4 KiB
TypeScript

/**
* 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);
}