113 lines
3.4 KiB
TypeScript
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);
|
|
}
|