diff --git a/Sources/CLI/CLI.swift b/Sources/CLI/CLI.swift index 0d146362..667cb37d 100644 --- a/Sources/CLI/CLI.swift +++ b/Sources/CLI/CLI.swift @@ -11,6 +11,7 @@ struct CLI: AsyncParsableCommand { Explore.self, Run.self, Wat2wasm.self, + Wasm2wat.self, ] ) } diff --git a/Sources/CLICommands/CMakeLists.txt b/Sources/CLICommands/CMakeLists.txt index 910c4120..30fc7c47 100644 --- a/Sources/CLICommands/CMakeLists.txt +++ b/Sources/CLICommands/CMakeLists.txt @@ -2,6 +2,7 @@ add_wasmkit_library(CLICommands Explore.swift Run.swift Wat2wasm.swift + Wasm2wat.swift ) target_link_wasmkit_libraries(CLICommands PUBLIC diff --git a/Sources/CLICommands/Wasm2wat.swift b/Sources/CLICommands/Wasm2wat.swift new file mode 100644 index 00000000..61ca73a0 --- /dev/null +++ b/Sources/CLICommands/Wasm2wat.swift @@ -0,0 +1,49 @@ +import ArgumentParser +import SystemPackage +import WAT +import WasmParser + +package struct Wasm2wat: ParsableCommand { + package static let configuration = CommandConfiguration( + commandName: "wasm2wat", + abstract: "Disassemble a WebAssembly binary into WebAssembly text format" + ) + + @Argument(help: "Path to a WebAssembly binary (`.wasm`) file to disassemble.") + var path: String + + @Option( + name: [.short, .long], + help: """ + Path to the output WebAssembly Text Format (`.wat`) file. + If not provided, the text is written to standard output. + """ + ) + var output: String? + + package init() {} + + package func run() throws { + let filePath = FilePath(path) + let fileHandle = try FileDescriptor.open(filePath, .readOnly) + defer { try? fileHandle.close() } + + let stream = try FileHandleStream(fileHandle: fileHandle) + + let wat = try wasm2wat(stream) + + if let outputPath = output { + let outPath = FilePath(outputPath) + let outHandle = try FileDescriptor.open( + outPath, + .writeOnly, + options: [.create, .truncate], + permissions: [.ownerReadWrite, .groupRead, .otherRead] + ) + defer { try? outHandle.close() } + try outHandle.writeAll(Array(wat.utf8)) + } else { + print(wat) + } + } +} diff --git a/Sources/WAT/CMakeLists.txt b/Sources/WAT/CMakeLists.txt index 07d9072f..f97fcb77 100644 --- a/Sources/WAT/CMakeLists.txt +++ b/Sources/WAT/CMakeLists.txt @@ -2,13 +2,16 @@ add_wasmkit_library(WAT BinaryEncoding/BinaryInstructionEncoder.swift BinaryEncoding/Encoder.swift Lexer.swift - Location.swift NameMapping.swift ParseTextInstruction.swift Parser.swift Parser/ExpressionParser.swift Parser/WastParser.swift Parser/WatParser.swift + Printer/InstructionMnemonics.swift + Printer/ModuleCollector.swift + Printer/TextInstructionVisitor.swift + Printer/WatPrinter.swift WAT.swift WatParserError.swift ) diff --git a/Sources/WAT/Lexer.swift b/Sources/WAT/Lexer.swift index 94ede4fb..6d9ede11 100644 --- a/Sources/WAT/Lexer.swift +++ b/Sources/WAT/Lexer.swift @@ -1,4 +1,5 @@ import WasmParser +import WasmTypes enum TokenKind: Equatable { case leftParen diff --git a/Sources/WAT/Printer/InstructionMnemonics.swift b/Sources/WAT/Printer/InstructionMnemonics.swift new file mode 100644 index 00000000..17b540fa --- /dev/null +++ b/Sources/WAT/Printer/InstructionMnemonics.swift @@ -0,0 +1,588 @@ +// swift-format-ignore-file +//// Automatically generated by Utilities/Sources/WasmGen.swift +//// DO NOT EDIT DIRECTLY + +import WasmParser +import WasmTypes + +func generatedMnemonic(for op: Instruction.Load) -> String { + switch op { + case .i32Load: return "i32.load" + case .i64Load: return "i64.load" + case .f32Load: return "f32.load" + case .f64Load: return "f64.load" + case .i32Load8S: return "i32.load8_s" + case .i32Load8U: return "i32.load8_u" + case .i32Load16S: return "i32.load16_s" + case .i32Load16U: return "i32.load16_u" + case .i64Load8S: return "i64.load8_s" + case .i64Load8U: return "i64.load8_u" + case .i64Load16S: return "i64.load16_s" + case .i64Load16U: return "i64.load16_u" + case .i64Load32S: return "i64.load32_s" + case .i64Load32U: return "i64.load32_u" + case .i32AtomicLoad: return "i32.atomic.load" + case .i64AtomicLoad: return "i64.atomic.load" + case .i32AtomicLoad8U: return "i32.atomic.load8_u" + case .i32AtomicLoad16U: return "i32.atomic.load16_u" + case .i64AtomicLoad8U: return "i64.atomic.load8_u" + case .i64AtomicLoad16U: return "i64.atomic.load16_u" + case .i64AtomicLoad32U: return "i64.atomic.load32_u" + case .v128Load: return "v128.load" + case .v128Load8X8S: return "v128.load8x8_s" + case .v128Load8X8U: return "v128.load8x8_u" + case .v128Load16X4S: return "v128.load16x4_s" + case .v128Load16X4U: return "v128.load16x4_u" + case .v128Load32X2S: return "v128.load32x2_s" + case .v128Load32X2U: return "v128.load32x2_u" + case .v128Load8Splat: return "v128.load8_splat" + case .v128Load16Splat: return "v128.load16_splat" + case .v128Load32Splat: return "v128.load32_splat" + case .v128Load64Splat: return "v128.load64_splat" + case .v128Load32Zero: return "v128.load32_zero" + case .v128Load64Zero: return "v128.load64_zero" + } +} + +func generatedDefaultAlign(for op: Instruction.Load) -> UInt32 { + switch op { + case .i32Load: return 2 + case .i64Load: return 3 + case .f32Load: return 2 + case .f64Load: return 3 + case .i32Load8S: return 0 + case .i32Load8U: return 0 + case .i32Load16S: return 1 + case .i32Load16U: return 1 + case .i64Load8S: return 0 + case .i64Load8U: return 0 + case .i64Load16S: return 1 + case .i64Load16U: return 1 + case .i64Load32S: return 2 + case .i64Load32U: return 2 + case .i32AtomicLoad: return 2 + case .i64AtomicLoad: return 3 + case .i32AtomicLoad8U: return 0 + case .i32AtomicLoad16U: return 1 + case .i64AtomicLoad8U: return 0 + case .i64AtomicLoad16U: return 1 + case .i64AtomicLoad32U: return 2 + case .v128Load: return 4 + case .v128Load8X8S: return 4 + case .v128Load8X8U: return 4 + case .v128Load16X4S: return 4 + case .v128Load16X4U: return 4 + case .v128Load32X2S: return 4 + case .v128Load32X2U: return 4 + case .v128Load8Splat: return 0 + case .v128Load16Splat: return 1 + case .v128Load32Splat: return 2 + case .v128Load64Splat: return 3 + case .v128Load32Zero: return 4 + case .v128Load64Zero: return 4 + } +} + +func generatedMnemonic(for op: Instruction.Store) -> String { + switch op { + case .i32Store: return "i32.store" + case .i64Store: return "i64.store" + case .f32Store: return "f32.store" + case .f64Store: return "f64.store" + case .i32Store8: return "i32.store8" + case .i32Store16: return "i32.store16" + case .i64Store8: return "i64.store8" + case .i64Store16: return "i64.store16" + case .i64Store32: return "i64.store32" + case .i32AtomicStore: return "i32.atomic.store" + case .i64AtomicStore: return "i64.atomic.store" + case .i32AtomicStore8: return "i32.atomic.store8" + case .i32AtomicStore16: return "i32.atomic.store16" + case .i64AtomicStore8: return "i64.atomic.store8" + case .i64AtomicStore16: return "i64.atomic.store16" + case .i64AtomicStore32: return "i64.atomic.store32" + case .v128Store: return "v128.store" + } +} + +func generatedDefaultAlign(for op: Instruction.Store) -> UInt32 { + switch op { + case .i32Store: return 2 + case .i64Store: return 3 + case .f32Store: return 2 + case .f64Store: return 3 + case .i32Store8: return 0 + case .i32Store16: return 1 + case .i64Store8: return 0 + case .i64Store16: return 1 + case .i64Store32: return 2 + case .i32AtomicStore: return 2 + case .i64AtomicStore: return 3 + case .i32AtomicStore8: return 0 + case .i32AtomicStore16: return 1 + case .i64AtomicStore8: return 0 + case .i64AtomicStore16: return 1 + case .i64AtomicStore32: return 2 + case .v128Store: return 4 + } +} + +func generatedMnemonic(for op: Instruction.Cmp) -> String { + switch op { + case .i32Eq: return "i32.eq" + case .i32Ne: return "i32.ne" + case .i32LtS: return "i32.lt_s" + case .i32LtU: return "i32.lt_u" + case .i32GtS: return "i32.gt_s" + case .i32GtU: return "i32.gt_u" + case .i32LeS: return "i32.le_s" + case .i32LeU: return "i32.le_u" + case .i32GeS: return "i32.ge_s" + case .i32GeU: return "i32.ge_u" + case .i64Eq: return "i64.eq" + case .i64Ne: return "i64.ne" + case .i64LtS: return "i64.lt_s" + case .i64LtU: return "i64.lt_u" + case .i64GtS: return "i64.gt_s" + case .i64GtU: return "i64.gt_u" + case .i64LeS: return "i64.le_s" + case .i64LeU: return "i64.le_u" + case .i64GeS: return "i64.ge_s" + case .i64GeU: return "i64.ge_u" + case .f32Eq: return "f32.eq" + case .f32Ne: return "f32.ne" + case .f32Lt: return "f32.lt" + case .f32Gt: return "f32.gt" + case .f32Le: return "f32.le" + case .f32Ge: return "f32.ge" + case .f64Eq: return "f64.eq" + case .f64Ne: return "f64.ne" + case .f64Lt: return "f64.lt" + case .f64Gt: return "f64.gt" + case .f64Le: return "f64.le" + case .f64Ge: return "f64.ge" + } +} + +func generatedMnemonic(for op: Instruction.Unary) -> String { + switch op { + case .i32Clz: return "i32.clz" + case .i32Ctz: return "i32.ctz" + case .i32Popcnt: return "i32.popcnt" + case .i64Clz: return "i64.clz" + case .i64Ctz: return "i64.ctz" + case .i64Popcnt: return "i64.popcnt" + case .f32Abs: return "f32.abs" + case .f32Neg: return "f32.neg" + case .f32Ceil: return "f32.ceil" + case .f32Floor: return "f32.floor" + case .f32Trunc: return "f32.trunc" + case .f32Nearest: return "f32.nearest" + case .f32Sqrt: return "f32.sqrt" + case .f64Abs: return "f64.abs" + case .f64Neg: return "f64.neg" + case .f64Ceil: return "f64.ceil" + case .f64Floor: return "f64.floor" + case .f64Trunc: return "f64.trunc" + case .f64Nearest: return "f64.nearest" + case .f64Sqrt: return "f64.sqrt" + case .i32Extend8S: return "i32.extend8_s" + case .i32Extend16S: return "i32.extend16_s" + case .i64Extend8S: return "i64.extend8_s" + case .i64Extend16S: return "i64.extend16_s" + case .i64Extend32S: return "i64.extend32_s" + } +} + +func generatedMnemonic(for op: Instruction.Binary) -> String { + switch op { + case .i32Add: return "i32.add" + case .i32Sub: return "i32.sub" + case .i32Mul: return "i32.mul" + case .i32DivS: return "i32.div_s" + case .i32DivU: return "i32.div_u" + case .i32RemS: return "i32.rem_s" + case .i32RemU: return "i32.rem_u" + case .i32And: return "i32.and" + case .i32Or: return "i32.or" + case .i32Xor: return "i32.xor" + case .i32Shl: return "i32.shl" + case .i32ShrS: return "i32.shr_s" + case .i32ShrU: return "i32.shr_u" + case .i32Rotl: return "i32.rotl" + case .i32Rotr: return "i32.rotr" + case .i64Add: return "i64.add" + case .i64Sub: return "i64.sub" + case .i64Mul: return "i64.mul" + case .i64DivS: return "i64.div_s" + case .i64DivU: return "i64.div_u" + case .i64RemS: return "i64.rem_s" + case .i64RemU: return "i64.rem_u" + case .i64And: return "i64.and" + case .i64Or: return "i64.or" + case .i64Xor: return "i64.xor" + case .i64Shl: return "i64.shl" + case .i64ShrS: return "i64.shr_s" + case .i64ShrU: return "i64.shr_u" + case .i64Rotl: return "i64.rotl" + case .i64Rotr: return "i64.rotr" + case .f32Add: return "f32.add" + case .f32Sub: return "f32.sub" + case .f32Mul: return "f32.mul" + case .f32Div: return "f32.div" + case .f32Min: return "f32.min" + case .f32Max: return "f32.max" + case .f32Copysign: return "f32.copysign" + case .f64Add: return "f64.add" + case .f64Sub: return "f64.sub" + case .f64Mul: return "f64.mul" + case .f64Div: return "f64.div" + case .f64Min: return "f64.min" + case .f64Max: return "f64.max" + case .f64Copysign: return "f64.copysign" + } +} + +func generatedMnemonic(for op: Instruction.Conversion) -> String { + switch op { + case .i32WrapI64: return "i32.wrap_i64" + case .i32TruncF32S: return "i32.trunc_f32_s" + case .i32TruncF32U: return "i32.trunc_f32_u" + case .i32TruncF64S: return "i32.trunc_f64_s" + case .i32TruncF64U: return "i32.trunc_f64_u" + case .i64ExtendI32S: return "i64.extend_i32_s" + case .i64ExtendI32U: return "i64.extend_i32_u" + case .i64TruncF32S: return "i64.trunc_f32_s" + case .i64TruncF32U: return "i64.trunc_f32_u" + case .i64TruncF64S: return "i64.trunc_f64_s" + case .i64TruncF64U: return "i64.trunc_f64_u" + case .f32ConvertI32S: return "f32.convert_i32_s" + case .f32ConvertI32U: return "f32.convert_i32_u" + case .f32ConvertI64S: return "f32.convert_i64_s" + case .f32ConvertI64U: return "f32.convert_i64_u" + case .f32DemoteF64: return "f32.demote_f64" + case .f64ConvertI32S: return "f64.convert_i32_s" + case .f64ConvertI32U: return "f64.convert_i32_u" + case .f64ConvertI64S: return "f64.convert_i64_s" + case .f64ConvertI64U: return "f64.convert_i64_u" + case .f64PromoteF32: return "f64.promote_f32" + case .i32ReinterpretF32: return "i32.reinterpret_f32" + case .i64ReinterpretF64: return "i64.reinterpret_f64" + case .f32ReinterpretI32: return "f32.reinterpret_i32" + case .f64ReinterpretI64: return "f64.reinterpret_i64" + case .i32TruncSatF32S: return "i32.trunc_sat_f32_s" + case .i32TruncSatF32U: return "i32.trunc_sat_f32_u" + case .i32TruncSatF64S: return "i32.trunc_sat_f64_s" + case .i32TruncSatF64U: return "i32.trunc_sat_f64_u" + case .i64TruncSatF32S: return "i64.trunc_sat_f32_s" + case .i64TruncSatF32U: return "i64.trunc_sat_f32_u" + case .i64TruncSatF64S: return "i64.trunc_sat_f64_s" + case .i64TruncSatF64U: return "i64.trunc_sat_f64_u" + } +} + +func generatedMnemonic(for op: Instruction.Simd) -> String { + switch op { + case .i8x16Swizzle: return "i8x16.swizzle" + case .i8x16Splat: return "i8x16.splat" + case .i16x8Splat: return "i16x8.splat" + case .i32x4Splat: return "i32x4.splat" + case .i64x2Splat: return "i64x2.splat" + case .f32x4Splat: return "f32x4.splat" + case .f64x2Splat: return "f64x2.splat" + case .i8x16Eq: return "i8x16.eq" + case .i8x16Ne: return "i8x16.ne" + case .i8x16LtS: return "i8x16.lt_s" + case .i8x16LtU: return "i8x16.lt_u" + case .i8x16GtS: return "i8x16.gt_s" + case .i8x16GtU: return "i8x16.gt_u" + case .i8x16LeS: return "i8x16.le_s" + case .i8x16LeU: return "i8x16.le_u" + case .i8x16GeS: return "i8x16.ge_s" + case .i8x16GeU: return "i8x16.ge_u" + case .i16x8Eq: return "i16x8.eq" + case .i16x8Ne: return "i16x8.ne" + case .i16x8LtS: return "i16x8.lt_s" + case .i16x8LtU: return "i16x8.lt_u" + case .i16x8GtS: return "i16x8.gt_s" + case .i16x8GtU: return "i16x8.gt_u" + case .i16x8LeS: return "i16x8.le_s" + case .i16x8LeU: return "i16x8.le_u" + case .i16x8GeS: return "i16x8.ge_s" + case .i16x8GeU: return "i16x8.ge_u" + case .i32x4Eq: return "i32x4.eq" + case .i32x4Ne: return "i32x4.ne" + case .i32x4LtS: return "i32x4.lt_s" + case .i32x4LtU: return "i32x4.lt_u" + case .i32x4GtS: return "i32x4.gt_s" + case .i32x4GtU: return "i32x4.gt_u" + case .i32x4LeS: return "i32x4.le_s" + case .i32x4LeU: return "i32x4.le_u" + case .i32x4GeS: return "i32x4.ge_s" + case .i32x4GeU: return "i32x4.ge_u" + case .f32x4Eq: return "f32x4.eq" + case .f32x4Ne: return "f32x4.ne" + case .f32x4Lt: return "f32x4.lt" + case .f32x4Gt: return "f32x4.gt" + case .f32x4Le: return "f32x4.le" + case .f32x4Ge: return "f32x4.ge" + case .f64x2Eq: return "f64x2.eq" + case .f64x2Ne: return "f64x2.ne" + case .f64x2Lt: return "f64x2.lt" + case .f64x2Gt: return "f64x2.gt" + case .f64x2Le: return "f64x2.le" + case .f64x2Ge: return "f64x2.ge" + case .v128Not: return "v128.not" + case .v128And: return "v128.and" + case .v128Andnot: return "v128.andnot" + case .v128Or: return "v128.or" + case .v128Xor: return "v128.xor" + case .v128Bitselect: return "v128.bitselect" + case .i8x16Abs: return "i8x16.abs" + case .i8x16Neg: return "i8x16.neg" + case .i8x16AllTrue: return "i8x16.all_true" + case .i8x16Bitmask: return "i8x16.bitmask" + case .i8x16NarrowI16X8S: return "i8x16.narrow_i16x8_s" + case .i8x16NarrowI16X8U: return "i8x16.narrow_i16x8_u" + case .i8x16Shl: return "i8x16.shl" + case .i8x16ShrS: return "i8x16.shr_s" + case .i8x16ShrU: return "i8x16.shr_u" + case .i8x16Add: return "i8x16.add" + case .i8x16AddSatS: return "i8x16.add_sat_s" + case .i8x16AddSatU: return "i8x16.add_sat_u" + case .i8x16Sub: return "i8x16.sub" + case .i8x16SubSatS: return "i8x16.sub_sat_s" + case .i8x16SubSatU: return "i8x16.sub_sat_u" + case .i8x16MinS: return "i8x16.min_s" + case .i8x16MinU: return "i8x16.min_u" + case .i8x16MaxS: return "i8x16.max_s" + case .i8x16MaxU: return "i8x16.max_u" + case .i8x16AvgrU: return "i8x16.avgr_u" + case .i16x8Abs: return "i16x8.abs" + case .i16x8Neg: return "i16x8.neg" + case .i16x8AllTrue: return "i16x8.all_true" + case .i16x8Bitmask: return "i16x8.bitmask" + case .i16x8NarrowI32X4S: return "i16x8.narrow_i32x4_s" + case .i16x8NarrowI32X4U: return "i16x8.narrow_i32x4_u" + case .i16x8ExtendLowI8X16S: return "i16x8.extend_low_i8x16_s" + case .i16x8ExtendHighI8X16S: return "i16x8.extend_high_i8x16_s" + case .i16x8ExtendLowI8X16U: return "i16x8.extend_low_i8x16_u" + case .i16x8ExtendHighI8X16U: return "i16x8.extend_high_i8x16_u" + case .i16x8Shl: return "i16x8.shl" + case .i16x8ShrS: return "i16x8.shr_s" + case .i16x8ShrU: return "i16x8.shr_u" + case .i16x8Add: return "i16x8.add" + case .i16x8AddSatS: return "i16x8.add_sat_s" + case .i16x8AddSatU: return "i16x8.add_sat_u" + case .i16x8Sub: return "i16x8.sub" + case .i16x8SubSatS: return "i16x8.sub_sat_s" + case .i16x8SubSatU: return "i16x8.sub_sat_u" + case .i16x8Mul: return "i16x8.mul" + case .i16x8MinS: return "i16x8.min_s" + case .i16x8MinU: return "i16x8.min_u" + case .i16x8MaxS: return "i16x8.max_s" + case .i16x8MaxU: return "i16x8.max_u" + case .i16x8AvgrU: return "i16x8.avgr_u" + case .i32x4Abs: return "i32x4.abs" + case .i32x4Neg: return "i32x4.neg" + case .i32x4AllTrue: return "i32x4.all_true" + case .i32x4Bitmask: return "i32x4.bitmask" + case .i32x4ExtendLowI16X8S: return "i32x4.extend_low_i16x8_s" + case .i32x4ExtendHighI16X8S: return "i32x4.extend_high_i16x8_s" + case .i32x4ExtendLowI16X8U: return "i32x4.extend_low_i16x8_u" + case .i32x4ExtendHighI16X8U: return "i32x4.extend_high_i16x8_u" + case .i32x4Shl: return "i32x4.shl" + case .i32x4ShrS: return "i32x4.shr_s" + case .i32x4ShrU: return "i32x4.shr_u" + case .i32x4Add: return "i32x4.add" + case .i32x4Sub: return "i32x4.sub" + case .i32x4Mul: return "i32x4.mul" + case .i32x4MinS: return "i32x4.min_s" + case .i32x4MinU: return "i32x4.min_u" + case .i32x4MaxS: return "i32x4.max_s" + case .i32x4MaxU: return "i32x4.max_u" + case .i32x4DotI16X8S: return "i32x4.dot_i16x8_s" + case .i64x2Abs: return "i64x2.abs" + case .i64x2Neg: return "i64x2.neg" + case .i64x2Bitmask: return "i64x2.bitmask" + case .i64x2ExtendLowI32X4S: return "i64x2.extend_low_i32x4_s" + case .i64x2ExtendHighI32X4S: return "i64x2.extend_high_i32x4_s" + case .i64x2ExtendLowI32X4U: return "i64x2.extend_low_i32x4_u" + case .i64x2ExtendHighI32X4U: return "i64x2.extend_high_i32x4_u" + case .i64x2Shl: return "i64x2.shl" + case .i64x2ShrS: return "i64x2.shr_s" + case .i64x2ShrU: return "i64x2.shr_u" + case .i64x2Add: return "i64x2.add" + case .i64x2Sub: return "i64x2.sub" + case .i64x2Mul: return "i64x2.mul" + case .f32x4Ceil: return "f32x4.ceil" + case .f32x4Floor: return "f32x4.floor" + case .f32x4Trunc: return "f32x4.trunc" + case .f32x4Nearest: return "f32x4.nearest" + case .f64x2Ceil: return "f64x2.ceil" + case .f64x2Floor: return "f64x2.floor" + case .f64x2Trunc: return "f64x2.trunc" + case .f64x2Nearest: return "f64x2.nearest" + case .f32x4Abs: return "f32x4.abs" + case .f32x4Neg: return "f32x4.neg" + case .f32x4Sqrt: return "f32x4.sqrt" + case .f32x4Add: return "f32x4.add" + case .f32x4Sub: return "f32x4.sub" + case .f32x4Mul: return "f32x4.mul" + case .f32x4Div: return "f32x4.div" + case .f32x4Min: return "f32x4.min" + case .f32x4Max: return "f32x4.max" + case .f32x4Pmin: return "f32x4.pmin" + case .f32x4Pmax: return "f32x4.pmax" + case .f64x2Abs: return "f64x2.abs" + case .f64x2Neg: return "f64x2.neg" + case .f64x2Sqrt: return "f64x2.sqrt" + case .f64x2Add: return "f64x2.add" + case .f64x2Sub: return "f64x2.sub" + case .f64x2Mul: return "f64x2.mul" + case .f64x2Div: return "f64x2.div" + case .f64x2Min: return "f64x2.min" + case .f64x2Max: return "f64x2.max" + case .f64x2Pmin: return "f64x2.pmin" + case .f64x2Pmax: return "f64x2.pmax" + case .i32x4TruncSatF32X4S: return "i32x4.trunc_sat_f32x4_s" + case .i32x4TruncSatF32X4U: return "i32x4.trunc_sat_f32x4_u" + case .f32x4ConvertI32X4S: return "f32x4.convert_i32x4_s" + case .f32x4ConvertI32X4U: return "f32x4.convert_i32x4_u" + case .i16x8ExtmulLowI8X16S: return "i16x8.extmul_low_i8x16_s" + case .i16x8ExtmulHighI8X16S: return "i16x8.extmul_high_i8x16_s" + case .i16x8ExtmulLowI8X16U: return "i16x8.extmul_low_i8x16_u" + case .i16x8ExtmulHighI8X16U: return "i16x8.extmul_high_i8x16_u" + case .i32x4ExtmulLowI16X8S: return "i32x4.extmul_low_i16x8_s" + case .i32x4ExtmulHighI16X8S: return "i32x4.extmul_high_i16x8_s" + case .i32x4ExtmulLowI16X8U: return "i32x4.extmul_low_i16x8_u" + case .i32x4ExtmulHighI16X8U: return "i32x4.extmul_high_i16x8_u" + case .i64x2ExtmulLowI32X4S: return "i64x2.extmul_low_i32x4_s" + case .i64x2ExtmulHighI32X4S: return "i64x2.extmul_high_i32x4_s" + case .i64x2ExtmulLowI32X4U: return "i64x2.extmul_low_i32x4_u" + case .i64x2ExtmulHighI32X4U: return "i64x2.extmul_high_i32x4_u" + case .i16x8Q15MulrSatS: return "i16x8.q15mulr_sat_s" + case .v128AnyTrue: return "v128.any_true" + case .i64x2Eq: return "i64x2.eq" + case .i64x2Ne: return "i64x2.ne" + case .i64x2LtS: return "i64x2.lt_s" + case .i64x2GtS: return "i64x2.gt_s" + case .i64x2LeS: return "i64x2.le_s" + case .i64x2GeS: return "i64x2.ge_s" + case .i64x2AllTrue: return "i64x2.all_true" + case .f64x2ConvertLowI32X4S: return "f64x2.convert_low_i32x4_s" + case .f64x2ConvertLowI32X4U: return "f64x2.convert_low_i32x4_u" + case .i32x4TruncSatF64X2SZero: return "i32x4.trunc_sat_f64x2_s_zero" + case .i32x4TruncSatF64X2UZero: return "i32x4.trunc_sat_f64x2_u_zero" + case .f32x4DemoteF64X2Zero: return "f32x4.demote_f64x2_zero" + case .f64x2PromoteLowF32X4: return "f64x2.promote_low_f32x4" + case .i8x16Popcnt: return "i8x16.popcnt" + case .i16x8ExtaddPairwiseI8X16S: return "i16x8.extadd_pairwise_i8x16_s" + case .i16x8ExtaddPairwiseI8X16U: return "i16x8.extadd_pairwise_i8x16_u" + case .i32x4ExtaddPairwiseI16X8S: return "i32x4.extadd_pairwise_i16x8_s" + case .i32x4ExtaddPairwiseI16X8U: return "i32x4.extadd_pairwise_i16x8_u" + } +} + +func generatedMnemonic(for op: Instruction.SimdLane) -> String { + switch op { + case .i8x16ExtractLaneS: return "i8x16.extract_lane_s" + case .i8x16ExtractLaneU: return "i8x16.extract_lane_u" + case .i8x16ReplaceLane: return "i8x16.replace_lane" + case .i16x8ExtractLaneS: return "i16x8.extract_lane_s" + case .i16x8ExtractLaneU: return "i16x8.extract_lane_u" + case .i16x8ReplaceLane: return "i16x8.replace_lane" + case .i32x4ExtractLane: return "i32x4.extract_lane" + case .i32x4ReplaceLane: return "i32x4.replace_lane" + case .i64x2ExtractLane: return "i64x2.extract_lane" + case .i64x2ReplaceLane: return "i64x2.replace_lane" + case .f32x4ExtractLane: return "f32x4.extract_lane" + case .f32x4ReplaceLane: return "f32x4.replace_lane" + case .f64x2ExtractLane: return "f64x2.extract_lane" + case .f64x2ReplaceLane: return "f64x2.replace_lane" + } +} + +func generatedMnemonic(for op: Instruction.SimdMemLane) -> String { + switch op { + case .v128Load8Lane: return "v128.load8_lane" + case .v128Load16Lane: return "v128.load16_lane" + case .v128Load32Lane: return "v128.load32_lane" + case .v128Load64Lane: return "v128.load64_lane" + case .v128Store8Lane: return "v128.store8_lane" + case .v128Store16Lane: return "v128.store16_lane" + case .v128Store32Lane: return "v128.store32_lane" + case .v128Store64Lane: return "v128.store64_lane" + } +} + +func generatedDefaultAlign(for op: Instruction.SimdMemLane) -> UInt32 { + switch op { + case .v128Load8Lane: return 0 + case .v128Load16Lane: return 1 + case .v128Load32Lane: return 2 + case .v128Load64Lane: return 3 + case .v128Store8Lane: return 0 + case .v128Store16Lane: return 1 + case .v128Store32Lane: return 2 + case .v128Store64Lane: return 3 + } +} + +func generatedMemargInstruction(_ instruction: Instruction) -> (mnemonic: String, memarg: MemArg, defaultAlign: UInt32)? { + switch instruction { + case .memoryAtomicNotify(let m): return ("memory.atomic.notify", m, 2) + case .memoryAtomicWait32(let m): return ("memory.atomic.wait32", m, 2) + case .memoryAtomicWait64(let m): return ("memory.atomic.wait64", m, 3) + case .i32AtomicRmwAdd(let m): return ("i32.atomic.rmw.add", m, 2) + case .i64AtomicRmwAdd(let m): return ("i64.atomic.rmw.add", m, 3) + case .i32AtomicRmw8AddU(let m): return ("i32.atomic.rmw8.add_u", m, 0) + case .i32AtomicRmw16AddU(let m): return ("i32.atomic.rmw16.add_u", m, 1) + case .i64AtomicRmw8AddU(let m): return ("i64.atomic.rmw8.add_u", m, 0) + case .i64AtomicRmw16AddU(let m): return ("i64.atomic.rmw16.add_u", m, 1) + case .i64AtomicRmw32AddU(let m): return ("i64.atomic.rmw32.add_u", m, 2) + case .i32AtomicRmwSub(let m): return ("i32.atomic.rmw.sub", m, 2) + case .i64AtomicRmwSub(let m): return ("i64.atomic.rmw.sub", m, 3) + case .i32AtomicRmw8SubU(let m): return ("i32.atomic.rmw8.sub_u", m, 0) + case .i32AtomicRmw16SubU(let m): return ("i32.atomic.rmw16.sub_u", m, 1) + case .i64AtomicRmw8SubU(let m): return ("i64.atomic.rmw8.sub_u", m, 0) + case .i64AtomicRmw16SubU(let m): return ("i64.atomic.rmw16.sub_u", m, 1) + case .i64AtomicRmw32SubU(let m): return ("i64.atomic.rmw32.sub_u", m, 2) + case .i32AtomicRmwAnd(let m): return ("i32.atomic.rmw.and", m, 2) + case .i64AtomicRmwAnd(let m): return ("i64.atomic.rmw.and", m, 3) + case .i32AtomicRmw8AndU(let m): return ("i32.atomic.rmw8.and_u", m, 0) + case .i32AtomicRmw16AndU(let m): return ("i32.atomic.rmw16.and_u", m, 1) + case .i64AtomicRmw8AndU(let m): return ("i64.atomic.rmw8.and_u", m, 0) + case .i64AtomicRmw16AndU(let m): return ("i64.atomic.rmw16.and_u", m, 1) + case .i64AtomicRmw32AndU(let m): return ("i64.atomic.rmw32.and_u", m, 2) + case .i32AtomicRmwOr(let m): return ("i32.atomic.rmw.or", m, 2) + case .i64AtomicRmwOr(let m): return ("i64.atomic.rmw.or", m, 3) + case .i32AtomicRmw8OrU(let m): return ("i32.atomic.rmw8.or_u", m, 0) + case .i32AtomicRmw16OrU(let m): return ("i32.atomic.rmw16.or_u", m, 1) + case .i64AtomicRmw8OrU(let m): return ("i64.atomic.rmw8.or_u", m, 0) + case .i64AtomicRmw16OrU(let m): return ("i64.atomic.rmw16.or_u", m, 1) + case .i64AtomicRmw32OrU(let m): return ("i64.atomic.rmw32.or_u", m, 2) + case .i32AtomicRmwXor(let m): return ("i32.atomic.rmw.xor", m, 2) + case .i64AtomicRmwXor(let m): return ("i64.atomic.rmw.xor", m, 3) + case .i32AtomicRmw8XorU(let m): return ("i32.atomic.rmw8.xor_u", m, 0) + case .i32AtomicRmw16XorU(let m): return ("i32.atomic.rmw16.xor_u", m, 1) + case .i64AtomicRmw8XorU(let m): return ("i64.atomic.rmw8.xor_u", m, 0) + case .i64AtomicRmw16XorU(let m): return ("i64.atomic.rmw16.xor_u", m, 1) + case .i64AtomicRmw32XorU(let m): return ("i64.atomic.rmw32.xor_u", m, 2) + case .i32AtomicRmwXchg(let m): return ("i32.atomic.rmw.xchg", m, 2) + case .i64AtomicRmwXchg(let m): return ("i64.atomic.rmw.xchg", m, 3) + case .i32AtomicRmw8XchgU(let m): return ("i32.atomic.rmw8.xchg_u", m, 0) + case .i32AtomicRmw16XchgU(let m): return ("i32.atomic.rmw16.xchg_u", m, 1) + case .i64AtomicRmw8XchgU(let m): return ("i64.atomic.rmw8.xchg_u", m, 0) + case .i64AtomicRmw16XchgU(let m): return ("i64.atomic.rmw16.xchg_u", m, 1) + case .i64AtomicRmw32XchgU(let m): return ("i64.atomic.rmw32.xchg_u", m, 2) + case .i32AtomicRmwCmpxchg(let m): return ("i32.atomic.rmw.cmpxchg", m, 2) + case .i64AtomicRmwCmpxchg(let m): return ("i64.atomic.rmw.cmpxchg", m, 3) + case .i32AtomicRmw8CmpxchgU(let m): return ("i32.atomic.rmw8.cmpxchg_u", m, 0) + case .i32AtomicRmw16CmpxchgU(let m): return ("i32.atomic.rmw16.cmpxchg_u", m, 1) + case .i64AtomicRmw8CmpxchgU(let m): return ("i64.atomic.rmw8.cmpxchg_u", m, 0) + case .i64AtomicRmw16CmpxchgU(let m): return ("i64.atomic.rmw16.cmpxchg_u", m, 1) + case .i64AtomicRmw32CmpxchgU(let m): return ("i64.atomic.rmw32.cmpxchg_u", m, 2) + default: return nil + } +} diff --git a/Sources/WAT/Printer/ModuleCollector.swift b/Sources/WAT/Printer/ModuleCollector.swift new file mode 100644 index 00000000..b8c12d73 --- /dev/null +++ b/Sources/WAT/Printer/ModuleCollector.swift @@ -0,0 +1,100 @@ +import WasmParser +import WasmTypes + +/// Collected information from all sections of a WebAssembly binary module. +struct ModuleInfo { + var types: [FunctionType] = [] + var imports: [Import] = [] + /// Type indices from the function section (one per local function). + var functionTypeIndices: [TypeIndex] = [] + var tables: [Table] = [] + var memories: [Memory] = [] + var globals: [Global] = [] + var exports: [Export] = [] + var start: FunctionIndex? = nil + var elements: [ElementSegment] = [] + /// Code bodies for local functions (parallel to functionTypeIndices). + var codes: [Code] = [] + var data: [DataSegment] = [] + /// Function names from the `name` custom section (index → name). + var functionNames: [UInt32: String] = [:] + /// Local variable names from the `name` custom section + /// (function index → (local index → name)). + var localNames: [UInt32: [UInt32: String]] = [:] + + /// Total number of functions (imports + local definitions). + var functionCount: Int { + let importedFunctions = imports.filter { + if case .function = $0.descriptor { return true } + return false + }.count + return importedFunctions + functionTypeIndices.count + } + + /// Number of imported functions (occupy the first N function indices). + var importedFunctionCount: Int { + imports.filter { + if case .function = $0.descriptor { return true } + return false + }.count + } +} + +/// Parses a WebAssembly binary and collects all sections into a `ModuleInfo`. +/// +/// - Parameters: +/// - bytes: The raw bytes of the WebAssembly binary. +/// - features: The feature set to use while parsing. +/// - Returns: A populated `ModuleInfo`. +func collectModule(stream: Stream, features: WasmFeatureSet = .default) throws -> ModuleInfo { + var info = ModuleInfo() + var parser = WasmParser.Parser(stream: stream, features: features) + while let payload = try parser.parseNext() { + switch payload { + case .header: + break + case .typeSection(let types): + info.types = types + case .importSection(let imports): + info.imports = imports + case .functionSection(let indices): + info.functionTypeIndices = indices + case .tableSection(let tables): + info.tables = tables + case .memorySection(let memories): + info.memories = memories + case .globalSection(let globals): + info.globals = globals + case .exportSection(let exports): + info.exports = exports + case .startSection(let start): + info.start = start + case .elementSection(let elements): + info.elements = elements + case .codeSection(let codes): + info.codes = codes + case .dataSection(let data): + info.data = data + case .dataCount: + break + case .customSection(let section) where section.name == "name": + parseNameSection(section.bytes, into: &info) + case .customSection: + break + } + } + return info +} + +// MARK: - Name section decoding + +private func parseNameSection(_ bytes: ArraySlice, into info: inout ModuleInfo) { + let stream = StaticByteStream(bytes: bytes) + let nameParser = NameSectionParser(stream: stream) + guard let parsedNames = try? nameParser.parseAll() else { return } + for parsedName in parsedNames { + if case .functions(let nameMap) = parsedName { + info.functionNames = nameMap + } + } +} diff --git a/Sources/WAT/Printer/TextInstructionVisitor.swift b/Sources/WAT/Printer/TextInstructionVisitor.swift new file mode 100644 index 00000000..31e71fba --- /dev/null +++ b/Sources/WAT/Printer/TextInstructionVisitor.swift @@ -0,0 +1,384 @@ +import Foundation +import WasmParser +import WasmTypes + +/// A visitor that emits WAT text for each WebAssembly instruction. +/// Conforms to `AnyInstructionVisitor` to handle all instructions via a single switch. +struct TextInstructionVisitor: AnyInstructionVisitor { + typealias VisitorError = Never + + var binaryOffset: Int = 0 + + // MARK: - Context + + /// Module-wide function names (function index → name). + let functionNames: [UInt32: String] + /// Module-wide global names (global index → name). + let globalNames: [UInt32: String] + /// Local variable names for the current function (local index → name). + let localNames: [UInt32: String] + + // MARK: - Output + + private var indentLevel: Int + private let append: (String) -> Void + + // MARK: - Label stack + + /// Stack of labels pushed by block/loop/if. Empty at function body start. + private var labelStack: [String] = [] + + init( + functionNames: [UInt32: String], + globalNames: [UInt32: String], + localNames: [UInt32: String], + indentLevel: Int, + append: @escaping (String) -> Void + ) { + self.functionNames = functionNames + self.globalNames = globalNames + self.localNames = localNames + self.indentLevel = indentLevel + self.append = append + } + + // MARK: - AnyInstructionVisitor + + mutating func visit(_ instruction: Instruction) { + switch instruction { + // ── Control ────────────────────────────────────────────────────────── + case .unreachable: + emit("unreachable") + case .nop: + emit("nop") + case .block(let blockType): + let blockLabel = "block_\(labelStack.count)" + labelStack.append(blockLabel) + emit("block $\(blockLabel)\(blockTypeSuffix(blockType))") + indentLevel += 1 + case .loop(let blockType): + let loopLabel = "loop_\(labelStack.count)" + labelStack.append(loopLabel) + emit("loop $\(loopLabel)\(blockTypeSuffix(blockType))") + indentLevel += 1 + case .if(let blockType): + let ifLabel = "if_\(labelStack.count)" + labelStack.append(ifLabel) + emit("if $\(ifLabel)\(blockTypeSuffix(blockType))") + indentLevel += 1 + case .else: + indentLevel -= 1 + emit("else") + indentLevel += 1 + case .end: + if labelStack.isEmpty { + // Function body end – represented by closing ')' in (func ...), skip. + return + } + labelStack.removeLast() + indentLevel -= 1 + emit("end") + case .br(let d): + emit("br \(labelText(d))") + case .brIf(let d): + emit("br_if \(labelText(d))") + case .brTable(let tbl): + var parts = tbl.labelIndices.map { labelText($0) } + parts.append(labelText(tbl.defaultIndex)) + emit("br_table \(parts.joined(separator: " "))") + case .return: + emit("return") + case .call(let fi): + emit("call \(funcRef(fi))") + case .callIndirect(let ti, let tbl): + emit("call_indirect \(tbl == 0 ? "" : "\(tbl) ")(type \(ti))") + case .returnCall(let fi): + emit("return_call \(funcRef(fi))") + case .returnCallIndirect(let ti, let tbl): + emit("return_call_indirect \(tbl == 0 ? "" : "\(tbl) ")(type \(ti))") + case .callRef(let ti): + emit("call_ref (type \(ti))") + case .returnCallRef(let ti): + emit("return_call_ref (type \(ti))") + case .drop: + emit("drop") + case .select: + emit("select") + case .typedSelect(let t): + emit("select (result \(valueTypeName(t)))") + + // ── Variables ──────────────────────────────────────────────────────── + case .localGet(let i): + emit("local.get \(localRef(i))") + case .localSet(let i): + emit("local.set \(localRef(i))") + case .localTee(let i): + emit("local.tee \(localRef(i))") + case .globalGet(let i): + emit("global.get \(globalRef(i))") + case .globalSet(let i): + emit("global.set \(globalRef(i))") + + // ── Memory ─────────────────────────────────────────────────────────── + case .load(let kind, let m): + emit("\(generatedMnemonic(for: kind))\(memargText(m, defaultAlign: generatedDefaultAlign(for: kind)))") + case .store(let kind, let m): + emit("\(generatedMnemonic(for: kind))\(memargText(m, defaultAlign: generatedDefaultAlign(for: kind)))") + case .memorySize(let mem): + emit(mem == 0 ? "memory.size" : "memory.size \(mem)") + case .memoryGrow(let mem): + emit(mem == 0 ? "memory.grow" : "memory.grow \(mem)") + + // ── Constants ──────────────────────────────────────────────────────── + case .i32Const(let v): + emit("i32.const \(v)") + case .i64Const(let v): + emit("i64.const \(v)") + case .f32Const(let v): + emit("f32.const \(formatF32(v))") + case .f64Const(let v): + emit("f64.const \(formatF64(v))") + + // ── References ─────────────────────────────────────────────────────── + case .refNull(let ht): + emit("ref.null \(heapTypeName(ht))") + case .refIsNull: + emit("ref.is_null") + case .refFunc(let fi): + emit("ref.func \(funcRef(fi))") + case .refAsNonNull: + emit("ref.as_non_null") + case .brOnNull(let d): + emit("br_on_null \(labelText(d))") + case .brOnNonNull(let d): + emit("br_on_non_null \(labelText(d))") + + // ── Comparison ─────────────────────────────────────────────────────── + case .i32Eqz: emit("i32.eqz") + case .i64Eqz: emit("i64.eqz") + case .cmp(let op): emit(generatedMnemonic(for: op)) + + // ── Numeric ────────────────────────────────────────────────────────── + case .unary(let op): emit(generatedMnemonic(for: op)) + case .binary(let op): emit(generatedMnemonic(for: op)) + case .conversion(let op): emit(generatedMnemonic(for: op)) + + // ── Bulk memory / table ────────────────────────────────────────────── + case .memoryInit(let di): + emit("memory.init \(di)") + case .dataDrop(let di): + emit("data.drop \(di)") + case .memoryCopy(let dst, let src): + if dst == 0 && src == 0 { emit("memory.copy") } else { emit("memory.copy \(dst) \(src)") } + case .memoryFill(let mem): + emit(mem == 0 ? "memory.fill" : "memory.fill \(mem)") + case .tableInit(let ei, let tbl): + emit("table.init \(tbl) \(ei)") + case .elemDrop(let ei): + emit("elem.drop \(ei)") + case .tableCopy(let dst, let src): + if dst == 0 && src == 0 { emit("table.copy") } else { emit("table.copy \(dst) \(src)") } + case .tableFill(let tbl): + emit(tbl == 0 ? "table.fill" : "table.fill \(tbl)") + case .tableGet(let tbl): + emit(tbl == 0 ? "table.get" : "table.get \(tbl)") + case .tableSet(let tbl): + emit(tbl == 0 ? "table.set" : "table.set \(tbl)") + case .tableGrow(let tbl): + emit(tbl == 0 ? "table.grow" : "table.grow \(tbl)") + case .tableSize(let tbl): + emit(tbl == 0 ? "table.size" : "table.size \(tbl)") + + // ── Atomics ────────────────────────────────────────────────────────── + case .atomicFence: + emit("atomic.fence") + + // ── SIMD ───────────────────────────────────────────────────────────── + case .v128Const(let v): + let bytes = v.bytes.map { String($0) }.joined(separator: " ") + emit("v128.const i8x16 \(bytes)") + case .i8x16Shuffle(let mask): + let lanes = mask.lanes.map { String($0) }.joined(separator: " ") + emit("i8x16.shuffle \(lanes)") + case .simd(let op): emit(generatedMnemonic(for: op)) + case .simdLane(let op, let lane): emit("\(generatedMnemonic(for: op)) \(lane)") + case .simdMemLane(let op, let m, let lane): + emit("\(generatedMnemonic(for: op))\(memargText(m, defaultAlign: generatedDefaultAlign(for: op))) \(lane)") + + // ── Non-categorized memarg instructions ────────────── + default: + if let (mnemonic, m, align) = generatedMemargInstruction(instruction) { + emit("\(mnemonic)\(memargText(m, defaultAlign: align))") + } + } + } + + // MARK: - Emit helper + + private func emit(_ text: String) { + let indent = String(repeating: " ", count: indentLevel) + append(indent + text + "\n") + } + + // MARK: - Label helpers + + private func labelText(_ relativeDepth: UInt32) -> String { + let idx = Int(relativeDepth) + let stackIdx = labelStack.count - 1 - idx + if stackIdx >= 0 { + return "$\(labelStack[stackIdx])" + } + return "\(relativeDepth)" + } + + // MARK: - Name helpers + + private func funcRef(_ idx: UInt32) -> String { + if let name = functionNames[idx] { return "$\(name)" } + return "\(idx)" + } + + private func globalRef(_ idx: UInt32) -> String { + if let name = globalNames[idx] { return "$\(name)" } + return "\(idx)" + } + + private func localRef(_ idx: UInt32) -> String { + if let name = localNames[idx] { return "$\(name)" } + return "\(idx)" + } + + // MARK: - BlockType suffix + + private func blockTypeSuffix(_ bt: BlockType) -> String { + switch bt { + case .empty: return "" + case .type(let vt): return " (result \(valueTypeName(vt)))" + case .funcType(let ti): return " (type \(ti))" + } + } + + // MARK: - MemArg + + private func memargText(_ m: MemArg, defaultAlign: UInt32) -> String { + var parts: [String] = [] + if m.offset != 0 { parts.append("offset=\(m.offset)") } + if m.align != defaultAlign { parts.append("align=\(1 << m.align)") } + if parts.isEmpty { return "" } + return " " + parts.joined(separator: " ") + } + +} + +// MARK: - Module-level formatting helpers (used by both WatPrinter and TextInstructionVisitor) + +func valueTypeName(_ vt: ValueType) -> String { + switch vt { + case .i32: return "i32" + case .i64: return "i64" + case .f32: return "f32" + case .f64: return "f64" + case .v128: return "v128" + case .ref(let rt): + return refTypeName(rt) + } +} + +func refTypeName(_ rt: ReferenceType) -> String { + let base = heapTypeName(rt.heapType) + if rt.isNullable { + // funcref and externref are canonical shorthands + if rt.heapType == .funcRef { return "funcref" } + if rt.heapType == .externRef { return "externref" } + return "(ref null \(base))" + } + return "(ref \(base))" +} + +func heapTypeName(_ ht: HeapType) -> String { + switch ht { + case .abstract(let abs): + switch abs { + case .funcRef: return "func" + case .externRef: return "extern" + } + case .concrete(let ti): + return "\(ti)" + } +} + +func formatF32(_ v: IEEE754.Float32) -> String { + let f = Float32(bitPattern: v.bitPattern) + if f.isNaN { + // Preserve NaN payload for exact round-trip. + let payload = v.bitPattern & 0x7FFFFF + let signStr = (v.bitPattern & 0x8000_0000) != 0 ? "-" : "" + if payload == 0x400000 { + return "\(signStr)nan" // canonical NaN + } + return "\(signStr)nan:0x\(String(payload, radix: 16))" + } + if f.isInfinite { return f > 0 ? "inf" : "-inf" } + // Use hex float for exact round-trip. + return hexFloat32(v.bitPattern) +} + +func formatF64(_ v: IEEE754.Float64) -> String { + let f = Double(bitPattern: v.bitPattern) + if f.isNaN { + let payload = v.bitPattern & 0x000F_FFFF_FFFF_FFFF + let signStr = (v.bitPattern & 0x8000_0000_0000_0000) != 0 ? "-" : "" + if payload == 0x0008_0000_0000_0000 { + return "\(signStr)nan" // canonical NaN + } + return "\(signStr)nan:0x\(String(payload, radix: 16))" + } + if f.isInfinite { return f > 0 ? "inf" : "-inf" } + return hexFloat64(v.bitPattern) +} + +/// Formats a 32-bit float bit pattern as a WAT hex float string. +private func hexFloat32(_ bits: UInt32) -> String { + let sign = (bits >> 31) != 0 ? "-" : "" + let exp = Int((bits >> 23) & 0xFF) + let mantissa = bits & 0x7FFFFF + + if exp == 0 { + // Subnormal or zero + if mantissa == 0 { return "\(sign)0x0p+0" } + // Subnormal: no implicit leading 1 + return "\(sign)0x\(String(mantissa, radix: 16))p-149" + } + // Normal: exponent biased by 127, implicit leading 1 + let e = exp - 127 + // f32 mantissa is 23 bits; shift left by 1 to fill 24 bits (6 hex digits). + let mantHex = String(format: "%06x", mantissa << 1) + // Trim trailing zeros + let trimmed = + mantHex.hasSuffix("000000") + ? String(mantHex.dropLast(6)) + : mantHex.hasSuffix("0000") ? String(mantHex.dropLast(4)) : mantHex.hasSuffix("00") ? String(mantHex.dropLast(2)) : mantHex.hasSuffix("0") ? String(mantHex.dropLast(1)) : mantHex + let mant = trimmed.isEmpty ? "" : ".\(trimmed)" + let eSign = e >= 0 ? "+" : "" + return "\(sign)0x1\(mant)p\(eSign)\(e)" +} + +/// Formats a 64-bit float bit pattern as a WAT hex float string. +private func hexFloat64(_ bits: UInt64) -> String { + let sign = (bits >> 63) != 0 ? "-" : "" + let exp = Int((bits >> 52) & 0x7FF) + let mantissa = bits & 0x000F_FFFF_FFFF_FFFF + + if exp == 0 { + if mantissa == 0 { return "\(sign)0x0p+0" } + return "\(sign)0x\(String(mantissa, radix: 16))p-1074" + } + let e = exp - 1023 + // Use Swift's native radix formatting to avoid %x truncating UInt64 to 32 bits. + let rawHex = String(mantissa, radix: 16) + let mantHex = String(repeating: "0", count: max(0, 13 - rawHex.count)) + rawHex + let trimmed = String(mantHex.reversed().drop(while: { $0 == "0" }).reversed()) + let mant = trimmed.isEmpty ? "" : ".\(trimmed)" + let eSign = e >= 0 ? "+" : "" + return "\(sign)0x1\(mant)p\(eSign)\(e)" +} diff --git a/Sources/WAT/Printer/WatPrinter.swift b/Sources/WAT/Printer/WatPrinter.swift new file mode 100644 index 00000000..e5217478 --- /dev/null +++ b/Sources/WAT/Printer/WatPrinter.swift @@ -0,0 +1,335 @@ +import Foundation +import WasmParser +import WasmTypes + +/// Converts a `ModuleInfo` (parsed from a Wasm binary) into WAT text. +struct WatPrinter { + private let info: ModuleInfo + private var output: String = "" + + init(info: ModuleInfo) { + self.info = info + } + + /// Produces the full WAT text for the module. + mutating func print() throws -> String { + output = "" + writeLine("(module") + try printTypes() + try printImports() + try printFunctions() + printTables() + printMemories() + printGlobals() + printExports() + printStart() + try printElements() + printData() + writeLine(")") + return output + } + + // MARK: - Section printers + + private mutating func printTypes() throws { + for (i, ft) in info.types.enumerated() { + writeLine(" (type (;\(i);) (func\(funcTypeSuffix(ft))))") + } + } + + private mutating func printImports() throws { + for imp in info.imports { + let desc: String + switch imp.descriptor { + case .function(let ti): + desc = "(func (type \(ti)))" + case .table(let tt): + desc = "(table \(tableLimitsText(tt.limits)) \(refTypeName(tt.elementType)))" + case .memory(let mt): + desc = "(memory \(memoryLimitsText(mt)))" + case .global(let gt): + let mut = + gt.mutability == .variable + ? "(mut \(valueTypeName(gt.valueType)))" + : valueTypeName(gt.valueType) + desc = "(global \(mut))" + } + writeLine(" (import \(quotedString(imp.module)) \(quotedString(imp.name)) \(desc))") + } + } + + private mutating func printFunctions() throws { + let importedFuncCount = info.importedFunctionCount + for (localIdx, typeIdx) in info.functionTypeIndices.enumerated() { + let funcIdx = UInt32(importedFuncCount + localIdx) + guard localIdx < info.codes.count else { continue } + let code = info.codes[localIdx] + let ft = typeIdx < info.types.count ? info.types[Int(typeIdx)] : FunctionType(parameters: [], results: []) + + let funcName = info.functionNames[funcIdx].map { " $\($0)" } ?? "" + var header = " (func\(funcName) (;\(funcIdx);) (type \(typeIdx))" + + let params = ft.parameters + if !params.isEmpty { + header += " (param" + for vt in params { header += " \(valueTypeName(vt))" } + header += ")" + } + if !ft.results.isEmpty { + header += " (result" + for vt in ft.results { header += " \(valueTypeName(vt))" } + header += ")" + } + writeLine(header) + + let localNameMap = info.localNames[funcIdx] ?? [:] + for (li, localType) in code.locals.enumerated() { + let lIdx = UInt32(params.count + li) + let localName = localNameMap[lIdx].map { " $\($0)" } ?? "" + writeLine(" (local\(localName) \(valueTypeName(localType)))") + } + + // Collect instruction lines + var instrLines: [String] = [] + var visitor = TextInstructionVisitor( + functionNames: info.functionNames, + globalNames: [:], + localNames: localNameMap, + indentLevel: 2, + append: { line in instrLines.append(line) } + ) + var exprParser = WasmParser.ExpressionParser(code: code) + while let visit = try exprParser.parse() { + visit(visitor: &visitor) + } + for line in instrLines { + output += line + } + + writeLine(" )") + } + } + + private mutating func printTables() { + let importedCount = info.imports.filter { + if case .table = $0.descriptor { return true } + return false + }.count + for (i, table) in info.tables.enumerated() { + let idx = importedCount + i + let limits = tableLimitsText(table.type.limits) + let elemType = refTypeName(table.type.elementType) + writeLine(" (table (;\(idx);) \(limits) \(elemType))") + } + } + + private mutating func printMemories() { + let importedCount = info.imports.filter { + if case .memory = $0.descriptor { return true } + return false + }.count + for (i, mem) in info.memories.enumerated() { + let idx = importedCount + i + writeLine(" (memory (;\(idx);) \(memoryLimitsText(mem.type)))") + } + } + + private mutating func printGlobals() { + let importedCount = info.imports.filter { + if case .global = $0.descriptor { return true } + return false + }.count + for (i, global) in info.globals.enumerated() { + let idx = importedCount + i + let mut = global.type.mutability == .variable + let typeStr = + mut + ? "(mut \(valueTypeName(global.type.valueType)))" + : valueTypeName(global.type.valueType) + let initStr = constExprStr(global.initializer) + writeLine(" (global (;\(idx);) \(typeStr) (\(initStr)))") + } + } + + private mutating func printExports() { + for exp in info.exports { + let desc: String + switch exp.descriptor { + case .function(let i): desc = "(func \(i))" + case .table(let i): desc = "(table \(i))" + case .memory(let i): desc = "(memory \(i))" + case .global(let i): desc = "(global \(i))" + } + writeLine(" (export \(quotedString(exp.name)) \(desc))") + } + } + + private mutating func printStart() { + if let s = info.start { + let name = info.functionNames[s].map { "$\($0)" } ?? "\(s)" + writeLine(" (start \(name))") + } + } + + private mutating func printElements() throws { + for (i, elem) in info.elements.enumerated() { + let useExpressions = elemUsesExpressions(elem) + let typeStr = refTypeName(elem.type) + + let indicesPart: String + if useExpressions { + let exprs = elem.initializer.map { expr -> String in + let parts = constExprParts(expr) + if parts.count == 1 { + return "(\(singleInstrStr(parts[0])))" + } + return "(" + parts.map { singleInstrStr($0) }.joined(separator: " ") + ")" + } + indicesPart = "\(typeStr) \(exprs.joined(separator: " "))" + } else { + let indices = elem.initializer.compactMap { expr -> String? in + guard let first = expr.first, case .refFunc(let fi) = first else { return nil } + return info.functionNames[fi].map { "$\($0)" } ?? "\(fi)" + } + indicesPart = "func \(indices.joined(separator: " "))" + } + + switch elem.mode { + case .active(let table, let offset): + let offsetStr = constExprStr(offset) + if table == 0 { + writeLine(" (elem (;\(i);) (\(offsetStr)) \(indicesPart))") + } else { + writeLine(" (elem (;\(i);) (table \(table)) (\(offsetStr)) \(indicesPart))") + } + case .passive: + writeLine(" (elem (;\(i);) \(indicesPart))") + case .declarative: + writeLine(" (elem (;\(i);) declare \(indicesPart))") + } + } + } + + private mutating func printData() { + for (i, seg) in info.data.enumerated() { + switch seg { + case .active(let active): + let offsetStr = constExprStr(active.offset) + let dataStr = bytesToWatString(Array(active.initializer)) + writeLine(" (data (;\(i);) (\(offsetStr)) \"\(dataStr)\")") + case .passive(let bytes): + let dataStr = bytesToWatString(Array(bytes)) + writeLine(" (data (;\(i);) \"\(dataStr)\")") + } + } + } + + // MARK: - Helpers + + private mutating func writeLine(_ s: String) { + output += s + "\n" + } + + private func funcTypeSuffix(_ ft: FunctionType) -> String { + var s = "" + if !ft.parameters.isEmpty { + s += " (param" + for vt in ft.parameters { s += " \(valueTypeName(vt))" } + s += ")" + } + if !ft.results.isEmpty { + s += " (result" + for vt in ft.results { s += " \(valueTypeName(vt))" } + s += ")" + } + return s + } + + private func tableLimitsText(_ limits: Limits) -> String { + var s = "" + if limits.isMemory64 { s += "i64 " } + s += "\(limits.min)" + if let max = limits.max { s += " \(max)" } + return s + } + + private func memoryLimitsText(_ limits: Limits) -> String { + var s = "" + if limits.isMemory64 { s += "i64 " } + s += "\(limits.min)" + if let max = limits.max { s += " \(max)" } + if limits.shared { s += " shared" } + return s + } + + /// Returns whether an element segment was stored using const expressions + /// vs bare function indices. + private func elemUsesExpressions(_ seg: ElementSegment) -> Bool { + guard let first = seg.initializer.first else { return false } + return first.last == .end + } + + /// Renders a `ConstExpression` as text, stripping the trailing `end`. + private func constExprStr(_ expr: ConstExpression) -> String { + let parts = constExprParts(expr) + return parts.map { singleInstrStr($0) }.joined(separator: " ") + } + + /// Returns the instructions of a const expression without the trailing `end`. + private func constExprParts(_ expr: ConstExpression) -> [Instruction] { + if let last = expr.last, last == .end { + return Array(expr.dropLast()) + } + return expr + } + + /// Returns the WAT text for a single instruction (for const expressions). + private func singleInstrStr(_ instr: Instruction) -> String { + switch instr { + case .i32Const(let v): return "i32.const \(v)" + case .i64Const(let v): return "i64.const \(v)" + case .f32Const(let v): return "f32.const \(formatF32(v))" + case .f64Const(let v): return "f64.const \(formatF64(v))" + case .v128Const(let v): + let bytes = v.bytes.map { String($0) }.joined(separator: " ") + return "v128.const i8x16 \(bytes)" + case .globalGet(let i): return "global.get \(i)" + case .refNull(let ht): return "ref.null \(heapTypeName(ht))" + case .refFunc(let fi): + return "ref.func \(info.functionNames[fi].map { "$\($0)" } ?? "\(fi)")" + default: + return "nop" + } + } + + /// Converts raw bytes to a WAT-safe escaped string (without surrounding quotes). + private func bytesToWatString(_ bytes: [UInt8]) -> String { + var s = "" + for byte in bytes { + switch byte { + case 0x09: s += "\\t" + case 0x0A: s += "\\n" + case 0x0D: s += "\\r" + case 0x22: s += "\\\"" + case 0x5C: s += "\\\\" + case 0x20...0x7E: s += String(UnicodeScalar(byte)) + default: s += String(format: "\\%02x", byte) + } + } + return s + } + + /// Wraps a string in WAT-style double quotes with proper escaping. + private func quotedString(_ s: String) -> String { + var result = "\"" + for byte in s.utf8 { + switch byte { + case 0x22: result += "\\\"" + case 0x5C: result += "\\\\" + default: result += String(UnicodeScalar(byte)) + } + } + result += "\"" + return result + } +} diff --git a/Sources/WAT/WAT.swift b/Sources/WAT/WAT.swift index 7e8a54d3..1e97165d 100644 --- a/Sources/WAT/WAT.swift +++ b/Sources/WAT/WAT.swift @@ -1,4 +1,5 @@ import WasmParser +import WasmTypes /// Options for encoding a WebAssembly module into a binary format. public struct EncodeOptions: Sendable { @@ -447,3 +448,28 @@ func parseWAT(_ parser: inout Parser, features: WasmFeatureSet) throws(WatParser parser: parser ) } + +// MARK: - wasm2wat + +/// Converts a WebAssembly binary into its WebAssembly Text (WAT) representation. +/// +/// - Parameters: +/// - stream: The raw bytes stream of a WebAssembly binary module, either `StaticByteStream` or `FileHandleStream`. +/// - features: The feature set to use while parsing the binary (default: `.default`). +/// - Returns: A `String` containing the WAT representation of the module. +/// +/// ```swift +/// import WAT +/// +/// let wasm = ... // A stream of valid .wasm binary data +/// let wat = try wasm2wat(wasm) +/// print(wat) +/// ``` +public func wasm2wat( + _ stream: Stream, + features: WasmFeatureSet = .default +) throws -> String { + let info = try collectModule(stream: stream, features: features) + var printer = WatPrinter(info: info) + return try printer.print() +} diff --git a/Sources/WAT/WatParserError.swift b/Sources/WAT/WatParserError.swift index 053cddf7..24da5188 100644 --- a/Sources/WAT/WatParserError.swift +++ b/Sources/WAT/WatParserError.swift @@ -1,3 +1,5 @@ +import WasmTypes + /// An error type thrown during WAT (WebAssembly Text Format) parsing. public struct WatParserError: Swift.Error { package let message: String diff --git a/Sources/WasmParser/Stream/FileHandleStream.swift b/Sources/WasmParser/Stream/FileHandleStream.swift index da4a87b6..1f3f7435 100644 --- a/Sources/WasmParser/Stream/FileHandleStream.swift +++ b/Sources/WasmParser/Stream/FileHandleStream.swift @@ -1,5 +1,6 @@ import struct SystemPackage.FileDescriptor +/// A stream backed by an open `FileDescriptor`. public final class FileHandleStream: ByteStream { private(set) public var currentIndex: Int = 0 @@ -10,6 +11,12 @@ public final class FileHandleStream: ByteStream { private var startOffset: Int = 0 private var bytes: [UInt8] = [] + /// Initializes a new stream at 0 offset for a given open file descriptor. + /// - Parameters: + /// - fileHandle: open `FileDescriptor` that's semantically (not enforced statically due to copyability of + /// `FileDescriptor` type) borrowed and not owned by this stream. It's the responsibility of the user to + /// close the file descriptor after `FileHandleStream` is consumed. + /// - bufferLength: size of stream's underlying buffer. public init(fileHandle: FileDescriptor, bufferLength: Int = 1024 * 8) throws { self.fileHandle = fileHandle self.bufferLength = bufferLength diff --git a/Sources/WasmTypes/CMakeLists.txt b/Sources/WasmTypes/CMakeLists.txt index 4378acb8..9365a060 100644 --- a/Sources/WasmTypes/CMakeLists.txt +++ b/Sources/WasmTypes/CMakeLists.txt @@ -1,4 +1,5 @@ add_wasmkit_library(WasmTypes GuestMemory.swift + Location.swift WasmTypes.swift ) diff --git a/Sources/WAT/Location.swift b/Sources/WasmTypes/Location.swift similarity index 82% rename from Sources/WAT/Location.swift rename to Sources/WasmTypes/Location.swift index 91a51df7..6713a161 100644 --- a/Sources/WAT/Location.swift +++ b/Sources/WasmTypes/Location.swift @@ -1,9 +1,10 @@ -/// A location in a WAT source file. +/// A location in a source file. public struct Location: Equatable, CustomDebugStringConvertible, Sendable { - let index: Lexer.Index + package typealias Index = String.UnicodeScalarView.Index + let index: Index let source: String.UnicodeScalarView - init(at index: Lexer.Index, in source: String.UnicodeScalarView) { + package init(at index: Index, in source: String.UnicodeScalarView) { self.index = index self.source = source } @@ -28,7 +29,7 @@ public struct Location: Equatable, CustomDebugStringConvertible, Sendable { /// - index: The index in the source /// - source: The source string /// - Returns: The location of the index in line-column style. Line is 1-indexed and column is 0-indexed. -func sourceLocation(at index: Lexer.Index, in source: String.UnicodeScalarView) -> (line: Int, column: Int) { +func sourceLocation(at index: Location.Index, in source: String.UnicodeScalarView) -> (line: Int, column: Int) { let slice = source[.. StaticByteStream { + try StaticByteStream(bytes: wat2wasm(wat)) + } + + // MARK: - ModuleCollector unit tests + + @Test func collectEmptyModule() throws { + let binary = try assemble("(module)") + let info = try collectModule(stream: binary) + #expect(info.types.isEmpty) + #expect(info.imports.isEmpty) + #expect(info.functionTypeIndices.isEmpty) + #expect(info.tables.isEmpty) + #expect(info.memories.isEmpty) + #expect(info.globals.isEmpty) + #expect(info.exports.isEmpty) + #expect(info.start == nil) + #expect(info.elements.isEmpty) + #expect(info.codes.isEmpty) + #expect(info.data.isEmpty) + } + + @Test func collectTypeSection() throws { + let binary = try assemble( + """ + (module + (type (func (param i32 i32) (result i32))) + (type (func)) + ) + """) + let info = try collectModule(stream: binary) + #expect(info.types.count == 2) + #expect(info.types[0].parameters == [.i32, .i32]) + #expect(info.types[0].results == [.i32]) + #expect(info.types[1].parameters == []) + #expect(info.types[1].results == []) + } + + @Test func collectFunctionAndCode() throws { + let binary = try assemble( + """ + (module + (func (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + ) + """) + let info = try collectModule(stream: binary) + #expect(info.functionTypeIndices.count == 1) + #expect(info.codes.count == 1) + #expect(info.codes[0].locals == []) + } + + @Test func collectImports() throws { + let binary = try assemble( + """ + (module + (import "env" "log" (func (param i32))) + (import "env" "mem" (memory 1)) + ) + """) + let info = try collectModule(stream: binary) + #expect(info.imports.count == 2) + #expect(info.imports[0].module == "env") + #expect(info.imports[0].name == "log") + if case .function = info.imports[0].descriptor { + // ok + } else { + Issue.record("Expected function import") + } + #expect(info.imports[1].name == "mem") + if case .memory = info.imports[1].descriptor { + // ok + } else { + Issue.record("Expected memory import") + } + } + + @Test func collectExports() throws { + let binary = try assemble( + """ + (module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + ) + """) + let info = try collectModule(stream: binary) + #expect(info.exports.count == 1) + #expect(info.exports[0].name == "add") + if case .function(let idx) = info.exports[0].descriptor { + #expect(idx == 0) + } else { + Issue.record("Expected function export") + } + } + + @Test func collectMemory() throws { + let binary = try assemble("(module (memory 1 4))") + let info = try collectModule(stream: binary) + #expect(info.memories.count == 1) + #expect(info.memories[0].type.min == 1) + #expect(info.memories[0].type.max == 4) + } + + @Test func collectGlobal() throws { + let binary = try assemble( + """ + (module + (global i32 (i32.const 42)) + (global (mut i64) (i64.const -1)) + ) + """) + let info = try collectModule(stream: binary) + #expect(info.globals.count == 2) + #expect(info.globals[0].type.mutability == .constant) + #expect(info.globals[0].type.valueType == .i32) + #expect(info.globals[1].type.mutability == .variable) + #expect(info.globals[1].type.valueType == .i64) + } + + @Test func collectDataSegment() throws { + let binary = try assemble( + """ + (module + (memory 1) + (data (i32.const 0) "hello") + ) + """) + let info = try collectModule(stream: binary) + #expect(info.data.count == 1) + if case .active(let seg) = info.data[0] { + #expect(seg.index == 0) + #expect(Array(seg.initializer) == Array("hello".utf8)) + } else { + Issue.record("Expected active data segment") + } + } + + // MARK: - WatPrinter unit tests + + @Test func printEmptyModule() throws { + let binary = try assemble("(module)") + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat == "(module\n)\n") + } + + @Test func printFunctionType() throws { + let binary = try assemble( + """ + (module + (type (func (param i32 i32) (result i32))) + ) + """) + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat.contains("(type (;0;) (func (param i32 i32) (result i32)))")) + } + + @Test func printMemoryWithMax() throws { + let binary = try assemble("(module (memory 1 4))") + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat.contains("(memory (;0;) 1 4)")) + } + + @Test func printExport() throws { + let binary = try assemble( + """ + (module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + ) + """) + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat.contains(#"(export "add" (func 0))"#)) + } + + @Test func printGlobalMutable() throws { + let binary = try assemble( + """ + (module + (global (mut i32) (i32.const 0)) + ) + """) + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat.contains("(global (;0;) (mut i32) (i32.const 0))")) + } + + @Test func printDataSegment() throws { + let binary = try assemble( + """ + (module + (memory 1) + (data (i32.const 0) "hello") + ) + """) + let info = try collectModule(stream: binary) + var printer = WatPrinter(info: info) + let wat = try printer.print() + #expect(wat.contains("(data (;0;)")) + #expect(wat.contains("hello")) + } +} diff --git a/Tests/WasmKitTests/SpectestTests.swift b/Tests/WasmKitTests/SpectestTests.swift index 3c3be6f7..09f5d8b7 100644 --- a/Tests/WasmKitTests/SpectestTests.swift +++ b/Tests/WasmKitTests/SpectestTests.swift @@ -1,6 +1,9 @@ import Foundation +import SystemPackage import Testing +import WAT import WasmKit +import WasmParser @Suite struct SpectestTests { @@ -41,5 +44,29 @@ struct SpectestTests { let runner = try SpectestRunner(configuration: defaultConfig) try runner.run(test: test, reporter: NullSpectestProgressReporter()) } + + @Test(arguments: try SpectestDiscovery(path: SpectestTests.testPaths).discover()) + func roundTrip(test: TestCase) throws { + guard let data = FileManager.default.contents(atPath: test.path) else { return } + let rootPath = FilePath(test.path).removingLastComponent() + let features = WastRunContext.deriveFeatureSet(rootPath: rootPath) + var content = try parseWAST(String(data: data, encoding: .utf8)!, features: features) + + while let (directive, _) = try content.nextDirective() { + guard case .module(let moduleDirective) = directive else { continue } + let binary: [UInt8] + switch moduleDirective.source { + case .text(let wat): binary = try wat.encode() + case .binary: + // Binary modules may use non-canonical encodings (e.g. non-minimal LEB128). + continue + case .quote(let text): binary = try wat2wasm(String(decoding: text, as: UTF8.self), features: features) + } + + let text = try wasm2wat(StaticByteStream(bytes: binary), features: features) + let binary2 = try wat2wasm(text, features: features) + #expect(binary == binary2, "Round-trip mismatch in \(test.relativePath)") + } + } #endif } diff --git a/Utilities/Instructions.json b/Utilities/Instructions.json index 050e74f7..0395d498 100644 --- a/Utilities/Instructions.json +++ b/Utilities/Instructions.json @@ -24,29 +24,29 @@ ["mvp" , "local.tee" , ["0x22"] , [["localIndex", "UInt32"]] , null ], ["mvp" , "global.get" , ["0x23"] , [["globalIndex", "UInt32"]] , null ], ["mvp" , "global.set" , ["0x24"] , [["globalIndex", "UInt32"]] , null ], - ["mvp" , "i32.load" , ["0x28"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load" , ["0x29"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "f32.load" , ["0x2A"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "f64.load" , ["0x2B"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i32.load8_s" , ["0x2C"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i32.load8_u" , ["0x2D"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i32.load16_s" , ["0x2E"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i32.load16_u" , ["0x2F"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load8_s" , ["0x30"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load8_u" , ["0x31"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load16_s" , ["0x32"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load16_u" , ["0x33"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load32_s" , ["0x34"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i64.load32_u" , ["0x35"] , [["memarg", "MemArg"]] , "load" ], - ["mvp" , "i32.store" , ["0x36"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i64.store" , ["0x37"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "f32.store" , ["0x38"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "f64.store" , ["0x39"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i32.store8" , ["0x3A"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i32.store16" , ["0x3B"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i64.store8" , ["0x3C"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i64.store16" , ["0x3D"] , [["memarg", "MemArg"]] , "store" ], - ["mvp" , "i64.store32" , ["0x3E"] , [["memarg", "MemArg"]] , "store" ], + ["mvp" , "i32.load" , ["0x28"] , [["memarg", "MemArg"]] , "load" , 2], + ["mvp" , "i64.load" , ["0x29"] , [["memarg", "MemArg"]] , "load" , 3], + ["mvp" , "f32.load" , ["0x2A"] , [["memarg", "MemArg"]] , "load" , 2], + ["mvp" , "f64.load" , ["0x2B"] , [["memarg", "MemArg"]] , "load" , 3], + ["mvp" , "i32.load8_s" , ["0x2C"] , [["memarg", "MemArg"]] , "load" , 0], + ["mvp" , "i32.load8_u" , ["0x2D"] , [["memarg", "MemArg"]] , "load" , 0], + ["mvp" , "i32.load16_s" , ["0x2E"] , [["memarg", "MemArg"]] , "load" , 1], + ["mvp" , "i32.load16_u" , ["0x2F"] , [["memarg", "MemArg"]] , "load" , 1], + ["mvp" , "i64.load8_s" , ["0x30"] , [["memarg", "MemArg"]] , "load" , 0], + ["mvp" , "i64.load8_u" , ["0x31"] , [["memarg", "MemArg"]] , "load" , 0], + ["mvp" , "i64.load16_s" , ["0x32"] , [["memarg", "MemArg"]] , "load" , 1], + ["mvp" , "i64.load16_u" , ["0x33"] , [["memarg", "MemArg"]] , "load" , 1], + ["mvp" , "i64.load32_s" , ["0x34"] , [["memarg", "MemArg"]] , "load" , 2], + ["mvp" , "i64.load32_u" , ["0x35"] , [["memarg", "MemArg"]] , "load" , 2], + ["mvp" , "i32.store" , ["0x36"] , [["memarg", "MemArg"]] , "store" , 2], + ["mvp" , "i64.store" , ["0x37"] , [["memarg", "MemArg"]] , "store" , 3], + ["mvp" , "f32.store" , ["0x38"] , [["memarg", "MemArg"]] , "store" , 2], + ["mvp" , "f64.store" , ["0x39"] , [["memarg", "MemArg"]] , "store" , 3], + ["mvp" , "i32.store8" , ["0x3A"] , [["memarg", "MemArg"]] , "store" , 0], + ["mvp" , "i32.store16" , ["0x3B"] , [["memarg", "MemArg"]] , "store" , 1], + ["mvp" , "i64.store8" , ["0x3C"] , [["memarg", "MemArg"]] , "store" , 0], + ["mvp" , "i64.store16" , ["0x3D"] , [["memarg", "MemArg"]] , "store" , 1], + ["mvp" , "i64.store32" , ["0x3E"] , [["memarg", "MemArg"]] , "store" , 2], ["mvp" , "memory.size" , ["0x3F"] , [["memory", "UInt32"]] , null ], ["mvp" , "memory.grow" , ["0x40"] , [["memory", "UInt32"]] , null ], ["mvp" , "i32.const" , ["0x41"] , [["value", "Int32"]] , null ], @@ -208,84 +208,84 @@ ["saturatingFloatToInt", "i64.trunc_sat_f64_s" , ["0xFC", "0x06"] , [] , "conversion" ], ["saturatingFloatToInt", "i64.trunc_sat_f64_u" , ["0xFC", "0x07"] , [] , "conversion" ], ["threads" , "atomic.fence" , ["0xFE", "0x03", "0x00"], [] , null ], - ["threads" , "i32.atomic.load" , ["0xFE", "0x10"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i64.atomic.load" , ["0xFE", "0x11"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i32.atomic.load8_u" , ["0xFE", "0x12"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i32.atomic.load16_u" , ["0xFE", "0x13"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i64.atomic.load8_u" , ["0xFE", "0x14"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i64.atomic.load16_u" , ["0xFE", "0x15"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i64.atomic.load32_u" , ["0xFE", "0x16"] , [["memarg", "MemArg"]] , "load" ], - ["threads" , "i32.atomic.store" , ["0xFE", "0x17"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i64.atomic.store" , ["0xFE", "0x18"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i32.atomic.store8" , ["0xFE", "0x19"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i32.atomic.store16" , ["0xFE", "0x1A"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i64.atomic.store8" , ["0xFE", "0x1B"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i64.atomic.store16" , ["0xFE", "0x1C"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "i64.atomic.store32" , ["0xFE", "0x1D"] , [["memarg", "MemArg"]] , "store" ], - ["threads" , "memory.atomic.notify" , ["0xFE", "0x00"] , [["memarg", "MemArg"]] , null ], - ["threads" , "memory.atomic.wait32" , ["0xFE", "0x01"] , [["memarg", "MemArg"]] , null ], - ["threads" , "memory.atomic.wait64" , ["0xFE", "0x02"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.add" , ["0xFE", "0x1E"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.add" , ["0xFE", "0x1F"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.add_u" , ["0xFE", "0x20"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.add_u" , ["0xFE", "0x21"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.add_u" , ["0xFE", "0x22"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.add_u" , ["0xFE", "0x23"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.add_u" , ["0xFE", "0x24"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.sub" , ["0xFE", "0x25"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.sub" , ["0xFE", "0x26"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.sub_u" , ["0xFE", "0x27"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.sub_u" , ["0xFE", "0x28"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.sub_u" , ["0xFE", "0x29"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.sub_u" , ["0xFE", "0x2A"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.sub_u" , ["0xFE", "0x2B"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.and" , ["0xFE", "0x2C"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.and" , ["0xFE", "0x2D"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.and_u" , ["0xFE", "0x2E"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.and_u" , ["0xFE", "0x2F"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.and_u" , ["0xFE", "0x30"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.and_u" , ["0xFE", "0x31"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.and_u" , ["0xFE", "0x32"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.or" , ["0xFE", "0x33"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.or" , ["0xFE", "0x34"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.or_u" , ["0xFE", "0x35"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.or_u" , ["0xFE", "0x36"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.or_u" , ["0xFE", "0x37"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.or_u" , ["0xFE", "0x38"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.or_u" , ["0xFE", "0x39"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.xor" , ["0xFE", "0x3A"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.xor" , ["0xFE", "0x3B"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.xor_u" , ["0xFE", "0x3C"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.xor_u" , ["0xFE", "0x3D"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.xor_u" , ["0xFE", "0x3E"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.xor_u" , ["0xFE", "0x3F"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.xor_u" , ["0xFE", "0x40"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.xchg" , ["0xFE", "0x41"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.xchg" , ["0xFE", "0x42"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.xchg_u" , ["0xFE", "0x43"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.xchg_u" , ["0xFE", "0x44"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.xchg_u" , ["0xFE", "0x45"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.xchg_u" , ["0xFE", "0x46"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.xchg_u" , ["0xFE", "0x47"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw.cmpxchg" , ["0xFE", "0x48"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw.cmpxchg" , ["0xFE", "0x49"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw8.cmpxchg_u" , ["0xFE", "0x4A"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i32.atomic.rmw16.cmpxchg_u" , ["0xFE", "0x4B"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw8.cmpxchg_u" , ["0xFE", "0x4C"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw16.cmpxchg_u" , ["0xFE", "0x4D"] , [["memarg", "MemArg"]] , null ], - ["threads" , "i64.atomic.rmw32.cmpxchg_u" , ["0xFE", "0x4E"] , [["memarg", "MemArg"]] , null ], - ["simd" , "v128.load" , ["0xFD", "0x00"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load8x8_s" , ["0xFD", "0x01"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load8x8_u" , ["0xFD", "0x02"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load16x4_s" , ["0xFD", "0x03"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load16x4_u" , ["0xFD", "0x04"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load32x2_s" , ["0xFD", "0x05"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load32x2_u" , ["0xFD", "0x06"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load8_splat" , ["0xFD", "0x07"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load16_splat" , ["0xFD", "0x08"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load32_splat" , ["0xFD", "0x09"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load64_splat" , ["0xFD", "0x0A"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.store" , ["0xFD", "0x0B"] , [["memarg", "MemArg"]] , "store" ], + ["threads" , "i32.atomic.load" , ["0xFE", "0x10"] , [["memarg", "MemArg"]] , "load" , 2], + ["threads" , "i64.atomic.load" , ["0xFE", "0x11"] , [["memarg", "MemArg"]] , "load" , 3], + ["threads" , "i32.atomic.load8_u" , ["0xFE", "0x12"] , [["memarg", "MemArg"]] , "load" , 0], + ["threads" , "i32.atomic.load16_u" , ["0xFE", "0x13"] , [["memarg", "MemArg"]] , "load" , 1], + ["threads" , "i64.atomic.load8_u" , ["0xFE", "0x14"] , [["memarg", "MemArg"]] , "load" , 0], + ["threads" , "i64.atomic.load16_u" , ["0xFE", "0x15"] , [["memarg", "MemArg"]] , "load" , 1], + ["threads" , "i64.atomic.load32_u" , ["0xFE", "0x16"] , [["memarg", "MemArg"]] , "load" , 2], + ["threads" , "i32.atomic.store" , ["0xFE", "0x17"] , [["memarg", "MemArg"]] , "store" , 2], + ["threads" , "i64.atomic.store" , ["0xFE", "0x18"] , [["memarg", "MemArg"]] , "store" , 3], + ["threads" , "i32.atomic.store8" , ["0xFE", "0x19"] , [["memarg", "MemArg"]] , "store" , 0], + ["threads" , "i32.atomic.store16" , ["0xFE", "0x1A"] , [["memarg", "MemArg"]] , "store" , 1], + ["threads" , "i64.atomic.store8" , ["0xFE", "0x1B"] , [["memarg", "MemArg"]] , "store" , 0], + ["threads" , "i64.atomic.store16" , ["0xFE", "0x1C"] , [["memarg", "MemArg"]] , "store" , 1], + ["threads" , "i64.atomic.store32" , ["0xFE", "0x1D"] , [["memarg", "MemArg"]] , "store" , 2], + ["threads" , "memory.atomic.notify" , ["0xFE", "0x00"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "memory.atomic.wait32" , ["0xFE", "0x01"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "memory.atomic.wait64" , ["0xFE", "0x02"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw.add" , ["0xFE", "0x1E"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.add" , ["0xFE", "0x1F"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.add_u" , ["0xFE", "0x20"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.add_u" , ["0xFE", "0x21"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.add_u" , ["0xFE", "0x22"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.add_u" , ["0xFE", "0x23"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.add_u" , ["0xFE", "0x24"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.sub" , ["0xFE", "0x25"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.sub" , ["0xFE", "0x26"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.sub_u" , ["0xFE", "0x27"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.sub_u" , ["0xFE", "0x28"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.sub_u" , ["0xFE", "0x29"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.sub_u" , ["0xFE", "0x2A"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.sub_u" , ["0xFE", "0x2B"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.and" , ["0xFE", "0x2C"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.and" , ["0xFE", "0x2D"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.and_u" , ["0xFE", "0x2E"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.and_u" , ["0xFE", "0x2F"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.and_u" , ["0xFE", "0x30"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.and_u" , ["0xFE", "0x31"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.and_u" , ["0xFE", "0x32"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.or" , ["0xFE", "0x33"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.or" , ["0xFE", "0x34"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.or_u" , ["0xFE", "0x35"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.or_u" , ["0xFE", "0x36"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.or_u" , ["0xFE", "0x37"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.or_u" , ["0xFE", "0x38"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.or_u" , ["0xFE", "0x39"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.xor" , ["0xFE", "0x3A"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.xor" , ["0xFE", "0x3B"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.xor_u" , ["0xFE", "0x3C"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.xor_u" , ["0xFE", "0x3D"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.xor_u" , ["0xFE", "0x3E"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.xor_u" , ["0xFE", "0x3F"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.xor_u" , ["0xFE", "0x40"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.xchg" , ["0xFE", "0x41"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.xchg" , ["0xFE", "0x42"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.xchg_u" , ["0xFE", "0x43"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.xchg_u" , ["0xFE", "0x44"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.xchg_u" , ["0xFE", "0x45"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.xchg_u" , ["0xFE", "0x46"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.xchg_u" , ["0xFE", "0x47"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i32.atomic.rmw.cmpxchg" , ["0xFE", "0x48"] , [["memarg", "MemArg"]] , null , 2], + ["threads" , "i64.atomic.rmw.cmpxchg" , ["0xFE", "0x49"] , [["memarg", "MemArg"]] , null , 3], + ["threads" , "i32.atomic.rmw8.cmpxchg_u" , ["0xFE", "0x4A"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i32.atomic.rmw16.cmpxchg_u" , ["0xFE", "0x4B"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw8.cmpxchg_u" , ["0xFE", "0x4C"] , [["memarg", "MemArg"]] , null , 0], + ["threads" , "i64.atomic.rmw16.cmpxchg_u" , ["0xFE", "0x4D"] , [["memarg", "MemArg"]] , null , 1], + ["threads" , "i64.atomic.rmw32.cmpxchg_u" , ["0xFE", "0x4E"] , [["memarg", "MemArg"]] , null , 2], + ["simd" , "v128.load" , ["0xFD", "0x00"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load8x8_s" , ["0xFD", "0x01"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load8x8_u" , ["0xFD", "0x02"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load16x4_s" , ["0xFD", "0x03"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load16x4_u" , ["0xFD", "0x04"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load32x2_s" , ["0xFD", "0x05"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load32x2_u" , ["0xFD", "0x06"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load8_splat" , ["0xFD", "0x07"] , [["memarg", "MemArg"]] , "load" , 0], + ["simd" , "v128.load16_splat" , ["0xFD", "0x08"] , [["memarg", "MemArg"]] , "load" , 1], + ["simd" , "v128.load32_splat" , ["0xFD", "0x09"] , [["memarg", "MemArg"]] , "load" , 2], + ["simd" , "v128.load64_splat" , ["0xFD", "0x0A"] , [["memarg", "MemArg"]] , "load" , 3], + ["simd" , "v128.store" , ["0xFD", "0x0B"] , [["memarg", "MemArg"]] , "store" , 4], ["simd" , "v128.const" , ["0xFD", "0x0C"] , [["value", "V128"]] , null ], ["simd" , "i8x16.shuffle" , ["0xFD", "0x0D"] , [["lanes", "V128ShuffleMask"]] , null ], ["simd" , "i8x16.swizzle" , ["0xFD", "0x0E"] , [] , "simd" ], @@ -468,8 +468,8 @@ ["simd" , "i32x4.trunc_sat_f32x4_u" , ["0xFD", "0xF9", "0x01"], [] , "simd" ], ["simd" , "f32x4.convert_i32x4_s" , ["0xFD", "0xFA", "0x01"], [] , "simd" ], ["simd" , "f32x4.convert_i32x4_u" , ["0xFD", "0xFB", "0x01"], [] , "simd" ], - ["simd" , "v128.load32_zero" , ["0xFD", "0x5C"] , [["memarg", "MemArg"]] , "load" ], - ["simd" , "v128.load64_zero" , ["0xFD", "0x5D"] , [["memarg", "MemArg"]] , "load" ], + ["simd" , "v128.load32_zero" , ["0xFD", "0x5C"] , [["memarg", "MemArg"]] , "load" , 4], + ["simd" , "v128.load64_zero" , ["0xFD", "0x5D"] , [["memarg", "MemArg"]] , "load" , 4], ["simd" , "i16x8.extmul_low_i8x16_s" , ["0xFD", "0x9C", "0x01"], [] , "simd" ], ["simd" , "i16x8.extmul_high_i8x16_s" , ["0xFD", "0x9D", "0x01"], [] , "simd" ], ["simd" , "i16x8.extmul_low_i8x16_u" , ["0xFD", "0x9E", "0x01"], [] , "simd" ], @@ -484,14 +484,14 @@ ["simd" , "i64x2.extmul_high_i32x4_u" , ["0xFD", "0xDF", "0x01"], [] , "simd" ], ["simd" , "i16x8.q15mulr_sat_s" , ["0xFD", "0x82", "0x01"], [] , "simd" ], ["simd" , "v128.any_true" , ["0xFD", "0x53"] , [] , "simd" ], - ["simd" , "v128.load8_lane" , ["0xFD", "0x54"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.load16_lane" , ["0xFD", "0x55"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.load32_lane" , ["0xFD", "0x56"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.load64_lane" , ["0xFD", "0x57"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.store8_lane" , ["0xFD", "0x58"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.store16_lane" , ["0xFD", "0x59"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.store32_lane" , ["0xFD", "0x5A"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], - ["simd" , "v128.store64_lane" , ["0xFD", "0x5B"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane"], + ["simd" , "v128.load8_lane" , ["0xFD", "0x54"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 0], + ["simd" , "v128.load16_lane" , ["0xFD", "0x55"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 1], + ["simd" , "v128.load32_lane" , ["0xFD", "0x56"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 2], + ["simd" , "v128.load64_lane" , ["0xFD", "0x57"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 3], + ["simd" , "v128.store8_lane" , ["0xFD", "0x58"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 0], + ["simd" , "v128.store16_lane" , ["0xFD", "0x59"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 1], + ["simd" , "v128.store32_lane" , ["0xFD", "0x5A"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 2], + ["simd" , "v128.store64_lane" , ["0xFD", "0x5B"] , [["memarg", "MemArg"], ["lane", "UInt8"]] , "simdMemLane", 3], ["simd" , "i64x2.eq" , ["0xFD", "0xD6", "0x01"], [] , "simd" ], ["simd" , "i64x2.ne" , ["0xFD", "0xD7", "0x01"], [] , "simd" ], ["simd" , "i64x2.lt_s" , ["0xFD", "0xD8", "0x01"], [] , "simd" ], diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index a2608b1e..1769ef4d 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -13,6 +13,7 @@ enum WasmGen { let opcode: [UInt8] let immediates: [Immediate] let category: String? + let defaultAlign: UInt32? var visitMethodName: String { if let explicitCategory = category { @@ -83,7 +84,8 @@ enum WasmGen { opcode = try decodeHexArray() let rawImmediates = try container.decode([[String]].self) immediates = rawImmediates.map { Immediate(label: $0[0], type: $0[1]) } - category = try? container.decode(String.self) + category = try container.decodeIfPresent(String.self) + defaultAlign = container.isAtEnd ? nil : try container.decode(UInt32.self) } } @@ -410,6 +412,61 @@ enum WasmGen { return code } + static func generateTextInstructionPrinter(_ instructions: InstructionSet) -> String { + var code = """ + import WasmParser + import WasmTypes + + + """ + + let categorized = instructions.categorized + + // 1. Generate mnemonic functions for each multi-instruction category + for cat in categorized where cat.sourceInstructions.count > 1 { + guard let categoryTypeName = cat.categoryTypeName else { continue } + guard cat.sourceInstructions.allSatisfy({ $0.name.text != nil }) else { continue } + + code += "func generatedMnemonic(for op: Instruction.\(categoryTypeName)) -> String {\n" + code += " switch op {\n" + for instr in cat.sourceInstructions { + code += " case .\(instr.name.enumCase): return \"\(instr.name.text!)\"\n" + } + code += " }\n" + code += "}\n\n" + + // 2. Generate defaultAlign function if this category has memarg instructions + let hasMemarg = cat.sourceInstructions.contains(where: { $0.defaultAlign != nil }) + if hasMemarg { + code += "func generatedDefaultAlign(for op: Instruction.\(categoryTypeName)) -> UInt32 {\n" + code += " switch op {\n" + for instr in cat.sourceInstructions { + if let align = instr.defaultAlign { + code += " case .\(instr.name.enumCase): return \(align)\n" + } + } + code += " }\n" + code += "}\n\n" + } + } + + // 3. Generate lookup for non-categorized memarg instructions (atomics etc.) + let nonCategorizedMemarg = instructions.filter { $0.category == nil && $0.defaultAlign != nil } + if !nonCategorizedMemarg.isEmpty { + code += "func generatedMemargInstruction(_ instruction: Instruction) -> (mnemonic: String, memarg: MemArg, defaultAlign: UInt32)? {\n" + code += " switch instruction {\n" + for instr in nonCategorizedMemarg { + guard let text = instr.name.text else { continue } + code += " case .\(instr.name.enumCase)(let m): return (\"\(text)\", m, \(instr.defaultAlign!))\n" + } + code += " default: return nil\n" + code += " }\n" + code += "}\n" + } + + return code + } + static func generateBinaryInstructionEncoder(_ instructions: InstructionSet) -> String { var code = """ import WasmParser @@ -665,6 +722,7 @@ enum WasmGen { struct ColumnInfo { var header: String var maxWidth: Int = 0 + var isOptional: Bool = false var value: (Instruction) -> String } @@ -698,6 +756,16 @@ enum WasmGen { return "null" } }), + ColumnInfo( + header: "DefaultAlign", + isOptional: true, + value: { i in + if let defaultAlign = i.defaultAlign { + return String(defaultAlign) + } else { + return "" + } + }), ] for instruction in instructions { for columnIndex in columns.indices { @@ -710,10 +778,18 @@ enum WasmGen { for (index, instruction) in instructions.enumerated() { json += " [" - for (columnIndex, column) in columns.enumerated() { + // Determine which columns to include (required + optional with values) + var lastColumnIndex = columns.lastIndex(where: { !$0.isOptional })! + for (columnIndex, column) in columns.enumerated() where column.isOptional { + if !column.value(instruction).isEmpty { + lastColumnIndex = columnIndex + } + } + for columnIndex in 0...lastColumnIndex { + let column = columns[columnIndex] let value = column.value(instruction) json += value.padding(toLength: column.maxWidth, withPad: " ", startingAt: 0) - if columnIndex != columns.count - 1 { + if columnIndex != lastColumnIndex { json += ", " } } @@ -771,6 +847,10 @@ enum WasmGen { projectSources + ["WAT", "BinaryEncoding", "BinaryInstructionEncoder.swift"], header + generateBinaryInstructionEncoder(instructions) ), + GeneratedFile( + projectSources + ["WAT", "Printer", "InstructionMnemonics.swift"], + header + generateTextInstructionPrinter(instructions) + ), ] for file in generatedFiles {