diff --git a/Sources/CLI/Run/Run.swift b/Sources/CLI/Run/Run.swift index f4d6602f..5e19a2c4 100644 --- a/Sources/CLI/Run/Run.swift +++ b/Sources/CLI/Run/Run.swift @@ -54,6 +54,11 @@ struct Run: ParsableCommand { } let interceptor = try deriveInterceptor() + #if !DEBUG + guard interceptor == nil else { + fatalError("Internal Error: Interceptor API is unavailable with Release build due to performance reasons") + } + #endif defer { interceptor?.finalize() } let invoke: () throws -> Void diff --git a/Sources/WasmKit/Execution/Instructions/Control.swift b/Sources/WasmKit/Execution/Instructions/Control.swift index aa875f74..0f48fb70 100644 --- a/Sources/WasmKit/Execution/Instructions/Control.swift +++ b/Sources/WasmKit/Execution/Instructions/Control.swift @@ -1,163 +1,164 @@ /// > Note: /// -enum ControlInstruction: Equatable { - case unreachable - case nop - case block(expression: Expression, type: ResultType) - case loop(expression: Expression, type: ResultType) - case `if`(then: Expression, else: Expression, type: ResultType) - case br(_ labelIndex: LabelIndex) - case brIf(_ labelIndex: LabelIndex) - case brTable(_ labelIndices: [LabelIndex], default: LabelIndex) - case `return` - case call(functionIndex: UInt32) - case callIndirect(tableIndex: TableIndex, typeIndex: TypeIndex) - - func execute(runtime: Runtime, execution: inout ExecutionState) throws { - switch self { - case .unreachable: - throw Trap.unreachable - - case .nop: - execution.programCounter += 1 - - case let .block(expression, type): - let (paramSize, resultSize) = type.arity(typeSection: { execution.stack.currentFrame.module.types }) - let values = try execution.stack.popValues(count: paramSize) - execution.enter(expression, continuation: execution.programCounter + 1, arity: resultSize) - execution.stack.push(values: values) - - case let .loop(expression, type): - let (paramSize, _) = type.arity(typeSection: { execution.stack.currentFrame.module.types }) - let values = try execution.stack.popValues(count: paramSize) - execution.enter(expression, continuation: execution.programCounter, arity: paramSize) - execution.stack.push(values: values) - - case let .if(then, `else`, type): - let isTrue = try execution.stack.popValue().i32 != 0 - - let expression: Expression - if isTrue { - expression = then - } else { - expression = `else` - } - - if !expression.instructions.isEmpty { - let derived = ControlInstruction.block(expression: expression, type: type) - try derived.execute(runtime: runtime, execution: &execution) - } else { - execution.programCounter += 1 - } - - case let .brIf(labelIndex): - guard try execution.stack.popValue().i32 != 0 else { - execution.programCounter += 1 - return - } - - fallthrough - - case let .br(labelIndex): - try execution.branch(labelIndex: Int(labelIndex)) - - case let .brTable(labelIndices, defaultLabelIndex): - let value = try execution.stack.popValue().i32 - let labelIndex: LabelIndex - if labelIndices.indices.contains(Int(value)) { - labelIndex = labelIndices[Int(value)] - } else { - labelIndex = defaultLabelIndex - } - - try execution.branch(labelIndex: Int(labelIndex)) - - case .return: - let values = try execution.stack.popValues(count: execution.stack.currentFrame.arity) - - let currentFrame = Stack.Element.frame(execution.stack.currentFrame) - var lastLabel: Label? - while execution.stack.top != currentFrame { - execution.stack.discardTopValues() - lastLabel = try execution.stack.popLabel() - } - if let lastLabel { - execution.programCounter = lastLabel.continuation - } - execution.stack.push(values: values) - - case let .call(functionIndex): - let functionAddresses = execution.stack.currentFrame.module.functionAddresses - - guard functionAddresses.indices.contains(Int(functionIndex)) else { - throw Trap.invalidFunctionIndex(functionIndex) - } - - try execution.invoke(functionAddress: functionAddresses[Int(functionIndex)], runtime: runtime) - - case let .callIndirect(tableIndex, typeIndex): - let moduleInstance = execution.stack.currentFrame.module - let tableAddresses = moduleInstance.tableAddresses[Int(tableIndex)] - let tableInstance = runtime.store.tables[tableAddresses] - let expectedType = moduleInstance.types[Int(typeIndex)] - let value = try execution.stack.popValue().i32 - let elementIndex = Int(value) - guard elementIndex < tableInstance.elements.count else { - throw Trap.undefinedElement - } - guard case let .function(functionAddress?) = tableInstance.elements[elementIndex] - else { - throw Trap.tableUninitialized(ElementIndex(elementIndex)) - } - let function = runtime.store.functions[functionAddress] - guard function.type == expectedType else { - throw Trap.callIndirectFunctionTypeMismatch(actual: function.type, expected: expectedType) - } - - try execution.invoke(functionAddress: functionAddress, runtime: runtime) +extension ExecutionState { + func unreachable(runtime: Runtime) throws { + throw Trap.unreachable + } + mutating func nop(runtime: Runtime) throws { + programCounter += 1 + } + private func getTypeSection(store: Store) -> [FunctionType] { + store.module(address: stack.currentFrame.module).types + } + + typealias BlockType = Instruction.BlockType + + mutating func block(runtime: Runtime, endRef: ExpressionRef, type: BlockType) { + enter( + jumpTo: programCounter + 1, + continuation: programCounter + endRef.relativeOffset, + arity: Int(type.results), + pushPopValues: Int(type.parameters) + ) + } + mutating func loop(runtime: Runtime, type: BlockType) { + let paramSize = Int(type.parameters) + enter(jumpTo: programCounter + 1, continuation: programCounter, arity: paramSize, pushPopValues: paramSize) + } + + mutating func ifThen(runtime: Runtime, endRef: ExpressionRef, type: BlockType) { + let isTrue = stack.popValue().i32 != 0 + if isTrue { + enter( + jumpTo: programCounter + 1, + continuation: programCounter.advanced(by: endRef.relativeOffset), + arity: Int(type.results), + pushPopValues: Int(type.parameters) + ) + } else { + programCounter += endRef.relativeOffset } } -} -extension ControlInstruction: CustomStringConvertible { - public var description: String { - switch self { - case .loop: - return "loop" + mutating func ifThenElse(runtime: Runtime, elseRef: ExpressionRef, endRef: ExpressionRef, type: BlockType) { + let isTrue = stack.popValue().i32 != 0 + let addendToPC: Int + if isTrue { + addendToPC = 1 + } else { + addendToPC = elseRef.relativeOffset + } + enter( + jumpTo: programCounter + addendToPC, + continuation: programCounter + endRef.relativeOffset, + arity: Int(type.results), + pushPopValues: Int(type.parameters) + ) + } + mutating func end(runtime: Runtime) { + if let currentLabel = self.stack.currentLabel { + stack.exit(label: currentLabel) + } + programCounter += 1 + } + mutating func `else`(runtime: Runtime) { + let label = self.stack.currentLabel! + stack.exit(label: label) + programCounter = label.continuation // if-then-else's continuation points the "end" + } + + private mutating func branch(labelIndex: Int, runtime: Runtime) throws { + if stack.numberOfLabelsInCurrentFrame() == labelIndex { + try self.return(runtime: runtime) + return + } + let label = stack.getLabel(index: Int(labelIndex)) + let values = stack.popValues(count: label.arity) - case .block: - return "block" + stack.unwindLabels(upto: labelIndex) - case let .br(i): - return "br \(i)" + stack.push(values: values) + programCounter = label.continuation + } + mutating func br(runtime: Runtime, labelIndex: LabelIndex) throws { + try branch(labelIndex: Int(labelIndex), runtime: runtime) + } + mutating func brIf(runtime: Runtime, labelIndex: LabelIndex) throws { + guard stack.popValue().i32 != 0 else { + programCounter += 1 + return + } + try br(runtime: runtime, labelIndex: labelIndex) + } + mutating func brTable(runtime: Runtime, brTable: Instruction.BrTable) throws { + let labelIndices = brTable.labelIndices + let defaultIndex = brTable.defaultIndex + let value = stack.popValue().i32 + let labelIndex: LabelIndex + if labelIndices.indices.contains(Int(value)) { + labelIndex = labelIndices[Int(value)] + } else { + labelIndex = defaultIndex + } - case let .brIf(i): - return "br_if \(i)" + try branch(labelIndex: Int(labelIndex), runtime: runtime) + } + mutating func `return`(runtime: Runtime) throws { + let currentFrame = stack.currentFrame! + _ = stack.exit(frame: currentFrame) + try endOfFunction(runtime: runtime, currentFrame: currentFrame) + } - case let .brTable(i, d): - return "br_if \(i.map(\.description).joined(separator: " ")) \(d)" + mutating func endOfFunction(runtime: Runtime) throws { + try self.endOfFunction(runtime: runtime, currentFrame: stack.currentFrame) + } - case let .call(functionIndex): - return "call \(functionIndex)" + mutating func endOfExecution(runtime: Runtime) throws { + reachedEndOfExecution = true + } - case let .callIndirect(tableIndex, typeIndex): - return "call_indirect \(tableIndex) \(typeIndex)" + private mutating func endOfFunction(runtime: Runtime, currentFrame: Frame) throws { + // When reached at "end" of function + #if DEBUG + if let address = currentFrame.address { + runtime.interceptor?.onExitFunction(address, store: runtime.store) + } + #endif + let values = stack.popValues(count: currentFrame.arity) + stack.popFrame() + stack.push(values: values) + programCounter = currentFrame.returnPC + } - case let .if(type, then, `else`): - return """ - if \(type)\n \(then) - else\n \(`else`) - end - """ + mutating func call(runtime: Runtime, functionIndex: UInt32) throws { + let functionAddresses = runtime.store.module(address: stack.currentFrame.module).functionAddresses - case .unreachable: - return "unreachable" + guard functionAddresses.indices.contains(Int(functionIndex)) else { + throw Trap.invalidFunctionIndex(functionIndex) + } - case .nop: - return "nop" + try invoke(functionAddress: functionAddresses[Int(functionIndex)], runtime: runtime) + } - case .return: - return "return" + mutating func callIndirect(runtime: Runtime, tableIndex: TableIndex, typeIndex: TypeIndex) throws { + let moduleInstance = runtime.store.module(address: stack.currentFrame.module) + let tableAddresses = moduleInstance.tableAddresses[Int(tableIndex)] + let tableInstance = runtime.store.tables[tableAddresses] + let expectedType = moduleInstance.types[Int(typeIndex)] + let value = stack.popValue().i32 + let elementIndex = Int(value) + guard elementIndex < tableInstance.elements.count else { + throw Trap.undefinedElement + } + guard case let .function(functionAddress?) = tableInstance.elements[elementIndex] + else { + throw Trap.tableUninitialized(ElementIndex(elementIndex)) } + let function = runtime.store.functions[functionAddress] + guard function.type == expectedType else { + throw Trap.callIndirectFunctionTypeMismatch(actual: function.type, expected: expectedType) + } + + try invoke(functionAddress: functionAddress, runtime: runtime) } } diff --git a/Sources/WasmKit/Execution/Instructions/Expression.swift b/Sources/WasmKit/Execution/Instructions/Expression.swift index e2f3511a..1998256c 100644 --- a/Sources/WasmKit/Execution/Instructions/Expression.swift +++ b/Sources/WasmKit/Execution/Instructions/Expression.swift @@ -13,19 +13,50 @@ enum PseudoInstruction { case end } -/// > Note: -/// -struct Expression: Equatable { - /// Note that `end` or `else` pseudo instructions are omitted in this array - let instructions: [Instruction] +struct InstructionSequence: Equatable { + let instructions: UnsafeBufferPointer init(instructions: [Instruction]) { - self.instructions = instructions + assert(_isPOD(Instruction.self)) + let buffer = UnsafeMutableBufferPointer.allocate(capacity: instructions.count + 1) + for (idx, instruction) in instructions.enumerated() { + buffer[idx] = instruction + } + buffer[instructions.count] = .endOfFunction + self.instructions = UnsafeBufferPointer(buffer) + } + + func deallocate() { + instructions.deallocate() + } + + var baseAddress: UnsafePointer { + self.instructions.baseAddress! + } + + static func == (lhs: InstructionSequence, rhs: InstructionSequence) -> Bool { + lhs.instructions.baseAddress == rhs.instructions.baseAddress } } -extension Expression: ExpressibleByArrayLiteral { +extension InstructionSequence: ExpressibleByArrayLiteral { init(arrayLiteral elements: Instruction...) { self.init(instructions: elements) } } + +struct ExpressionRef: Equatable { + let _relativeOffset: UInt32 + var relativeOffset: Int { + Int(_relativeOffset) + } + + init(_ relativeOffset: Int) { + self._relativeOffset = UInt32(relativeOffset) + } +} + +/// > Note: +/// + +typealias Expression = [Instruction] diff --git a/Sources/WasmKit/Execution/Instructions/Instruction.swift b/Sources/WasmKit/Execution/Instructions/Instruction.swift index 5720149e..13dbd085 100644 --- a/Sources/WasmKit/Execution/Instructions/Instruction.swift +++ b/Sources/WasmKit/Execution/Instructions/Instruction.swift @@ -1,33 +1,107 @@ enum Instruction: Equatable { - case control(ControlInstruction) - case memory(MemoryInstruction) - case numeric(NumericInstruction) - case parametric(ParametricInstruction) - case reference(ReferenceInstruction) - case table(TableInstruction) - case variable(VariableInstruction) - case pseudo(PseudoInstruction) -} - -extension Instruction: CustomStringConvertible { - var description: String { - switch self { - case let .numeric(n): - return n.description - case let .variable(v): - return v.description - case let .control(c): - return String(reflecting: c) - case let .pseudo(p): - return String(reflecting: p) - case let .memory(m): - return String(reflecting: m) - case let .parametric(p): - return String(reflecting: p) - case let .reference(r): - return String(reflecting: r) - case let .table(t): - return String(reflecting: t) - } - } + case unreachable + case nop + case block(endRef: ExpressionRef, type: BlockType) + case loop(type: BlockType) + case ifThen(endRef: ExpressionRef, type: BlockType) + case ifThenElse(elseRef: ExpressionRef, endRef: ExpressionRef, type: BlockType) + case end + case `else` + case br(labelIndex: LabelIndex) + case brIf(labelIndex: LabelIndex) + case brTable(BrTable) + case `return` + case call(functionIndex: UInt32) + case callIndirect(tableIndex: TableIndex, typeIndex: TypeIndex) + case endOfFunction + case endOfExecution + case i32Load(memarg: Memarg) + case i64Load(memarg: Memarg) + case f32Load(memarg: Memarg) + case f64Load(memarg: Memarg) + case i32Load8S(memarg: Memarg) + case i32Load8U(memarg: Memarg) + case i32Load16S(memarg: Memarg) + case i32Load16U(memarg: Memarg) + case i64Load8S(memarg: Memarg) + case i64Load8U(memarg: Memarg) + case i64Load16S(memarg: Memarg) + case i64Load16U(memarg: Memarg) + case i64Load32S(memarg: Memarg) + case i64Load32U(memarg: Memarg) + case i32Store(memarg: Memarg) + case i64Store(memarg: Memarg) + case f32Store(memarg: Memarg) + case f64Store(memarg: Memarg) + case i32Store8(memarg: Memarg) + case i32Store16(memarg: Memarg) + case i64Store8(memarg: Memarg) + case i64Store16(memarg: Memarg) + case i64Store32(memarg: Memarg) + case memorySize + case memoryGrow + case memoryInit(DataIndex) + case memoryDataDrop(DataIndex) + case memoryCopy + case memoryFill + case numericConst(Value) + case numericIntUnary(NumericInstruction.IntUnary) + case numericFloatUnary(NumericInstruction.FloatUnary) + case numericIntBinary(NumericInstruction.IntBinary) + case numericFloatBinary(NumericInstruction.FloatBinary) + case numericConversion(NumericInstruction.Conversion) + case i32Add + case i64Add + case f32Add + case f64Add + case i32Sub + case i64Sub + case f32Sub + case f64Sub + case i32Mul + case i64Mul + case f32Mul + case f64Mul + case i32Eq + case i64Eq + case f32Eq + case f64Eq + case i32Ne + case i64Ne + case f32Ne + case f64Ne + case i32LtS + case i64LtS + case i32LtU + case i64LtU + case i32GtS + case i64GtS + case i32GtU + case i64GtU + case i32LeS + case i64LeS + case i32LeU + case i64LeU + case i32GeS + case i64GeS + case i32GeU + case i64GeU + case drop + case select + case refNull(ReferenceType) + case refIsNull + case refFunc(FunctionIndex) + case tableGet(TableIndex) + case tableSet(TableIndex) + case tableSize(TableIndex) + case tableGrow(TableIndex) + case tableFill(TableIndex) + case tableCopy(dest: TableIndex, src: TableIndex) + case tableInit(TableIndex, ElementIndex) + case tableElementDrop(ElementIndex) + case localGet(index: LocalIndex) + case localSet(index: LocalIndex) + case localTee(index: LocalIndex) + case globalGet(index: GlobalIndex) + case globalSet(index: GlobalIndex) } diff --git a/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift b/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift new file mode 100644 index 00000000..b5feb292 --- /dev/null +++ b/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift @@ -0,0 +1,43 @@ +extension Instruction { + struct Memarg: Equatable { + let offset: UInt64 + let align: UInt32 + } + + struct BlockType: Equatable { + let parameters: UInt16 + let results: UInt16 + } + + struct BrTable: Equatable { + private let bufferBase: UnsafePointer + private let bufferCount: UInt32 + var labelIndices: UnsafeBufferPointer { + UnsafeBufferPointer(start: bufferBase, count: Int(bufferCount - 1)) + } + var defaultIndex: LabelIndex { + bufferBase[Int(bufferCount - 1)] + } + + init(labelIndices: [LabelIndex], defaultIndex: LabelIndex) { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: labelIndices.count + 1) + for (index, labelindex) in labelIndices.enumerated() { + buffer[index] = labelindex + } + buffer[labelIndices.count] = defaultIndex + self.bufferBase = UnsafePointer(buffer.baseAddress!) + self.bufferCount = UInt32(buffer.count) + } + + static func == (lhs: Instruction.BrTable, rhs: Instruction.BrTable) -> Bool { + lhs.labelIndices.baseAddress == rhs.labelIndices.baseAddress + } + } + + // Just for migration purpose + static func control(_ x: Instruction) -> Instruction { x } + static func numeric(_ x: Instruction) -> Instruction { x } + static func parametric(_ x: Instruction) -> Instruction { x } + static func variable(_ x: Instruction) -> Instruction { x } + static func reference(_ x: Instruction) -> Instruction { x } +} diff --git a/Sources/WasmKit/Execution/Instructions/Memory.swift b/Sources/WasmKit/Execution/Instructions/Memory.swift index 263fdd05..8e8909c2 100644 --- a/Sources/WasmKit/Execution/Instructions/Memory.swift +++ b/Sources/WasmKit/Execution/Instructions/Memory.swift @@ -1,85 +1,160 @@ /// > Note: /// -enum MemoryInstruction: Equatable { - struct Memarg: Equatable { - let offset: UInt64 - let align: UInt32 - } - - case load( - _ memarg: Memarg, - bitWidth: UInt8, - _ type: NumericType, - isSigned: Bool = true - ) - case store(_ memarg: Memarg, bitWidth: UInt8, _ type: ValueType) - case size - case grow - case `init`(DataIndex) - case dataDrop(DataIndex) - case copy - case fill - - func execute(_ stack: inout Stack, _ store: Store) throws { - let moduleInstance = stack.currentFrame.module - - switch self { - case let .load(memarg, bitWidth, type, isSigned): - let memoryAddress = moduleInstance.memoryAddresses[0] +extension ExecutionState { + typealias Memarg = Instruction.Memarg + + mutating func i32Load(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt32.self, castToValue: { .i32($0) }) + } + mutating func i64Load(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt64.self, castToValue: { .i64($0) }) + } + mutating func f32Load(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt32.self, castToValue: { .f32($0) }) + } + mutating func f64Load(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt64.self, castToValue: { .f64($0) }) + } + mutating func i32Load8S(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: Int8.self, castToValue: { .init(signed: Int32($0)) }) + } + mutating func i32Load8U(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt8.self, castToValue: { .i32(UInt32($0)) }) + } + mutating func i32Load16S(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: Int16.self, castToValue: { .init(signed: Int32($0)) }) + } + mutating func i32Load16U(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt16.self, castToValue: { .i32(UInt32($0)) }) + } + mutating func i64Load8S(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: Int8.self, castToValue: { .init(signed: Int64($0)) }) + } + mutating func i64Load8U(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt8.self, castToValue: { .i64(UInt64($0)) }) + } + mutating func i64Load16S(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: Int16.self, castToValue: { .init(signed: Int64($0)) }) + } + mutating func i64Load16U(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt16.self, castToValue: { .i64(UInt64($0)) }) + } + mutating func i64Load32S(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: Int32.self, castToValue: { .init(signed: Int64($0)) }) + } + mutating func i64Load32U(runtime: Runtime, memarg: Memarg) throws { + try memoryLoad(runtime: runtime, memarg: memarg, loadAs: UInt32.self, castToValue: { .i64(UInt64($0)) }) + } + + @_transparent + private mutating func memoryLoad( + runtime: Runtime, memarg: Instruction.Memarg, loadAs _: T.Type = T.self, castToValue: (T) -> Value + ) throws { + let moduleInstance = currentModule(store: runtime.store) + let store = runtime.store + + let memoryAddress = moduleInstance.memoryAddresses[0] + let memoryInstance = store.memories[memoryAddress] + let i = stack.popValue().asAddressOffset(memoryInstance.limit.isMemory64) + let (address, isOverflow) = memarg.offset.addingReportingOverflow(i) + guard !isOverflow else { + throw Trap.outOfBoundsMemoryAccess + } + let length = UInt64(T.bitWidth) / 8 + let (endAddress, isEndOverflow) = address.addingReportingOverflow(length) + guard !isEndOverflow, endAddress <= memoryInstance.data.count else { + throw Trap.outOfBoundsMemoryAccess + } + + let loaded = memoryInstance.data.withUnsafeBufferPointer { buffer in + let rawBuffer = UnsafeRawBufferPointer(buffer) + return rawBuffer.loadUnaligned(fromByteOffset: Int(address), as: T.self) + } + stack.push(value: castToValue(loaded)) + + } + + mutating func i32Store(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { $0.i32 }) + } + mutating func i64Store(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { $0.i64 }) + } + mutating func f32Store(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { $0.f32 }) + } + mutating func f64Store(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { $0.f64 }) + } + mutating func i32Store8(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { UInt8(truncatingIfNeeded: $0.i32) }) + } + mutating func i32Store16(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { UInt16(truncatingIfNeeded: $0.i32) }) + } + mutating func i64Store8(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { UInt8(truncatingIfNeeded: $0.i64) }) + } + mutating func i64Store16(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { UInt16(truncatingIfNeeded: $0.i64) }) + } + mutating func i64Store32(runtime: Runtime, memarg: Memarg) throws { + try memoryStore(runtime: runtime, memarg: memarg, castFromValue: { UInt32(truncatingIfNeeded: $0.i64) }) + } + + /// `[type].store[bitWidth]` + @_transparent + private mutating func memoryStore(runtime: Runtime, memarg: Instruction.Memarg, castFromValue: (Value) -> T) throws { + let moduleInstance = currentModule(store: runtime.store) + let store = runtime.store + + let value = stack.popValue() + + let memoryAddress = moduleInstance.memoryAddresses[0] + let address: UInt64 + let endAddress: UInt64 + let length: UInt64 + do { let memoryInstance = store.memories[memoryAddress] - let i = try stack.popValue().asAddressOffset(memoryInstance.limit.isMemory64) - let (address, isOverflow) = memarg.offset.addingReportingOverflow(i) + let i = stack.popValue().asAddressOffset(memoryInstance.limit.isMemory64) + var isOverflow: Bool + (address, isOverflow) = memarg.offset.addingReportingOverflow(i) guard !isOverflow else { throw Trap.outOfBoundsMemoryAccess } - let length = UInt64(bitWidth) / 8 - let (endAddress, isEndOverflow) = address.addingReportingOverflow(length) - guard !isEndOverflow, endAddress <= memoryInstance.data.count else { + length = UInt64(T.bitWidth) / 8 + (endAddress, isOverflow) = address.addingReportingOverflow(length) + guard !isOverflow, endAddress <= memoryInstance.data.count else { throw Trap.outOfBoundsMemoryAccess } + } - let bytes = memoryInstance.data[Int(address).. 0 else { return } - + guard !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(UInt64(copyCounter)).overflow @@ -114,65 +193,72 @@ enum MemoryInstruction: Equatable { else { throw Trap.outOfBoundsMemoryAccess } - + // FIXME: benchmark if using `replaceSubrange` is faster than this loop for i in 0..= destinationIndex + copyCounter - else { - throw Trap.outOfBoundsMemoryAccess - } - - store.memories[memoryAddress].data.replaceSubrange( - destinationIndex.. 0 else { return } guard !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow - && store.memories[memoryAddress].data.count >= destinationIndex + copyCounter - && store.memories[memoryAddress].data.count >= sourceIndex + copyCounter + && memoryInstance.data.count >= destinationIndex + copyCounter + && memoryInstance.data.count >= sourceIndex + copyCounter else { throw Trap.outOfBoundsMemoryAccess } if destinationIndex <= sourceIndex { for i in 0..= destinationIndex + copyCounter + else { + throw Trap.outOfBoundsMemoryAccess + } + + memoryInstance.data.replaceSubrange( + destinationIndex.. Note: /// -enum NumericInstruction: Equatable { - case const(Value) - case intUnary(IntUnary) - case floatUnary(FloatUnary) - case binary(Binary) - case intBinary(IntBinary) - case floatBinary(FloatBinary) - case conversion(Conversion) +extension ExecutionState { + mutating func numericConst(runtime: Runtime, value: Value) { + stack.push(value: value) + } + mutating func numericIntUnary(runtime: Runtime, intUnary: NumericInstruction.IntUnary) { + let value = stack.popValue() - func execute(_ stack: inout Stack) throws { - switch self { - case let .const(value): - stack.push(value: value) + stack.push(value: intUnary(value)) + } + mutating func numericFloatUnary(runtime: Runtime, floatUnary: NumericInstruction.FloatUnary) { + let value = stack.popValue() - case let .intUnary(instruction): - let value = try stack.popValue() + stack.push(value: floatUnary(value)) + } + @inline(__always) + private mutating func numericBinary(castTo: (Value) -> T, binary: (T, T) -> Value) { + let value2 = stack.popValue() + let value1 = stack.popValue() - stack.push(value: instruction(value)) + stack.push(value: binary(castTo(value1), castTo(value2))) + } - case let .floatUnary(instruction): - let value = try stack.popValue() + mutating func i32Add(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { .i32($0 &+ $1) }) + } - stack.push(value: instruction(value)) + mutating func i64Add(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { .i64($0 &+ $1) }) + } - case let .binary(instruction): - let value2 = try stack.popValue() - let value1 = try stack.popValue() + mutating func f32Add(runtime: Runtime) { + numericBinary(castTo: \.f32, binary: { .f32((Float32(bitPattern: $0) + Float32(bitPattern: $1)).bitPattern) }) + } - stack.push(value: instruction(value1, value2)) + mutating func f64Add(runtime: Runtime) { + numericBinary(castTo: \.f64, binary: { .f64((Float64(bitPattern: $0) + Float64(bitPattern: $1)).bitPattern) }) + } - case let .intBinary(instruction): - let value2 = try stack.popValue() - let value1 = try stack.popValue() + mutating func i32Sub(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { .i32($0 &- $1) }) + } - try stack.push(value: instruction(value1, value2)) + mutating func i64Sub(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { .i64($0 &- $1) }) + } - case let .floatBinary(instruction): - let value2 = try stack.popValue() - let value1 = try stack.popValue() + mutating func f32Sub(runtime: Runtime) { + numericBinary(castTo: \.f32, binary: { .f32((Float32(bitPattern: $0) - Float32(bitPattern: $1)).bitPattern) }) + } - try stack.push(value: instruction(value1, value2)) + mutating func f64Sub(runtime: Runtime) { + numericBinary(castTo: \.f64, binary: { .f64((Float64(bitPattern: $0) - Float64(bitPattern: $1)).bitPattern) }) + } - case let .conversion(instruction): - let value = try stack.popValue() + mutating func i32Mul(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { .i32($0 &* $1) }) + } - try stack.push(value: instruction(value)) - } + mutating func i64Mul(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { .i64($0 &* $1) }) + } + + mutating func f32Mul(runtime: Runtime) { + numericBinary(castTo: \.f32, binary: { .f32((Float32(bitPattern: $0) * Float32(bitPattern: $1)).bitPattern) }) + } + + mutating func f64Mul(runtime: Runtime) { + numericBinary(castTo: \.f64, binary: { .f64((Float64(bitPattern: $0) * Float64(bitPattern: $1)).bitPattern) }) + } + + mutating func i32Eq(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 == $1 ? true : false }) + } + + mutating func i64Eq(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 == $1 ? true : false }) + } + + mutating func f32Eq(runtime: Runtime) { + numericBinary(castTo: \.f32, binary: { Float32(bitPattern: $0) == Float32(bitPattern: $1) ? true : false }) + } + + mutating func f64Eq(runtime: Runtime) { + numericBinary(castTo: \.f64, binary: { Float64(bitPattern: $0) == Float64(bitPattern: $1) ? true : false }) + } + + mutating func i32Ne(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 == $1 ? false : true }) + } + + mutating func i64Ne(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 == $1 ? false : true }) + } + + mutating func f32Ne(runtime: Runtime) { + numericBinary(castTo: \.f32, binary: { Float32(bitPattern: $0) == Float32(bitPattern: $1) ? false : true }) + } + + mutating func f64Ne(runtime: Runtime) { + numericBinary(castTo: \.f64, binary: { Float64(bitPattern: $0) == Float64(bitPattern: $1) ? false : true }) + } + + mutating func i32LtS(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0.signed < $1.signed ? true : false }) + } + mutating func i64LtS(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0.signed < $1.signed ? true : false }) + } + mutating func i32LtU(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 < $1 ? true : false }) + } + mutating func i64LtU(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 < $1 ? true : false }) + } + mutating func i32GtS(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0.signed > $1.signed ? true : false }) + } + mutating func i64GtS(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0.signed > $1.signed ? true : false }) + } + mutating func i32GtU(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 > $1 ? true : false }) + } + mutating func i64GtU(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 > $1 ? true : false }) + } + mutating func i32LeS(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0.signed <= $1.signed ? true : false }) + } + mutating func i64LeS(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0.signed <= $1.signed ? true : false }) + } + mutating func i32LeU(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 <= $1 ? true : false }) + } + mutating func i64LeU(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 <= $1 ? true : false }) + } + mutating func i32GeS(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0.signed >= $1.signed ? true : false }) + } + mutating func i64GeS(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0.signed >= $1.signed ? true : false }) + } + mutating func i32GeU(runtime: Runtime) { + numericBinary(castTo: \.i32, binary: { $0 >= $1 ? true : false }) + } + mutating func i64GeU(runtime: Runtime) { + numericBinary(castTo: \.i64, binary: { $0 >= $1 ? true : false }) + } + + mutating func numericIntBinary(runtime: Runtime, intBinary: NumericInstruction.IntBinary) throws { + let value2 = stack.popValue() + let value1 = stack.popValue() + + try stack.push(value: intBinary(value1, value2)) + } + mutating func numericFloatBinary(runtime: Runtime, floatBinary: NumericInstruction.FloatBinary) { + let value2 = stack.popValue() + let value1 = stack.popValue() + + stack.push(value: floatBinary(value1, value2)) + } + mutating func numericConversion(runtime: Runtime, conversion: NumericInstruction.Conversion) throws { + let value = stack.popValue() + + try stack.push(value: conversion(value)) } } +enum NumericInstruction {} + /// Numeric Instructions extension NumericInstruction { internal enum Constant { @@ -135,44 +257,6 @@ extension NumericInstruction { } } - public enum Binary: Equatable { - // binop - case add(NumericType) - case sub(NumericType) - case mul(NumericType) - - // relop - case eq(NumericType) - case ne(NumericType) - - var type: NumericType { - switch self { - case let .add(type), - let .sub(type), - let .mul(type), - let .eq(type), - let .ne(type): - return type - } - } - - func callAsFunction(_ value1: Value, _ value2: Value) -> Value { - switch self { - case .add: - return value1 + value2 - case .sub: - return value1 - value2 - case .mul: - return value1 * value2 - - case .eq: - return value1 == value2 ? true : false - case .ne: - return value1 == value2 ? false : true - } - } - } - public enum IntBinary: Equatable { // ibinop case divS(IntValueType) @@ -188,16 +272,6 @@ extension NumericInstruction { case rotl(IntValueType) case rotr(IntValueType) - // irelop - case ltS(IntValueType) - case ltU(IntValueType) - case gtS(IntValueType) - case gtU(IntValueType) - case leS(IntValueType) - case leU(IntValueType) - case geS(IntValueType) - case geU(IntValueType) - var type: NumericType { switch self { case let .divS(type), @@ -211,15 +285,7 @@ extension NumericInstruction { let .shrS(type), let .shrU(type), let .rotl(type), - let .rotr(type), - let .ltS(type), - let .ltU(type), - let .gtS(type), - let .gtU(type), - let .leS(type), - let .leU(type), - let .geS(type), - let .geU(type): + let .rotr(type): return .int(type) } } @@ -261,43 +327,6 @@ extension NumericInstruction { return value1.rotl(value2) case (.rotr, _): return value1.rotr(value2) - - case (.ltS, .int(.i32)): - return value1.i32.signed < value2.i32.signed ? true : false - case (.ltU, .int(.i32)): - return value1.i32 < value2.i32 ? true : false - case (.gtS, .int(.i32)): - return value1.i32.signed > value2.i32.signed ? true : false - case (.gtU, .int(.i32)): - return value1.i32 > value2.i32 ? true : false - case (.leS, .int(.i32)): - return value1.i32.signed <= value2.i32.signed ? true : false - case (.leU, .int(.i32)): - return value1.i32 <= value2.i32 ? true : false - case (.geS, .int(.i32)): - return value1.i32.signed >= value2.i32.signed ? true : false - case (.geU, .int(.i32)): - return value1.i32 >= value2.i32 ? true : false - - case (.ltS, .int(.i64)): - return value1.i64.signed < value2.i64.signed ? true : false - case (.ltU, .int(.i64)): - return value1.i64 < value2.i64 ? true : false - case (.gtS, .int(.i64)): - return value1.i64.signed > value2.i64.signed ? true : false - case (.gtU, .int(.i64)): - return value1.i64 > value2.i64 ? true : false - case (.leS, .int(.i64)): - return value1.i64.signed <= value2.i64.signed ? true : false - case (.leU, .int(.i64)): - return value1.i64 <= value2.i64 ? true : false - case (.geS, .int(.i64)): - return value1.i64.signed >= value2.i64.signed ? true : false - case (.geU, .int(.i64)): - return value1.i64 >= value2.i64 ? true : false - - default: - fatalError("Invalid type \(type) for instruction \(self)") } } } @@ -329,7 +358,7 @@ extension NumericInstruction { } } - func callAsFunction(_ value1: Value, _ value2: Value) throws -> Value { + func callAsFunction(_ value1: Value, _ value2: Value) -> Value { switch self { case .div: guard !value1.isNan && !value2.isNan else { @@ -816,72 +845,3 @@ extension NumericInstruction { } } } - -extension NumericInstruction: CustomStringConvertible { - public var description: String { - switch self { - case let .const(v): - switch v { - case let .f32(f32): return "f32.const \(f32)" - case let .f64(f64): return "f64.const \(f64)" - case let .i32(i32): return "i32.const \(i32.signed)" - case let .i64(i64): return "i64.const \(i64.signed)" - case let .ref(.function(f?)): return "ref.func \(f)" - case .ref(.function(nil)): return "ref.null funcref" - case .ref(.extern(nil)): return "ref.null externref" - default: fatalError("unsuppported const instruction for value \(v)") - } - - case let .binary(b): - switch b { - case let .add(t): - return "\(t).add" - - case let .eq(t): - return "\(t).eq" - - case let .mul(t): - return "\(t).mul" - - case let .ne(t): - return "\(t).ne" - - case let .sub(t): - return "\(t).sub" - } - - case let .intBinary(ib): - switch ib { - case let .and(it): return "\(it).and" - case let .xor(it): return "\(it).xor" - case let .or(it): return "\(it).or" - case let .shl(it): return "\(it).shl" - case let .shrS(it): return "\(it).shr_s" - case let .shrU(it): return "\(it).shr_u" - case let .rotl(it): return "\(it).rotl" - case let .rotr(it): return "\(it).rotr" - case let .remS(it): return "\(it).rem_s" - case let .remU(it): return "\(it).rem_u" - case let .divS(it): return "\(it).div_s" - case let .divU(it): return "\(it).div_u" - case let .ltS(it): return "\(it).lt_s" - case let .ltU(it): return "\(it).lt_u" - case let .gtS(it): return "\(it).gt_s" - case let .gtU(it): return "\(it).gt_u" - case let .leS(it): return "\(it).le_s" - case let .leU(it): return "\(it).le_u" - case let .geS(it): return "\(it).ge_s" - case let .geU(it): return "\(it).ge_u" - } - - case let .conversion(c): - return String(reflecting: c) - case let .intUnary(iu): - return String(reflecting: iu) - case let .floatUnary(fu): - return String(reflecting: fu) - case let .floatBinary(fb): - return String(reflecting: fb) - } - } -} diff --git a/Sources/WasmKit/Execution/Instructions/Parametric.swift b/Sources/WasmKit/Execution/Instructions/Parametric.swift index 603bd4b7..a12f3660 100644 --- a/Sources/WasmKit/Execution/Instructions/Parametric.swift +++ b/Sources/WasmKit/Execution/Instructions/Parametric.swift @@ -1,27 +1,24 @@ /// > Note: /// -enum ParametricInstruction: Equatable { - case drop - case select - case typedSelect([ValueType]) - - func execute(_ stack: inout Stack) throws { - switch self { - case .drop: - _ = try stack.popValue() +extension ExecutionState { + mutating func drop(runtime: Runtime) { + _ = stack.popValue() + } + mutating func select(runtime: Runtime) throws { + try doSelect() + } - case .select, .typedSelect: - let flagValue = try stack.popValue() - guard case let .i32(flag) = flagValue else { - throw Trap.stackValueTypesMismatch(expected: .i32, actual: flagValue.type) - } - let value2 = try stack.popValue() - let value1 = try stack.popValue() - if flag != 0 { - stack.push(value: value1) - } else { - stack.push(value: value2) - } + private mutating func doSelect() throws { + let flagValue = stack.popValue() + guard case let .i32(flag) = flagValue else { + throw Trap.stackValueTypesMismatch(expected: .i32, actual: flagValue.type) + } + let value2 = stack.popValue() + let value1 = stack.popValue() + if flag != 0 { + stack.push(value: value1) + } else { + stack.push(value: value2) } } } diff --git a/Sources/WasmKit/Execution/Instructions/Reference.swift b/Sources/WasmKit/Execution/Instructions/Reference.swift index fa759680..514c3cbd 100644 --- a/Sources/WasmKit/Execution/Instructions/Reference.swift +++ b/Sources/WasmKit/Execution/Instructions/Reference.swift @@ -1,36 +1,30 @@ /// > Note: /// -enum ReferenceInstruction: Equatable { - case refNull(ReferenceType) - case refIsNull - case refFunc(FunctionIndex) - - func execute(_ stack: inout Stack) throws { - switch self { - case let .refNull(type): - switch type { - case .externRef: - stack.push(value: .ref(.extern(nil))) - case .funcRef: - stack.push(value: .ref(.function(nil))) - } - - case .refIsNull: - let value = try stack.popValue() - - switch value { - case .ref(.extern(nil)), .ref(.function(nil)): - stack.push(value: .i32(1)) - case .ref(.extern(_)), .ref(.function(_)): - stack.push(value: .i32(0)) - default: - fatalError("Invalid type \(value.type) for `\(#function)` implementation") - } - - case let .refFunc(functionIndex): - let functionAddress = stack.currentFrame.module.functionAddresses[Int(functionIndex)] +extension ExecutionState { + mutating func refNull(runtime: Runtime, referenceType: ReferenceType) { + switch referenceType { + case .externRef: + stack.push(value: .ref(.extern(nil))) + case .funcRef: + stack.push(value: .ref(.function(nil))) + } + } + mutating func refIsNull(runtime: Runtime) { + let value = stack.popValue() - stack.push(value: .ref(.function(functionAddress))) + switch value { + case .ref(.extern(nil)), .ref(.function(nil)): + stack.push(value: .i32(1)) + case .ref(.extern(_)), .ref(.function(_)): + stack.push(value: .i32(0)) + default: + fatalError("Invalid type \(value.type) for `\(#function)` implementation") } } + mutating func refFunc(runtime: Runtime, functionIndex: FunctionIndex) { + let module = runtime.store.module(address: stack.currentFrame.module) + let functionAddress = module.functionAddresses[Int(functionIndex)] + + stack.push(value: .ref(.function(functionAddress))) + } } diff --git a/Sources/WasmKit/Execution/Instructions/Table.swift b/Sources/WasmKit/Execution/Instructions/Table.swift index 2ee957c6..30ba970e 100644 --- a/Sources/WasmKit/Execution/Instructions/Table.swift +++ b/Sources/WasmKit/Execution/Instructions/Table.swift @@ -1,153 +1,141 @@ /// > Note: /// -enum TableInstruction: Equatable { - case get(TableIndex) - case set(TableIndex) - case size(TableIndex) - case grow(TableIndex) - case fill(TableIndex) - case copy(TableIndex, TableIndex) - case `init`(TableIndex, ElementIndex) - case elementDrop(ElementIndex) - - func execute(runtime: Runtime, execution: inout ExecutionState) throws { - switch self { - case let .get(tableIndex): - let (_, table) = try execution.getTable(tableIndex, store: runtime.store) - - let elementIndex = try execution.getElementIndex(table) - - guard let reference = table.elements[Int(elementIndex)] else { - throw Trap.readingDroppedReference(index: elementIndex) - } - - execution.stack.push(value: .ref(reference)) - - case let .set(tableIndex): - let (tableAddress, table) = try execution.getTable(tableIndex, store: runtime.store) - - let reference = try execution.stack.getReference() - let elementIndex = try execution.getElementIndex(table) - setTableElement(store: runtime.store, tableAddress: tableAddress, elementIndex, reference) - - case let .size(tableIndex): - let (_, table) = try execution.getTable(tableIndex, store: runtime.store) - - execution.stack.push(value: .i32(UInt32(table.elements.count))) - - case let .grow(tableIndex): - let (tableAddress, table) = try execution.getTable(tableIndex, store: runtime.store) - - let growthSize = try execution.stack.popValue() - - guard case let .i32(growthSize) = growthSize else { - fatalError("invalid value at the top of the stack \(growthSize)") - } - - let growthValue = try execution.stack.getReference() - - let oldSize = UInt32(table.elements.count) - guard runtime.store.tables[tableAddress].grow(by: growthSize, value: growthValue) else { - execution.stack.push(value: .i32(Int32(-1).unsigned)) - break - } - - execution.stack.push(value: .i32(oldSize)) - - case let .fill(tableIndex): - let (tableAddress, table) = try execution.getTable(tableIndex, store: runtime.store) - let fillCounter = try execution.stack.popValue().i32 - let fillValue = try execution.stack.getReference() - let startIndex = try execution.stack.popValue().i32 - - guard fillCounter > 0 else { - break - } - - guard Int(startIndex + fillCounter) <= table.elements.count else { - throw Trap.outOfBoundsTableAccess(index: startIndex + fillCounter) - } - - for i in 0.. 0 else { - break - } - - guard - !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow - else { - throw Trap.tableSizeOverflow - } - guard destinationIndex + copyCounter <= sourceTable.elements.count else { - throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) - } - guard destinationIndex + copyCounter <= sourceTable.elements.count && sourceIndex + copyCounter <= destinationTable.elements.count else { - throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) - } - - for i in 0.. 0 else { - break - } - - guard - !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow - else { - throw Trap.tableSizeOverflow - } - - guard sourceIndex + copyCounter <= sourceElement.references.count else { - throw Trap.outOfBoundsTableAccess(index: sourceIndex + copyCounter) - } - guard destinationIndex + copyCounter <= destinationTable.elements.count else { - throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) - } - - for i in 0.. 0 else { + return + } + + guard Int(startIndex + fillCounter) <= table.elements.count else { + throw Trap.outOfBoundsTableAccess(index: startIndex + fillCounter) + } + + for i in 0.. 0 else { + return + } + + guard + !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow + else { + throw Trap.tableSizeOverflow + } + guard destinationIndex + copyCounter <= sourceTable.elements.count else { + throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) + } + guard destinationIndex + copyCounter <= sourceTable.elements.count && sourceIndex + copyCounter <= destinationTable.elements.count else { + throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) + } + + for i in 0.. 0 else { + return + } + + guard + !sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow + else { + throw Trap.tableSizeOverflow + } + + guard sourceIndex + copyCounter <= sourceElement.references.count else { + throw Trap.outOfBoundsTableAccess(index: sourceIndex + copyCounter) + } + guard destinationIndex + copyCounter <= destinationTable.elements.count else { + throw Trap.outOfBoundsTableAccess(index: destinationIndex + copyCounter) + } + + for i in 0.. (TableAddress, TableInstance) { - let address = stack.currentFrame.module.tableAddresses[Int(tableIndex)] + fileprivate func getTable(_ tableIndex: UInt32, store: Store) -> (TableAddress, TableInstance) { + let address = currentModule(store: store).tableAddresses[Int(tableIndex)] return (address, store.tables[address]) } fileprivate mutating func getElementIndex(_ table: TableInstance) throws -> ElementIndex { - let elementIndex = try stack.popValue().i32 + let elementIndex = stack.popValue().i32 guard elementIndex < table.elements.count else { throw Trap.outOfBoundsTableAccess(index: elementIndex) @@ -176,8 +164,8 @@ extension ExecutionState { } extension Stack { - fileprivate mutating func getReference() throws -> Reference { - let value = try popValue() + fileprivate mutating func getReference() -> Reference { + let value = popValue() guard case let .ref(reference) = value else { fatalError("invalid value at the top of the stack \(value)") diff --git a/Sources/WasmKit/Execution/Instructions/Variable.swift b/Sources/WasmKit/Execution/Instructions/Variable.swift index af3a6b57..27fd7393 100644 --- a/Sources/WasmKit/Execution/Instructions/Variable.swift +++ b/Sources/WasmKit/Execution/Instructions/Variable.swift @@ -1,68 +1,31 @@ /// > Note: /// -enum VariableInstruction: Equatable { - case localGet(index: LocalIndex) - case localSet(index: LocalIndex) - case localTee(index: LocalIndex) - case globalGet(index: GlobalIndex) - case globalSet(index: GlobalIndex) - - func execute(_ stack: inout Stack, _ globals: inout [GlobalInstance]) throws { - switch self { - case let .localGet(index): - let value = try stack.currentFrame.localGet(index: index) - stack.push(value: value) - - case let .localSet(index): - let value = try stack.popValue() - try stack.currentFrame.localSet(index: index, value: value) - - case let .localTee(index): - guard case let .value(value) = stack.top else { - throw Trap.stackTypeMismatch(expected: Value.self, actual: stack.top) - } - try stack.currentFrame.localSet(index: index, value: value) - - case let .globalGet(index): - let address = Int(stack.currentFrame.module.globalAddresses[Int(index)]) - - guard globals.indices.contains(address) else { - throw Trap.globalAddressOutOfRange(index: address) - } - let value = globals[address].value - stack.push(value: value) - - case let .globalSet(index): - let address = Int(stack.currentFrame.module.globalAddresses[Int(index)]) - let value = try stack.popValue() - - guard globals.indices.contains(address) else { - throw Trap.globalAddressOutOfRange(index: address) - } - - let mutability = globals[address].globalType.mutability - guard mutability == .variable else { - throw Trap.globalImmutable(index: address) - } - - globals[address].value = value - } +extension ExecutionState { + mutating func localGet(runtime: Runtime, locals: UnsafeMutablePointer, index: LocalIndex) { + let value = locals[Int(index)] + stack.push(value: value) + } + mutating func localSet(runtime: Runtime, locals: UnsafeMutablePointer, index: LocalIndex) { + let value = stack.popValue() + locals[Int(index)] = value + } + mutating func localTee(runtime: Runtime, locals: UnsafeMutablePointer, index: LocalIndex) { + let value = stack.topValue + locals[Int(index)] = value + } + mutating func globalGet(runtime: Runtime, index: GlobalIndex) throws { + let address = Int(currentModule(store: runtime.store).globalAddresses[Int(index)]) + let globals = runtime.store.globals + let value = globals[address].value + stack.push(value: value) } -} -extension VariableInstruction: CustomStringConvertible { - public var description: String { - switch self { - case let .globalGet(index: i): - return "global.get \(i)" - case let .globalSet(index: i): - return "global.set \(i)" - case let .localGet(index: i): - return "local.get \(i)" - case let .localSet(index: i): - return "local.set \(i)" - case let .localTee(index: i): - return "local.tee \(i)" - } + mutating func globalSet(runtime: Runtime, index: GlobalIndex) throws { + let address = Int(currentModule(store: runtime.store).globalAddresses[Int(index)]) + try doGlobalSet(address: address, &runtime.store.globals) + } + mutating func doGlobalSet(address: GlobalAddress, _ globals: inout [GlobalInstance]) throws { + let value = stack.popValue() + globals[address].value = value } } diff --git a/Sources/WasmKit/Execution/Runtime/ExecutionState.swift b/Sources/WasmKit/Execution/Runtime/ExecutionState.swift index 18f89f6e..285567df 100644 --- a/Sources/WasmKit/Execution/Runtime/ExecutionState.swift +++ b/Sources/WasmKit/Execution/Runtime/ExecutionState.swift @@ -1,3 +1,5 @@ +typealias ProgramCounter = UnsafePointer + /// An execution state of an invocation of exported function. /// /// Each new invocation through exported function has a separate ``ExecutionState`` @@ -5,126 +7,110 @@ struct ExecutionState { var stack = Stack() /// Index of an instruction to be executed in the current function. - var programCounter = 0 + var programCounter: ProgramCounter + var reachedEndOfExecution: Bool = false var isStackEmpty: Bool { - stack.top == nil + stack.isEmpty } -} - -extension ExecutionState { - mutating func execute(_ instruction: Instruction, runtime: Runtime) throws { - switch instruction { - case let .control(instruction): - return try instruction.execute(runtime: runtime, execution: &self) - - case let .memory(instruction): - try instruction.execute(&stack, runtime.store) - - case let .numeric(instruction): - try instruction.execute(&stack) - - case let .parametric(instruction): - try instruction.execute(&stack) - case let .reference(instruction): - try instruction.execute(&stack) - - case let .table(instruction): - try instruction.execute(runtime: runtime, execution: &self) - - case let .variable(instruction): - try instruction.execute(&stack, &runtime.store.globals) - case .pseudo: - // Structured pseudo instructions (end/else) should not appear at runtime - throw Trap.unreachable - } - - programCounter += 1 + fileprivate init(stack: Stack = Stack(), programCounter: ProgramCounter) { + self.stack = stack + self.programCounter = programCounter } +} - mutating func branch(labelIndex: Int) throws { - let label = try stack.getLabel(index: Int(labelIndex)) - let values = try stack.popValues(count: label.arity) +@_transparent +func withExecution(_ body: (inout ExecutionState) throws -> Return) rethrows -> Return { + try withUnsafeTemporaryAllocation(of: Instruction.self, capacity: 1) { rootISeq in + rootISeq.baseAddress?.pointee = .endOfExecution + // NOTE: unwinding a function jump into previous frame's PC + 1, so initial PC is -1ed + var execution = ExecutionState(programCounter: rootISeq.baseAddress! - 1) + return try body(&execution) + } +} - var lastLabel: Label? - for _ in 0...labelIndex { - stack.discardTopValues() - lastLabel = try stack.popLabel() - } +extension ExecutionState: CustomStringConvertible { + var description: String { + var result = "======== PC=\(programCounter) =========\n" + result += "\(stack.debugDescription)" - stack.push(values: values) - programCounter = lastLabel!.continuation + return result } +} +extension ExecutionState { /// > Note: /// - mutating func enter(_ expression: Expression, continuation: Int, arity: Int) { - let exit = programCounter + 1 - let label = Label(arity: arity, expression: expression, continuation: continuation, exit: exit) - stack.push(label: label) - programCounter = label.expression.instructions.startIndex + @inline(__always) + mutating func enter(jumpTo targetPC: ProgramCounter, continuation: ProgramCounter, arity: Int, pushPopValues: Int = 0) { + stack.pushLabel( + arity: arity, + continuation: continuation, + popPushValues: pushPopValues + ) + programCounter = targetPC } /// > Note: /// mutating func exit(label: Label) throws { - let values = try stack.popTopValues() - let lastLabel = try stack.popLabel() - assert(lastLabel == label) - stack.push(values: values) - programCounter = label.exit + stack.exit(label: label) + programCounter += 1 } /// > Note: /// mutating func invoke(functionAddress address: FunctionAddress, runtime: Runtime) throws { + #if DEBUG runtime.interceptor?.onEnterFunction(address, store: runtime.store) + #endif switch try runtime.store.function(at: address) { case let .host(function): - let parameters = try stack.popValues(count: function.type.parameters.count) - let caller = Caller(runtime: runtime, instance: stack.currentFrame.module) - stack.push(values: try function.implementation(caller, parameters)) + let parameters = stack.popValues(count: function.type.parameters.count) + let moduleInstance = runtime.store.module(address: stack.currentFrame.module) + let caller = Caller(runtime: runtime, instance: moduleInstance) + stack.push(values: try function.implementation(caller, Array(parameters))) programCounter += 1 case let .wasm(function, body: body): - let locals = function.code.locals.map { $0.defaultValue } let expression = body - let arguments = try stack.popValues(count: function.type.parameters.count) - let arity = function.type.results.count - try stack.push(frame: .init(arity: arity, module: function.module, locals: arguments + locals, address: address)) - - self.enter( - expression, continuation: programCounter + 1, - arity: arity + try stack.pushFrame( + iseq: expression, + arity: arity, + module: function.module, + argc: function.type.parameters.count, + defaultLocals: function.code.defaultLocals, + returnPC: programCounter.advanced(by: 1), + address: address ) + programCounter = expression.baseAddress } } - public mutating func step(runtime: Runtime) throws { - if let label = stack.currentLabel { - if programCounter < label.expression.instructions.count { - try execute(stack.currentLabel.expression.instructions[programCounter], runtime: runtime) - } else { - try self.exit(label: label) - } - } else { - if let address = stack.currentFrame.address { - runtime.interceptor?.onExitFunction(address, store: runtime.store) - } - let values = try stack.popValues(count: stack.currentFrame.arity) - try stack.popFrame() - stack.push(values: values) + mutating func run(runtime: Runtime) throws { + while !reachedEndOfExecution { + let locals = self.stack.currentLocalsPointer + // Regular path + var inst: Instruction + // `doExecute` returns false when current frame *may* be updated + repeat { + inst = programCounter.pointee + } while try doExecute(inst, runtime: runtime, locals: locals) } } - public mutating func run(runtime: Runtime) throws { - while stack.currentFrame != nil { - try step(runtime: runtime) - } + func currentModule(store: Store) -> ModuleInstance { + store.module(address: stack.currentFrame.module) + } +} + +extension ExecutionState { + mutating func pseudo(runtime: Runtime, pseudoInstruction: PseudoInstruction) throws { + fatalError("Unimplemented instruction: pseudo") } } diff --git a/Sources/WasmKit/Execution/Runtime/Function.swift b/Sources/WasmKit/Execution/Runtime/Function.swift index 1fc3dbce..be982417 100644 --- a/Sources/WasmKit/Execution/Runtime/Function.swift +++ b/Sources/WasmKit/Execution/Runtime/Function.swift @@ -4,10 +4,11 @@ public struct Function: Equatable { /// Invokes a function of the given address with the given parameters. public func invoke(_ arguments: [Value] = [], runtime: Runtime) throws -> [Value] { - var execution = ExecutionState() - try invoke(execution: &execution, with: arguments, runtime: runtime) - try execution.run(runtime: runtime) - return try execution.stack.popTopValues() + try withExecution { execution in + try invoke(execution: &execution, with: arguments, runtime: runtime) + try execution.run(runtime: runtime) + return try Array(execution.stack.popTopValues()) + } } private func invoke(execution: inout ExecutionState, with arguments: [Value], runtime: Runtime) throws { @@ -15,10 +16,11 @@ public struct Function: Equatable { case let .host(function): try check(functionType: function.type, parameters: arguments) - let parameters = try execution.stack.popValues(count: function.type.parameters.count) + let parameters = execution.stack.popValues(count: function.type.parameters.count) - let caller = Caller(runtime: runtime, instance: execution.stack.currentFrame.module) - let results = try function.implementation(caller, parameters) + let moduleInstance = runtime.store.module(address: execution.stack.currentFrame.module) + let caller = Caller(runtime: runtime, instance: moduleInstance) + let results = try function.implementation(caller, Array(parameters)) try check(functionType: function.type, results: results) execution.stack.push(values: results) diff --git a/Sources/WasmKit/Execution/Runtime/InstDispatch.swift b/Sources/WasmKit/Execution/Runtime/InstDispatch.swift new file mode 100644 index 00000000..12b0657f --- /dev/null +++ b/Sources/WasmKit/Execution/Runtime/InstDispatch.swift @@ -0,0 +1,348 @@ +// This file is generated by Utilities/generate_inst_dispatch.swift +extension ExecutionState { + @inline(__always) + mutating func doExecute(_ instruction: Instruction, runtime: Runtime, locals: UnsafeMutablePointer) throws -> Bool { + switch instruction { + case .unreachable: + try self.unreachable(runtime: runtime) + return true + case .nop: + try self.nop(runtime: runtime) + return true + case .block(let endRef, let type): + self.block(runtime: runtime, endRef: endRef, type: type) + return true + case .loop(let type): + self.loop(runtime: runtime, type: type) + return true + case .ifThen(let endRef, let type): + self.ifThen(runtime: runtime, endRef: endRef, type: type) + return true + case .ifThenElse(let elseRef, let endRef, let type): + self.ifThenElse(runtime: runtime, elseRef: elseRef, endRef: endRef, type: type) + return true + case .end: + self.end(runtime: runtime) + return true + case .`else`: + self.`else`(runtime: runtime) + return true + case .br(let labelIndex): + try self.br(runtime: runtime, labelIndex: labelIndex) + return false + case .brIf(let labelIndex): + try self.brIf(runtime: runtime, labelIndex: labelIndex) + return false + case .brTable(let brTable): + try self.brTable(runtime: runtime, brTable: brTable) + return false + case .`return`: + try self.`return`(runtime: runtime) + return false + case .call(let functionIndex): + try self.call(runtime: runtime, functionIndex: functionIndex) + return false + case .callIndirect(let tableIndex, let typeIndex): + try self.callIndirect(runtime: runtime, tableIndex: tableIndex, typeIndex: typeIndex) + return false + case .endOfFunction: + try self.endOfFunction(runtime: runtime) + return false + case .endOfExecution: + try self.endOfExecution(runtime: runtime) + return false + case .i32Load(let memarg): + try self.i32Load(runtime: runtime, memarg: memarg) + case .i64Load(let memarg): + try self.i64Load(runtime: runtime, memarg: memarg) + case .f32Load(let memarg): + try self.f32Load(runtime: runtime, memarg: memarg) + case .f64Load(let memarg): + try self.f64Load(runtime: runtime, memarg: memarg) + case .i32Load8S(let memarg): + try self.i32Load8S(runtime: runtime, memarg: memarg) + case .i32Load8U(let memarg): + try self.i32Load8U(runtime: runtime, memarg: memarg) + case .i32Load16S(let memarg): + try self.i32Load16S(runtime: runtime, memarg: memarg) + case .i32Load16U(let memarg): + try self.i32Load16U(runtime: runtime, memarg: memarg) + case .i64Load8S(let memarg): + try self.i64Load8S(runtime: runtime, memarg: memarg) + case .i64Load8U(let memarg): + try self.i64Load8U(runtime: runtime, memarg: memarg) + case .i64Load16S(let memarg): + try self.i64Load16S(runtime: runtime, memarg: memarg) + case .i64Load16U(let memarg): + try self.i64Load16U(runtime: runtime, memarg: memarg) + case .i64Load32S(let memarg): + try self.i64Load32S(runtime: runtime, memarg: memarg) + case .i64Load32U(let memarg): + try self.i64Load32U(runtime: runtime, memarg: memarg) + case .i32Store(let memarg): + try self.i32Store(runtime: runtime, memarg: memarg) + case .i64Store(let memarg): + try self.i64Store(runtime: runtime, memarg: memarg) + case .f32Store(let memarg): + try self.f32Store(runtime: runtime, memarg: memarg) + case .f64Store(let memarg): + try self.f64Store(runtime: runtime, memarg: memarg) + case .i32Store8(let memarg): + try self.i32Store8(runtime: runtime, memarg: memarg) + case .i32Store16(let memarg): + try self.i32Store16(runtime: runtime, memarg: memarg) + case .i64Store8(let memarg): + try self.i64Store8(runtime: runtime, memarg: memarg) + case .i64Store16(let memarg): + try self.i64Store16(runtime: runtime, memarg: memarg) + case .i64Store32(let memarg): + try self.i64Store32(runtime: runtime, memarg: memarg) + case .memorySize: + self.memorySize(runtime: runtime) + case .memoryGrow: + try self.memoryGrow(runtime: runtime) + case .memoryInit(let dataIndex): + try self.memoryInit(runtime: runtime, dataIndex: dataIndex) + case .memoryDataDrop(let dataIndex): + self.memoryDataDrop(runtime: runtime, dataIndex: dataIndex) + case .memoryCopy: + try self.memoryCopy(runtime: runtime) + case .memoryFill: + try self.memoryFill(runtime: runtime) + case .numericConst(let value): + self.numericConst(runtime: runtime, value: value) + case .numericIntUnary(let intUnary): + self.numericIntUnary(runtime: runtime, intUnary: intUnary) + case .numericFloatUnary(let floatUnary): + self.numericFloatUnary(runtime: runtime, floatUnary: floatUnary) + case .numericIntBinary(let intBinary): + try self.numericIntBinary(runtime: runtime, intBinary: intBinary) + case .numericFloatBinary(let floatBinary): + self.numericFloatBinary(runtime: runtime, floatBinary: floatBinary) + case .numericConversion(let conversion): + try self.numericConversion(runtime: runtime, conversion: conversion) + case .i32Add: + self.i32Add(runtime: runtime) + case .i64Add: + self.i64Add(runtime: runtime) + case .f32Add: + self.f32Add(runtime: runtime) + case .f64Add: + self.f64Add(runtime: runtime) + case .i32Sub: + self.i32Sub(runtime: runtime) + case .i64Sub: + self.i64Sub(runtime: runtime) + case .f32Sub: + self.f32Sub(runtime: runtime) + case .f64Sub: + self.f64Sub(runtime: runtime) + case .i32Mul: + self.i32Mul(runtime: runtime) + case .i64Mul: + self.i64Mul(runtime: runtime) + case .f32Mul: + self.f32Mul(runtime: runtime) + case .f64Mul: + self.f64Mul(runtime: runtime) + case .i32Eq: + self.i32Eq(runtime: runtime) + case .i64Eq: + self.i64Eq(runtime: runtime) + case .f32Eq: + self.f32Eq(runtime: runtime) + case .f64Eq: + self.f64Eq(runtime: runtime) + case .i32Ne: + self.i32Ne(runtime: runtime) + case .i64Ne: + self.i64Ne(runtime: runtime) + case .f32Ne: + self.f32Ne(runtime: runtime) + case .f64Ne: + self.f64Ne(runtime: runtime) + case .i32LtS: + self.i32LtS(runtime: runtime) + case .i64LtS: + self.i64LtS(runtime: runtime) + case .i32LtU: + self.i32LtU(runtime: runtime) + case .i64LtU: + self.i64LtU(runtime: runtime) + case .i32GtS: + self.i32GtS(runtime: runtime) + case .i64GtS: + self.i64GtS(runtime: runtime) + case .i32GtU: + self.i32GtU(runtime: runtime) + case .i64GtU: + self.i64GtU(runtime: runtime) + case .i32LeS: + self.i32LeS(runtime: runtime) + case .i64LeS: + self.i64LeS(runtime: runtime) + case .i32LeU: + self.i32LeU(runtime: runtime) + case .i64LeU: + self.i64LeU(runtime: runtime) + case .i32GeS: + self.i32GeS(runtime: runtime) + case .i64GeS: + self.i64GeS(runtime: runtime) + case .i32GeU: + self.i32GeU(runtime: runtime) + case .i64GeU: + self.i64GeU(runtime: runtime) + case .drop: + self.drop(runtime: runtime) + case .select: + try self.select(runtime: runtime) + case .refNull(let referenceType): + self.refNull(runtime: runtime, referenceType: referenceType) + case .refIsNull: + self.refIsNull(runtime: runtime) + case .refFunc(let functionIndex): + self.refFunc(runtime: runtime, functionIndex: functionIndex) + case .tableGet(let tableIndex): + try self.tableGet(runtime: runtime, tableIndex: tableIndex) + case .tableSet(let tableIndex): + try self.tableSet(runtime: runtime, tableIndex: tableIndex) + case .tableSize(let tableIndex): + self.tableSize(runtime: runtime, tableIndex: tableIndex) + case .tableGrow(let tableIndex): + self.tableGrow(runtime: runtime, tableIndex: tableIndex) + case .tableFill(let tableIndex): + try self.tableFill(runtime: runtime, tableIndex: tableIndex) + case .tableCopy(let dest, let src): + try self.tableCopy(runtime: runtime, dest: dest, src: src) + case .tableInit(let tableIndex, let elementIndex): + try self.tableInit(runtime: runtime, tableIndex: tableIndex, elementIndex: elementIndex) + case .tableElementDrop(let elementIndex): + self.tableElementDrop(runtime: runtime, elementIndex: elementIndex) + case .localGet(let index): + self.localGet(runtime: runtime, locals: locals, index: index) + case .localSet(let index): + self.localSet(runtime: runtime, locals: locals, index: index) + case .localTee(let index): + self.localTee(runtime: runtime, locals: locals, index: index) + case .globalGet(let index): + try self.globalGet(runtime: runtime, index: index) + case .globalSet(let index): + try self.globalSet(runtime: runtime, index: index) + } + programCounter += 1 + return true + } +} + +extension Instruction { + var name: String { + switch self { + case .unreachable: return "unreachable" + case .nop: return "nop" + case .block: return "block" + case .loop: return "loop" + case .ifThen: return "ifThen" + case .ifThenElse: return "ifThenElse" + case .end: return "end" + case .`else`: return "`else`" + case .br: return "br" + case .brIf: return "brIf" + case .brTable: return "brTable" + case .`return`: return "`return`" + case .call: return "call" + case .callIndirect: return "callIndirect" + case .endOfFunction: return "endOfFunction" + case .endOfExecution: return "endOfExecution" + case .i32Load: return "i32Load" + case .i64Load: return "i64Load" + case .f32Load: return "f32Load" + case .f64Load: return "f64Load" + case .i32Load8S: return "i32Load8S" + case .i32Load8U: return "i32Load8U" + case .i32Load16S: return "i32Load16S" + case .i32Load16U: return "i32Load16U" + case .i64Load8S: return "i64Load8S" + case .i64Load8U: return "i64Load8U" + case .i64Load16S: return "i64Load16S" + case .i64Load16U: return "i64Load16U" + case .i64Load32S: return "i64Load32S" + case .i64Load32U: return "i64Load32U" + case .i32Store: return "i32Store" + case .i64Store: return "i64Store" + case .f32Store: return "f32Store" + case .f64Store: return "f64Store" + case .i32Store8: return "i32Store8" + case .i32Store16: return "i32Store16" + case .i64Store8: return "i64Store8" + case .i64Store16: return "i64Store16" + case .i64Store32: return "i64Store32" + case .memorySize: return "memorySize" + case .memoryGrow: return "memoryGrow" + case .memoryInit: return "memoryInit" + case .memoryDataDrop: return "memoryDataDrop" + case .memoryCopy: return "memoryCopy" + case .memoryFill: return "memoryFill" + case .numericConst: return "numericConst" + case .numericIntUnary: return "numericIntUnary" + case .numericFloatUnary: return "numericFloatUnary" + case .numericIntBinary: return "numericIntBinary" + case .numericFloatBinary: return "numericFloatBinary" + case .numericConversion: return "numericConversion" + case .i32Add: return "i32Add" + case .i64Add: return "i64Add" + case .f32Add: return "f32Add" + case .f64Add: return "f64Add" + case .i32Sub: return "i32Sub" + case .i64Sub: return "i64Sub" + case .f32Sub: return "f32Sub" + case .f64Sub: return "f64Sub" + case .i32Mul: return "i32Mul" + case .i64Mul: return "i64Mul" + case .f32Mul: return "f32Mul" + case .f64Mul: return "f64Mul" + case .i32Eq: return "i32Eq" + case .i64Eq: return "i64Eq" + case .f32Eq: return "f32Eq" + case .f64Eq: return "f64Eq" + case .i32Ne: return "i32Ne" + case .i64Ne: return "i64Ne" + case .f32Ne: return "f32Ne" + case .f64Ne: return "f64Ne" + case .i32LtS: return "i32LtS" + case .i64LtS: return "i64LtS" + case .i32LtU: return "i32LtU" + case .i64LtU: return "i64LtU" + case .i32GtS: return "i32GtS" + case .i64GtS: return "i64GtS" + case .i32GtU: return "i32GtU" + case .i64GtU: return "i64GtU" + case .i32LeS: return "i32LeS" + case .i64LeS: return "i64LeS" + case .i32LeU: return "i32LeU" + case .i64LeU: return "i64LeU" + case .i32GeS: return "i32GeS" + case .i64GeS: return "i64GeS" + case .i32GeU: return "i32GeU" + case .i64GeU: return "i64GeU" + case .drop: return "drop" + case .select: return "select" + case .refNull: return "refNull" + case .refIsNull: return "refIsNull" + case .refFunc: return "refFunc" + case .tableGet: return "tableGet" + case .tableSet: return "tableSet" + case .tableSize: return "tableSize" + case .tableGrow: return "tableGrow" + case .tableFill: return "tableFill" + case .tableCopy: return "tableCopy" + case .tableInit: return "tableInit" + case .tableElementDrop: return "tableElementDrop" + case .localGet: return "localGet" + case .localSet: return "localSet" + case .localTee: return "localTee" + case .globalGet: return "globalGet" + case .globalSet: return "globalSet" + } + } +} diff --git a/Sources/WasmKit/Execution/Runtime/Runtime.swift b/Sources/WasmKit/Execution/Runtime/Runtime.swift index b4243114..3d9a4978 100644 --- a/Sources/WasmKit/Execution/Runtime/Runtime.swift +++ b/Sources/WasmKit/Execution/Runtime/Runtime.swift @@ -64,31 +64,27 @@ extension Runtime { initialGlobals: initialGlobals ) - // Step 12. - let frame = Frame(arity: 0, module: instance, locals: []) - - // Step 13. - var initExecution = ExecutionState() - try initExecution.stack.push(frame: frame) + // Step 12-13. // Steps 14-15. do { for (elementIndex, element) in module.elements.enumerated() { let elementIndex = UInt32(elementIndex) - switch element.mode { case let .active(tableIndex, offsetExpression): - for i in offsetExpression.instructions + [ - .numeric(.const(.i32(0))), - .numeric(.const(.i32(UInt32(element.initializer.count)))), - .table(.`init`(tableIndex, elementIndex)), - .table(.elementDrop(elementIndex)), - ] { - try initExecution.execute(i, runtime: self) - } + let initIseq = InstructionSequence(instructions: offsetExpression + [ + .numericConst(.i32(0)), + .numericConst(.i32(UInt32(element.initializer.count))), + .tableInit(tableIndex, elementIndex), + .tableElementDrop(elementIndex), + ]) + defer { initIseq.deallocate() } + try evaluateConstExpr(initIseq, instance: instance) case .declarative: - try initExecution.execute(.table(.elementDrop(elementIndex)), runtime: self) + let initIseq: InstructionSequence = [.tableElementDrop(elementIndex)] + defer { initIseq.deallocate() } + try evaluateConstExpr(initIseq, instance: instance) case .passive: continue @@ -104,15 +100,14 @@ extension Runtime { do { for case let (dataIndex, .active(data)) in module.data.enumerated() { assert(data.index == 0) - - for i in data.offset.instructions + [ - .numeric(.const(.i32(0))), - .numeric(.const(.i32(UInt32(data.initializer.count)))), - .memory(.`init`(UInt32(dataIndex))), - .memory(.dataDrop(UInt32(dataIndex))), - ] { - try initExecution.execute(i, runtime: self) - } + let iseq = InstructionSequence(instructions: data.offset + [ + .numericConst(.i32(0)), + .numericConst(.i32(UInt32(data.initializer.count))), + .memoryInit(UInt32(dataIndex)), + .memoryDataDrop(UInt32(dataIndex)), + ]) + defer { iseq.deallocate() } + try evaluateConstExpr(iseq, instance: instance) } } catch Trap.outOfBoundsMemoryAccess { throw InstantiationError.outOfBoundsMemoryAccess @@ -122,53 +117,70 @@ extension Runtime { // Step 17. if let startIndex = module.start { - try initExecution.invoke(functionAddress: instance.functionAddresses[Int(startIndex)], runtime: self) - while initExecution.stack.elements.count != 1 || initExecution.stack.currentFrame != frame { - try initExecution.step(runtime: self) + try withExecution { initExecution in + try initExecution.invoke(functionAddress: instance.functionAddresses[Int(startIndex)], runtime: self) + try initExecution.run(runtime: self) } } - // Steps 18-19. - try initExecution.stack.popFrame() - return instance } private func evaluateGlobals(module: Module, externalValues: [ExternalValue]) throws -> [Value] { - let globalModuleInstance = ModuleInstance() - - for externalValue in externalValues { - switch externalValue { - case let .global(address): - globalModuleInstance.globalAddresses.append(address) - case let .function(address): - globalModuleInstance.functionAddresses.append(address.address) - default: - continue + try store.withTemporaryModuleInstance { globalModuleInstance in + for externalValue in externalValues { + switch externalValue { + case let .global(address): + globalModuleInstance.globalAddresses.append(address) + case let .function(address): + globalModuleInstance.functionAddresses.append(address.address) + default: + continue + } } - } - - globalModuleInstance.types = module.types - - for function in module.functions { - let address = store.allocate(function: function, module: globalModuleInstance) - globalModuleInstance.functionAddresses.append(address) - } - - var initExecution = ExecutionState() - try initExecution.stack.push(frame: .init(arity: 0, module: globalModuleInstance, locals: [])) - - let globalInitializers = try module.globals.map { global in - for i in global.initializer.instructions { - try initExecution.execute(i, runtime: self) + + globalModuleInstance.types = module.types + + for function in module.functions { + let address = store.allocate(function: function, module: globalModuleInstance) + globalModuleInstance.functionAddresses.append(address) } - - return try initExecution.stack.popValue() + + let globalInitializers = try module.globals.map { global in + let iseq = InstructionSequence(instructions: global.initializer) + defer { iseq.deallocate() } + return try evaluateConstExpr(iseq, instance: globalModuleInstance, arity: 1) { initExecution in + return initExecution.stack.popValue() + } + } + + return globalInitializers } + } - try initExecution.stack.popFrame() + func evaluateConstExpr(_ iseq: InstructionSequence, instance: ModuleInstance, arity: Int = 0) throws { + try evaluateConstExpr(iseq, instance: instance, arity: arity, body: { _ in }) + } - return globalInitializers + func evaluateConstExpr( + _ iseq: InstructionSequence, + instance: ModuleInstance, + arity: Int = 0, + body: (inout ExecutionState) throws -> T + ) throws -> T { + try withExecution { initExecution in + try initExecution.stack.pushFrame( + iseq: iseq, + arity: arity, + module: instance.selfAddress, + argc: 0, + defaultLocals: nil, + returnPC: initExecution.programCounter + 1 + ) + initExecution.programCounter = iseq.baseAddress + try initExecution.run(runtime: self) + return try body(&initExecution) + } } } diff --git a/Sources/WasmKit/Execution/Runtime/Stack.swift b/Sources/WasmKit/Execution/Runtime/Stack.swift index 4df02bac..1b0551ba 100644 --- a/Sources/WasmKit/Execution/Runtime/Stack.swift +++ b/Sources/WasmKit/Execution/Runtime/Stack.swift @@ -8,159 +8,269 @@ public struct Stack { case frame(Frame) } - private(set) var limit = UInt16.max - private(set) var elements = [Element]() - private(set) var currentFrame: Frame! - private(set) var currentLabel: Label! - - var top: Element? { elements.last } + private var limit: UInt16 { UInt16.max } + private var valueStack: ValueStack + private var numberOfValues: Int { valueStack.count } + private var labels: FixedSizeStack