///|
/// The instruction set, table-driven.
///
/// Every opcode the decoder accepts is listed exactly once with the shape of the
/// immediates that follow it, so adding an instruction is a one-line edit and the
/// decoder itself never grows. Three encodings are folded in here: single-byte
/// opcodes, the `0xFC` saturating/bulk-memory prefix and the `0xFB`
/// garbage-collection prefix.
///
/// The `0xFB` layout is the one MoonBit's `wasm-gc` backend emits: a
/// `struct.get` carries a type index *and* a field index, `array.new_fixed` a
/// length, and `array.copy` two type indices. Misreading any of those widths
/// desynchronises the whole body, which is why every width here is checked
/// against real output in `opcodes_wbtest.mbt`.
///|
/// Every instruction the decoder recognises, one variant per mnemonic.
///
/// The set covers the MVP, the sign-extension, saturating-truncation, bulk-memory
/// and reference-type extensions MoonBit's linear-memory backend emits, and the
/// garbage-collection instructions its `wasm-gc` backend emits.
pub enum Opcode {
Unreachable
Nop
Block
Loop
If
Else
Try
Catch
Throw
Rethrow
ThrowRef
End
Br
BrIf
BrTable
Return
Call
CallIndirect
ReturnCall
ReturnCallIndirect
CallRef
ReturnCallRef
Drop
Select
Delegate
CatchAll
SelectT
LocalGet
LocalSet
LocalTee
GlobalGet
GlobalSet
TableGet
TableSet
I32Load
I64Load
F32Load
F64Load
I32Load8S
I32Load8U
I32Load16S
I32Load16U
I64Load8S
I64Load8U
I64Load16S
I64Load16U
I64Load32S
I64Load32U
I32Store
I64Store
F32Store
F64Store
I32Store8
I32Store16
I64Store8
I64Store16
I64Store32
MemorySize
MemoryGrow
I32Const
I64Const
F32Const
F64Const
I32Eqz
I32Eq
I32Ne
I32LtS
I32LtU
I32GtS
I32GtU
I32LeS
I32LeU
I32GeS
I32GeU
I64Eqz
I64Eq
I64Ne
I64LtS
I64LtU
I64GtS
I64GtU
I64LeS
I64LeU
I64GeS
I64GeU
F32Eq
F32Ne
F32Lt
F32Gt
F32Le
F32Ge
F64Eq
F64Ne
F64Lt
F64Gt
F64Le
F64Ge
I32Clz
I32Ctz
I32Popcnt
I32Add
I32Sub
I32Mul
I32DivS
I32DivU
I32RemS
I32RemU
I32And
I32Or
I32Xor
I32Shl
I32ShrS
I32ShrU
I32Rotl
I32Rotr
I64Clz
I64Ctz
I64Popcnt
I64Add
I64Sub
I64Mul
I64DivS
I64DivU
I64RemS
I64RemU
I64And
I64Or
I64Xor
I64Shl
I64ShrS
I64ShrU
I64Rotl
I64Rotr
F32Abs
F32Neg
F32Ceil
F32Floor
F32Trunc
F32Nearest
F32Sqrt
F32Add
F32Sub
F32Mul
F32Div
F32Min
F32Max
F32Copysign
F64Abs
F64Neg
F64Ceil
F64Floor
F64Trunc
F64Nearest
F64Sqrt
F64Add
F64Sub
F64Mul
F64Div
F64Min
F64Max
F64Copysign
I32WrapI64
I32TruncF32S
I32TruncF32U
I32TruncF64S
I32TruncF64U
I64ExtendI32S
I64ExtendI32U
I64TruncF32S
I64TruncF32U
I64TruncF64S
I64TruncF64U
F32ConvertI32S
F32ConvertI32U
F32ConvertI64S
F32ConvertI64U
F32DemoteF64
F64ConvertI32S
F64ConvertI32U
F64ConvertI64S
F64ConvertI64U
F64PromoteF32
I32ReinterpretF32
I64ReinterpretF64
F32ReinterpretI32
F64ReinterpretI64
I32Extend8S
I32Extend16S
I64Extend8S
I64Extend16S
I64Extend32S
RefNull
RefIsNull
RefFunc
RefEq
RefAsNonNull
BrOnNull
BrOnNonNull
I32TruncSatF32S
I32TruncSatF32U
I32TruncSatF64S
I32TruncSatF64U
I64TruncSatF32S
I64TruncSatF32U
I64TruncSatF64S
I64TruncSatF64U
MemoryInit
DataDrop
MemoryCopy
MemoryFill
TableInit
ElemDrop
TableCopy
TableGrow
TableSize
TableFill
StructNew
StructNewDefault
StructGet
StructGetS
StructGetU
StructSet
ArrayNew
ArrayNewDefault
ArrayNewFixed
ArrayNewData
ArrayNewElem
ArrayGet
ArrayGetS
ArrayGetU
ArraySet
ArrayLen
ArrayFill
ArrayCopy
ArrayInitData
ArrayInitElem
RefTest
RefTestNull
RefCast
RefCastNull
BrOnCast
BrOnCastFail
AnyConvertExtern
ExternConvertAny
RefI31
I31GetS
I31GetU
}
///|
/// The immediate operands each opcode carries.
pub fn Opcode::operands(self : Opcode) -> Operands {
match self {
Unreachable => None_
Nop => None_
Block => BlockType
Loop => BlockType
If => BlockType
Else => None_
Try => BlockType
Catch => TagIndex
Throw => TagIndex
Rethrow => Label
ThrowRef => None_
End => None_
Br => Label
BrIf => Label
BrTable => LabelTable
Return => None_
Call => FunctionIndex
CallIndirect => CallIndirect
ReturnCall => FunctionIndex
ReturnCallIndirect => CallIndirect
CallRef => TypeIndex
ReturnCallRef => TypeIndex
Drop => None_
Select => None_
Delegate => Label
CatchAll => None_
SelectT => TypeVector
LocalGet => LocalIndex
LocalSet => LocalIndex
LocalTee => LocalIndex
GlobalGet => GlobalIndex
GlobalSet => GlobalIndex
TableGet => TableIndex
TableSet => TableIndex
I32Load => MemoryArg
I64Load => MemoryArg
F32Load => MemoryArg
F64Load => MemoryArg
I32Load8S => MemoryArg
I32Load8U => MemoryArg
I32Load16S => MemoryArg
I32Load16U => MemoryArg
I64Load8S => MemoryArg
I64Load8U => MemoryArg
I64Load16S => MemoryArg
I64Load16U => MemoryArg
I64Load32S => MemoryArg
I64Load32U => MemoryArg
I32Store => MemoryArg
I64Store => MemoryArg
F32Store => MemoryArg
F64Store => MemoryArg
I32Store8 => MemoryArg
I32Store16 => MemoryArg
I64Store8 => MemoryArg
I64Store16 => MemoryArg
I64Store32 => MemoryArg
MemorySize => MemoryIndex
MemoryGrow => MemoryIndex
I32Const => I32Const
I64Const => I64Const
F32Const => F32Const
F64Const => F64Const
I32Eqz => None_
I32Eq => None_
I32Ne => None_
I32LtS => None_
I32LtU => None_
I32GtS => None_
I32GtU => None_
I32LeS => None_
I32LeU => None_
I32GeS => None_
I32GeU => None_
I64Eqz => None_
I64Eq => None_
I64Ne => None_
I64LtS => None_
I64LtU => None_
I64GtS => None_
I64GtU => None_
I64LeS => None_
I64LeU => None_
I64GeS => None_
I64GeU => None_
F32Eq => None_
F32Ne => None_
F32Lt => None_
F32Gt => None_
F32Le => None_
F32Ge => None_
F64Eq => None_
F64Ne => None_
F64Lt => None_
F64Gt => None_
F64Le => None_
F64Ge => None_
I32Clz => None_
I32Ctz => None_
I32Popcnt => None_
I32Add => None_
I32Sub => None_
I32Mul => None_
I32DivS => None_
I32DivU => None_
I32RemS => None_
I32RemU => None_
I32And => None_
I32Or => None_
I32Xor => None_
I32Shl => None_
I32ShrS => None_
I32ShrU => None_
I32Rotl => None_
I32Rotr => None_
I64Clz => None_
I64Ctz => None_
I64Popcnt => None_
I64Add => None_
I64Sub => None_
I64Mul => None_
I64DivS => None_
I64DivU => None_
I64RemS => None_
I64RemU => None_
I64And => None_
I64Or => None_
I64Xor => None_
I64Shl => None_
I64ShrS => None_
I64ShrU => None_
I64Rotl => None_
I64Rotr => None_
F32Abs => None_
F32Neg => None_
F32Ceil => None_
F32Floor => None_
F32Trunc => None_
F32Nearest => None_
F32Sqrt => None_
F32Add => None_
F32Sub => None_
F32Mul => None_
F32Div => None_
F32Min => None_
F32Max => None_
F32Copysign => None_
F64Abs => None_
F64Neg => None_
F64Ceil => None_
F64Floor => None_
F64Trunc => None_
F64Nearest => None_
F64Sqrt => None_
F64Add => None_
F64Sub => None_
F64Mul => None_
F64Div => None_
F64Min => None_
F64Max => None_
F64Copysign => None_
I32WrapI64 => None_
I32TruncF32S => None_
I32TruncF32U => None_
I32TruncF64S => None_
I32TruncF64U => None_
I64ExtendI32S => None_
I64ExtendI32U => None_
I64TruncF32S => None_
I64TruncF32U => None_
I64TruncF64S => None_
I64TruncF64U => None_
F32ConvertI32S => None_
F32ConvertI32U => None_
F32ConvertI64S => None_
F32ConvertI64U => None_
F32DemoteF64 => None_
F64ConvertI32S => None_
F64ConvertI32U => None_
F64ConvertI64S => None_
F64ConvertI64U => None_
F64PromoteF32 => None_
I32ReinterpretF32 => None_
I64ReinterpretF64 => None_
F32ReinterpretI32 => None_
F64ReinterpretI64 => None_
I32Extend8S => None_
I32Extend16S => None_
I64Extend8S => None_
I64Extend16S => None_
I64Extend32S => None_
RefNull => HeapType
RefIsNull => None_
RefFunc => FunctionIndex
RefEq => None_
RefAsNonNull => None_
BrOnNull => Label
BrOnNonNull => Label
I32TruncSatF32S => None_
I32TruncSatF32U => None_
I32TruncSatF64S => None_
I32TruncSatF64U => None_
I64TruncSatF32S => None_
I64TruncSatF32U => None_
I64TruncSatF64S => None_
I64TruncSatF64U => None_
MemoryInit => DataMemoryPair
DataDrop => DataIndex
MemoryCopy => MemoryPair
MemoryFill => MemoryIndex
TableInit => ElemTablePair
ElemDrop => ElemIndex
TableCopy => TablePair
TableGrow => TableIndex
TableSize => TableIndex
TableFill => TableIndex
StructNew => TypeIndex
StructNewDefault => TypeIndex
StructGet => TypeFieldPair
StructGetS => TypeFieldPair
StructGetU => TypeFieldPair
StructSet => TypeFieldPair
ArrayNew => TypeIndex
ArrayNewDefault => TypeIndex
ArrayNewFixed => TypeCountPair
ArrayNewData => TypeDataPair
ArrayNewElem => TypeElemPair
ArrayGet => TypeIndex
ArrayGetS => TypeIndex
ArrayGetU => TypeIndex
ArraySet => TypeIndex
ArrayLen => None_
ArrayFill => TypeIndex
ArrayCopy => TypePair
ArrayInitData => TypeDataPair
ArrayInitElem => TypeElemPair
RefTest => RefType
RefTestNull => RefType
RefCast => RefType
RefCastNull => RefType
BrOnCast => CastPair
BrOnCastFail => CastPair
AnyConvertExtern => None_
ExternConvertAny => None_
RefI31 => None_
I31GetS => None_
I31GetU => None_
}
}
///|
/// The spec mnemonic for an opcode, spelled as the text format writes it.
pub fn Opcode::name(self : Opcode) -> String {
match self {
Unreachable => "unreachable"
Nop => "nop"
Block => "block"
Loop => "loop"
If => "if"
Else => "else"
Try => "try"
Catch => "catch"
Throw => "throw"
Rethrow => "rethrow"
ThrowRef => "throw_ref"
End => "end"
Br => "br"
BrIf => "br_if"
BrTable => "br_table"
Return => "return"
Call => "call"
CallIndirect => "call_indirect"
ReturnCall => "return_call"
ReturnCallIndirect => "return_call_indirect"
CallRef => "call_ref"
ReturnCallRef => "return_call_ref"
Drop => "drop"
Select => "select"
Delegate => "delegate"
CatchAll => "catch_all"
SelectT => "select_t"
LocalGet => "local.get"
LocalSet => "local.set"
LocalTee => "local.tee"
GlobalGet => "global.get"
GlobalSet => "global.set"
TableGet => "table.get"
TableSet => "table.set"
I32Load => "i32.load"
I64Load => "i64.load"
F32Load => "f32.load"
F64Load => "f64.load"
I32Load8S => "i32.load8_s"
I32Load8U => "i32.load8_u"
I32Load16S => "i32.load16_s"
I32Load16U => "i32.load16_u"
I64Load8S => "i64.load8_s"
I64Load8U => "i64.load8_u"
I64Load16S => "i64.load16_s"
I64Load16U => "i64.load16_u"
I64Load32S => "i64.load32_s"
I64Load32U => "i64.load32_u"
I32Store => "i32.store"
I64Store => "i64.store"
F32Store => "f32.store"
F64Store => "f64.store"
I32Store8 => "i32.store8"
I32Store16 => "i32.store16"
I64Store8 => "i64.store8"
I64Store16 => "i64.store16"
I64Store32 => "i64.store32"
MemorySize => "memory.size"
MemoryGrow => "memory.grow"
I32Const => "i32.const"
I64Const => "i64.const"
F32Const => "f32.const"
F64Const => "f64.const"
I32Eqz => "i32.eqz"
I32Eq => "i32.eq"
I32Ne => "i32.ne"
I32LtS => "i32.lt_s"
I32LtU => "i32.lt_u"
I32GtS => "i32.gt_s"
I32GtU => "i32.gt_u"
I32LeS => "i32.le_s"
I32LeU => "i32.le_u"
I32GeS => "i32.ge_s"
I32GeU => "i32.ge_u"
I64Eqz => "i64.eqz"
I64Eq => "i64.eq"
I64Ne => "i64.ne"
I64LtS => "i64.lt_s"
I64LtU => "i64.lt_u"
I64GtS => "i64.gt_s"
I64GtU => "i64.gt_u"
I64LeS => "i64.le_s"
I64LeU => "i64.le_u"
I64GeS => "i64.ge_s"
I64GeU => "i64.ge_u"
F32Eq => "f32.eq"
F32Ne => "f32.ne"
F32Lt => "f32.lt"
F32Gt => "f32.gt"
F32Le => "f32.le"
F32Ge => "f32.ge"
F64Eq => "f64.eq"
F64Ne => "f64.ne"
F64Lt => "f64.lt"
F64Gt => "f64.gt"
F64Le => "f64.le"
F64Ge => "f64.ge"
I32Clz => "i32.clz"
I32Ctz => "i32.ctz"
I32Popcnt => "i32.popcnt"
I32Add => "i32.add"
I32Sub => "i32.sub"
I32Mul => "i32.mul"
I32DivS => "i32.div_s"
I32DivU => "i32.div_u"
I32RemS => "i32.rem_s"
I32RemU => "i32.rem_u"
I32And => "i32.and"
I32Or => "i32.or"
I32Xor => "i32.xor"
I32Shl => "i32.shl"
I32ShrS => "i32.shr_s"
I32ShrU => "i32.shr_u"
I32Rotl => "i32.rotl"
I32Rotr => "i32.rotr"
I64Clz => "i64.clz"
I64Ctz => "i64.ctz"
I64Popcnt => "i64.popcnt"
I64Add => "i64.add"
I64Sub => "i64.sub"
I64Mul => "i64.mul"
I64DivS => "i64.div_s"
I64DivU => "i64.div_u"
I64RemS => "i64.rem_s"
I64RemU => "i64.rem_u"
I64And => "i64.and"
I64Or => "i64.or"
I64Xor => "i64.xor"
I64Shl => "i64.shl"
I64ShrS => "i64.shr_s"
I64ShrU => "i64.shr_u"
I64Rotl => "i64.rotl"
I64Rotr => "i64.rotr"
F32Abs => "f32.abs"
F32Neg => "f32.neg"
F32Ceil => "f32.ceil"
F32Floor => "f32.floor"
F32Trunc => "f32.trunc"
F32Nearest => "f32.nearest"
F32Sqrt => "f32.sqrt"
F32Add => "f32.add"
F32Sub => "f32.sub"
F32Mul => "f32.mul"
F32Div => "f32.div"
F32Min => "f32.min"
F32Max => "f32.max"
F32Copysign => "f32.copysign"
F64Abs => "f64.abs"
F64Neg => "f64.neg"
F64Ceil => "f64.ceil"
F64Floor => "f64.floor"
F64Trunc => "f64.trunc"
F64Nearest => "f64.nearest"
F64Sqrt => "f64.sqrt"
F64Add => "f64.add"
F64Sub => "f64.sub"
F64Mul => "f64.mul"
F64Div => "f64.div"
F64Min => "f64.min"
F64Max => "f64.max"
F64Copysign => "f64.copysign"
I32WrapI64 => "i32.wrap_i64"
I32TruncF32S => "i32.trunc_f32_s"
I32TruncF32U => "i32.trunc_f32_u"
I32TruncF64S => "i32.trunc_f64_s"
I32TruncF64U => "i32.trunc_f64_u"
I64ExtendI32S => "i64.extend_i32_s"
I64ExtendI32U => "i64.extend_i32_u"
I64TruncF32S => "i64.trunc_f32_s"
I64TruncF32U => "i64.trunc_f32_u"
I64TruncF64S => "i64.trunc_f64_s"
I64TruncF64U => "i64.trunc_f64_u"
F32ConvertI32S => "f32.convert_i32_s"
F32ConvertI32U => "f32.convert_i32_u"
F32ConvertI64S => "f32.convert_i64_s"
F32ConvertI64U => "f32.convert_i64_u"
F32DemoteF64 => "f32.demote_f64"
F64ConvertI32S => "f64.convert_i32_s"
F64ConvertI32U => "f64.convert_i32_u"
F64ConvertI64S => "f64.convert_i64_s"
F64ConvertI64U => "f64.convert_i64_u"
F64PromoteF32 => "f64.promote_f32"
I32ReinterpretF32 => "i32.reinterpret_f32"
I64ReinterpretF64 => "i64.reinterpret_f64"
F32ReinterpretI32 => "f32.reinterpret_i32"
F64ReinterpretI64 => "f64.reinterpret_i64"
I32Extend8S => "i32.extend8_s"
I32Extend16S => "i32.extend16_s"
I64Extend8S => "i64.extend8_s"
I64Extend16S => "i64.extend16_s"
I64Extend32S => "i64.extend32_s"
RefNull => "ref.null"
RefIsNull => "ref.is_null"
RefFunc => "ref.func"
RefEq => "ref.eq"
RefAsNonNull => "ref.as_non_null"
BrOnNull => "br_on_null"
BrOnNonNull => "br_on_non_null"
I32TruncSatF32S => "i32.trunc_sat_f32_s"
I32TruncSatF32U => "i32.trunc_sat_f32_u"
I32TruncSatF64S => "i32.trunc_sat_f64_s"
I32TruncSatF64U => "i32.trunc_sat_f64_u"
I64TruncSatF32S => "i64.trunc_sat_f32_s"
I64TruncSatF32U => "i64.trunc_sat_f32_u"
I64TruncSatF64S => "i64.trunc_sat_f64_s"
I64TruncSatF64U => "i64.trunc_sat_f64_u"
MemoryInit => "memory.init"
DataDrop => "data.drop"
MemoryCopy => "memory.copy"
MemoryFill => "memory.fill"
TableInit => "table.init"
ElemDrop => "elem.drop"
TableCopy => "table.copy"
TableGrow => "table.grow"
TableSize => "table.size"
TableFill => "table.fill"
StructNew => "struct.new"
StructNewDefault => "struct.new_default"
StructGet => "struct.get"
StructGetS => "struct.get_s"
StructGetU => "struct.get_u"
StructSet => "struct.set"
ArrayNew => "array.new"
ArrayNewDefault => "array.new_default"
ArrayNewFixed => "array.new_fixed"
ArrayNewData => "array.new_data"
ArrayNewElem => "array.new_elem"
ArrayGet => "array.get"
ArrayGetS => "array.get_s"
ArrayGetU => "array.get_u"
ArraySet => "array.set"
ArrayLen => "array.len"
ArrayFill => "array.fill"
ArrayCopy => "array.copy"
ArrayInitData => "array.init_data"
ArrayInitElem => "array.init_elem"
RefTest => "ref.test"
RefTestNull => "ref.test_null"
RefCast => "ref.cast"
RefCastNull => "ref.cast_null"
BrOnCast => "br_on_cast"
BrOnCastFail => "br_on_cast_fail"
AnyConvertExtern => "any.convert_extern"
ExternConvertAny => "extern.convert_any"
RefI31 => "ref.i31"
I31GetS => "i31.get_s"
I31GetU => "i31.get_u"
}
}
///|
/// The opcode a single leading byte stands for, or `None` for a reserved byte.
fn simple_opcode(byte : Int) -> Opcode? {
match byte {
0x00 => Some(Unreachable)
0x01 => Some(Nop)
0x02 => Some(Block)
0x03 => Some(Loop)
0x04 => Some(If)
0x05 => Some(Else)
0x06 => Some(Try)
0x07 => Some(Catch)
0x08 => Some(Throw)
0x09 => Some(Rethrow)
0x0A => Some(ThrowRef)
0x0B => Some(End)
0x0C => Some(Br)
0x0D => Some(BrIf)
0x0E => Some(BrTable)
0x0F => Some(Return)
0x10 => Some(Call)
0x11 => Some(CallIndirect)
0x12 => Some(ReturnCall)
0x13 => Some(ReturnCallIndirect)
0x14 => Some(CallRef)
0x15 => Some(ReturnCallRef)
0x1A => Some(Drop)
0x1B => Some(Select)
0x18 => Some(Delegate)
0x19 => Some(CatchAll)
0x1C => Some(SelectT)
0x20 => Some(LocalGet)
0x21 => Some(LocalSet)
0x22 => Some(LocalTee)
0x23 => Some(GlobalGet)
0x24 => Some(GlobalSet)
0x25 => Some(TableGet)
0x26 => Some(TableSet)
0x28 => Some(I32Load)
0x29 => Some(I64Load)
0x2A => Some(F32Load)
0x2B => Some(F64Load)
0x2C => Some(I32Load8S)
0x2D => Some(I32Load8U)
0x2E => Some(I32Load16S)
0x2F => Some(I32Load16U)
0x30 => Some(I64Load8S)
0x31 => Some(I64Load8U)
0x32 => Some(I64Load16S)
0x33 => Some(I64Load16U)
0x34 => Some(I64Load32S)
0x35 => Some(I64Load32U)
0x36 => Some(I32Store)
0x37 => Some(I64Store)
0x38 => Some(F32Store)
0x39 => Some(F64Store)
0x3A => Some(I32Store8)
0x3B => Some(I32Store16)
0x3C => Some(I64Store8)
0x3D => Some(I64Store16)
0x3E => Some(I64Store32)
0x3F => Some(MemorySize)
0x40 => Some(MemoryGrow)
0x41 => Some(I32Const)
0x42 => Some(I64Const)
0x43 => Some(F32Const)
0x44 => Some(F64Const)
0x45 => Some(I32Eqz)
0x46 => Some(I32Eq)
0x47 => Some(I32Ne)
0x48 => Some(I32LtS)
0x49 => Some(I32LtU)
0x4A => Some(I32GtS)
0x4B => Some(I32GtU)
0x4C => Some(I32LeS)
0x4D => Some(I32LeU)
0x4E => Some(I32GeS)
0x4F => Some(I32GeU)
0x50 => Some(I64Eqz)
0x51 => Some(I64Eq)
0x52 => Some(I64Ne)
0x53 => Some(I64LtS)
0x54 => Some(I64LtU)
0x55 => Some(I64GtS)
0x56 => Some(I64GtU)
0x57 => Some(I64LeS)
0x58 => Some(I64LeU)
0x59 => Some(I64GeS)
0x5A => Some(I64GeU)
0x5B => Some(F32Eq)
0x5C => Some(F32Ne)
0x5D => Some(F32Lt)
0x5E => Some(F32Gt)
0x5F => Some(F32Le)
0x60 => Some(F32Ge)
0x61 => Some(F64Eq)
0x62 => Some(F64Ne)
0x63 => Some(F64Lt)
0x64 => Some(F64Gt)
0x65 => Some(F64Le)
0x66 => Some(F64Ge)
0x67 => Some(I32Clz)
0x68 => Some(I32Ctz)
0x69 => Some(I32Popcnt)
0x6A => Some(I32Add)
0x6B => Some(I32Sub)
0x6C => Some(I32Mul)
0x6D => Some(I32DivS)
0x6E => Some(I32DivU)
0x6F => Some(I32RemS)
0x70 => Some(I32RemU)
0x71 => Some(I32And)
0x72 => Some(I32Or)
0x73 => Some(I32Xor)
0x74 => Some(I32Shl)
0x75 => Some(I32ShrS)
0x76 => Some(I32ShrU)
0x77 => Some(I32Rotl)
0x78 => Some(I32Rotr)
0x79 => Some(I64Clz)
0x7A => Some(I64Ctz)
0x7B => Some(I64Popcnt)
0x7C => Some(I64Add)
0x7D => Some(I64Sub)
0x7E => Some(I64Mul)
0x7F => Some(I64DivS)
0x80 => Some(I64DivU)
0x81 => Some(I64RemS)
0x82 => Some(I64RemU)
0x83 => Some(I64And)
0x84 => Some(I64Or)
0x85 => Some(I64Xor)
0x86 => Some(I64Shl)
0x87 => Some(I64ShrS)
0x88 => Some(I64ShrU)
0x89 => Some(I64Rotl)
0x8A => Some(I64Rotr)
0x8B => Some(F32Abs)
0x8C => Some(F32Neg)
0x8D => Some(F32Ceil)
0x8E => Some(F32Floor)
0x8F => Some(F32Trunc)
0x90 => Some(F32Nearest)
0x91 => Some(F32Sqrt)
0x92 => Some(F32Add)
0x93 => Some(F32Sub)
0x94 => Some(F32Mul)
0x95 => Some(F32Div)
0x96 => Some(F32Min)
0x97 => Some(F32Max)
0x98 => Some(F32Copysign)
0x99 => Some(F64Abs)
0x9A => Some(F64Neg)
0x9B => Some(F64Ceil)
0x9C => Some(F64Floor)
0x9D => Some(F64Trunc)
0x9E => Some(F64Nearest)
0x9F => Some(F64Sqrt)
0xA0 => Some(F64Add)
0xA1 => Some(F64Sub)
0xA2 => Some(F64Mul)
0xA3 => Some(F64Div)
0xA4 => Some(F64Min)
0xA5 => Some(F64Max)
0xA6 => Some(F64Copysign)
0xA7 => Some(I32WrapI64)
0xA8 => Some(I32TruncF32S)
0xA9 => Some(I32TruncF32U)
0xAA => Some(I32TruncF64S)
0xAB => Some(I32TruncF64U)
0xAC => Some(I64ExtendI32S)
0xAD => Some(I64ExtendI32U)
0xAE => Some(I64TruncF32S)
0xAF => Some(I64TruncF32U)
0xB0 => Some(I64TruncF64S)
0xB1 => Some(I64TruncF64U)
0xB2 => Some(F32ConvertI32S)
0xB3 => Some(F32ConvertI32U)
0xB4 => Some(F32ConvertI64S)
0xB5 => Some(F32ConvertI64U)
0xB6 => Some(F32DemoteF64)
0xB7 => Some(F64ConvertI32S)
0xB8 => Some(F64ConvertI32U)
0xB9 => Some(F64ConvertI64S)
0xBA => Some(F64ConvertI64U)
0xBB => Some(F64PromoteF32)
0xBC => Some(I32ReinterpretF32)
0xBD => Some(I64ReinterpretF64)
0xBE => Some(F32ReinterpretI32)
0xBF => Some(F64ReinterpretI64)
0xC0 => Some(I32Extend8S)
0xC1 => Some(I32Extend16S)
0xC2 => Some(I64Extend8S)
0xC3 => Some(I64Extend16S)
0xC4 => Some(I64Extend32S)
0xD0 => Some(RefNull)
0xD1 => Some(RefIsNull)
0xD2 => Some(RefFunc)
0xD3 => Some(RefEq)
0xD4 => Some(RefAsNonNull)
0xD5 => Some(BrOnNull)
0xD6 => Some(BrOnNonNull)
_ => None
}
}
///|
/// The `0xFC`-prefixed opcode a sub-opcode stands for, or `None` when unassigned.
fn misc_opcode(sub : Int) -> Opcode? {
match sub {
0x00 => Some(I32TruncSatF32S)
0x01 => Some(I32TruncSatF32U)
0x02 => Some(I32TruncSatF64S)
0x03 => Some(I32TruncSatF64U)
0x04 => Some(I64TruncSatF32S)
0x05 => Some(I64TruncSatF32U)
0x06 => Some(I64TruncSatF64S)
0x07 => Some(I64TruncSatF64U)
0x08 => Some(MemoryInit)
0x09 => Some(DataDrop)
0x0A => Some(MemoryCopy)
0x0B => Some(MemoryFill)
0x0C => Some(TableInit)
0x0D => Some(ElemDrop)
0x0E => Some(TableCopy)
0x0F => Some(TableGrow)
0x10 => Some(TableSize)
0x11 => Some(TableFill)
_ => None
}
}
///|
/// The `0xFB`-prefixed opcode a sub-opcode stands for, or `None` when unassigned.
fn gc_opcode(sub : Int) -> Opcode? {
match sub {
0x00 => Some(StructNew)
0x01 => Some(StructNewDefault)
0x02 => Some(StructGet)
0x03 => Some(StructGetS)
0x04 => Some(StructGetU)
0x05 => Some(StructSet)
0x06 => Some(ArrayNew)
0x07 => Some(ArrayNewDefault)
0x08 => Some(ArrayNewFixed)
0x09 => Some(ArrayNewData)
0x0A => Some(ArrayNewElem)
0x0B => Some(ArrayGet)
0x0C => Some(ArrayGetS)
0x0D => Some(ArrayGetU)
0x0E => Some(ArraySet)
0x0F => Some(ArrayLen)
0x10 => Some(ArrayFill)
0x11 => Some(ArrayCopy)
0x12 => Some(ArrayInitData)
0x13 => Some(ArrayInitElem)
0x14 => Some(RefTest)
0x15 => Some(RefTestNull)
0x16 => Some(RefCast)
0x17 => Some(RefCastNull)
0x18 => Some(BrOnCast)
0x19 => Some(BrOnCastFail)
0x1A => Some(AnyConvertExtern)
0x1B => Some(ExternConvertAny)
0x1C => Some(RefI31)
0x1D => Some(I31GetS)
0x1E => Some(I31GetU)
_ => None
}
}
///|
/// How the bytes after an opcode are laid out.
///
/// The shapes named `Pair` read `a` then `b`, in encoding order. Naming
/// them here rather than inlining the reads keeps every immediate width in one
/// table that can be reviewed — and tested — against the specification.
pub enum Operands {
/// The instruction is exactly one byte.
None_
/// A block type: one byte for a value type or `0x40`, otherwise an s33 index.
BlockType
/// A single label index.
Label
/// `vec(labelidx)` and then the default label index.
LabelTable
/// A function index: `call`, `ref.func`.
FunctionIndex
/// `call_indirect`: a type index and a table index.
CallIndirect
/// A local index.
LocalIndex
/// A global index.
GlobalIndex
/// A table index.
TableIndex
/// An exception tag index: `try`, `catch`, `throw`.
TagIndex
/// `memory.size`, `memory.grow` and the bulk-memory fills: a single byte.
MemoryIndex
/// An alignment hint and an offset, plus a memory index when bit 6 is set.
MemoryArg
/// A 32-bit signed constant.
I32Const
/// A 64-bit signed constant, kept as its bit pattern.
I64Const
/// Four raw bytes.
F32Const
/// Eight raw bytes.
F64Const
/// A heap type: one byte, or an s33 type index.
HeapType
/// A reference type: a shorthand byte, or `0x63`/`0x64` and a heap type.
RefType
/// A type index.
TypeIndex
/// `struct.get` and friends: a type index and a field index.
TypeFieldPair
/// `array.copy`: two type indices.
TypePair
/// `array.new_fixed`: a type index and an element count.
TypeCountPair
/// `array.new_data` and `array.init_data`: a type index and a data index.
TypeDataPair
/// `array.new_elem` and `array.init_elem`: a type index and an element index.
TypeElemPair
/// A data index.
DataIndex
/// An element index.
ElemIndex
/// `memory.init`: a data index and a memory index.
DataMemoryPair
/// `memory.copy`: two memory indices.
MemoryPair
/// `table.init`: an element index and a table index.
ElemTablePair
/// `table.copy`: two table indices.
TablePair
/// `br_on_cast`: a label index and two reference types.
CastPair
/// `select` with an explicit type: `vec(valtype)`.
TypeVector
}
///|
/// A decoded instruction: what it is, where it starts and what it carries.
pub struct Instruction {
opcode : Opcode
/// Offset of the opcode byte from the start of the file.
offset : Int
/// Total encoded length, opcode byte and immediates included.
size : Int
/// Immediates in encoding order. Indices are exact; 64-bit and floating point
/// constants are kept as their bit pattern; `br_table` contributes its element
/// count first, so the target list can be split back off.
operands : Array[Int]
}
///|
/// The byte that introduces a garbage-collection instruction.
const PREFIX_GC : Int = 0xFB
///|
/// The byte that introduces a saturating-truncation or bulk-memory instruction.
const PREFIX_MISC : Int = 0xFC
///|
/// The two reference-type constructors that are followed by a heap type.
const REF_NULL_TYPE : Int = 0x63
///|
const REF_TYPE : Int = 0x64
///|
/// Multi-memory marks a memory index inside a memory argument with this bit of
/// the alignment field. Natural alignments never reach it, so a module without
/// multi-memory decodes identically either way.
const MEMARG_HAS_MEMORY : Int = 0x40
///|
/// Read an unsigned index immediate and record it.
fn take_index(reader : Reader, out : Array[Int]) -> Result[Unit, WasmError] {
match reader.read_u32_leb() {
Ok(value) => {
out.push(value)
Ok(())
}
Err(error) => Err(error)
}
}
///|
/// Read the bytes of a constant immediate and record them as one bit pattern.
fn take_bits(
reader : Reader,
out : Array[Int],
count : Int,
) -> Result[Unit, WasmError] {
match reader.read_bits(count) {
Ok(value) => {
out.push(value)
Ok(())
}
Err(error) => Err(error)
}
}
///|
/// Read a reference type into `out`.
fn take_reftype(reader : Reader, out : Array[Int]) -> Result[Unit, WasmError] {
let first = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
out.push(first)
if first == REF_NULL_TYPE || first == REF_TYPE {
match reader.read_signed_leb(33) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
}
Ok(())
}
///|
/// Read the immediates an opcode carries, in encoding order.
fn read_operands(
reader : Reader,
shape : Operands,
out : Array[Int],
) -> Result[Unit, WasmError] {
match shape {
None_ => ()
BlockType => {
// A block type is a value type, or an s33 type index. Value types are
// usually one byte, but a GC reference type is two, so the first byte has
// to be looked at before the s33 reading is committed to.
let at = reader.position()
let first = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
if first == REF_NULL_TYPE || first == REF_TYPE {
out.push(first)
match reader.read_signed_leb(33) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
} else {
reader.seek(at)
match reader.read_signed_leb(33) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
}
}
Label => return take_index(reader, out)
LabelTable => {
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
out.push(count)
let mut i = 0
while i <= count {
match take_index(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
i = i + 1
}
}
FunctionIndex
| LocalIndex
| GlobalIndex
| TableIndex
| TagIndex
| TypeIndex
| DataIndex
| ElemIndex => return take_index(reader, out)
CallIndirect => {
match take_index(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
return take_index(reader, out)
}
MemoryIndex =>
// The spec writes these as a literal zero byte, not a LEB128 field.
match reader.read_byte() {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
MemoryArg => {
let align = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
out.push(align)
match take_index(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
if (align & MEMARG_HAS_MEMORY) != 0 {
match reader.read_byte() {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
}
}
I32Const =>
match reader.read_signed_leb(32) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
I64Const =>
match reader.read_signed_leb(64) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
F32Const => return take_bits(reader, out, 4)
F64Const => return take_bits(reader, out, 8)
HeapType =>
match reader.read_signed_leb(33) {
Ok(value) => out.push(value)
Err(error) => return Err(error)
}
RefType => return take_reftype(reader, out)
TypeFieldPair
| TypePair
| TypeCountPair
| TypeDataPair
| TypeElemPair
| DataMemoryPair
| MemoryPair
| ElemTablePair
| TablePair => {
// Two immediates of the shapes their names spell out.
match take_index(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
return take_index(reader, out)
}
CastPair => {
match take_index(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
let mut i = 0
while i < 2 {
match take_reftype(reader, out) {
Ok(_) => ()
Err(error) => return Err(error)
}
i = i + 1
}
}
TypeVector => {
// Each element is a value type, and a GC reference type among them is
// two bytes wide, not one.
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
out.push(count)
let mut i = 0
while i < count {
match reader.read_valtype() {
Ok(_) => out.push(0)
Err(error) => return Err(error)
}
i = i + 1
}
}
}
Ok(())
}
///|
/// Decode the instruction that starts at `offset`.
///
/// An unassigned opcode is an error rather than a skip: silently stepping over
/// one byte would turn a decoding bug into a wrong call graph, and every number
/// downstream of this function is a size.
pub fn decode_instruction(
bytes : Bytes,
offset : Int,
) -> Result[Instruction, WasmError] {
let reader = Reader::new(bytes)
reader.seek(offset)
let first = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
let opcode = match first {
PREFIX_GC => {
let sub = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
match gc_opcode(sub) {
Some(opcode) => opcode
None =>
return Err(WasmError::UnknownPrefixedOpcode(offset, PREFIX_GC, sub))
}
}
PREFIX_MISC => {
let sub = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
match misc_opcode(sub) {
Some(opcode) => opcode
None =>
return Err(WasmError::UnknownPrefixedOpcode(offset, PREFIX_MISC, sub))
}
}
_ =>
match simple_opcode(first) {
Some(opcode) => opcode
None => return Err(WasmError::UnknownOpcode(offset, first))
}
}
let operands : Array[Int] = []
match read_operands(reader, opcode.operands(), operands) {
Ok(_) => ()
Err(error) => return Err(error)
}
Ok({ opcode, offset, size: reader.position() - offset, operands, })
}
///|
/// Consume a reference type.
///
/// A reference type is either a one-byte shorthand such as `funcref` (`0x70`),
/// or one of the two constructors `0x63`/`0x64` followed by a heap type. The
/// shorthand byte is returned, or `0` when the two-byte form was used.
pub fn Reader::read_reftype(self : Reader) -> Result[Int, WasmError] {
let first = match self.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
if first == REF_NULL_TYPE || first == REF_TYPE {
match self.read_signed_leb(33) {
Ok(_) => ()
Err(error) => return Err(error)
}
Ok(0)
} else {
Ok(first)
}
}
///|
/// Consume a value type, whose width is not always one byte.
///
/// Numeric and vector types are a single byte, but a garbage-collection
/// reference type is the two-byte `0x63`/`0x64` form followed by a heap type.
/// Reading one byte per type desynchronises the stream on the first struct-typed
/// local, so every value type goes through here.
pub fn Reader::read_valtype(self : Reader) -> Result[Unit, WasmError] {
let at = self.position()
let first = match self.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
if first == REF_NULL_TYPE || first == REF_TYPE {
match self.read_signed_leb(33) {
Ok(_) => Ok(())
Err(error) => Err(error)
}
} else {
self.seek(at)
match self.read_signed_leb(33) {
Ok(_) => Ok(())
Err(error) => Err(error)
}
}
}