diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift new file mode 100644 index 00000000..284c19b0 --- /dev/null +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -0,0 +1,562 @@ +// swift-format-ignore-file +//// Automatically generated by Utilities/Sources/WasmGen.swift +//// DO NOT EDIT DIRECTLY + +import WasmTypes + +protocol BinaryInstructionDecoder { + /// Claim the next byte to be decoded + func claimNextByte() throws -> UInt8 + /// Visit unknown instruction + func visitUnknown(_ opcode: [UInt8]) throws + /// Decode `block` immediates + mutating func visitBlock() throws -> BlockType + /// Decode `loop` immediates + mutating func visitLoop() throws -> BlockType + /// Decode `if` immediates + mutating func visitIf() throws -> BlockType + /// Decode `br` immediates + mutating func visitBr() throws -> UInt32 + /// Decode `br_if` immediates + mutating func visitBrIf() throws -> UInt32 + /// Decode `br_table` immediates + mutating func visitBrTable() throws -> BrTable + /// Decode `call` immediates + mutating func visitCall() throws -> UInt32 + /// Decode `call_indirect` immediates + mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) + /// Decode `typedSelect` immediates + mutating func visitTypedSelect() throws -> ValueType + /// Decode `local.get` immediates + mutating func visitLocalGet() throws -> UInt32 + /// Decode `local.set` immediates + mutating func visitLocalSet() throws -> UInt32 + /// Decode `local.tee` immediates + mutating func visitLocalTee() throws -> UInt32 + /// Decode `global.get` immediates + mutating func visitGlobalGet() throws -> UInt32 + /// Decode `global.set` immediates + mutating func visitGlobalSet() throws -> UInt32 + /// Decode `load` category immediates + mutating func visitLoad(_: Instruction.Load) throws -> MemArg + /// Decode `store` category immediates + mutating func visitStore(_: Instruction.Store) throws -> MemArg + /// Decode `memory.size` immediates + mutating func visitMemorySize() throws -> UInt32 + /// Decode `memory.grow` immediates + mutating func visitMemoryGrow() throws -> UInt32 + /// Decode `i32.const` immediates + mutating func visitI32Const() throws -> Int32 + /// Decode `i64.const` immediates + mutating func visitI64Const() throws -> Int64 + /// Decode `f32.const` immediates + mutating func visitF32Const() throws -> IEEE754.Float32 + /// Decode `f64.const` immediates + mutating func visitF64Const() throws -> IEEE754.Float64 + /// Decode `ref.null` immediates + mutating func visitRefNull() throws -> ReferenceType + /// Decode `ref.func` immediates + mutating func visitRefFunc() throws -> UInt32 + /// Decode `memory.init` immediates + mutating func visitMemoryInit() throws -> UInt32 + /// Decode `data.drop` immediates + mutating func visitDataDrop() throws -> UInt32 + /// Decode `memory.copy` immediates + mutating func visitMemoryCopy() throws -> (dstMem: UInt32, srcMem: UInt32) + /// Decode `memory.fill` immediates + mutating func visitMemoryFill() throws -> UInt32 + /// Decode `table.init` immediates + mutating func visitTableInit() throws -> (elemIndex: UInt32, table: UInt32) + /// Decode `elem.drop` immediates + mutating func visitElemDrop() throws -> UInt32 + /// Decode `table.copy` immediates + mutating func visitTableCopy() throws -> (dstTable: UInt32, srcTable: UInt32) + /// Decode `table.fill` immediates + mutating func visitTableFill() throws -> UInt32 + /// Decode `table.get` immediates + mutating func visitTableGet() throws -> UInt32 + /// Decode `table.set` immediates + mutating func visitTableSet() throws -> UInt32 + /// Decode `table.grow` immediates + mutating func visitTableGrow() throws -> UInt32 + /// Decode `table.size` immediates + mutating func visitTableSize() throws -> UInt32 +} +extension BinaryInstructionDecoder { + @usableFromInline + mutating func parseBinaryInstruction(visitor: inout V) throws -> Bool { + let opcode0 = try claimNextByte() + switch opcode0 { + case 0x00: + try visitor.visitUnreachable() + case 0x01: + try visitor.visitNop() + case 0x02: + let (blockType) = try visitBlock() + try visitor.visitBlock(blockType: blockType) + case 0x03: + let (blockType) = try visitLoop() + try visitor.visitLoop(blockType: blockType) + case 0x04: + let (blockType) = try visitIf() + try visitor.visitIf(blockType: blockType) + case 0x05: + try visitor.visitElse() + case 0x0B: + try visitor.visitEnd() + return true + case 0x0C: + let (relativeDepth) = try visitBr() + try visitor.visitBr(relativeDepth: relativeDepth) + case 0x0D: + let (relativeDepth) = try visitBrIf() + try visitor.visitBrIf(relativeDepth: relativeDepth) + case 0x0E: + let (targets) = try visitBrTable() + try visitor.visitBrTable(targets: targets) + case 0x0F: + try visitor.visitReturn() + case 0x10: + let (functionIndex) = try visitCall() + try visitor.visitCall(functionIndex: functionIndex) + case 0x11: + let (typeIndex, tableIndex) = try visitCallIndirect() + try visitor.visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) + case 0x1A: + try visitor.visitDrop() + case 0x1B: + try visitor.visitSelect() + case 0x1C: + let (type) = try visitTypedSelect() + try visitor.visitTypedSelect(type: type) + case 0x20: + let (localIndex) = try visitLocalGet() + try visitor.visitLocalGet(localIndex: localIndex) + case 0x21: + let (localIndex) = try visitLocalSet() + try visitor.visitLocalSet(localIndex: localIndex) + case 0x22: + let (localIndex) = try visitLocalTee() + try visitor.visitLocalTee(localIndex: localIndex) + case 0x23: + let (globalIndex) = try visitGlobalGet() + try visitor.visitGlobalGet(globalIndex: globalIndex) + case 0x24: + let (globalIndex) = try visitGlobalSet() + try visitor.visitGlobalSet(globalIndex: globalIndex) + case 0x25: + let (table) = try visitTableGet() + try visitor.visitTableGet(table: table) + case 0x26: + let (table) = try visitTableSet() + try visitor.visitTableSet(table: table) + case 0x28: + let (memarg) = try visitLoad(.i32Load) + try visitor.visitLoad(.i32Load, memarg: memarg) + case 0x29: + let (memarg) = try visitLoad(.i64Load) + try visitor.visitLoad(.i64Load, memarg: memarg) + case 0x2A: + let (memarg) = try visitLoad(.f32Load) + try visitor.visitLoad(.f32Load, memarg: memarg) + case 0x2B: + let (memarg) = try visitLoad(.f64Load) + try visitor.visitLoad(.f64Load, memarg: memarg) + case 0x2C: + let (memarg) = try visitLoad(.i32Load8S) + try visitor.visitLoad(.i32Load8S, memarg: memarg) + case 0x2D: + let (memarg) = try visitLoad(.i32Load8U) + try visitor.visitLoad(.i32Load8U, memarg: memarg) + case 0x2E: + let (memarg) = try visitLoad(.i32Load16S) + try visitor.visitLoad(.i32Load16S, memarg: memarg) + case 0x2F: + let (memarg) = try visitLoad(.i32Load16U) + try visitor.visitLoad(.i32Load16U, memarg: memarg) + case 0x30: + let (memarg) = try visitLoad(.i64Load8S) + try visitor.visitLoad(.i64Load8S, memarg: memarg) + case 0x31: + let (memarg) = try visitLoad(.i64Load8U) + try visitor.visitLoad(.i64Load8U, memarg: memarg) + case 0x32: + let (memarg) = try visitLoad(.i64Load16S) + try visitor.visitLoad(.i64Load16S, memarg: memarg) + case 0x33: + let (memarg) = try visitLoad(.i64Load16U) + try visitor.visitLoad(.i64Load16U, memarg: memarg) + case 0x34: + let (memarg) = try visitLoad(.i64Load32S) + try visitor.visitLoad(.i64Load32S, memarg: memarg) + case 0x35: + let (memarg) = try visitLoad(.i64Load32U) + try visitor.visitLoad(.i64Load32U, memarg: memarg) + case 0x36: + let (memarg) = try visitStore(.i32Store) + try visitor.visitStore(.i32Store, memarg: memarg) + case 0x37: + let (memarg) = try visitStore(.i64Store) + try visitor.visitStore(.i64Store, memarg: memarg) + case 0x38: + let (memarg) = try visitStore(.f32Store) + try visitor.visitStore(.f32Store, memarg: memarg) + case 0x39: + let (memarg) = try visitStore(.f64Store) + try visitor.visitStore(.f64Store, memarg: memarg) + case 0x3A: + let (memarg) = try visitStore(.i32Store8) + try visitor.visitStore(.i32Store8, memarg: memarg) + case 0x3B: + let (memarg) = try visitStore(.i32Store16) + try visitor.visitStore(.i32Store16, memarg: memarg) + case 0x3C: + let (memarg) = try visitStore(.i64Store8) + try visitor.visitStore(.i64Store8, memarg: memarg) + case 0x3D: + let (memarg) = try visitStore(.i64Store16) + try visitor.visitStore(.i64Store16, memarg: memarg) + case 0x3E: + let (memarg) = try visitStore(.i64Store32) + try visitor.visitStore(.i64Store32, memarg: memarg) + case 0x3F: + let (memory) = try visitMemorySize() + try visitor.visitMemorySize(memory: memory) + case 0x40: + let (memory) = try visitMemoryGrow() + try visitor.visitMemoryGrow(memory: memory) + case 0x41: + let (value) = try visitI32Const() + try visitor.visitI32Const(value: value) + case 0x42: + let (value) = try visitI64Const() + try visitor.visitI64Const(value: value) + case 0x43: + let (value) = try visitF32Const() + try visitor.visitF32Const(value: value) + case 0x44: + let (value) = try visitF64Const() + try visitor.visitF64Const(value: value) + case 0x45: + try visitor.visitI32Eqz() + case 0x46: + try visitor.visitCmp(.i32Eq) + case 0x47: + try visitor.visitCmp(.i32Ne) + case 0x48: + try visitor.visitCmp(.i32LtS) + case 0x49: + try visitor.visitCmp(.i32LtU) + case 0x4A: + try visitor.visitCmp(.i32GtS) + case 0x4B: + try visitor.visitCmp(.i32GtU) + case 0x4C: + try visitor.visitCmp(.i32LeS) + case 0x4D: + try visitor.visitCmp(.i32LeU) + case 0x4E: + try visitor.visitCmp(.i32GeS) + case 0x4F: + try visitor.visitCmp(.i32GeU) + case 0x50: + try visitor.visitI64Eqz() + case 0x51: + try visitor.visitCmp(.i64Eq) + case 0x52: + try visitor.visitCmp(.i64Ne) + case 0x53: + try visitor.visitCmp(.i64LtS) + case 0x54: + try visitor.visitCmp(.i64LtU) + case 0x55: + try visitor.visitCmp(.i64GtS) + case 0x56: + try visitor.visitCmp(.i64GtU) + case 0x57: + try visitor.visitCmp(.i64LeS) + case 0x58: + try visitor.visitCmp(.i64LeU) + case 0x59: + try visitor.visitCmp(.i64GeS) + case 0x5A: + try visitor.visitCmp(.i64GeU) + case 0x5B: + try visitor.visitCmp(.f32Eq) + case 0x5C: + try visitor.visitCmp(.f32Ne) + case 0x5D: + try visitor.visitCmp(.f32Lt) + case 0x5E: + try visitor.visitCmp(.f32Gt) + case 0x5F: + try visitor.visitCmp(.f32Le) + case 0x60: + try visitor.visitCmp(.f32Ge) + case 0x61: + try visitor.visitCmp(.f64Eq) + case 0x62: + try visitor.visitCmp(.f64Ne) + case 0x63: + try visitor.visitCmp(.f64Lt) + case 0x64: + try visitor.visitCmp(.f64Gt) + case 0x65: + try visitor.visitCmp(.f64Le) + case 0x66: + try visitor.visitCmp(.f64Ge) + case 0x67: + try visitor.visitUnary(.i32Clz) + case 0x68: + try visitor.visitUnary(.i32Ctz) + case 0x69: + try visitor.visitUnary(.i32Popcnt) + case 0x6A: + try visitor.visitBinary(.i32Add) + case 0x6B: + try visitor.visitBinary(.i32Sub) + case 0x6C: + try visitor.visitBinary(.i32Mul) + case 0x6D: + try visitor.visitBinary(.i32DivS) + case 0x6E: + try visitor.visitBinary(.i32DivU) + case 0x6F: + try visitor.visitBinary(.i32RemS) + case 0x70: + try visitor.visitBinary(.i32RemU) + case 0x71: + try visitor.visitBinary(.i32And) + case 0x72: + try visitor.visitBinary(.i32Or) + case 0x73: + try visitor.visitBinary(.i32Xor) + case 0x74: + try visitor.visitBinary(.i32Shl) + case 0x75: + try visitor.visitBinary(.i32ShrS) + case 0x76: + try visitor.visitBinary(.i32ShrU) + case 0x77: + try visitor.visitBinary(.i32Rotl) + case 0x78: + try visitor.visitBinary(.i32Rotr) + case 0x79: + try visitor.visitUnary(.i64Clz) + case 0x7A: + try visitor.visitUnary(.i64Ctz) + case 0x7B: + try visitor.visitUnary(.i64Popcnt) + case 0x7C: + try visitor.visitBinary(.i64Add) + case 0x7D: + try visitor.visitBinary(.i64Sub) + case 0x7E: + try visitor.visitBinary(.i64Mul) + case 0x7F: + try visitor.visitBinary(.i64DivS) + case 0x80: + try visitor.visitBinary(.i64DivU) + case 0x81: + try visitor.visitBinary(.i64RemS) + case 0x82: + try visitor.visitBinary(.i64RemU) + case 0x83: + try visitor.visitBinary(.i64And) + case 0x84: + try visitor.visitBinary(.i64Or) + case 0x85: + try visitor.visitBinary(.i64Xor) + case 0x86: + try visitor.visitBinary(.i64Shl) + case 0x87: + try visitor.visitBinary(.i64ShrS) + case 0x88: + try visitor.visitBinary(.i64ShrU) + case 0x89: + try visitor.visitBinary(.i64Rotl) + case 0x8A: + try visitor.visitBinary(.i64Rotr) + case 0x8B: + try visitor.visitUnary(.f32Abs) + case 0x8C: + try visitor.visitUnary(.f32Neg) + case 0x8D: + try visitor.visitUnary(.f32Ceil) + case 0x8E: + try visitor.visitUnary(.f32Floor) + case 0x8F: + try visitor.visitUnary(.f32Trunc) + case 0x90: + try visitor.visitUnary(.f32Nearest) + case 0x91: + try visitor.visitUnary(.f32Sqrt) + case 0x92: + try visitor.visitBinary(.f32Add) + case 0x93: + try visitor.visitBinary(.f32Sub) + case 0x94: + try visitor.visitBinary(.f32Mul) + case 0x95: + try visitor.visitBinary(.f32Div) + case 0x96: + try visitor.visitBinary(.f32Min) + case 0x97: + try visitor.visitBinary(.f32Max) + case 0x98: + try visitor.visitBinary(.f32Copysign) + case 0x99: + try visitor.visitUnary(.f64Abs) + case 0x9A: + try visitor.visitUnary(.f64Neg) + case 0x9B: + try visitor.visitUnary(.f64Ceil) + case 0x9C: + try visitor.visitUnary(.f64Floor) + case 0x9D: + try visitor.visitUnary(.f64Trunc) + case 0x9E: + try visitor.visitUnary(.f64Nearest) + case 0x9F: + try visitor.visitUnary(.f64Sqrt) + case 0xA0: + try visitor.visitBinary(.f64Add) + case 0xA1: + try visitor.visitBinary(.f64Sub) + case 0xA2: + try visitor.visitBinary(.f64Mul) + case 0xA3: + try visitor.visitBinary(.f64Div) + case 0xA4: + try visitor.visitBinary(.f64Min) + case 0xA5: + try visitor.visitBinary(.f64Max) + case 0xA6: + try visitor.visitBinary(.f64Copysign) + case 0xA7: + try visitor.visitConversion(.i32WrapI64) + case 0xA8: + try visitor.visitConversion(.i32TruncF32S) + case 0xA9: + try visitor.visitConversion(.i32TruncF32U) + case 0xAA: + try visitor.visitConversion(.i32TruncF64S) + case 0xAB: + try visitor.visitConversion(.i32TruncF64U) + case 0xAC: + try visitor.visitConversion(.i64ExtendI32S) + case 0xAD: + try visitor.visitConversion(.i64ExtendI32U) + case 0xAE: + try visitor.visitConversion(.i64TruncF32S) + case 0xAF: + try visitor.visitConversion(.i64TruncF32U) + case 0xB0: + try visitor.visitConversion(.i64TruncF64S) + case 0xB1: + try visitor.visitConversion(.i64TruncF64U) + case 0xB2: + try visitor.visitConversion(.f32ConvertI32S) + case 0xB3: + try visitor.visitConversion(.f32ConvertI32U) + case 0xB4: + try visitor.visitConversion(.f32ConvertI64S) + case 0xB5: + try visitor.visitConversion(.f32ConvertI64U) + case 0xB6: + try visitor.visitConversion(.f32DemoteF64) + case 0xB7: + try visitor.visitConversion(.f64ConvertI32S) + case 0xB8: + try visitor.visitConversion(.f64ConvertI32U) + case 0xB9: + try visitor.visitConversion(.f64ConvertI64S) + case 0xBA: + try visitor.visitConversion(.f64ConvertI64U) + case 0xBB: + try visitor.visitConversion(.f64PromoteF32) + case 0xBC: + try visitor.visitConversion(.i32ReinterpretF32) + case 0xBD: + try visitor.visitConversion(.i64ReinterpretF64) + case 0xBE: + try visitor.visitConversion(.f32ReinterpretI32) + case 0xBF: + try visitor.visitConversion(.f64ReinterpretI64) + case 0xC0: + try visitor.visitUnary(.i32Extend8S) + case 0xC1: + try visitor.visitUnary(.i32Extend16S) + case 0xC2: + try visitor.visitUnary(.i64Extend8S) + case 0xC3: + try visitor.visitUnary(.i64Extend16S) + case 0xC4: + try visitor.visitUnary(.i64Extend32S) + case 0xD0: + let (type) = try visitRefNull() + try visitor.visitRefNull(type: type) + case 0xD1: + try visitor.visitRefIsNull() + case 0xD2: + let (functionIndex) = try visitRefFunc() + try visitor.visitRefFunc(functionIndex: functionIndex) + case 0xFC: + + let opcode1 = try claimNextByte() + switch opcode1 { + case 0x00: + try visitor.visitConversion(.i32TruncSatF32S) + case 0x01: + try visitor.visitConversion(.i32TruncSatF32U) + case 0x02: + try visitor.visitConversion(.i32TruncSatF64S) + case 0x03: + try visitor.visitConversion(.i32TruncSatF64U) + case 0x04: + try visitor.visitConversion(.i64TruncSatF32S) + case 0x05: + try visitor.visitConversion(.i64TruncSatF32U) + case 0x06: + try visitor.visitConversion(.i64TruncSatF64S) + case 0x07: + try visitor.visitConversion(.i64TruncSatF64U) + case 0x08: + let (dataIndex) = try visitMemoryInit() + try visitor.visitMemoryInit(dataIndex: dataIndex) + case 0x09: + let (dataIndex) = try visitDataDrop() + try visitor.visitDataDrop(dataIndex: dataIndex) + case 0x0A: + let (dstMem, srcMem) = try visitMemoryCopy() + try visitor.visitMemoryCopy(dstMem: dstMem, srcMem: srcMem) + case 0x0B: + let (memory) = try visitMemoryFill() + try visitor.visitMemoryFill(memory: memory) + case 0x0C: + let (elemIndex, table) = try visitTableInit() + try visitor.visitTableInit(elemIndex: elemIndex, table: table) + case 0x0D: + let (elemIndex) = try visitElemDrop() + try visitor.visitElemDrop(elemIndex: elemIndex) + case 0x0E: + let (dstTable, srcTable) = try visitTableCopy() + try visitor.visitTableCopy(dstTable: dstTable, srcTable: srcTable) + case 0x0F: + let (table) = try visitTableGrow() + try visitor.visitTableGrow(table: table) + case 0x10: + let (table) = try visitTableSize() + try visitor.visitTableSize(table: table) + case 0x11: + let (table) = try visitTableFill() + try visitor.visitTableFill(table: table) + default: + try visitUnknown([opcode0, opcode1]) + } + default: + try visitUnknown([opcode0]) + } + return false + } +} diff --git a/Sources/WasmParser/CMakeLists.txt b/Sources/WasmParser/CMakeLists.txt index 3428378c..b5b05612 100644 --- a/Sources/WasmParser/CMakeLists.txt +++ b/Sources/WasmParser/CMakeLists.txt @@ -2,7 +2,7 @@ add_wasmkit_library(WasmParser Stream/ByteStream.swift Stream/FileHandleStream.swift Stream/Stream.swift - InstructionCode.swift + BinaryInstructionDecoder.swift InstructionVisitor.swift LEB.swift ParsingLimits.swift diff --git a/Sources/WasmParser/InstructionCode.swift b/Sources/WasmParser/InstructionCode.swift deleted file mode 100644 index 67ab4e62..00000000 --- a/Sources/WasmParser/InstructionCode.swift +++ /dev/null @@ -1,209 +0,0 @@ -@usableFromInline -enum InstructionCode: UInt8 { - case unreachable = 0x00 - case nop = 0x01 - case block = 0x02 - case loop = 0x03 - case `if` = 0x04 - case `else` = 0x05 - - case end = 0x0B - case br - case br_if - case br_table - case `return` - case call - case call_indirect - - case drop = 0x1A - case select = 0x1B - case typed_select = 0x1C - - case local_get = 0x20 - case local_set = 0x21 - case local_tee = 0x22 - - case global_get = 0x23 - case global_set = 0x24 - - case table_get = 0x25 - case table_set = 0x26 - - case i32_load = 0x28 - case i64_load = 0x29 - case f32_load = 0x2A - case f64_load = 0x2B - - case i32_load8_s = 0x2C - case i32_load8_u - case i32_load16_s - case i32_load16_u - case i64_load8_s - case i64_load8_u - case i64_load16_s - case i64_load16_u - case i64_load32_s - case i64_load32_u - - case i32_store = 0x36 - case i64_store = 0x37 - case f32_store = 0x38 - case f64_store = 0x39 - case i32_store8 = 0x3A - case i32_store16 = 0x3B - case i64_store8 = 0x3C - case i64_store16 = 0x3D - case i64_store32 = 0x3E - - case memory_size = 0x3F - case memory_grow = 0x40 - - case i32_const - case i64_const - case f32_const - case f64_const - - case i32_eqz - case i32_eq - case i32_ne - case i32_lt_s - case i32_lt_u - case i32_gt_s - case i32_gt_u - case i32_le_s - case i32_le_u - case i32_ge_s - case i32_ge_u - - case i64_eqz - case i64_eq - case i64_ne - case i64_lt_s - case i64_lt_u - case i64_gt_s - case i64_gt_u - case i64_le_s - case i64_le_u - case i64_ge_s - case i64_ge_u - - case f32_eq - case f32_ne - case f32_lt - case f32_gt - case f32_le - case f32_ge - - case f64_eq - case f64_ne - case f64_lt - case f64_gt - case f64_le - case f64_ge - - case i32_clz - case i32_ctz - case i32_popcnt - case i32_add - case i32_sub - case i32_mul - case i32_div_s - case i32_div_u - case i32_rem_s - case i32_rem_u - case i32_and - case i32_or - case i32_xor - case i32_shl - case i32_shr_s - case i32_shr_u - case i32_rotl - case i32_rotr - - case i64_clz - case i64_ctz - case i64_popcnt - case i64_add - case i64_sub - case i64_mul - case i64_div_s - case i64_div_u - case i64_rem_s - case i64_rem_u - case i64_and - case i64_or - case i64_xor - case i64_shl - case i64_shr_s - case i64_shr_u - case i64_rotl - case i64_rotr - - case f32_abs - case f32_neg - case f32_ceil - case f32_floor - case f32_trunc - case f32_nearest - case f32_sqrt - case f32_add - case f32_sub - case f32_mul - case f32_div - case f32_min - case f32_max - case f32_copysign - - case f64_abs - case f64_neg - case f64_ceil - case f64_floor - case f64_trunc - case f64_nearest - case f64_sqrt - case f64_add - case f64_sub - case f64_mul - case f64_div - case f64_min - case f64_max - case f64_copysign - - case i32_wrap_i64 = 0xA7 - case i32_trunc_f32_s - case i32_trunc_f32_u - case i32_trunc_f64_s - case i32_trunc_f64_u - case i64_extend_i32_s - case i64_extend_i32_u - case i64_trunc_f32_s - case i64_trunc_f32_u - case i64_trunc_f64_s - case i64_trunc_f64_u - case f32_convert_i32_s - case f32_convert_i32_u - case f32_convert_i64_s - case f32_convert_i64_u - case f32_demote_f64 - case f64_convert_i32_s - case f64_convert_i32_u - case f64_convert_i64_s - case f64_convert_i64_u - case f64_promote_f32 - case i32_reinterpret_f32 = 0xBC - case i64_reinterpret_f64 = 0xBD - case f32_reinterpret_i32 = 0xBE - case f64_reinterpret_i64 = 0xBF - - case i32_extend8_s = 0xC0 - case i32_extend16_s = 0xC1 - case i64_extend8_s = 0xC2 - case i64_extend16_s = 0xC3 - case i64_extend32_s = 0xC4 - - case ref_null = 0xD0 - case ref_is_null = 0xD1 - case ref_func = 0xD2 - - case wasm2InstructionPrefix = 0xFC -} diff --git a/Sources/WasmParser/WasmParser.swift b/Sources/WasmParser/WasmParser.swift index 8a7e4c94..b35be3bc 100644 --- a/Sources/WasmParser/WasmParser.swift +++ b/Sources/WasmParser/WasmParser.swift @@ -122,18 +122,17 @@ extension Code { /// ```` @inlinable public func parseExpression(visitor: inout V) throws { - let parser = Parser(stream: StaticByteStream(bytes: self.expression), features: self.features) - var lastCode: InstructionCode? + var parser = Parser(stream: StaticByteStream(bytes: self.expression), features: self.features) + var lastIsEnd: Bool? while try !parser.stream.hasReachedEnd() { - lastCode = try parser.parseInstruction(visitor: &visitor) + lastIsEnd = try parser.parseInstruction(visitor: &visitor) } - guard lastCode == .end else { + guard lastIsEnd == true else { throw parser.makeError(.endOpcodeExpected) } } } -// TODO: Move `doParseInstruction` under `ExpressionParser` struct @_documentation(visibility: internal) public struct ExpressionParser { /// The byte offset of the code in the module @@ -143,9 +142,9 @@ public struct ExpressionParser { /// is not a part of the initial `FileHandleStream` buffer let initialStreamOffset: Int @usableFromInline - let parser: Parser + var parser: Parser @usableFromInline - var lastCode: InstructionCode? + var isLastEnd: Bool? public var offset: Int { self.codeOffset + self.parser.offset - self.initialStreamOffset @@ -162,10 +161,10 @@ public struct ExpressionParser { @inlinable public mutating func visit(visitor: inout V) throws -> Bool { - lastCode = try parser.parseInstruction(visitor: &visitor) + isLastEnd = try parser.parseInstruction(visitor: &visitor) let shouldContinue = try !parser.stream.hasReachedEnd() if !shouldContinue { - guard lastCode == .end else { + guard isLastEnd == true else { throw WasmParserError(.endOpcodeExpected, offset: offset) } } @@ -267,11 +266,6 @@ extension WasmParserError.Message { Self("Expected reference type but got \(actual)") } - @usableFromInline static func unimplementedInstruction(_ opcode: UInt8, suffix: UInt32? = nil) -> Self { - let suffixText = suffix.map { " with suffix \($0)" } ?? "" - return Self("Unimplemented instruction: \(opcode)\(suffixText)") - } - @usableFromInline static func unexpectedElementKind(expected: UInt32, actual: UInt32) -> Self { Self("Unexpected element kind: expected \(expected) but got \(actual)") @@ -291,7 +285,7 @@ extension WasmParserError.Message { Self("Section size mismatch: expected \(expected) but got \(actual)") } - @usableFromInline static func illegalOpcode(_ opcode: UInt8) -> Self { + @usableFromInline static func illegalOpcode(_ opcode: [UInt8]) -> Self { Self("Illegal opcode: \(opcode)") } @@ -586,333 +580,147 @@ extension Parser { /// > Note: /// -extension Parser { - @inlinable - func parseInstruction(visitor v: inout V) throws -> InstructionCode { - let rawCode = try stream.consumeAny() - guard let code = InstructionCode(rawValue: rawCode) else { - throw makeError(.illegalOpcode(rawCode)) +extension Parser: BinaryInstructionDecoder { + func parseMemoryIndex() throws -> UInt32 { + let zero = try stream.consumeAny() + guard zero == 0x00 else { + throw makeError(.zeroExpected(actual: zero)) + } + return 0 + } + + func visitUnknown(_ opcode: [UInt8]) throws { + throw makeError(.illegalOpcode(opcode)) + } + + mutating func visitBlock() throws -> BlockType { try parseResultType() } + mutating func visitLoop() throws -> BlockType { try parseResultType() } + mutating func visitIf() throws -> BlockType { try parseResultType() } + mutating func visitBr() throws -> UInt32 { try parseUnsigned() } + mutating func visitBrIf() throws -> UInt32 { try parseUnsigned() } + mutating func visitBrTable() throws -> BrTable { + let labelIndices: [UInt32] = try parseVector { try parseUnsigned() } + let labelIndex: UInt32 = try parseUnsigned() + return BrTable(labelIndices: labelIndices, defaultIndex: labelIndex) + } + mutating func visitCall() throws -> UInt32 { try parseUnsigned() } + + mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) { + let typeIndex: TypeIndex = try parseUnsigned() + if try !features.contains(.referenceTypes) && stream.peek() != 0 { + // Check that reserved byte is zero when reference-types is disabled + throw makeError(.malformedIndirectCall) } - try doParseInstruction(code: code, visitor: &v) - return code + let tableIndex: TableIndex = try parseUnsigned() + return (typeIndex, tableIndex) } - - @inlinable - func doParseInstruction(code: InstructionCode, visitor v: inout V) throws { - switch code { - case .unreachable: return try v.visitUnreachable() - case .nop: return try v.visitNop() - case .block: return try v.visitBlock(blockType: try parseResultType()) - case .loop: return try v.visitLoop(blockType: try parseResultType()) - case .if: return try v.visitIf(blockType: try parseResultType()) - case .else: return try v.visitElse() - case .end: return try v.visitEnd() - case .br: - let label: UInt32 = try parseUnsigned() - return try v.visitBr(relativeDepth: label) - case .br_if: - let label: UInt32 = try parseUnsigned() - return try v.visitBrIf(relativeDepth: label) - case .br_table: - let labelIndices: [UInt32] = try parseVector { try parseUnsigned() } - let labelIndex: UInt32 = try parseUnsigned() - return try v.visitBrTable(targets: BrTable(labelIndices: labelIndices, defaultIndex: labelIndex)) - case .return: - return try v.visitReturn() - case .call: - let index: UInt32 = try parseUnsigned() - return try v.visitCall(functionIndex: index) - case .call_indirect: - let typeIndex: TypeIndex = try parseUnsigned() - if try !features.contains(.referenceTypes) && stream.peek() != 0 { - // Check that reserved byte is zero when reference-types is disabled - throw makeError(.malformedIndirectCall) - } - let tableIndex: TableIndex = try parseUnsigned() - return try v.visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) - case .drop: return try v.visitDrop() - case .select: return try v.visitSelect() - case .typed_select: - let results = try parseVector { try parseValueType() } - guard results.count == 1 else { - throw makeError(.invalidResultArity(expected: 1, actual: results.count)) - } - return try v.visitTypedSelect(type: results[0]) - - case .local_get: - let index: UInt32 = try parseUnsigned() - return try v.visitLocalGet(localIndex: index) - case .local_set: - let index: UInt32 = try parseUnsigned() - return try v.visitLocalSet(localIndex: index) - case .local_tee: - let index: UInt32 = try parseUnsigned() - return try v.visitLocalTee(localIndex: index) - case .global_get: - let index: UInt32 = try parseUnsigned() - return try v.visitGlobalGet(globalIndex: index) - case .global_set: - let index: UInt32 = try parseUnsigned() - return try v.visitGlobalSet(globalIndex: index) - - case .i32_load: return try v.visitLoad(.i32Load, memarg: try parseMemarg()) - case .i64_load: return try v.visitLoad(.i64Load, memarg: try parseMemarg()) - case .f32_load: return try v.visitLoad(.f32Load, memarg: try parseMemarg()) - case .f64_load: return try v.visitLoad(.f64Load, memarg: try parseMemarg()) - case .i32_load8_s: return try v.visitLoad(.i32Load8S, memarg: try parseMemarg()) - case .i32_load8_u: return try v.visitLoad(.i32Load8U, memarg: try parseMemarg()) - case .i32_load16_s: return try v.visitLoad(.i32Load16S, memarg: try parseMemarg()) - case .i32_load16_u: return try v.visitLoad(.i32Load16U, memarg: try parseMemarg()) - case .i64_load8_s: return try v.visitLoad(.i64Load8S, memarg: try parseMemarg()) - case .i64_load8_u: return try v.visitLoad(.i64Load8U, memarg: try parseMemarg()) - case .i64_load16_s: return try v.visitLoad(.i64Load16S, memarg: try parseMemarg()) - case .i64_load16_u: return try v.visitLoad(.i64Load16U, memarg: try parseMemarg()) - case .i64_load32_s: return try v.visitLoad(.i64Load32S, memarg: try parseMemarg()) - case .i64_load32_u: return try v.visitLoad(.i64Load32U, memarg: try parseMemarg()) - case .i32_store: return try v.visitStore(.i32Store, memarg: try parseMemarg()) - case .i64_store: return try v.visitStore(.i64Store, memarg: try parseMemarg()) - case .f32_store: return try v.visitStore(.f32Store, memarg: try parseMemarg()) - case .f64_store: return try v.visitStore(.f64Store, memarg: try parseMemarg()) - case .i32_store8: return try v.visitStore(.i32Store8, memarg: try parseMemarg()) - case .i32_store16: return try v.visitStore(.i32Store16, memarg: try parseMemarg()) - case .i64_store8: return try v.visitStore(.i64Store8, memarg: try parseMemarg()) - case .i64_store16: return try v.visitStore(.i64Store16, memarg: try parseMemarg()) - case .i64_store32: return try v.visitStore(.i64Store32, memarg: try parseMemarg()) - case .memory_size: - let zero = try stream.consumeAny() - guard zero == 0x00 else { - throw makeError(.zeroExpected(actual: zero)) - } - return try v.visitMemorySize(memory: UInt32(zero)) - case .memory_grow: - let zero = try stream.consumeAny() - guard zero == 0x00 else { - throw makeError(.zeroExpected(actual: zero)) - } - return try v.visitMemoryGrow(memory: UInt32(zero)) - - case .i32_const: - let n: UInt32 = try parseInteger() - return try v.visitI32Const(value: Int32(bitPattern: n)) - case .i64_const: - let n: UInt64 = try parseInteger() - return try v.visitI64Const(value: Int64(bitPattern: n)) - case .f32_const: - let n = try parseFloat() - return try v.visitF32Const(value: IEEE754.Float32(bitPattern: n)) - case .f64_const: - let n = try parseDouble() - return try v.visitF64Const(value: IEEE754.Float64(bitPattern: n)) - - case .i32_eqz: return try v.visitI32Eqz() - case .i32_eq: return try v.visitCmp(.i32Eq) - case .i32_ne: return try v.visitCmp(.i32Ne) - case .i32_lt_s: return try v.visitCmp(.i32LtS) - case .i32_lt_u: return try v.visitCmp(.i32LtU) - case .i32_gt_s: return try v.visitCmp(.i32GtS) - case .i32_gt_u: return try v.visitCmp(.i32GtU) - case .i32_le_s: return try v.visitCmp(.i32LeS) - case .i32_le_u: return try v.visitCmp(.i32LeU) - case .i32_ge_s: return try v.visitCmp(.i32GeS) - case .i32_ge_u: return try v.visitCmp(.i32GeU) - - case .i64_eqz: return try v.visitI64Eqz() - case .i64_eq: return try v.visitCmp(.i64Eq) - case .i64_ne: return try v.visitCmp(.i64Ne) - case .i64_lt_s: return try v.visitCmp(.i64LtS) - case .i64_lt_u: return try v.visitCmp(.i64LtU) - case .i64_gt_s: return try v.visitCmp(.i64GtS) - case .i64_gt_u: return try v.visitCmp(.i64GtU) - case .i64_le_s: return try v.visitCmp(.i64LeS) - case .i64_le_u: return try v.visitCmp(.i64LeU) - case .i64_ge_s: return try v.visitCmp(.i64GeS) - case .i64_ge_u: return try v.visitCmp(.i64GeU) - - case .f32_eq: return try v.visitCmp(.f32Eq) - case .f32_ne: return try v.visitCmp(.f32Ne) - case .f32_lt: return try v.visitCmp(.f32Lt) - case .f32_gt: return try v.visitCmp(.f32Gt) - case .f32_le: return try v.visitCmp(.f32Le) - case .f32_ge: return try v.visitCmp(.f32Ge) - - case .f64_eq: return try v.visitCmp(.f64Eq) - case .f64_ne: return try v.visitCmp(.f64Ne) - case .f64_lt: return try v.visitCmp(.f64Lt) - case .f64_gt: return try v.visitCmp(.f64Gt) - case .f64_le: return try v.visitCmp(.f64Le) - case .f64_ge: return try v.visitCmp(.f64Ge) - - case .i32_clz: return try v.visitUnary(.i32Clz) - case .i32_ctz: return try v.visitUnary(.i32Ctz) - case .i32_popcnt: return try v.visitUnary(.i32Popcnt) - case .i32_add: return try v.visitBinary(.i32Add) - case .i32_sub: return try v.visitBinary(.i32Sub) - case .i32_mul: return try v.visitBinary(.i32Mul) - case .i32_div_s: return try v.visitBinary(.i32DivS) - case .i32_div_u: return try v.visitBinary(.i32DivU) - case .i32_rem_s: return try v.visitBinary(.i32RemS) - case .i32_rem_u: return try v.visitBinary(.i32RemU) - case .i32_and: return try v.visitBinary(.i32And) - case .i32_or: return try v.visitBinary(.i32Or) - case .i32_xor: return try v.visitBinary(.i32Xor) - case .i32_shl: return try v.visitBinary(.i32Shl) - case .i32_shr_s: return try v.visitBinary(.i32ShrS) - case .i32_shr_u: return try v.visitBinary(.i32ShrU) - case .i32_rotl: return try v.visitBinary(.i32Rotl) - case .i32_rotr: return try v.visitBinary(.i32Rotr) - - case .i64_clz: return try v.visitUnary(.i64Clz) - case .i64_ctz: return try v.visitUnary(.i64Ctz) - case .i64_popcnt: return try v.visitUnary(.i64Popcnt) - case .i64_add: return try v.visitBinary(.i64Add) - case .i64_sub: return try v.visitBinary(.i64Sub) - case .i64_mul: return try v.visitBinary(.i64Mul) - case .i64_div_s: return try v.visitBinary(.i64DivS) - case .i64_div_u: return try v.visitBinary(.i64DivU) - case .i64_rem_s: return try v.visitBinary(.i64RemS) - case .i64_rem_u: return try v.visitBinary(.i64RemU) - case .i64_and: return try v.visitBinary(.i64And) - case .i64_or: return try v.visitBinary(.i64Or) - case .i64_xor: return try v.visitBinary(.i64Xor) - case .i64_shl: return try v.visitBinary(.i64Shl) - case .i64_shr_s: return try v.visitBinary(.i64ShrS) - case .i64_shr_u: return try v.visitBinary(.i64ShrU) - case .i64_rotl: return try v.visitBinary(.i64Rotl) - case .i64_rotr: return try v.visitBinary(.i64Rotr) - - case .f32_abs: return try v.visitUnary(.f32Abs) - case .f32_neg: return try v.visitUnary(.f32Neg) - case .f32_ceil: return try v.visitUnary(.f32Ceil) - case .f32_floor: return try v.visitUnary(.f32Floor) - case .f32_trunc: return try v.visitUnary(.f32Trunc) - case .f32_nearest: return try v.visitUnary(.f32Nearest) - case .f32_sqrt: return try v.visitUnary(.f32Sqrt) - - case .f32_add: return try v.visitBinary(.f32Add) - case .f32_sub: return try v.visitBinary(.f32Sub) - case .f32_mul: return try v.visitBinary(.f32Mul) - case .f32_div: return try v.visitBinary(.f32Div) - case .f32_min: return try v.visitBinary(.f32Min) - case .f32_max: return try v.visitBinary(.f32Max) - case .f32_copysign: return try v.visitBinary(.f32Copysign) - - case .f64_abs: return try v.visitUnary(.f64Abs) - case .f64_neg: return try v.visitUnary(.f64Neg) - case .f64_ceil: return try v.visitUnary(.f64Ceil) - case .f64_floor: return try v.visitUnary(.f64Floor) - case .f64_trunc: return try v.visitUnary(.f64Trunc) - case .f64_nearest: return try v.visitUnary(.f64Nearest) - case .f64_sqrt: return try v.visitUnary(.f64Sqrt) - - case .f64_add: return try v.visitBinary(.f64Add) - case .f64_sub: return try v.visitBinary(.f64Sub) - case .f64_mul: return try v.visitBinary(.f64Mul) - case .f64_div: return try v.visitBinary(.f64Div) - case .f64_min: return try v.visitBinary(.f64Min) - case .f64_max: return try v.visitBinary(.f64Max) - case .f64_copysign: return try v.visitBinary(.f64Copysign) - - case .i32_wrap_i64: return try v.visitConversion(.i32WrapI64) - case .i32_trunc_f32_s: return try v.visitConversion(.i32TruncF32S) - case .i32_trunc_f32_u: return try v.visitConversion(.i32TruncF32U) - case .i32_trunc_f64_s: return try v.visitConversion(.i32TruncF64S) - case .i32_trunc_f64_u: return try v.visitConversion(.i32TruncF64U) - case .i64_extend_i32_s: return try v.visitConversion(.i64ExtendI32S) - case .i64_extend_i32_u: return try v.visitConversion(.i64ExtendI32U) - case .i64_trunc_f32_s: return try v.visitConversion(.i64TruncF32S) - case .i64_trunc_f32_u: return try v.visitConversion(.i64TruncF32U) - case .i64_trunc_f64_s: return try v.visitConversion(.i64TruncF64S) - case .i64_trunc_f64_u: return try v.visitConversion(.i64TruncF64U) - case .f32_convert_i32_s: return try v.visitConversion(.f32ConvertI32S) - case .f32_convert_i32_u: return try v.visitConversion(.f32ConvertI32U) - case .f32_convert_i64_s: return try v.visitConversion(.f32ConvertI64S) - case .f32_convert_i64_u: return try v.visitConversion(.f32ConvertI64U) - case .f32_demote_f64: return try v.visitConversion(.f32DemoteF64) - case .f64_convert_i32_s: return try v.visitConversion(.f64ConvertI32S) - case .f64_convert_i32_u: return try v.visitConversion(.f64ConvertI32U) - case .f64_convert_i64_s: return try v.visitConversion(.f64ConvertI64S) - case .f64_convert_i64_u: return try v.visitConversion(.f64ConvertI64U) - case .f64_promote_f32: return try v.visitConversion(.f64PromoteF32) - case .i32_reinterpret_f32: return try v.visitConversion(.i32ReinterpretF32) - case .i64_reinterpret_f64: return try v.visitConversion(.i64ReinterpretF64) - case .f32_reinterpret_i32: return try v.visitConversion(.f32ReinterpretI32) - case .f64_reinterpret_i64: return try v.visitConversion(.f64ReinterpretI64) - case .i32_extend8_s: return try v.visitUnary(.i32Extend8S) - case .i32_extend16_s: return try v.visitUnary(.i32Extend16S) - case .i64_extend8_s: return try v.visitUnary(.i64Extend8S) - case .i64_extend16_s: return try v.visitUnary(.i64Extend16S) - case .i64_extend32_s: return try v.visitUnary(.i64Extend32S) - - case .ref_null: - let type = try parseValueType() - - guard case let .ref(refType) = type else { - throw makeError(.expectedRefType(actual: type)) - } - - return try v.visitRefNull(type: refType) - - case .ref_is_null: return try v.visitRefIsNull() - - case .ref_func: return try v.visitRefFunc(functionIndex: try parseUnsigned()) - - case .table_get: return try v.visitTableGet(table: try parseUnsigned()) - - case .table_set: return try v.visitTableSet(table: try parseUnsigned()) - - case .wasm2InstructionPrefix: - let codeSuffix: UInt32 = try parseUnsigned() - switch codeSuffix { - case 0: return try v.visitConversion(.i32TruncSatF32S) - case 1: return try v.visitConversion(.i32TruncSatF32U) - case 2: return try v.visitConversion(.i32TruncSatF64S) - case 3: return try v.visitConversion(.i32TruncSatF64U) - case 4: return try v.visitConversion(.i64TruncSatF32S) - case 5: return try v.visitConversion(.i64TruncSatF32U) - case 6: return try v.visitConversion(.i64TruncSatF64S) - case 7: return try v.visitConversion(.i64TruncSatF64U) - case 8: - let dataIndex: DataIndex = try parseUnsigned() - let zero = try stream.consumeAny() - guard zero == 0x00 else { - throw makeError(.zeroExpected(actual: zero)) - } - - return try v.visitMemoryInit(dataIndex: dataIndex) - case 9: - return try v.visitDataDrop(dataIndex: try parseUnsigned()) - case 10: - let (zero1, zero2) = try (stream.consumeAny(), stream.consumeAny()) - guard zero1 == 0x00 else { - throw makeError(.zeroExpected(actual: zero1)) - } - guard zero2 == 0x00 else { - throw makeError(.zeroExpected(actual: zero2)) - } - return try v.visitMemoryCopy(dstMem: 0, srcMem: 0) - case 11: - let zero = try stream.consumeAny() - guard zero == 0x00 else { - throw makeError(.zeroExpected(actual: zero)) - } - - return try v.visitMemoryFill(memory: 0) - case 12: - let elementIndex: ElementIndex = try parseUnsigned() - let tableIndex: TableIndex = try parseUnsigned() - return try v.visitTableInit(elemIndex: elementIndex, table: tableIndex) - case 13: return try v.visitElemDrop(elemIndex: try parseUnsigned()) - case 14: - let destinationTableIndex: TableIndex = try parseUnsigned() - let sourceTableIndex: TableIndex = try parseUnsigned() - return try v.visitTableCopy(dstTable: destinationTableIndex, srcTable: sourceTableIndex) - case 15: return try v.visitTableGrow(table: try parseUnsigned()) - case 16: return try v.visitTableSize(table: try parseUnsigned()) - case 17: return try v.visitTableFill(table: try parseUnsigned()) - default: - throw makeError(.unimplementedInstruction(code.rawValue, suffix: codeSuffix)) - } + + mutating func visitTypedSelect() throws -> WasmTypes.ValueType { + let results = try parseVector { try parseValueType() } + guard results.count == 1 else { + throw makeError(.invalidResultArity(expected: 1, actual: results.count)) + } + return results[0] + } + + mutating func visitLocalGet() throws -> UInt32 { try parseUnsigned() } + mutating func visitLocalSet() throws -> UInt32 { try parseUnsigned() } + mutating func visitLocalTee() throws -> UInt32 { try parseUnsigned() } + mutating func visitGlobalGet() throws -> UInt32 { try parseUnsigned() } + mutating func visitGlobalSet() throws -> UInt32 { try parseUnsigned() } + mutating func visitLoad(_: Instruction.Load) throws -> MemArg { try parseMemarg() } + mutating func visitStore(_: Instruction.Store) throws -> MemArg { try parseMemarg() } + mutating func visitMemorySize() throws -> UInt32 { + try parseMemoryIndex() + } + mutating func visitMemoryGrow() throws -> UInt32 { + try parseMemoryIndex() + } + mutating func visitI32Const() throws -> Int32 { + let n: UInt32 = try parseInteger() + return Int32(bitPattern: n) + } + mutating func visitI64Const() throws -> Int64 { + let n: UInt64 = try parseInteger() + return Int64(bitPattern: n) + } + mutating func visitF32Const() throws -> IEEE754.Float32 { + let n = try parseFloat() + return IEEE754.Float32(bitPattern: n) + } + mutating func visitF64Const() throws -> IEEE754.Float64 { + let n = try parseDouble() + return IEEE754.Float64(bitPattern: n) + } + mutating func visitRefNull() throws -> WasmTypes.ReferenceType { + let type = try parseValueType() + guard case let .ref(refType) = type else { + throw makeError(.expectedRefType(actual: type)) } + return refType + } + + mutating func visitRefFunc() throws -> UInt32 { try parseUnsigned() } + mutating func visitMemoryInit() throws -> UInt32 { + let dataIndex: DataIndex = try parseUnsigned() + _ = try parseMemoryIndex() + return dataIndex + } + + mutating func visitDataDrop() throws -> UInt32 { + try parseUnsigned() + } + + mutating func visitMemoryCopy() throws -> (dstMem: UInt32, srcMem: UInt32) { + _ = try parseMemoryIndex() + _ = try parseMemoryIndex() + return (0, 0) + } + + mutating func visitMemoryFill() throws -> UInt32 { + let zero = try stream.consumeAny() + guard zero == 0x00 else { + throw makeError(.zeroExpected(actual: zero)) + } + return 0 + } + + mutating func visitTableInit() throws -> (elemIndex: UInt32, table: UInt32) { + let elementIndex: ElementIndex = try parseUnsigned() + let tableIndex: TableIndex = try parseUnsigned() + return (elementIndex, tableIndex) + } + mutating func visitElemDrop() throws -> UInt32 { + try parseUnsigned() + } + mutating func visitTableCopy() throws -> (dstTable: UInt32, srcTable: UInt32) { + let destination: TableIndex = try parseUnsigned() + let source: TableIndex = try parseUnsigned() + return (destination, source) + } + mutating func visitTableFill() throws -> UInt32 { + try parseUnsigned() + } + mutating func visitTableGet() throws -> UInt32 { + try parseUnsigned() + } + mutating func visitTableSet() throws -> UInt32 { + try parseUnsigned() + } + mutating func visitTableGrow() throws -> UInt32 { + try parseUnsigned() + } + mutating func visitTableSize() throws -> UInt32 { + try parseUnsigned() + } + func claimNextByte() throws -> UInt8 { + return try stream.consumeAny() + } + + @inline(__always) + @usableFromInline + mutating func parseInstruction(visitor v: inout V) throws -> Bool { + return try self.parseBinaryInstruction(visitor: &v) } @usableFromInline @@ -928,12 +736,12 @@ extension Parser { } @usableFromInline - func parseConstExpression() throws -> ConstExpression { + mutating func parseConstExpression() throws -> ConstExpression { var factory = InstructionFactory() - var inst: InstructionCode + var isEnd: Bool repeat { - inst = try self.parseInstruction(visitor: &factory) - } while inst != .end + isEnd = try self.parseInstruction(visitor: &factory) + } while !isEnd return factory.insts } } @@ -1016,7 +824,7 @@ extension Parser { /// > Note: /// @usableFromInline - func parseGlobalSection() throws -> [Global] { + mutating func parseGlobalSection() throws -> [Global] { return try parseVector { let type = try parseGlobalType() let expression = try parseConstExpression() @@ -1059,7 +867,7 @@ extension Parser { /// > Note: /// @inlinable - func parseElementSection() throws -> [ElementSegment] { + mutating func parseElementSection() throws -> [ElementSegment] { return try parseVector { let flag = try ElementSegment.Flag(rawValue: parseUnsigned()) @@ -1152,7 +960,7 @@ extension Parser { /// > Note: /// @inlinable - func parseDataSection() throws -> [DataSegment] { + mutating func parseDataSection() throws -> [DataSegment] { return try parseVector { let kind: UInt32 = try parseUnsigned() switch kind { diff --git a/Tests/WasmKitTests/SpectestTests.swift b/Tests/WasmKitTests/SpectestTests.swift index 5a5bec48..b174edac 100644 --- a/Tests/WasmKitTests/SpectestTests.swift +++ b/Tests/WasmKitTests/SpectestTests.swift @@ -21,7 +21,7 @@ final class SpectestTests: XCTestCase { let defaultConfig = EngineConfiguration() let ok = try await spectest( path: Self.testPaths, - include: [], + include: ["bulk.wast"], exclude: [], parallel: true, configuration: defaultConfig diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index fb7cbe85..5788f68e 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -494,6 +494,128 @@ enum WasmGen { return code } + static func generateBinaryInstructionDecoder(_ instructions: InstructionSet) -> String { + struct Trie { + var children: [UInt8: Trie] = [:] + /// An instruction corresponding to this terminal trie node + let instruction: Instruction? + + init(instruction: Instruction? = nil) { + self.instruction = instruction + } + + mutating func insert(_ opcode: S, instruction: Instruction) where S.Element == UInt8 { + guard let first = opcode.first else { return } + let isTermination = opcode.count == 1 + if isTermination { + assert(children[first] == nil) + children[first] = Trie(instruction: instruction) + } else { + children[first, default: Trie(instruction: nil)].insert(opcode.dropFirst(), instruction: instruction) + } + } + } + + var root = Trie() + for instruction in instructions { + root.insert(instruction.opcode, instruction: instruction) + } + var code = """ + import WasmTypes + + protocol BinaryInstructionDecoder { + /// Claim the next byte to be decoded + func claimNextByte() throws -> UInt8 + /// Visit unknown instruction + func visitUnknown(_ opcode: [UInt8]) throws + + """ + for instruction in instructions.categorized { + guard !instruction.immediates.isEmpty else { continue } + code += " /// Decode \(instruction.description) immediates\n" + code += " mutating func \(instruction.visitMethodName)(" + if let categoryType = instruction.categoryTypeName { + code += "_: Instruction.\(categoryType)" + } + code += ") throws -> " + if instruction.immediates.count == 1 { + code += "\(instruction.immediates[0].type)" + } else { + code += "(" + instruction.immediates.map { "\($0.label): \($0.type)" }.joined(separator: ", ") + ")" + } + code += "\n" + } + code += """ + } + + """ + + code += """ + extension BinaryInstructionDecoder { + @usableFromInline + mutating func parseBinaryInstruction(visitor: inout V) throws -> Bool { + """ + + func renderSwitchCase(_ root: Trie, depth: Int = 0) { + let indent = String(repeating: " ", count: (depth + 2) * 4) + func opcodeByteName(_ depth: Int) -> String { "opcode\(depth)" } + let opcodeByte = opcodeByteName(depth) + code += """ + + \(indent)let \(opcodeByte) = try claimNextByte() + \(indent)switch \(opcodeByte) { + + """ + for (opcode, trie) in root.children.sorted(by: { $0.key < $1.key }) { + code += "\(indent)case \(String(format: "0x%02X", opcode)):\n" + if let instruction = trie.instruction { + if !instruction.immediates.isEmpty { + code += "\(indent) let (" + code += instruction.immediates.map(\.label).joined(separator: ", ") + code += ") = try \(instruction.visitMethodName)(" + if instruction.category != nil { + code += ".\(instruction.name.enumCase)" + } + code += ")\n" + } + + code += "\(indent) try visitor.\(instruction.visitMethodName)(" + var arguments: [(label: String?, value: String)] = [] + if instruction.category != nil { + arguments.append((label: nil, value: ".\(instruction.name.enumCase)")) + } + for immediate in instruction.immediates { + arguments.append((label: immediate.label, value: immediate.label)) + } + code += arguments.map { i in + if let label = i.label { + return "\(label): \(i.value)" + } else { + return i.value + } + }.joined(separator: ", ") + code += ")\n" + if instruction.name.text == "end" { + code += "\(indent) return true\n" + } + } else { + renderSwitchCase(trie, depth: depth + 1) + } + } + code += "\(indent)default:\n" + code += "\(indent) try visitUnknown(" + code += "[" + (0...depth).map { opcodeByteName($0) }.joined(separator: ", ") + "]" + code += ")\n" + code += "\(indent)}\n" + } + + renderSwitchCase(root) + code += " return false\n" + code += " }\n" + code += "}\n" + return code + } + static func formatInstructionSet(_ instructions: InstructionSet) -> String { var json = "" json += "[\n" @@ -595,6 +717,10 @@ enum WasmGen { + generateVisitorProtocol(instructions) + "\n" ), + GeneratedFile( + projectSources + ["WasmParser", "BinaryInstructionDecoder.swift"], + header + generateBinaryInstructionDecoder(instructions) + ), GeneratedFile( projectSources + ["WAT", "ParseTextInstruction.swift"], header + generateTextInstructionParser(instructions)