From 1ea8c2eef7441ed4e191ded0be1c53812cffa87a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 20 Oct 2025 14:36:53 +0100 Subject: [PATCH 1/4] Make `InstructionTranslator` non-copyable This avoids unintentional possible ARC traffic if `InstructionTranslator` is accidentally copied, as it stores references to classes like `ISeqAllocator`. --- Sources/WasmKit/Execution/Function.swift | 23 +++++++++---------- Sources/WasmKit/Translator.swift | 16 ++++++------- .../WasmParser/BinaryInstructionDecoder.swift | 2 +- Sources/WasmParser/InstructionVisitor.swift | 4 ++-- Sources/WasmParser/WasmParser.swift | 4 ++-- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Sources/WasmKit/Execution/Function.swift b/Sources/WasmKit/Execution/Function.swift index 3786cf16..a2d57d86 100644 --- a/Sources/WasmKit/Execution/Function.swift +++ b/Sources/WasmKit/Execution/Function.swift @@ -253,19 +253,18 @@ struct WasmFunctionEntity { let store = store.value let engine = store.engine let type = self.type - var translator = try InstructionTranslator( - allocator: store.allocator.iseqAllocator, - engineConfiguration: engine.configuration, - funcTypeInterner: engine.funcTypeInterner, - module: instance, - type: engine.resolveType(type), - locals: code.locals, - functionIndex: index, - codeSize: code.expression.count, - isIntercepting: engine.interceptor != nil - ) let iseq = try code.withValue { code in - try translator.translate(code: code) + try InstructionTranslator( + allocator: store.allocator.iseqAllocator, + engineConfiguration: engine.configuration, + funcTypeInterner: engine.funcTypeInterner, + module: instance, + type: engine.resolveType(type), + locals: code.locals, + functionIndex: index, + codeSize: code.expression.count, + isIntercepting: engine.interceptor != nil + ).translate(code: code) } self.code = .compiled(iseq) return iseq diff --git a/Sources/WasmKit/Translator.swift b/Sources/WasmKit/Translator.swift index 6f3e1116..b7544472 100644 --- a/Sources/WasmKit/Translator.swift +++ b/Sources/WasmKit/Translator.swift @@ -293,7 +293,7 @@ struct StackLayout { } } -struct InstructionTranslator: InstructionVisitor { +struct InstructionTranslator: ~Copyable, InstructionVisitor { typealias Output = Void typealias LabelRef = Int @@ -526,15 +526,15 @@ struct InstructionTranslator: InstructionVisitor { } } - fileprivate struct ISeqBuilder { + fileprivate struct ISeqBuilder: ~Copyable { typealias InstructionFactoryWithLabel = ( - ISeqBuilder, + borrowing ISeqBuilder, // The position of the next slot of the creating instruction _ source: MetaProgramCounter, // The position of the resolved label _ target: MetaProgramCounter ) -> (WasmKit.Instruction) - typealias BrTableEntryFactory = (ISeqBuilder, MetaProgramCounter) -> Instruction.BrTableOperand.Entry + typealias BrTableEntryFactory = (borrowing ISeqBuilder, MetaProgramCounter) -> Instruction.BrTableOperand.Entry typealias BuildingBrTable = UnsafeMutableBufferPointer enum OnPinAction { @@ -639,7 +639,7 @@ struct InstructionTranslator: InstructionVisitor { } } - func finalize() -> [UInt64] { + consuming func finalize() -> [UInt64] { return instructions } @@ -707,7 +707,7 @@ struct InstructionTranslator: InstructionVisitor { line: UInt = #line, make: @escaping ( - ISeqBuilder, + borrowing ISeqBuilder, // The position of the next slot of the creating instruction _ source: MetaProgramCounter, // The position of the resolved label @@ -1077,7 +1077,7 @@ struct InstructionTranslator: InstructionVisitor { try valueStack.truncate(height: currentFrame.stackHeight) } - private mutating func finalize() throws -> InstructionSequence { + private consuming func finalize() throws -> InstructionSequence { if controlStack.numberOfFrames > 1 { throw ValidationError(.expectedMoreEndInstructions(count: controlStack.numberOfFrames - 1)) } @@ -1102,7 +1102,7 @@ struct InstructionTranslator: InstructionVisitor { // MARK: Main entry point /// Translate a Wasm expression into a sequence of instructions. - mutating func translate(code: Code) throws -> InstructionSequence { + consuming func translate(code: Code) throws -> InstructionSequence { if isIntercepting { // Emit `onEnter` instruction at the beginning of the function emit(.onEnter(functionIndex)) diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift index 5cc84908..4ea7a0d7 100644 --- a/Sources/WasmParser/BinaryInstructionDecoder.swift +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -90,7 +90,7 @@ protocol BinaryInstructionDecoder { } @inlinable -func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool { +func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { let opcode0 = try decoder.claimNextByte() switch opcode0 { case 0x00: diff --git a/Sources/WasmParser/InstructionVisitor.swift b/Sources/WasmParser/InstructionVisitor.swift index 2a6b0271..e236890c 100644 --- a/Sources/WasmParser/InstructionVisitor.swift +++ b/Sources/WasmParser/InstructionVisitor.swift @@ -309,7 +309,7 @@ extension AnyInstructionVisitor { /// /// The visitor pattern is used while parsing WebAssembly expressions to allow for easy extensibility. /// See the expression parsing method ``Code/parseExpression(visitor:)`` -public protocol InstructionVisitor { +public protocol InstructionVisitor: ~Copyable { /// Visiting `unreachable` instruction. mutating func visitUnreachable() throws /// Visiting `nop` instruction. @@ -482,7 +482,7 @@ extension InstructionVisitor { } // MARK: - Placeholder implementations -extension InstructionVisitor { +extension InstructionVisitor where Self: ~Copyable { public mutating func visitUnreachable() throws {} public mutating func visitNop() throws {} public mutating func visitBlock(blockType: BlockType) throws {} diff --git a/Sources/WasmParser/WasmParser.swift b/Sources/WasmParser/WasmParser.swift index 0bca6049..791b0712 100644 --- a/Sources/WasmParser/WasmParser.swift +++ b/Sources/WasmParser/WasmParser.swift @@ -160,7 +160,7 @@ public struct ExpressionParser { } @inlinable - public mutating func visit(visitor: inout V) throws -> Bool { + public mutating func visit(visitor: inout some InstructionVisitor & ~Copyable) throws -> Bool { isLastEnd = try parser.parseInstruction(visitor: &visitor) let shouldContinue = try !parser.stream.hasReachedEnd() if !shouldContinue { @@ -751,7 +751,7 @@ extension Parser: BinaryInstructionDecoder { /// Returns: `true` if the parsed instruction is the block end instruction. @inline(__always) @inlinable - mutating func parseInstruction(visitor v: inout V) throws -> Bool { + mutating func parseInstruction(visitor v: inout some InstructionVisitor & ~Copyable) throws -> Bool { return try parseBinaryInstruction(visitor: &v, decoder: &self) } From a544abedab03671a811f91678b4a4edac1dcaac4 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 20 Oct 2025 15:04:08 +0100 Subject: [PATCH 2/4] Fix missing atomic `store` instructions --- Sources/WasmParser/BinaryInstructionDecoder.swift | 2 +- Sources/WasmParser/InstructionVisitor.swift | 2 +- Utilities/Sources/WasmGen.swift | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift index 4ea7a0d7..5cc84908 100644 --- a/Sources/WasmParser/BinaryInstructionDecoder.swift +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -90,7 +90,7 @@ protocol BinaryInstructionDecoder { } @inlinable -func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { +func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool { let opcode0 = try decoder.claimNextByte() switch opcode0 { case 0x00: diff --git a/Sources/WasmParser/InstructionVisitor.swift b/Sources/WasmParser/InstructionVisitor.swift index e236890c..cd7d9fb6 100644 --- a/Sources/WasmParser/InstructionVisitor.swift +++ b/Sources/WasmParser/InstructionVisitor.swift @@ -420,7 +420,7 @@ public protocol InstructionVisitor: ~Copyable { mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool } -extension InstructionVisitor { +extension InstructionVisitor where Self: ~Copyable { /// Visits an instruction. public mutating func visit(_ instruction: Instruction) throws { switch instruction { diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index af354614..f03b2dc4 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -95,7 +95,7 @@ enum WasmGen { /// /// The visitor pattern is used while parsing WebAssembly expressions to allow for easy extensibility. /// See the expression parsing method ``Code/parseExpression(visitor:)`` - public protocol InstructionVisitor { + public protocol InstructionVisitor: ~Copyable { """ for instruction in instructions.categorized { @@ -118,7 +118,7 @@ enum WasmGen { code += """ - extension InstructionVisitor { + extension InstructionVisitor where Self: ~Copyable { /// Visits an instruction. public mutating func visit(_ instruction: Instruction) throws { switch instruction { @@ -150,7 +150,7 @@ enum WasmGen { code += """ // MARK: - Placeholder implementations - extension InstructionVisitor { + extension InstructionVisitor where Self: ~Copyable { """ for instruction in instructions.categorized { From f7023390d47a82e0a15b51c322ba53da920f5463 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 20 Oct 2025 15:04:54 +0100 Subject: [PATCH 3/4] Add missing `~Copyable` constraint to `WasmGen.swift` --- Sources/WasmParser/BinaryInstructionDecoder.swift | 2 +- Utilities/Sources/WasmGen.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift index 5cc84908..4ea7a0d7 100644 --- a/Sources/WasmParser/BinaryInstructionDecoder.swift +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -90,7 +90,7 @@ protocol BinaryInstructionDecoder { } @inlinable -func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool { +func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { let opcode0 = try decoder.claimNextByte() switch opcode0 { case 0x00: diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index f03b2dc4..732559c1 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -561,7 +561,7 @@ enum WasmGen { code += """ @inlinable - func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool { + func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { """ func renderSwitchCase(_ root: Trie, depth: Int = 0) { From c6a99f88ba598062ec8fd15295118a050a8f6e72 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 20 Oct 2025 15:07:08 +0100 Subject: [PATCH 4/4] Clean up formatting --- Sources/WasmParser/BinaryInstructionDecoder.swift | 5 ++++- Utilities/Sources/WasmGen.swift | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/WasmParser/BinaryInstructionDecoder.swift b/Sources/WasmParser/BinaryInstructionDecoder.swift index 4ea7a0d7..cc1337af 100644 --- a/Sources/WasmParser/BinaryInstructionDecoder.swift +++ b/Sources/WasmParser/BinaryInstructionDecoder.swift @@ -90,7 +90,10 @@ protocol BinaryInstructionDecoder { } @inlinable -func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { +func parseBinaryInstruction( + visitor: inout some InstructionVisitor & ~Copyable, + decoder: inout some BinaryInstructionDecoder +) throws -> Bool { let opcode0 = try decoder.claimNextByte() switch opcode0 { case 0x00: diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index 732559c1..a1addda2 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -561,7 +561,10 @@ enum WasmGen { code += """ @inlinable - func parseBinaryInstruction(visitor: inout some InstructionVisitor & ~Copyable, decoder: inout some BinaryInstructionDecoder) throws -> Bool { + func parseBinaryInstruction( + visitor: inout some InstructionVisitor & ~Copyable, + decoder: inout some BinaryInstructionDecoder + ) throws -> Bool { """ func renderSwitchCase(_ root: Trie, depth: Int = 0) {