// WASM Binary Encoder
// Encodes a Module to WebAssembly binary format (.wasm)
//
// Reference: WebAssembly Core Specification
// https://webassembly.github.io/spec/core/binary/
// ============ Binary Writer ============
///|
/// Binary writer for encoding WASM
priv struct BinaryWriter {
buf : Array[Int]
}
///|
fn BinaryWriter::BinaryWriter() -> BinaryWriter {
{ buf: [] }
}
///|
fn BinaryWriter::write_byte(self : BinaryWriter, b : Int) -> Unit {
self.buf.push(b & 0xFF)
}
///|
fn BinaryWriter::write_array(self : BinaryWriter, bytes : Array[Int]) -> Unit {
for b in bytes {
self.buf.push(b & 0xFF)
}
}
///|
/// Write unsigned LEB128
fn BinaryWriter::write_u32(self : BinaryWriter, val : Int) -> Unit {
let mut v = val & 0x7FFFFFFF // Treat as unsigned
if val < 0 {
// For negative values treated as unsigned, we need special handling
self.write_u64(val.to_int64() & 0xFFFFFFFFL)
return
}
while true {
let byte = v & 0x7F
v = v >> 7
if v == 0 {
self.buf.push(byte)
break
} else {
self.buf.push(byte | 0x80)
}
}
}
///|
/// Write unsigned LEB128 for 64-bit values
fn BinaryWriter::write_u64(self : BinaryWriter, val : Int64) -> Unit {
let mut v = val
while true {
let byte = v.to_int() & 0x7F
v = (v.reinterpret_as_uint64() >> 7).reinterpret_as_int64()
if v == 0L {
self.buf.push(byte)
break
} else {
self.buf.push(byte | 0x80)
}
}
}
///|
/// Write signed LEB128 for 32-bit values
fn BinaryWriter::write_s32(self : BinaryWriter, val : Int) -> Unit {
let mut v = val
while true {
let byte = v & 0x7F
v = v >> 7
// Check if we can stop
let sign_bit = (byte & 0x40) != 0
if (v == 0 && !sign_bit) || (v == -1 && sign_bit) {
self.buf.push(byte)
break
} else {
self.buf.push(byte | 0x80)
}
}
}
///|
/// Write signed LEB128 for 64-bit values
fn BinaryWriter::write_s64(self : BinaryWriter, val : Int64) -> Unit {
let mut v = val
while true {
let byte = v.to_int() & 0x7F
v = v >> 7
let sign_bit = (byte & 0x40) != 0
if (v == 0L && !sign_bit) || (v == -1L && sign_bit) {
self.buf.push(byte)
break
} else {
self.buf.push(byte | 0x80)
}
}
}
///|
/// Write a UTF-8 string with length prefix
fn BinaryWriter::write_name(self : BinaryWriter, name : String) -> Unit {
let bytes = string_to_bytes(name)
self.write_u32(bytes.length())
self.write_array(bytes)
}
///|
/// Write f32 in little-endian IEEE 754
fn BinaryWriter::write_f32(self : BinaryWriter, val : Float) -> Unit {
let bits = val.reinterpret_as_int()
self.buf.push(bits & 0xFF)
self.buf.push((bits >> 8) & 0xFF)
self.buf.push((bits >> 16) & 0xFF)
self.buf.push((bits >> 24) & 0xFF)
}
///|
/// Write f64 in little-endian IEEE 754
fn BinaryWriter::write_f64(self : BinaryWriter, val : Double) -> Unit {
let bits = val.reinterpret_as_int64()
self.buf.push(bits.to_int() & 0xFF)
self.buf.push((bits >> 8).to_int() & 0xFF)
self.buf.push((bits >> 16).to_int() & 0xFF)
self.buf.push((bits >> 24).to_int() & 0xFF)
self.buf.push((bits >> 32).to_int() & 0xFF)
self.buf.push((bits >> 40).to_int() & 0xFF)
self.buf.push((bits >> 48).to_int() & 0xFF)
self.buf.push((bits >> 56).to_int() & 0xFF)
}
///|
fn BinaryWriter::to_bytes(self : BinaryWriter) -> Array[Int] {
self.buf
}
// ============ Type Encoding ============
///|
/// Encode a value type
fn encode_valtype(w : BinaryWriter, vt : @types.ValueType) -> Unit {
match vt {
I32 => w.write_byte(0x7F)
I64 => w.write_byte(0x7E)
F32 => w.write_byte(0x7D)
F64 => w.write_byte(0x7C)
V128 => w.write_byte(0x7B)
FuncRef => w.write_byte(0x70)
ExternRef => w.write_byte(0x6F)
// GC types
AnyRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x6E) // any
}
StructRef => w.write_byte(0x6B)
ArrayRef => w.write_byte(0x6A)
RefAny => {
w.write_byte(0x64) // ref
w.write_byte(0x6E) // any
}
RefEq => {
w.write_byte(0x64) // ref
w.write_byte(0x6D) // eq
}
RefNullEq => {
w.write_byte(0x63) // ref null
w.write_byte(0x6D) // eq
}
RefI31 => {
w.write_byte(0x64) // ref
w.write_byte(0x6C) // i31
}
RefNullI31 => {
w.write_byte(0x63) // ref null
w.write_byte(0x6C) // i31
}
RefStructAbs => {
w.write_byte(0x64) // ref
w.write_byte(0x6B) // struct
}
RefArrayAbs => {
w.write_byte(0x64) // ref
w.write_byte(0x6A) // array
}
RefFunc => {
w.write_byte(0x64) // ref
w.write_byte(0x70) // func
}
RefExtern => {
w.write_byte(0x64) // ref
w.write_byte(0x6F) // extern
}
ExnRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x69) // exn
}
NullRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x71) // none
}
NullFuncRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x73) // nofunc
}
NullExnRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x74) // noexn
}
NullExternRef => {
w.write_byte(0x63) // ref null
w.write_byte(0x72) // noextern
}
RefNone => {
w.write_byte(0x64) // ref
w.write_byte(0x71) // none
}
// Typed references
RefFuncTyped(idx) => {
w.write_byte(0x64) // ref
w.write_s32(idx)
}
RefNullFuncTyped(idx) => {
w.write_byte(0x63) // ref null
w.write_s32(idx)
}
RefStruct(idx) => {
w.write_byte(0x64) // ref
w.write_s32(idx)
}
RefNullStruct(idx) => {
w.write_byte(0x63) // ref null
w.write_s32(idx)
}
RefArray(idx) => {
w.write_byte(0x64) // ref
w.write_s32(idx)
}
RefNullArray(idx) => {
w.write_byte(0x63) // ref null
w.write_s32(idx)
}
}
}
///|
/// Encode a storage type (for GC struct/array fields)
fn encode_storage_type(w : BinaryWriter, st : @types.StorageType) -> Unit {
match st {
Val(vt) => encode_valtype(w, vt)
Packed(pt) =>
match pt {
I8 => w.write_byte(0x78)
I16 => w.write_byte(0x77)
}
}
}
///|
/// Encode a field type
fn encode_field_type(w : BinaryWriter, ft : @types.FieldType) -> Unit {
encode_storage_type(w, ft.storage_type)
w.write_byte(if ft.mutable { 1 } else { 0 })
}
///|
/// Encode a function type
fn encode_func_type(w : BinaryWriter, ft : @types.FuncType) -> Unit {
w.write_byte(0x60) // func type marker
// Params
w.write_u32(ft.params.length())
for p in ft.params {
encode_valtype(w, p)
}
// Results
w.write_u32(ft.results.length())
for r in ft.results {
encode_valtype(w, r)
}
}
///|
/// Encode a struct type
fn encode_struct_type(w : BinaryWriter, st : @types.StructType) -> Unit {
w.write_byte(0x5F) // struct type marker
w.write_u32(st.fields.length())
for field in st.fields {
encode_field_type(w, field)
}
}
///|
/// Encode an array type
fn encode_array_type(w : BinaryWriter, at : @types.ArrayType) -> Unit {
w.write_byte(0x5E) // array type marker
encode_field_type(w, at.element)
}
///|
/// Encode a composite type
fn encode_composite_type(w : BinaryWriter, ct : @types.CompositeType) -> Unit {
match ct {
Func(ft) => encode_func_type(w, ft)
Struct(st) => encode_struct_type(w, st)
Array(at) => encode_array_type(w, at)
}
}
///|
/// Encode a subtype
fn encode_subtype(w : BinaryWriter, st : @types.SubType) -> Unit {
if st.supertypes.is_empty() && st.final_ {
// Simple case: no supertypes, final
encode_composite_type(w, st.composite)
} else if st.supertypes.is_empty() && !st.final_ {
// Non-final, no supertypes
w.write_byte(0x50) // sub marker
w.write_u32(0) // no supertypes
encode_composite_type(w, st.composite)
} else {
// Has supertypes
if st.final_ {
w.write_byte(0x4F) // sub final marker
} else {
w.write_byte(0x50) // sub marker
}
w.write_u32(st.supertypes.length())
for idx in st.supertypes {
w.write_u32(idx)
}
encode_composite_type(w, st.composite)
}
}
///|
/// Encode table limits (supports table64 via flag 0x04)
fn encode_table_limits(
w : BinaryWriter,
limits : @types.Limits,
is_table64 : Bool,
) -> Unit {
let has_max = limits.max is Some(_)
let mut flags = 0x00
if has_max {
flags = flags | 0x01
}
if is_table64 {
flags = flags | 0x04
}
w.write_byte(flags)
if is_table64 {
w.write_u64(limits.min)
if limits.max is Some(max) {
w.write_u64(max)
}
} else {
w.write_u32(limits.min.to_int())
if limits.max is Some(max) {
w.write_u32(max.to_int())
}
}
}
///|
/// Encode a table type
fn encode_table_type(w : BinaryWriter, tt : @types.TableType) -> Unit {
encode_valtype(w, tt.elem_type)
encode_table_limits(w, tt.limits, tt.is_table64)
}
///|
/// Encode memory limits (supports memory64 and custom page sizes)
fn encode_memory_limits(w : BinaryWriter, mt : @types.MemoryType) -> Unit {
let has_max = mt.limits.max is Some(_)
let has_custom_page_size = mt.page_size_log2 != 16
let mut flags = 0x00
if has_max {
flags = flags | 0x01
}
if mt.is_memory64 {
flags = flags | 0x04
}
if has_custom_page_size {
flags = flags | 0x08
}
w.write_byte(flags)
if mt.is_memory64 {
w.write_u64(mt.limits.min)
if mt.limits.max is Some(max) {
w.write_u64(max)
}
} else {
w.write_u32(mt.limits.min.to_int())
if mt.limits.max is Some(max) {
w.write_u32(max.to_int())
}
}
if has_custom_page_size {
w.write_u32(mt.page_size_log2)
}
}
///|
/// Encode a memory type
fn encode_memory_type(w : BinaryWriter, mt : @types.MemoryType) -> Unit {
encode_memory_limits(w, mt)
}
///|
/// Encode a global type
fn encode_global_type(w : BinaryWriter, gt : @types.GlobalType) -> Unit {
encode_valtype(w, gt.value_type)
w.write_byte(if gt.mutable { 1 } else { 0 })
}
// ============ Instruction Encoding ============
///|
/// Encode a block type
fn encode_block_type(w : BinaryWriter, bt : @types.BlockType) -> Unit {
match bt {
Empty => w.write_byte(0x40) // empty block type
Value(vt) => encode_valtype(w, vt)
TypeIndex(idx) => w.write_s32(idx)
MultiValue(_) | InlineType(_, _) =>
// These should be resolved to TypeIndex before encoding
w.write_byte(0x40)
}
}
///|
/// Encode a memory argument (align, offset)
fn encode_memarg(
w : BinaryWriter,
_memidx : Int,
align : Int,
offset : Int64,
) -> Unit {
w.write_u32(align)
w.write_u64(offset)
}
///|
/// Encode instructions
fn encode_instructions(
w : BinaryWriter,
instrs : Array[@types.Instruction],
) -> Unit {
for instr in instrs {
encode_instruction(w, instr)
}
}
///|
/// Encode a single instruction
fn encode_instruction(w : BinaryWriter, instr : @types.Instruction) -> Unit {
match instr {
// Control instructions
Unreachable => w.write_byte(0x00)
Nop => w.write_byte(0x01)
Block(bt, body) => {
w.write_byte(0x02)
encode_block_type(w, bt)
encode_instructions(w, body)
w.write_byte(0x0B) // end
}
Loop(bt, body) => {
w.write_byte(0x03)
encode_block_type(w, bt)
encode_instructions(w, body)
w.write_byte(0x0B) // end
}
If(bt, then_body, else_body) => {
w.write_byte(0x04)
encode_block_type(w, bt)
encode_instructions(w, then_body)
if !else_body.is_empty() {
w.write_byte(0x05) // else
encode_instructions(w, else_body)
}
w.write_byte(0x0B) // end
}
Br(label) => {
w.write_byte(0x0C)
w.write_u32(label)
}
BrIf(label) => {
w.write_byte(0x0D)
w.write_u32(label)
}
BrTable(labels, default) => {
w.write_byte(0x0E)
w.write_u32(labels.length())
for l in labels {
w.write_u32(l)
}
w.write_u32(default)
}
Return => w.write_byte(0x0F)
Call(idx) => {
w.write_byte(0x10)
w.write_u32(idx)
}
CallIndirect(type_idx, table_idx) => {
w.write_byte(0x11)
w.write_u32(type_idx)
w.write_u32(table_idx)
}
ReturnCall(idx) => {
w.write_byte(0x12)
w.write_u32(idx)
}
ReturnCallIndirect(type_idx, table_idx) => {
w.write_byte(0x13)
w.write_u32(type_idx)
w.write_u32(table_idx)
}
CallRef(type_idx) => {
w.write_byte(0x14)
w.write_u32(type_idx)
}
ReturnCallRef(type_idx) => {
w.write_byte(0x15)
w.write_u32(type_idx)
}
// Exception handling
Throw(tag_idx) => {
w.write_byte(0x08)
w.write_u32(tag_idx)
}
ThrowRef => w.write_byte(0x0A)
TryTable(bt, handlers, body) => {
w.write_byte(0x1F)
encode_block_type(w, bt)
w.write_u32(handlers.length())
for handler in handlers {
encode_catch_handler(w, handler)
}
encode_instructions(w, body)
w.write_byte(0x0B) // end
}
// Parametric instructions
Drop => w.write_byte(0x1A)
Select => w.write_byte(0x1B)
SelectTyped(types) => {
w.write_byte(0x1C)
w.write_u32(types.length())
for t in types {
encode_valtype(w, t)
}
}
// Variable instructions
LocalGet(idx) => {
w.write_byte(0x20)
w.write_u32(idx)
}
LocalSet(idx) => {
w.write_byte(0x21)
w.write_u32(idx)
}
LocalTee(idx) => {
w.write_byte(0x22)
w.write_u32(idx)
}
GlobalGet(idx) => {
w.write_byte(0x23)
w.write_u32(idx)
}
GlobalSet(idx) => {
w.write_byte(0x24)
w.write_u32(idx)
}
// Table instructions
TableGet(idx) => {
w.write_byte(0x25)
w.write_u32(idx)
}
TableSet(idx) => {
w.write_byte(0x26)
w.write_u32(idx)
}
TableSize(idx) => {
w.write_byte(0xFC)
w.write_u32(16)
w.write_u32(idx)
}
TableGrow(idx) => {
w.write_byte(0xFC)
w.write_u32(15)
w.write_u32(idx)
}
TableFill(idx) => {
w.write_byte(0xFC)
w.write_u32(17)
w.write_u32(idx)
}
TableCopy(dst, src) => {
w.write_byte(0xFC)
w.write_u32(14)
w.write_u32(dst)
w.write_u32(src)
}
TableInit(table_idx, elem_idx) => {
w.write_byte(0xFC)
w.write_u32(12)
w.write_u32(elem_idx)
w.write_u32(table_idx)
}
// Memory instructions
I32Load(memidx, align, offset) => {
w.write_byte(0x28)
encode_memarg(w, memidx, align, offset)
}
I64Load(memidx, align, offset) => {
w.write_byte(0x29)
encode_memarg(w, memidx, align, offset)
}
F32Load(memidx, align, offset) => {
w.write_byte(0x2A)
encode_memarg(w, memidx, align, offset)
}
F64Load(memidx, align, offset) => {
w.write_byte(0x2B)
encode_memarg(w, memidx, align, offset)
}
I32Load8S(memidx, align, offset) => {
w.write_byte(0x2C)
encode_memarg(w, memidx, align, offset)
}
I32Load8U(memidx, align, offset) => {
w.write_byte(0x2D)
encode_memarg(w, memidx, align, offset)
}
I32Load16S(memidx, align, offset) => {
w.write_byte(0x2E)
encode_memarg(w, memidx, align, offset)
}
I32Load16U(memidx, align, offset) => {
w.write_byte(0x2F)
encode_memarg(w, memidx, align, offset)
}
I64Load8S(memidx, align, offset) => {
w.write_byte(0x30)
encode_memarg(w, memidx, align, offset)
}
I64Load8U(memidx, align, offset) => {
w.write_byte(0x31)
encode_memarg(w, memidx, align, offset)
}
I64Load16S(memidx, align, offset) => {
w.write_byte(0x32)
encode_memarg(w, memidx, align, offset)
}
I64Load16U(memidx, align, offset) => {
w.write_byte(0x33)
encode_memarg(w, memidx, align, offset)
}
I64Load32S(memidx, align, offset) => {
w.write_byte(0x34)
encode_memarg(w, memidx, align, offset)
}
I64Load32U(memidx, align, offset) => {
w.write_byte(0x35)
encode_memarg(w, memidx, align, offset)
}
I32Store(memidx, align, offset) => {
w.write_byte(0x36)
encode_memarg(w, memidx, align, offset)
}
I64Store(memidx, align, offset) => {
w.write_byte(0x37)
encode_memarg(w, memidx, align, offset)
}
F32Store(memidx, align, offset) => {
w.write_byte(0x38)
encode_memarg(w, memidx, align, offset)
}
F64Store(memidx, align, offset) => {
w.write_byte(0x39)
encode_memarg(w, memidx, align, offset)
}
I32Store8(memidx, align, offset) => {
w.write_byte(0x3A)
encode_memarg(w, memidx, align, offset)
}
I32Store16(memidx, align, offset) => {
w.write_byte(0x3B)
encode_memarg(w, memidx, align, offset)
}
I64Store8(memidx, align, offset) => {
w.write_byte(0x3C)
encode_memarg(w, memidx, align, offset)
}
I64Store16(memidx, align, offset) => {
w.write_byte(0x3D)
encode_memarg(w, memidx, align, offset)
}
I64Store32(memidx, align, offset) => {
w.write_byte(0x3E)
encode_memarg(w, memidx, align, offset)
}
MemorySize(memidx) => {
w.write_byte(0x3F)
w.write_u32(memidx)
}
MemoryGrow(memidx) => {
w.write_byte(0x40)
w.write_u32(memidx)
}
MemoryInit(memidx, data_idx) => {
w.write_byte(0xFC)
w.write_u32(8)
w.write_u32(data_idx)
w.write_u32(memidx)
}
DataDrop(data_idx) => {
w.write_byte(0xFC)
w.write_u32(9)
w.write_u32(data_idx)
}
MemoryCopy(dst, src) => {
w.write_byte(0xFC)
w.write_u32(10)
w.write_u32(dst)
w.write_u32(src)
}
MemoryFill(memidx) => {
w.write_byte(0xFC)
w.write_u32(11)
w.write_u32(memidx)
}
ElemDrop(elem_idx) => {
w.write_byte(0xFC)
w.write_u32(13)
w.write_u32(elem_idx)
}
// Reference instructions
RefNull(vt) => {
w.write_byte(0xD0)
encode_valtype(w, vt)
}
RefIsNull => w.write_byte(0xD1)
RefFunc(idx) => {
w.write_byte(0xD2)
w.write_u32(idx)
}
RefAsNonNull => w.write_byte(0xD4)
RefEqInstr => w.write_byte(0xD3)
BrOnNull(label) => {
w.write_byte(0xD5)
w.write_u32(label)
}
BrOnNonNull(label) => {
w.write_byte(0xD6)
w.write_u32(label)
}
// Numeric constants
I32Const(val) => {
w.write_byte(0x41)
w.write_s32(val)
}
I64Const(val) => {
w.write_byte(0x42)
w.write_s64(val)
}
F32Const(val) => {
w.write_byte(0x43)
w.write_f32(val)
}
F64Const(val) => {
w.write_byte(0x44)
w.write_f64(val)
}
// i32 operations
I32Eqz => w.write_byte(0x45)
I32Eq => w.write_byte(0x46)
I32Ne => w.write_byte(0x47)
I32LtS => w.write_byte(0x48)
I32LtU => w.write_byte(0x49)
I32GtS => w.write_byte(0x4A)
I32GtU => w.write_byte(0x4B)
I32LeS => w.write_byte(0x4C)
I32LeU => w.write_byte(0x4D)
I32GeS => w.write_byte(0x4E)
I32GeU => w.write_byte(0x4F)
I32Clz => w.write_byte(0x67)
I32Ctz => w.write_byte(0x68)
I32Popcnt => w.write_byte(0x69)
I32Add => w.write_byte(0x6A)
I32Sub => w.write_byte(0x6B)
I32Mul => w.write_byte(0x6C)
I32DivS => w.write_byte(0x6D)
I32DivU => w.write_byte(0x6E)
I32RemS => w.write_byte(0x6F)
I32RemU => w.write_byte(0x70)
I32And => w.write_byte(0x71)
I32Or => w.write_byte(0x72)
I32Xor => w.write_byte(0x73)
I32Shl => w.write_byte(0x74)
I32ShrS => w.write_byte(0x75)
I32ShrU => w.write_byte(0x76)
I32Rotl => w.write_byte(0x77)
I32Rotr => w.write_byte(0x78)
I32Extend8S => w.write_byte(0xC0)
I32Extend16S => w.write_byte(0xC1)
// i64 operations
I64Eqz => w.write_byte(0x50)
I64Eq => w.write_byte(0x51)
I64Ne => w.write_byte(0x52)
I64LtS => w.write_byte(0x53)
I64LtU => w.write_byte(0x54)
I64GtS => w.write_byte(0x55)
I64GtU => w.write_byte(0x56)
I64LeS => w.write_byte(0x57)
I64LeU => w.write_byte(0x58)
I64GeS => w.write_byte(0x59)
I64GeU => w.write_byte(0x5A)
I64Clz => w.write_byte(0x79)
I64Ctz => w.write_byte(0x7A)
I64Popcnt => w.write_byte(0x7B)
I64Add => w.write_byte(0x7C)
I64Sub => w.write_byte(0x7D)
I64Mul => w.write_byte(0x7E)
I64DivS => w.write_byte(0x7F)
I64DivU => w.write_byte(0x80)
I64RemS => w.write_byte(0x81)
I64RemU => w.write_byte(0x82)
I64And => w.write_byte(0x83)
I64Or => w.write_byte(0x84)
I64Xor => w.write_byte(0x85)
I64Shl => w.write_byte(0x86)
I64ShrS => w.write_byte(0x87)
I64ShrU => w.write_byte(0x88)
I64Rotl => w.write_byte(0x89)
I64Rotr => w.write_byte(0x8A)
I64Extend8S => w.write_byte(0xC2)
I64Extend16S => w.write_byte(0xC3)
I64Extend32S => w.write_byte(0xC4)
// f32 operations
F32Eq => w.write_byte(0x5B)
F32Ne => w.write_byte(0x5C)
F32Lt => w.write_byte(0x5D)
F32Gt => w.write_byte(0x5E)
F32Le => w.write_byte(0x5F)
F32Ge => w.write_byte(0x60)
F32Abs => w.write_byte(0x8B)
F32Neg => w.write_byte(0x8C)
F32Ceil => w.write_byte(0x8D)
F32Floor => w.write_byte(0x8E)
F32Trunc => w.write_byte(0x8F)
F32Nearest => w.write_byte(0x90)
F32Sqrt => w.write_byte(0x91)
F32Add => w.write_byte(0x92)
F32Sub => w.write_byte(0x93)
F32Mul => w.write_byte(0x94)
F32Div => w.write_byte(0x95)
F32Min => w.write_byte(0x96)
F32Max => w.write_byte(0x97)
F32Copysign => w.write_byte(0x98)
// f64 operations
F64Eq => w.write_byte(0x61)
F64Ne => w.write_byte(0x62)
F64Lt => w.write_byte(0x63)
F64Gt => w.write_byte(0x64)
F64Le => w.write_byte(0x65)
F64Ge => w.write_byte(0x66)
F64Abs => w.write_byte(0x99)
F64Neg => w.write_byte(0x9A)
F64Ceil => w.write_byte(0x9B)
F64Floor => w.write_byte(0x9C)
F64Trunc => w.write_byte(0x9D)
F64Nearest => w.write_byte(0x9E)
F64Sqrt => w.write_byte(0x9F)
F64Add => w.write_byte(0xA0)
F64Sub => w.write_byte(0xA1)
F64Mul => w.write_byte(0xA2)
F64Div => w.write_byte(0xA3)
F64Min => w.write_byte(0xA4)
F64Max => w.write_byte(0xA5)
F64Copysign => w.write_byte(0xA6)
// Conversions
I32WrapI64 => w.write_byte(0xA7)
I32TruncF32S => w.write_byte(0xA8)
I32TruncF32U => w.write_byte(0xA9)
I32TruncF64S => w.write_byte(0xAA)
I32TruncF64U => w.write_byte(0xAB)
I64ExtendI32S => w.write_byte(0xAC)
I64ExtendI32U => w.write_byte(0xAD)
I64TruncF32S => w.write_byte(0xAE)
I64TruncF32U => w.write_byte(0xAF)
I64TruncF64S => w.write_byte(0xB0)
I64TruncF64U => w.write_byte(0xB1)
F32ConvertI32S => w.write_byte(0xB2)
F32ConvertI32U => w.write_byte(0xB3)
F32ConvertI64S => w.write_byte(0xB4)
F32ConvertI64U => w.write_byte(0xB5)
F32DemoteF64 => w.write_byte(0xB6)
F64ConvertI32S => w.write_byte(0xB7)
F64ConvertI32U => w.write_byte(0xB8)
F64ConvertI64S => w.write_byte(0xB9)
F64ConvertI64U => w.write_byte(0xBA)
F64PromoteF32 => w.write_byte(0xBB)
I32ReinterpretF32 => w.write_byte(0xBC)
I64ReinterpretF64 => w.write_byte(0xBD)
F32ReinterpretI32 => w.write_byte(0xBE)
F64ReinterpretI64 => w.write_byte(0xBF)
// Saturating truncation
I32TruncSatF32S => {
w.write_byte(0xFC)
w.write_u32(0)
}
I32TruncSatF32U => {
w.write_byte(0xFC)
w.write_u32(1)
}
I32TruncSatF64S => {
w.write_byte(0xFC)
w.write_u32(2)
}
I32TruncSatF64U => {
w.write_byte(0xFC)
w.write_u32(3)
}
I64TruncSatF32S => {
w.write_byte(0xFC)
w.write_u32(4)
}
I64TruncSatF32U => {
w.write_byte(0xFC)
w.write_u32(5)
}
I64TruncSatF64S => {
w.write_byte(0xFC)
w.write_u32(6)
}
I64TruncSatF64U => {
w.write_byte(0xFC)
w.write_u32(7)
}
// GC instructions
StructNew(type_idx) => {
w.write_byte(0xFB)
w.write_u32(0)
w.write_u32(type_idx)
}
StructNewDefault(type_idx) => {
w.write_byte(0xFB)
w.write_u32(1)
w.write_u32(type_idx)
}
StructGet(type_idx, field_idx) => {
w.write_byte(0xFB)
w.write_u32(2)
w.write_u32(type_idx)
w.write_u32(field_idx)
}
StructGetS(type_idx, field_idx) => {
w.write_byte(0xFB)
w.write_u32(3)
w.write_u32(type_idx)
w.write_u32(field_idx)
}
StructGetU(type_idx, field_idx) => {
w.write_byte(0xFB)
w.write_u32(4)
w.write_u32(type_idx)
w.write_u32(field_idx)
}
StructSet(type_idx, field_idx) => {
w.write_byte(0xFB)
w.write_u32(5)
w.write_u32(type_idx)
w.write_u32(field_idx)
}
ArrayNew(type_idx) => {
w.write_byte(0xFB)
w.write_u32(6)
w.write_u32(type_idx)
}
ArrayNewDefault(type_idx) => {
w.write_byte(0xFB)
w.write_u32(7)
w.write_u32(type_idx)
}
ArrayNewFixed(type_idx, len) => {
w.write_byte(0xFB)
w.write_u32(8)
w.write_u32(type_idx)
w.write_u32(len)
}
ArrayNewData(type_idx, data_idx) => {
w.write_byte(0xFB)
w.write_u32(9)
w.write_u32(type_idx)
w.write_u32(data_idx)
}
ArrayNewElem(type_idx, elem_idx) => {
w.write_byte(0xFB)
w.write_u32(10)
w.write_u32(type_idx)
w.write_u32(elem_idx)
}
ArrayGet(type_idx) => {
w.write_byte(0xFB)
w.write_u32(11)
w.write_u32(type_idx)
}
ArrayGetS(type_idx) => {
w.write_byte(0xFB)
w.write_u32(12)
w.write_u32(type_idx)
}
ArrayGetU(type_idx) => {
w.write_byte(0xFB)
w.write_u32(13)
w.write_u32(type_idx)
}
ArraySet(type_idx) => {
w.write_byte(0xFB)
w.write_u32(14)
w.write_u32(type_idx)
}
ArrayLen => {
w.write_byte(0xFB)
w.write_u32(15)
}
ArrayFill(type_idx) => {
w.write_byte(0xFB)
w.write_u32(16)
w.write_u32(type_idx)
}
ArrayCopy(dst_type, src_type) => {
w.write_byte(0xFB)
w.write_u32(17)
w.write_u32(dst_type)
w.write_u32(src_type)
}
ArrayInitData(type_idx, data_idx) => {
w.write_byte(0xFB)
w.write_u32(18)
w.write_u32(type_idx)
w.write_u32(data_idx)
}
ArrayInitElem(type_idx, elem_idx) => {
w.write_byte(0xFB)
w.write_u32(19)
w.write_u32(type_idx)
w.write_u32(elem_idx)
}
RefTest(vt) => {
w.write_byte(0xFB)
w.write_u32(20)
encode_valtype(w, vt)
}
RefTestNull(vt) => {
w.write_byte(0xFB)
w.write_u32(21)
encode_valtype(w, vt)
}
RefCast(vt) => {
w.write_byte(0xFB)
w.write_u32(22)
encode_valtype(w, vt)
}
RefCastNull(vt) => {
w.write_byte(0xFB)
w.write_u32(23)
encode_valtype(w, vt)
}
BrOnCast(label, from_type, to_type) => {
w.write_byte(0xFB)
w.write_u32(24)
// Cast flags: bit 0 = from nullable, bit 1 = to nullable
let from_null = if from_type.is_nullable() { 1 } else { 0 }
let to_null = if to_type.is_nullable() { 2 } else { 0 }
w.write_byte(from_null | to_null)
w.write_u32(label)
encode_valtype(w, from_type)
encode_valtype(w, to_type)
}
BrOnCastFail(label, from_type, to_type) => {
w.write_byte(0xFB)
w.write_u32(25)
let from_null = if from_type.is_nullable() { 1 } else { 0 }
let to_null = if to_type.is_nullable() { 2 } else { 0 }
w.write_byte(from_null | to_null)
w.write_u32(label)
encode_valtype(w, from_type)
encode_valtype(w, to_type)
}
RefI31 => {
w.write_byte(0xFB)
w.write_u32(28)
}
I31GetS => {
w.write_byte(0xFB)
w.write_u32(29)
}
I31GetU => {
w.write_byte(0xFB)
w.write_u32(30)
}
AnyConvertExtern => {
w.write_byte(0xFB)
w.write_u32(26)
}
ExternConvertAny => {
w.write_byte(0xFB)
w.write_u32(27)
}
// SIMD - V128 constant and load/store
V128Const(bytes) => {
w.write_byte(0xFD)
w.write_u32(12)
for i in 0..<16 {
w.write_byte(bytes[i].to_int())
}
}
V128Load(memidx, align, offset) => {
w.write_byte(0xFD)
w.write_u32(0)
encode_memarg(w, memidx, align, offset)
}
V128Store(memidx, align, offset) => {
w.write_byte(0xFD)
w.write_u32(11)
encode_memarg(w, memidx, align, offset)
}
// For unhandled SIMD instructions, use unreachable as placeholder
_ => w.write_byte(0x00)
}
}
///|
/// Encode a catch handler
fn encode_catch_handler(
w : BinaryWriter,
handler : @types.CatchHandler,
) -> Unit {
match handler {
Catch(tag_idx, label) => {
w.write_byte(0x00)
w.write_u32(tag_idx)
w.write_u32(label)
}
CatchRef(tag_idx, label) => {
w.write_byte(0x01)
w.write_u32(tag_idx)
w.write_u32(label)
}
CatchAll(label) => {
w.write_byte(0x02)
w.write_u32(label)
}
CatchAllRef(label) => {
w.write_byte(0x03)
w.write_u32(label)
}
}
}
// ============ Section Encoding ============
///|
/// Write a section with its content
fn write_section(
w : BinaryWriter,
section_id : Int,
content : Array[Int],
) -> Unit {
w.write_byte(section_id)
w.write_u32(content.length())
w.write_array(content)
}
///|
/// Encode the type section
fn encode_type_section(mod : @types.Module) -> Array[Int] {
if mod.types.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.types.length())
for subtype in mod.types {
encode_subtype(w, subtype)
}
w.to_bytes()
}
///|
/// Encode the import section
fn encode_import_section(mod : @types.Module) -> Array[Int] {
if mod.imports.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.imports.length())
for imp in mod.imports {
w.write_name(imp.mod_name)
w.write_name(imp.name)
match imp.desc {
Func(type_idx) => {
w.write_byte(0x00)
w.write_u32(type_idx)
}
Table(tt) => {
w.write_byte(0x01)
encode_table_type(w, tt)
}
Memory(mt) => {
w.write_byte(0x02)
encode_memory_type(w, mt)
}
Global(gt) => {
w.write_byte(0x03)
encode_global_type(w, gt)
}
Tag(type_idx) => {
w.write_byte(0x04)
w.write_byte(0x00) // exception kind
w.write_u32(type_idx)
}
}
}
w.to_bytes()
}
///|
/// Encode the function section
fn encode_function_section(mod : @types.Module) -> Array[Int] {
if mod.funcs.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.funcs.length())
for type_idx in mod.funcs {
w.write_u32(type_idx)
}
w.to_bytes()
}
///|
/// Encode the table section
fn encode_table_section(mod : @types.Module) -> Array[Int] {
if mod.tables.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.tables.length())
for table in mod.tables {
match table.init {
Some(init_expr) => {
// Table with init expression (0x40 prefix)
w.write_byte(0x40)
w.write_byte(0x00) // reserved
encode_table_type(w, table.type_)
encode_instructions(w, init_expr)
w.write_byte(0x0B) // end
}
None => encode_table_type(w, table.type_)
}
}
w.to_bytes()
}
///|
/// Encode the memory section
fn encode_memory_section(mod : @types.Module) -> Array[Int] {
if mod.memories.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.memories.length())
for mem in mod.memories {
encode_memory_type(w, mem)
}
w.to_bytes()
}
///|
/// Encode the global section
fn encode_global_section(mod : @types.Module) -> Array[Int] {
if mod.globals.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.globals.length())
for global in mod.globals {
encode_global_type(w, global.type_)
encode_instructions(w, global.init)
w.write_byte(0x0B) // end
}
w.to_bytes()
}
///|
/// Encode the export section
fn encode_export_section(mod : @types.Module) -> Array[Int] {
if mod.exports.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.exports.length())
for exp in mod.exports {
w.write_name(exp.name)
match exp.desc {
Func(idx) => {
w.write_byte(0x00)
w.write_u32(idx)
}
Table(idx) => {
w.write_byte(0x01)
w.write_u32(idx)
}
Memory(idx) => {
w.write_byte(0x02)
w.write_u32(idx)
}
Global(idx) => {
w.write_byte(0x03)
w.write_u32(idx)
}
Tag(idx) => {
w.write_byte(0x04)
w.write_u32(idx)
}
}
}
w.to_bytes()
}
///|
/// Encode the start section
fn encode_start_section(mod : @types.Module) -> Array[Int] {
match mod.start {
Some(func_idx) => {
let w = BinaryWriter::BinaryWriter()
w.write_u32(func_idx)
w.to_bytes()
}
None => []
}
}
///|
/// Encode the element section
fn encode_element_section(mod : @types.Module) -> Array[Int] {
if mod.elems.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.elems.length())
for elem in mod.elems {
match elem.mode {
Active(table_idx, offset_expr) =>
if table_idx == 0 && elem.type_ is FuncRef {
// Simplified encoding for table 0 with funcref
w.write_byte(0x00)
encode_instructions(w, offset_expr)
w.write_byte(0x0B) // end
// Write function indices directly
w.write_u32(elem.init.length())
for init_expr in elem.init {
// Extract function index from ref.func instruction
match init_expr {
[RefFunc(idx)] => w.write_u32(idx)
_ => w.write_u32(0) // fallback
}
}
} else {
// Full encoding
w.write_byte(0x06) // active with table index, elem type, expr init
w.write_u32(table_idx)
encode_instructions(w, offset_expr)
w.write_byte(0x0B) // end
encode_valtype(w, elem.type_)
w.write_u32(elem.init.length())
for init_expr in elem.init {
encode_instructions(w, init_expr)
w.write_byte(0x0B) // end
}
}
Passive => {
w.write_byte(0x05) // passive with elem type and expr init
encode_valtype(w, elem.type_)
w.write_u32(elem.init.length())
for init_expr in elem.init {
encode_instructions(w, init_expr)
w.write_byte(0x0B) // end
}
}
Declarative => {
w.write_byte(0x07) // declarative with elem type and expr init
encode_valtype(w, elem.type_)
w.write_u32(elem.init.length())
for init_expr in elem.init {
encode_instructions(w, init_expr)
w.write_byte(0x0B) // end
}
}
}
}
w.to_bytes()
}
///|
/// Encode the code section
fn encode_code_section(mod : @types.Module) -> Array[Int] {
if mod.codes.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.codes.length())
for code in mod.codes {
// Encode function body
let body_writer = BinaryWriter::BinaryWriter()
// Compress locals: group consecutive same-type locals
let compressed_locals : Array[(Int, @types.ValueType)] = []
let mut i = 0
while i < code.locals.length() {
let vt = code.locals[i]
let mut count = 1
while i + count < code.locals.length() && code.locals[i + count] == vt {
count += 1
}
compressed_locals.push((count, vt))
i += count
}
// Write locals
body_writer.write_u32(compressed_locals.length())
for loc in compressed_locals {
let (count, vt) = loc
body_writer.write_u32(count)
encode_valtype(body_writer, vt)
}
// Write body
encode_instructions(body_writer, code.body)
body_writer.write_byte(0x0B) // end
// Write function body with size prefix
let body_bytes = body_writer.to_bytes()
w.write_u32(body_bytes.length())
w.write_array(body_bytes)
}
w.to_bytes()
}
///|
/// Encode the data section
fn encode_data_section(mod : @types.Module) -> Array[Int] {
if mod.datas.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.datas.length())
for data in mod.datas {
if data.memory_idx < 0 {
// Passive data segment
w.write_byte(0x01)
} else if data.memory_idx == 0 && !data.offset.is_empty() {
// Active data segment with memory 0
w.write_byte(0x00)
encode_instructions(w, data.offset)
w.write_byte(0x0B) // end
} else {
// Active with explicit memory index
w.write_byte(0x02)
w.write_u32(data.memory_idx)
encode_instructions(w, data.offset)
w.write_byte(0x0B) // end
}
// Write data bytes
w.write_u32(data.init.length())
for i in 0.. Array[Int] {
if mod.datas.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.datas.length())
w.to_bytes()
}
///|
/// Encode the tag section
fn encode_tag_section(mod : @types.Module) -> Array[Int] {
if mod.tags.is_empty() {
return []
}
let w = BinaryWriter::BinaryWriter()
w.write_u32(mod.tags.length())
for tag in mod.tags {
w.write_byte(0x00) // exception kind
w.write_u32(tag.type_idx)
}
w.to_bytes()
}
// ============ Main Encoder ============
///|
/// Encode a Module to WebAssembly binary format
pub fn encode(mod : @types.Module) -> Array[Int] {
let w = BinaryWriter::BinaryWriter()
// Write magic number: \0asm
w.write_byte(0x00)
w.write_byte(0x61)
w.write_byte(0x73)
w.write_byte(0x6D)
// Write version 1
w.write_byte(0x01)
w.write_byte(0x00)
w.write_byte(0x00)
w.write_byte(0x00)
// Type section (id=1)
let type_section = encode_type_section(mod)
if !type_section.is_empty() {
write_section(w, 1, type_section)
}
// Import section (id=2)
let import_section = encode_import_section(mod)
if !import_section.is_empty() {
write_section(w, 2, import_section)
}
// Function section (id=3)
let function_section = encode_function_section(mod)
if !function_section.is_empty() {
write_section(w, 3, function_section)
}
// Table section (id=4)
let table_section = encode_table_section(mod)
if !table_section.is_empty() {
write_section(w, 4, table_section)
}
// Memory section (id=5)
let memory_section = encode_memory_section(mod)
if !memory_section.is_empty() {
write_section(w, 5, memory_section)
}
// Tag section (id=13)
let tag_section = encode_tag_section(mod)
if !tag_section.is_empty() {
write_section(w, 13, tag_section)
}
// Global section (id=6)
let global_section = encode_global_section(mod)
if !global_section.is_empty() {
write_section(w, 6, global_section)
}
// Export section (id=7)
let export_section = encode_export_section(mod)
if !export_section.is_empty() {
write_section(w, 7, export_section)
}
// Start section (id=8)
let start_section = encode_start_section(mod)
if !start_section.is_empty() {
write_section(w, 8, start_section)
}
// Element section (id=9)
let element_section = encode_element_section(mod)
if !element_section.is_empty() {
write_section(w, 9, element_section)
}
// Data count section (id=12)
let datacount_section = encode_datacount_section(mod)
if !datacount_section.is_empty() {
write_section(w, 12, datacount_section)
}
// Code section (id=10)
let code_section = encode_code_section(mod)
if !code_section.is_empty() {
write_section(w, 10, code_section)
}
// Data section (id=11)
let data_section = encode_data_section(mod)
if !data_section.is_empty() {
write_section(w, 11, data_section)
}
w.to_bytes()
}