diff --git a/Sources/WAT/BinaryInstructionEncoder.swift b/Sources/WAT/BinaryInstructionEncoder.swift index 7ac44df9..c9bfe7c2 100644 --- a/Sources/WAT/BinaryInstructionEncoder.swift +++ b/Sources/WAT/BinaryInstructionEncoder.swift @@ -74,6 +74,14 @@ extension BinaryInstructionEncoder { try encodeInstruction([0x11]) try encodeImmediates(typeIndex: typeIndex, tableIndex: tableIndex) } + mutating func visitReturnCall(functionIndex: UInt32) throws { + try encodeInstruction([0x12]) + try encodeImmediates(functionIndex: functionIndex) + } + mutating func visitReturnCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws { + try encodeInstruction([0x13]) + try encodeImmediates(typeIndex: typeIndex, tableIndex: tableIndex) + } mutating func visitDrop() throws { try encodeInstruction([0x1A]) } mutating func visitSelect() throws { try encodeInstruction([0x1B]) } mutating func visitTypedSelect(type: ValueType) throws { diff --git a/Sources/WAT/ParseTextInstruction.swift b/Sources/WAT/ParseTextInstruction.swift index 8494bd15..fb57a2e3 100644 --- a/Sources/WAT/ParseTextInstruction.swift +++ b/Sources/WAT/ParseTextInstruction.swift @@ -43,6 +43,12 @@ func parseTextInstruction(keyword: String, expressionPars case "call_indirect": let (typeIndex, tableIndex) = try expressionParser.visitCallIndirect(wat: &wat) return { return try $0.visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) } + case "return_call": + let (functionIndex) = try expressionParser.visitReturnCall(wat: &wat) + return { return try $0.visitReturnCall(functionIndex: functionIndex) } + case "return_call_indirect": + let (typeIndex, tableIndex) = try expressionParser.visitReturnCallIndirect(wat: &wat) + return { return try $0.visitReturnCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) } case "drop": return { return try $0.visitDrop() } case "select": return { return try $0.visitSelect() } case "local.get": diff --git a/Sources/WAT/Parser/ExpressionParser.swift b/Sources/WAT/Parser/ExpressionParser.swift index 6c7f4194..b832e1a4 100644 --- a/Sources/WAT/Parser/ExpressionParser.swift +++ b/Sources/WAT/Parser/ExpressionParser.swift @@ -450,6 +450,12 @@ extension ExpressionParser { let (_, typeIndex) = try wat.types.resolve(use: typeUse) return (UInt32(typeIndex), tableIndex) } + mutating func visitReturnCall(wat: inout Wat) throws -> UInt32 { + return try visitCall(wat: &wat) + } + mutating func visitReturnCallIndirect(wat: inout Wat) throws -> (typeIndex: UInt32, tableIndex: UInt32) { + return try visitCallIndirect(wat: &wat) + } mutating func visitTypedSelect(wat: inout Wat) throws -> ValueType { fatalError("unreachable because Instruction.json does not define the name of typed select and it is handled in parseTextInstruction() manually") } diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift index 488bbb34..a89443c2 100644 --- a/Sources/WasmParser/BinaryInstructionDecoder.swift +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -26,6 +26,10 @@ protocol BinaryInstructionDecoder { @inlinable mutating func visitCall() throws -> UInt32 /// Decode `call_indirect` immediates @inlinable mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) + /// Decode `return_call` immediates + @inlinable mutating func visitReturnCall() throws -> UInt32 + /// Decode `return_call_indirect` immediates + @inlinable mutating func visitReturnCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) /// Decode `typedSelect` immediates @inlinable mutating func visitTypedSelect() throws -> ValueType /// Decode `local.get` immediates @@ -122,6 +126,12 @@ func parseBinaryInstruction( case 0x11: let (typeIndex, tableIndex) = try decoder.visitCallIndirect() try visitor.visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) + case 0x12: + let (functionIndex) = try decoder.visitReturnCall() + try visitor.visitReturnCall(functionIndex: functionIndex) + case 0x13: + let (typeIndex, tableIndex) = try decoder.visitReturnCallIndirect() + try visitor.visitReturnCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) case 0x1A: try visitor.visitDrop() case 0x1B: diff --git a/Sources/WasmParser/InstructionVisitor.swift b/Sources/WasmParser/InstructionVisitor.swift index 7e404f43..230ba132 100644 --- a/Sources/WasmParser/InstructionVisitor.swift +++ b/Sources/WasmParser/InstructionVisitor.swift @@ -187,6 +187,8 @@ public enum Instruction: Equatable { case `return` case `call`(functionIndex: UInt32) case `callIndirect`(typeIndex: UInt32, tableIndex: UInt32) + case `returnCall`(functionIndex: UInt32) + case `returnCallIndirect`(typeIndex: UInt32, tableIndex: UInt32) case `drop` case `select` case `typedSelect`(type: ValueType) @@ -246,6 +248,8 @@ extension AnyInstructionVisitor { public mutating func visitReturn() throws { return try self.visit(.return) } public mutating func visitCall(functionIndex: UInt32) throws { return try self.visit(.call(functionIndex: functionIndex)) } public mutating func visitCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws { return try self.visit(.callIndirect(typeIndex: typeIndex, tableIndex: tableIndex)) } + public mutating func visitReturnCall(functionIndex: UInt32) throws { return try self.visit(.returnCall(functionIndex: functionIndex)) } + public mutating func visitReturnCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws { return try self.visit(.returnCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex)) } public mutating func visitDrop() throws { return try self.visit(.drop) } public mutating func visitSelect() throws { return try self.visit(.select) } public mutating func visitTypedSelect(type: ValueType) throws { return try self.visit(.typedSelect(type: type)) } @@ -316,6 +320,10 @@ public protocol InstructionVisitor { mutating func visitCall(functionIndex: UInt32) throws /// Visiting `call_indirect` instruction. mutating func visitCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws + /// Visiting `return_call` instruction. + mutating func visitReturnCall(functionIndex: UInt32) throws + /// Visiting `return_call_indirect` instruction. + mutating func visitReturnCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws /// Visiting `drop` instruction. mutating func visitDrop() throws /// Visiting `select` instruction. @@ -409,6 +417,8 @@ extension InstructionVisitor { case .return: return try visitReturn() case let .call(functionIndex): return try visitCall(functionIndex: functionIndex) case let .callIndirect(typeIndex, tableIndex): return try visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) + case let .returnCall(functionIndex): return try visitReturnCall(functionIndex: functionIndex) + case let .returnCallIndirect(typeIndex, tableIndex): return try visitReturnCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex) case .drop: return try visitDrop() case .select: return try visitSelect() case let .typedSelect(type): return try visitTypedSelect(type: type) @@ -465,6 +475,8 @@ extension InstructionVisitor { public mutating func visitReturn() throws {} public mutating func visitCall(functionIndex: UInt32) throws {} public mutating func visitCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws {} + public mutating func visitReturnCall(functionIndex: UInt32) throws {} + public mutating func visitReturnCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws {} public mutating func visitDrop() throws {} public mutating func visitSelect() throws {} public mutating func visitTypedSelect(type: ValueType) throws {} diff --git a/Sources/WasmParser/WasmParser.swift b/Sources/WasmParser/WasmParser.swift index db7bb815..2f8374b2 100644 --- a/Sources/WasmParser/WasmParser.swift +++ b/Sources/WasmParser/WasmParser.swift @@ -193,11 +193,14 @@ public struct WasmFeatureSet: OptionSet { /// The WebAssembly threads proposal @_alwaysEmitIntoClient public static var threads: WasmFeatureSet { WasmFeatureSet(rawValue: 1 << 2) } + /// The WebAssembly tail-call proposal + @_alwaysEmitIntoClient + public static var tailCall: WasmFeatureSet { WasmFeatureSet(rawValue: 1 << 3) } /// The default feature set public static let `default`: WasmFeatureSet = [.referenceTypes] /// The feature set with all features enabled - public static let all: WasmFeatureSet = [.memory64, .referenceTypes, .threads] + public static let all: WasmFeatureSet = [.memory64, .referenceTypes, .threads, .tailCall] } /// An error that occurs during parsing of a WebAssembly binary @@ -629,6 +632,16 @@ extension Parser: BinaryInstructionDecoder { return (typeIndex, tableIndex) } + @inlinable mutating func visitReturnCall() throws -> UInt32 { + try parseUnsigned() + } + + @inlinable mutating func visitReturnCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) { + let typeIndex: TypeIndex = try parseUnsigned() + let tableIndex: TableIndex = try parseUnsigned() + return (typeIndex, tableIndex) + } + @inlinable mutating func visitTypedSelect() throws -> WasmTypes.ValueType { let results = try parseVector { try parseValueType() } guard results.count == 1 else { diff --git a/Tests/WATTests/EncoderTests.swift b/Tests/WATTests/EncoderTests.swift index 07e4f4a1..d5adb8ca 100644 --- a/Tests/WATTests/EncoderTests.swift +++ b/Tests/WATTests/EncoderTests.swift @@ -116,7 +116,12 @@ class EncoderTests: XCTestCase { let wast2jsonProcess = try Process.run( wast2json, - arguments: [wastFile.path, "-o", json.path] + arguments: [ + wastFile.path, + "--enable-memory64", + "--enable-tail-call", + "-o", json.path, + ] ) wast2jsonProcess.waitUntilExit() diff --git a/Tests/WATTests/Spectest.swift b/Tests/WATTests/Spectest.swift index b4f23681..bc743ecf 100644 --- a/Tests/WATTests/Spectest.swift +++ b/Tests/WATTests/Spectest.swift @@ -20,6 +20,7 @@ enum Spectest { var allFiles = [ testsuitePath, testsuitePath.appendingPathComponent("proposals/memory64"), + testsuitePath.appendingPathComponent("proposals/tail-call"), rootDirectory.appendingPathComponent("Tests/WasmKitTests/ExtraSuite"), ].flatMap { try! FileManager.default.contentsOfDirectory(at: $0, includingPropertiesForKeys: nil) diff --git a/Utilities/Instructions.json b/Utilities/Instructions.json index ce43a2d0..953a4073 100644 --- a/Utilities/Instructions.json +++ b/Utilities/Instructions.json @@ -12,6 +12,8 @@ ["mvp" , "return" , ["0x0F"] , [] , null ], ["mvp" , "call" , ["0x10"] , [["functionIndex", "UInt32"]] , null ], ["mvp" , "call_indirect" , ["0x11"] , [["typeIndex", "UInt32"], ["tableIndex", "UInt32"]], null ], + ["tailCall" , "return_call" , ["0x12"] , [["functionIndex", "UInt32"]] , null ], + ["tailCall" , "return_call_indirect" , ["0x13"] , [["typeIndex", "UInt32"], ["tableIndex", "UInt32"]], null ], ["mvp" , "drop" , ["0x1A"] , [] , null ], ["mvp" , "select" , ["0x1B"] , [] , null ], ["referenceTypes" , {"enumCase": "typedSelect"}, ["0x1C"] , [["type", "ValueType"]] , null ],