Lines
95.33 %
Functions
45.78 %
Branches
100 %
use wasm_encoder::{BlockType, Catch, Encode, Function, HeapType, Instruction, MemArg, ValType};
pub struct FunctionEmitter {
bytes: Vec<u8>,
block_depth: u32,
}
impl FunctionEmitter {
pub fn new() -> Self {
Self {
bytes: Vec::new(),
block_depth: 0,
/// A fresh scratch emitter whose `block_depth` is pre-seeded to
/// `depth`, as if it were already nested that many structured-control
/// frames deep. Used by BLOCK emit-time exit discovery: the body is
/// compiled into a scratch buffer seeded at the depth the block's
/// frame will occupy, so every relative `br` (RETURN-FROM / GO,
/// including exits to outer blocks) computes the same target it will
/// need once the bytes are spliced into the parent after the parent
/// opens the block. Pair with [`Self::take_bytes`] + [`Self::splice`].
pub fn new_seeded(depth: u32) -> Self {
block_depth: depth,
/// Consume the emitter, yielding its raw instruction bytes for
/// splicing into a parent via [`Self::splice`].
pub fn take_bytes(self) -> Vec<u8> {
self.bytes
/// Borrow the raw instruction bytes for splicing without consuming the
/// emitter (e.g. handler-case keeps several clause scratches and splices
/// each in turn).
pub fn bytes_ref(&self) -> &[u8] {
&self.bytes
/// Append a scratch emitter's raw bytes verbatim. The scratch body is
/// internally depth-balanced (it ends at the depth it started), so the
/// parent's `block_depth` is unchanged — the caller manages the
/// enclosing frame with `block_start_typed` / `block_end` around this.
pub fn splice(&mut self, bytes: &[u8]) {
self.bytes.extend_from_slice(bytes);
/// Current wasm structured-control depth — the number of open
/// `block` / `loop` / `if` frames around the next instruction. Used
/// by labelled exits (BLOCK / RETURN-FROM, TAGBODY / GO) to compute
/// the relative `br` depth.
pub fn block_depth(&self) -> u32 {
self.block_depth
pub fn finish(self, locals: &[(u32, ValType)]) -> Function {
let mut func = Function::new(locals.iter().copied());
func.raw(self.bytes);
func
pub fn memory_init(&mut self, data_idx: u32, dst: u32, len: u32) {
Instruction::I32Const(dst as i32).encode(&mut self.bytes);
Instruction::I32Const(0).encode(&mut self.bytes);
Instruction::I32Const(len as i32).encode(&mut self.bytes);
Instruction::MemoryInit {
mem: 0,
data_index: data_idx,
.encode(&mut self.bytes);
pub fn local_get(&mut self, idx: u32) {
Instruction::LocalGet(idx).encode(&mut self.bytes);
pub fn local_set(&mut self, idx: u32) {
Instruction::LocalSet(idx).encode(&mut self.bytes);
pub fn local_tee(&mut self, idx: u32) {
Instruction::LocalTee(idx).encode(&mut self.bytes);
pub fn i32_const(&mut self, value: i32) {
Instruction::I32Const(value).encode(&mut self.bytes);
pub fn i64_const(&mut self, value: i64) {
Instruction::I64Const(value).encode(&mut self.bytes);
pub fn i32_add(&mut self) {
Instruction::I32Add.encode(&mut self.bytes);
pub fn i32_sub(&mut self) {
Instruction::I32Sub.encode(&mut self.bytes);
pub fn i32_eqz(&mut self) {
Instruction::I32Eqz.encode(&mut self.bytes);
pub fn i32_eq(&mut self) {
Instruction::I32Eq.encode(&mut self.bytes);
pub fn i32_lt_s(&mut self) {
Instruction::I32LtS.encode(&mut self.bytes);
pub fn i32_ge_s(&mut self) {
Instruction::I32GeS.encode(&mut self.bytes);
pub fn i32_gt_s(&mut self) {
Instruction::I32GtS.encode(&mut self.bytes);
pub fn i32_le_s(&mut self) {
Instruction::I32LeS.encode(&mut self.bytes);
pub fn i32_ne(&mut self) {
Instruction::I32Ne.encode(&mut self.bytes);
pub fn i32_shl(&mut self) {
Instruction::I32Shl.encode(&mut self.bytes);
pub fn i32_or(&mut self) {
Instruction::I32Or.encode(&mut self.bytes);
pub fn i64_ne(&mut self) {
Instruction::I64Ne.encode(&mut self.bytes);
pub fn i32_mul(&mut self) {
Instruction::I32Mul.encode(&mut self.bytes);
pub fn i32_div_s(&mut self) {
Instruction::I32DivS.encode(&mut self.bytes);
pub fn i32_rem_s(&mut self) {
Instruction::I32RemS.encode(&mut self.bytes);
pub fn i32_load(&mut self, offset: u64) {
Instruction::I32Load(MemArg {
offset,
align: 2,
memory_index: 0,
})
pub fn i32_load8_u(&mut self, offset: u64) {
Instruction::I32Load8U(MemArg {
align: 0,
pub fn i64_load(&mut self, offset: u64) {
Instruction::I64Load(MemArg {
align: 3,
pub fn i32_ge_u(&mut self) {
Instruction::I32GeU.encode(&mut self.bytes);
pub fn struct_get(&mut self, type_idx: u32, field_idx: u32) {
Instruction::StructGet {
struct_type_index: type_idx,
field_index: field_idx,
pub fn if_block(&mut self, block_type: BlockType) {
Instruction::If(block_type).encode(&mut self.bytes);
self.block_depth += 1;
pub fn else_block(&mut self) {
Instruction::Else.encode(&mut self.bytes);
pub fn i32_store_raw(&mut self) {
Instruction::I32Store(MemArg {
offset: 0,
pub fn i64_store_raw(&mut self) {
Instruction::I64Store(MemArg {
pub fn i64_extend_i32_u(&mut self) {
Instruction::I64ExtendI32U.encode(&mut self.bytes);
pub fn i64_extend_i32_s(&mut self) {
Instruction::I64ExtendI32S.encode(&mut self.bytes);
pub fn i32_wrap_i64(&mut self) {
Instruction::I32WrapI64.encode(&mut self.bytes);
pub fn array_new_data(&mut self, type_idx: u32, data_idx: u32) {
Instruction::ArrayNewData {
array_type_index: type_idx,
array_data_index: data_idx,
pub fn array_get_u(&mut self, type_idx: u32) {
Instruction::ArrayGetU(type_idx).encode(&mut self.bytes);
pub fn array_new_default(&mut self, type_idx: u32) {
Instruction::ArrayNewDefault(type_idx).encode(&mut self.bytes);
pub fn array_set(&mut self, type_idx: u32) {
Instruction::ArraySet(type_idx).encode(&mut self.bytes);
pub fn array_len(&mut self) {
Instruction::ArrayLen.encode(&mut self.bytes);
pub fn block_start(&mut self) {
Instruction::Block(BlockType::Empty).encode(&mut self.bytes);
/// Open a wasm `block` whose result type is known up-front. Used
/// by BLOCK / RETURN-FROM where the lexically-named block carries
/// a value-yielding result.
pub fn block_start_typed(&mut self, block_type: BlockType) {
Instruction::Block(block_type).encode(&mut self.bytes);
pub fn loop_start(&mut self) {
Instruction::Loop(BlockType::Empty).encode(&mut self.bytes);
pub fn block_end(&mut self) {
Instruction::End.encode(&mut self.bytes);
self.block_depth = self.block_depth.saturating_sub(1);
pub fn br(&mut self, depth: u32) {
Instruction::Br(depth).encode(&mut self.bytes);
pub fn br_if(&mut self, depth: u32) {
Instruction::BrIf(depth).encode(&mut self.bytes);
/// Indexed branch — pops an i32 selector, jumps to `targets[i]` when the
/// selector is in range, else `default`. Used by TAGBODY / GO to dispatch
/// to the segment matching the current pc.
pub fn br_table(&mut self, targets: &[u32], default: u32) {
Instruction::BrTable(std::borrow::Cow::Borrowed(targets), default).encode(&mut self.bytes);
pub fn call(&mut self, func_idx: u32) {
Instruction::Call(func_idx).encode(&mut self.bytes);
/// Indirect call through a typed funcref popped from the stack.
/// `type_idx` is the wasm fn-type index; the engine validates the
/// funcref's declared shape against it at compile time. Used by the
/// Tier 1.5 closure call site after `struct.get $closure_<sig>
/// $fn_ref` has produced the funcref.
pub fn call_ref(&mut self, type_idx: u32) {
Instruction::CallRef(type_idx).encode(&mut self.bytes);
pub fn store_u8_dynamic(&mut self, local_base: u32, offset: u32, value: u8) {
Instruction::LocalGet(local_base).encode(&mut self.bytes);
Instruction::I32Const(offset as i32).encode(&mut self.bytes);
Instruction::I32Const(i32::from(value)).encode(&mut self.bytes);
Instruction::I32Store8(MemArg {
pub fn store_i32_dynamic(&mut self, local_base: u32, offset: u32, value: i32) {
pub fn store_i64_dynamic(&mut self, local_base: u32, offset: u32, value: i64) {
pub fn i32_store8_raw(&mut self) {
pub fn i32_load16_u(&mut self, offset: u64) {
Instruction::I32Load16U(MemArg {
align: 1,
pub fn ref_null(&mut self, type_idx: u32) {
Instruction::RefNull(HeapType::Concrete(type_idx)).encode(&mut self.bytes);
/// Emits `ref.null any` — the polymorphic null that satisfies every
/// `(ref null any)` slot. Used as nomi-eval's "no result" return value
/// for empty programs / definition-only forms.
pub fn ref_null_any(&mut self) {
Instruction::RefNull(HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Any,
pub fn ref_is_null(&mut self) {
Instruction::RefIsNull.encode(&mut self.bytes);
/// Boxes the top-of-stack i32 into an `(ref i31)` so it can ride an
/// `anyref` slot — the canonical way to embed small ints in a
/// `$pair` car. Pairs with [`Self::i31_get_s`] for the reverse.
pub fn ref_i31(&mut self) {
Instruction::RefI31.encode(&mut self.bytes);
/// Unpacks a `(ref i31)` back to its signed i32 value.
pub fn i31_get_s(&mut self) {
Instruction::I31GetS.encode(&mut self.bytes);
/// Downcasts a `(ref null any)` (or any subtype) to a concrete struct
/// type's non-null reference. Traps at runtime if the cast fails.
/// Caller is responsible for ensuring the value's dynamic type
/// matches; the compile-time `PairElement` carries that proof.
pub fn ref_cast(&mut self, type_idx: u32) {
Instruction::RefCastNonNull(HeapType::Concrete(type_idx)).encode(&mut self.bytes);
/// Nullable counterpart to [`ref_cast`]. Use when the value's
/// declared type is `(ref null …)` and a null pointer is a valid
/// runtime outcome — e.g. host fns returning
/// `Option<Rooted<…>>`, which the wasmtime ABI surfaces as a
/// nullable reference. The non-null variant traps on null; this
/// one preserves null and only fails on type mismatch.
pub fn ref_cast_nullable(&mut self, type_idx: u32) {
Instruction::RefCastNullable(HeapType::Concrete(type_idx)).encode(&mut self.bytes);
/// Downcast to the abstract `i31` heap type — paired with
/// [`Self::i31_get_s`] to retrieve the boxed i32.
pub fn ref_cast_i31(&mut self) {
Instruction::RefCastNonNull(HeapType::I31).encode(&mut self.bytes);
pub fn end(&mut self) {
pub fn drop_value(&mut self) {
Instruction::Drop.encode(&mut self.bytes);
/// Emit `unreachable`. Used after a `br` out of a value-yielding
/// block where the validator still expects a typed value on the
/// stack but control will never reach the next instruction —
/// wasm's stack-polymorphism after `unreachable` keeps the
/// type-check happy without forcing us to materialize a real value.
pub fn unreachable(&mut self) {
Instruction::Unreachable.encode(&mut self.bytes);
/// Open a `try_table` frame (exception-handling proposal). `catches`
/// routes matching `throw`s to a relative `br` depth resolved against
/// the open blocks. Opens one structured-control frame like `block`,
/// so `block_depth` increments — keeping `(return-from name v)` and
/// `(go tag)` relative-`br` math correct across a wrapped body. Pair
/// with [`Self::block_end`]. Used by the Tier 3 boundary wrapper and
/// by `(handler-case)` / `(unwind-protect)` (ADR-0026).
pub fn try_table(&mut self, block_type: BlockType, catches: &[Catch]) {
Instruction::TryTable(block_type, std::borrow::Cow::Borrowed(catches))
/// Throw the exception with the given tag index, consuming one stack
/// value per tag parameter (the `$nomi_error` tag takes a single
/// `(ref null $nomi_condition)`). Control never returns past a
/// `throw`, so the stack goes polymorphic — value-position callers
/// pair this with a trailing type the validator accepts.
pub fn throw(&mut self, tag_idx: u32) {
Instruction::Throw(tag_idx).encode(&mut self.bytes);
/// Pushes a typed funcref pointing at the wasm function with the
/// given index. Tier 1.5 closure construction pairs this with
/// [`Self::struct_new`] to populate a `$closure_<sig>` struct.
pub fn ref_func(&mut self, func_idx: u32) {
Instruction::RefFunc(func_idx).encode(&mut self.bytes);
/// Constructs a struct of the registered struct type. Consumes one
/// stack value per field in declaration order; pushes a
/// `(ref $type_idx)` of the resulting struct.
pub fn struct_new(&mut self, type_idx: u32) {
Instruction::StructNew(type_idx).encode(&mut self.bytes);
impl Default for FunctionEmitter {
fn default() -> Self {
Self::new()