From d279ac0604e8ec0f116d98dd6ec63d486b70bd5d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 22 Sep 2024 14:44:53 +0900 Subject: [PATCH] VMGen: Overhaul the instruction generation system --- Sources/WasmKit/CMakeLists.txt | 28 +- ...spatch.swift => DispatchInstruction.swift} | 9 +- .../Execution/{Types => }/Errors.swift | 0 .../ExecutionState.swift => Execution.swift} | 14 +- .../Execution/{Runtime => }/Function.swift | 2 +- .../Execution/{Types => }/Instances.swift | 0 .../Execution/Instructions/Control.swift | 2 +- .../Execution/Instructions/Instruction.swift | 2 + .../Instructions/InstructionSupport.swift | 3 - .../Execution/Instructions/Memory.swift | 2 +- .../Execution/Instructions/Numeric.swift | 2 +- .../Execution/Instructions/Parametric.swift | 2 +- .../Execution/Instructions/Reference.swift | 2 +- .../Execution/Instructions/Table.swift | 4 +- .../Execution/Instructions/Variable.swift | 2 +- .../{Runtime => }/NameRegistry.swift | 0 .../Execution/{Runtime => }/Profiler.swift | 0 .../Execution/{Runtime => }/Runtime.swift | 4 +- .../{Runtime => }/RuntimeInterceptor.swift | 0 .../{Runtime => }/SignpostTracer.swift | 0 .../Execution/{Runtime => }/Store.swift | 0 .../{Runtime => }/StoreAllocator.swift | 0 .../Execution/{Types => }/UntypedValue.swift | 0 .../WasmKit/Execution/{Types => }/Value.swift | 0 .../_CWasmKit/include/DirectThreadedCode.inc | 2 + Utilities/Sources/VMGen.swift | 547 +++--------------- Utilities/Sources/VMSpec.swift | 421 ++++++++++++++ 27 files changed, 543 insertions(+), 505 deletions(-) rename Sources/WasmKit/Execution/{Runtime/InstDispatch.swift => DispatchInstruction.swift} (99%) rename Sources/WasmKit/Execution/{Types => }/Errors.swift (100%) rename Sources/WasmKit/Execution/{Runtime/ExecutionState.swift => Execution.swift} (97%) rename Sources/WasmKit/Execution/{Runtime => }/Function.swift (98%) rename Sources/WasmKit/Execution/{Types => }/Instances.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/NameRegistry.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/Profiler.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/Runtime.swift (98%) rename Sources/WasmKit/Execution/{Runtime => }/RuntimeInterceptor.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/SignpostTracer.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/Store.swift (100%) rename Sources/WasmKit/Execution/{Runtime => }/StoreAllocator.swift (100%) rename Sources/WasmKit/Execution/{Types => }/UntypedValue.swift (100%) rename Sources/WasmKit/Execution/{Types => }/Value.swift (100%) create mode 100644 Utilities/Sources/VMSpec.swift diff --git a/Sources/WasmKit/CMakeLists.txt b/Sources/WasmKit/CMakeLists.txt index c547948f..b76a978c 100644 --- a/Sources/WasmKit/CMakeLists.txt +++ b/Sources/WasmKit/CMakeLists.txt @@ -18,20 +18,20 @@ add_wasmkit_library(WasmKit Execution/Instructions/InstructionSupport.swift Execution/Instructions/Numeric.swift Execution/Instructions/Variable.swift - Execution/Types/Instances.swift - Execution/Types/Errors.swift - Execution/Types/Value.swift - Execution/Types/UntypedValue.swift - Execution/Runtime/InstDispatch.swift - Execution/Runtime/Runtime.swift - Execution/Runtime/RuntimeInterceptor.swift - Execution/Runtime/ExecutionState.swift - Execution/Runtime/Profiler.swift - Execution/Runtime/NameRegistry.swift - Execution/Runtime/Store.swift - Execution/Runtime/StoreAllocator.swift - Execution/Runtime/SignpostTracer.swift - Execution/Runtime/Function.swift + Execution/DispatchInstruction.swift + Execution/Errors.swift + Execution/Execution.swift + Execution/Function.swift + Execution/Instances.swift + Execution/NameRegistry.swift + Execution/Profiler.swift + Execution/Runtime.swift + Execution/RuntimeInterceptor.swift + Execution/SignpostTracer.swift + Execution/Store.swift + Execution/StoreAllocator.swift + Execution/UntypedValue.swift + Execution/Value.swift ) target_link_wasmkit_libraries(WasmKit PUBLIC diff --git a/Sources/WasmKit/Execution/Runtime/InstDispatch.swift b/Sources/WasmKit/Execution/DispatchInstruction.swift similarity index 99% rename from Sources/WasmKit/Execution/Runtime/InstDispatch.swift rename to Sources/WasmKit/Execution/DispatchInstruction.swift index 48ef0332..6161c81b 100644 --- a/Sources/WasmKit/Execution/Runtime/InstDispatch.swift +++ b/Sources/WasmKit/Execution/DispatchInstruction.swift @@ -1,5 +1,6 @@ -// This file is generated by Utilities/generate_inst_dispatch.swift -extension ExecutionState { +//// Automatically generated by Utilities/Sources/VMGen.swift +//// DO NOT EDIT DIRECTLY +extension Execution { @inline(__always) mutating func doExecute(_ instruction: UInt64, sp: inout Sp, pc: inout Pc, md: inout Md, ms: inout Ms) throws { switch instruction { @@ -409,7 +410,7 @@ extension Instruction { } -extension ExecutionState { +extension Execution { @inline(__always) mutating func i32Add(sp: Sp, binaryOperand: Instruction.BinaryOperand) { sp[i32: binaryOperand.result] = sp[i32: binaryOperand.lhs].add(sp[i32: binaryOperand.rhs]) } @@ -890,7 +891,7 @@ extension ExecutionState { } -extension ExecutionState { +extension Execution { @_silgen_name("wasmkit_execute_copyStack") @inline(__always) mutating func execute_copyStack(sp: UnsafeMutablePointer, pc: UnsafeMutablePointer, md: UnsafeMutablePointer, ms: UnsafeMutablePointer) { let copyStackOperand = Instruction.CopyStackOperand.load(from: &pc.pointee) diff --git a/Sources/WasmKit/Execution/Types/Errors.swift b/Sources/WasmKit/Execution/Errors.swift similarity index 100% rename from Sources/WasmKit/Execution/Types/Errors.swift rename to Sources/WasmKit/Execution/Errors.swift diff --git a/Sources/WasmKit/Execution/Runtime/ExecutionState.swift b/Sources/WasmKit/Execution/Execution.swift similarity index 97% rename from Sources/WasmKit/Execution/Runtime/ExecutionState.swift rename to Sources/WasmKit/Execution/Execution.swift index b3e6e502..8e9cb3bc 100644 --- a/Sources/WasmKit/Execution/Runtime/ExecutionState.swift +++ b/Sources/WasmKit/Execution/Execution.swift @@ -2,9 +2,9 @@ import _CWasmKit /// An execution state of an invocation of exported function. /// -/// Each new invocation through exported function has a separate ``ExecutionState`` +/// Each new invocation through exported function has a separate ``Execution`` /// even though the invocation happens during another invocation. -struct ExecutionState { +struct Execution { /// The reference to the ``Runtime`` associated with the execution. let runtime: RuntimeRef /// The end of the VM stack space. @@ -17,14 +17,14 @@ struct ExecutionState { /// the given ``Runtime`` instance. static func with( runtime: RuntimeRef, - body: (inout ExecutionState, Sp) throws -> T + body: (inout Execution, Sp) throws -> T ) rethrows -> T { let limit = Int(UInt16.max) let valueStack = UnsafeMutablePointer.allocate(capacity: limit) defer { valueStack.deallocate() } - var context = ExecutionState(runtime: runtime, stackEnd: valueStack.advanced(by: limit)) + var context = Execution(runtime: runtime, stackEnd: valueStack.advanced(by: limit)) return try body(&context, valueStack) } @@ -136,7 +136,7 @@ func executeWasm( ) throws -> [Value] { // NOTE: `runtime` variable must not outlive this function let runtime = RuntimeRef(runtime) - return try ExecutionState.with(runtime: runtime) { (stack, sp) in + return try Execution.with(runtime: runtime) { (stack, sp) in // Advance the stack pointer to be able to reference negative indices // for saving slots. let sp = sp.advanced(by: FrameHeaderLayout.numberOfSavingSlots) @@ -164,7 +164,7 @@ func executeWasm( } } -extension ExecutionState { +extension Execution { /// A namespace for the "current memory" (Md and Ms) management. enum CurrentMemory { /// Assigns the current memory to the given internal memory. @@ -309,7 +309,7 @@ extension ExecutionState { returnPC: pc, spAddend: callLike.spAddend ) - ExecutionState.CurrentMemory.mayUpdateCurrentInstance( + Execution.CurrentMemory.mayUpdateCurrentInstance( instance: function.instance, from: callerInstance, md: &md, ms: &ms ) diff --git a/Sources/WasmKit/Execution/Runtime/Function.swift b/Sources/WasmKit/Execution/Function.swift similarity index 98% rename from Sources/WasmKit/Execution/Runtime/Function.swift rename to Sources/WasmKit/Execution/Function.swift index 6ad2220c..0a9b4f92 100644 --- a/Sources/WasmKit/Execution/Runtime/Function.swift +++ b/Sources/WasmKit/Execution/Function.swift @@ -165,7 +165,7 @@ struct WasmFunctionEntity { self.index = index } - mutating func ensureCompiled(context: inout ExecutionState) throws -> InstructionSequence { + mutating func ensureCompiled(context: inout Execution) throws -> InstructionSequence { try ensureCompiled(runtime: context.runtime) } diff --git a/Sources/WasmKit/Execution/Types/Instances.swift b/Sources/WasmKit/Execution/Instances.swift similarity index 100% rename from Sources/WasmKit/Execution/Types/Instances.swift rename to Sources/WasmKit/Execution/Instances.swift diff --git a/Sources/WasmKit/Execution/Instructions/Control.swift b/Sources/WasmKit/Execution/Instructions/Control.swift index 4c7b0ed8..91cc1fe1 100644 --- a/Sources/WasmKit/Execution/Instructions/Control.swift +++ b/Sources/WasmKit/Execution/Instructions/Control.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { func unreachable(sp: Sp, pc: Pc) throws -> Pc { throw Trap.unreachable } diff --git a/Sources/WasmKit/Execution/Instructions/Instruction.swift b/Sources/WasmKit/Execution/Instructions/Instruction.swift index 2d676fbe..0b54ae70 100644 --- a/Sources/WasmKit/Execution/Instructions/Instruction.swift +++ b/Sources/WasmKit/Execution/Instructions/Instruction.swift @@ -1,3 +1,5 @@ +//// Automatically generated by Utilities/Sources/VMGen.swift +//// DO NOT EDIT DIRECTLY enum Instruction: Equatable { case copyStack(Instruction.CopyStackOperand) case globalGet(Instruction.GlobalGetOperand) diff --git a/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift b/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift index 9a23dfc6..f21babce 100644 --- a/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift +++ b/Sources/WasmKit/Execution/Instructions/InstructionSupport.swift @@ -81,7 +81,6 @@ extension Int32: InstructionImmediate { } extension Instruction { - /// size = 6, alignment = 2 struct BinaryOperand: Equatable, InstructionImmediate { let result: LVReg let lhs: VReg @@ -94,7 +93,6 @@ extension Instruction { } } - /// size = 4, alignment = 2 struct UnaryOperand: Equatable, InstructionImmediate { let result: LVReg let input: LVReg @@ -125,7 +123,6 @@ extension Instruction { } } - /// size = 4, alignment = 8 struct LoadOperand: Equatable, InstructionImmediate { let offset: UInt64 let pointer: VReg diff --git a/Sources/WasmKit/Execution/Instructions/Memory.swift b/Sources/WasmKit/Execution/Instructions/Memory.swift index 17d0492b..f2de072e 100644 --- a/Sources/WasmKit/Execution/Instructions/Memory.swift +++ b/Sources/WasmKit/Execution/Instructions/Memory.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { @inline(never) func throwOutOfBoundsMemoryAccess() throws -> Never { throw Trap.outOfBoundsMemoryAccess } diff --git a/Sources/WasmKit/Execution/Instructions/Numeric.swift b/Sources/WasmKit/Execution/Instructions/Numeric.swift index 7f54f615..85b70887 100644 --- a/Sources/WasmKit/Execution/Instructions/Numeric.swift +++ b/Sources/WasmKit/Execution/Instructions/Numeric.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { @inline(__always) mutating func const32(sp: Sp, const32Operand: Instruction.Const32Operand) { sp[const32Operand.result] = UntypedValue(storage32: const32Operand.value) diff --git a/Sources/WasmKit/Execution/Instructions/Parametric.swift b/Sources/WasmKit/Execution/Instructions/Parametric.swift index fbee24c4..a090c1c7 100644 --- a/Sources/WasmKit/Execution/Instructions/Parametric.swift +++ b/Sources/WasmKit/Execution/Instructions/Parametric.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { mutating func select(sp: Sp, selectOperand: Instruction.SelectOperand) { let flag = sp[i32: selectOperand.condition] let selected = flag != 0 ? selectOperand.onTrue : selectOperand.onFalse diff --git a/Sources/WasmKit/Execution/Instructions/Reference.swift b/Sources/WasmKit/Execution/Instructions/Reference.swift index 25d7e03f..5a31e613 100644 --- a/Sources/WasmKit/Execution/Instructions/Reference.swift +++ b/Sources/WasmKit/Execution/Instructions/Reference.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { mutating func refNull(sp: Sp, refNullOperand: Instruction.RefNullOperand) { let value: Value switch refNullOperand.type { diff --git a/Sources/WasmKit/Execution/Instructions/Table.swift b/Sources/WasmKit/Execution/Instructions/Table.swift index 8b5612ff..161bec7d 100644 --- a/Sources/WasmKit/Execution/Instructions/Table.swift +++ b/Sources/WasmKit/Execution/Instructions/Table.swift @@ -2,7 +2,7 @@ /// import WasmParser -extension ExecutionState { +extension Execution { mutating func tableGet(sp: Sp, tableGetOperand: Instruction.TableGetOperand) throws { let runtime = runtime.value let table = getTable(tableGetOperand.tableIndex, sp: sp, store: runtime.store) @@ -131,7 +131,7 @@ extension ExecutionState { } } -extension ExecutionState { +extension Execution { fileprivate func getTable(_ tableIndex: UInt32, sp: Sp, store: Store) -> InternalTable { return currentInstance(sp: sp).tables[Int(tableIndex)] } diff --git a/Sources/WasmKit/Execution/Instructions/Variable.swift b/Sources/WasmKit/Execution/Instructions/Variable.swift index e0402131..b02000f2 100644 --- a/Sources/WasmKit/Execution/Instructions/Variable.swift +++ b/Sources/WasmKit/Execution/Instructions/Variable.swift @@ -1,6 +1,6 @@ /// > Note: /// -extension ExecutionState { +extension Execution { mutating func globalGet(sp: Sp, globalGetOperand: Instruction.GlobalGetOperand) { globalGetOperand.global.withValue{ sp[globalGetOperand.reg] = $0.rawValue diff --git a/Sources/WasmKit/Execution/Runtime/NameRegistry.swift b/Sources/WasmKit/Execution/NameRegistry.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/NameRegistry.swift rename to Sources/WasmKit/Execution/NameRegistry.swift diff --git a/Sources/WasmKit/Execution/Runtime/Profiler.swift b/Sources/WasmKit/Execution/Profiler.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/Profiler.swift rename to Sources/WasmKit/Execution/Profiler.swift diff --git a/Sources/WasmKit/Execution/Runtime/Runtime.swift b/Sources/WasmKit/Execution/Runtime.swift similarity index 98% rename from Sources/WasmKit/Execution/Runtime/Runtime.swift rename to Sources/WasmKit/Execution/Runtime.swift index bbbc7e27..5d20986d 100644 --- a/Sources/WasmKit/Execution/Runtime/Runtime.swift +++ b/Sources/WasmKit/Execution/Runtime.swift @@ -174,12 +174,12 @@ extension Runtime { } extension Runtime { - @available(*, unavailable, message: "Runtime doesn't manage execution state anymore. Use ExecutionState.step instead") + @available(*, unavailable, message: "Runtime doesn't manage execution state anymore. Use Execution.step instead") public func step() throws { fatalError() } - @available(*, unavailable, message: "Runtime doesn't manage execution state anymore. Use ExecutionState.step instead") + @available(*, unavailable, message: "Runtime doesn't manage execution state anymore. Use Execution.step instead") public func run() throws { fatalError() } diff --git a/Sources/WasmKit/Execution/Runtime/RuntimeInterceptor.swift b/Sources/WasmKit/Execution/RuntimeInterceptor.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/RuntimeInterceptor.swift rename to Sources/WasmKit/Execution/RuntimeInterceptor.swift diff --git a/Sources/WasmKit/Execution/Runtime/SignpostTracer.swift b/Sources/WasmKit/Execution/SignpostTracer.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/SignpostTracer.swift rename to Sources/WasmKit/Execution/SignpostTracer.swift diff --git a/Sources/WasmKit/Execution/Runtime/Store.swift b/Sources/WasmKit/Execution/Store.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/Store.swift rename to Sources/WasmKit/Execution/Store.swift diff --git a/Sources/WasmKit/Execution/Runtime/StoreAllocator.swift b/Sources/WasmKit/Execution/StoreAllocator.swift similarity index 100% rename from Sources/WasmKit/Execution/Runtime/StoreAllocator.swift rename to Sources/WasmKit/Execution/StoreAllocator.swift diff --git a/Sources/WasmKit/Execution/Types/UntypedValue.swift b/Sources/WasmKit/Execution/UntypedValue.swift similarity index 100% rename from Sources/WasmKit/Execution/Types/UntypedValue.swift rename to Sources/WasmKit/Execution/UntypedValue.swift diff --git a/Sources/WasmKit/Execution/Types/Value.swift b/Sources/WasmKit/Execution/Value.swift similarity index 100% rename from Sources/WasmKit/Execution/Types/Value.swift rename to Sources/WasmKit/Execution/Value.swift diff --git a/Sources/_CWasmKit/include/DirectThreadedCode.inc b/Sources/_CWasmKit/include/DirectThreadedCode.inc index 9c1ebeb2..3a542a3d 100644 --- a/Sources/_CWasmKit/include/DirectThreadedCode.inc +++ b/Sources/_CWasmKit/include/DirectThreadedCode.inc @@ -1,3 +1,5 @@ +//// Automatically generated by Utilities/Sources/VMGen.swift +//// DO NOT EDIT DIRECTLY SWIFT_CC(swiftasync) static inline void wasmkit_tc_copyStack(Sp sp, Pc pc, Md md, Ms ms, SWIFT_CONTEXT void *state) { SWIFT_CC(swift) void wasmkit_execute_copyStack(Sp *sp, Pc *pc, Md *md, Ms *ms, SWIFT_CONTEXT void *state, SWIFT_ERROR_RESULT void **error); void * _Nullable error = NULL; diff --git a/Utilities/Sources/VMGen.swift b/Utilities/Sources/VMGen.swift index fb0c9dc9..36699f76 100644 --- a/Utilities/Sources/VMGen.swift +++ b/Utilities/Sources/VMGen.swift @@ -3,433 +3,27 @@ import Foundation /// A utility to generate internal VM instruction related code. enum VMGen { - struct Immediate { - let name: String? - let type: String - - var label: String { - name ?? VMGen.camelCase(pascalCase: String(type.split(separator: ".").last!)) - } - } - enum RegisterUse { - case none - case read - case write - } - - /// A parameter passed to the `doExecute` method. Expected to be bound to a - /// physical register. - struct ExecParam: CaseIterable { - let label: String - let type: String - - static let sp = ExecParam(label: "sp", type: "Sp") - static let pc = ExecParam(label: "pc", type: "Pc") - static let md = ExecParam(label: "md", type: "Md") - static let ms = ExecParam(label: "ms", type: "Ms") - - static var allCases = [sp, pc, md, ms] - } - - struct Instruction { - var name: String - var isControl: Bool - var mayThrow: Bool - var mayUpdateFrame: Bool - var mayUpdateSp: Bool = false - var useCurrentMemory: RegisterUse - var immediate: Immediate? - - var mayUpdatePc: Bool { - self.isControl - } - - init( - name: String, isControl: Bool = false, - mayThrow: Bool = false, mayUpdateFrame: Bool = false, - useCurrentMemory: RegisterUse = .none, - immediate: Immediate? = nil - ) { - self.name = name - self.isControl = isControl - self.mayThrow = mayThrow - self.mayUpdateFrame = mayUpdateFrame - self.useCurrentMemory = useCurrentMemory - self.immediate = immediate - assert(isControl || !mayUpdateFrame, "non-control instruction should not update frame") - } - - typealias Parameter = (label: String, type: String, isInout: Bool) - var parameters: [Parameter] { - var vregs: [(reg: ExecParam, isInout: Bool)] = [] - if self.mayUpdateFrame { - vregs += [(ExecParam.sp, true)] - } else { - vregs += [(ExecParam.sp, false)] - } - if self.mayUpdatePc { - vregs += [(ExecParam.pc, false)] - } - switch useCurrentMemory { - case .none: break - case .read: - vregs += [(ExecParam.md, false), (ExecParam.ms, false)] - case .write: - vregs += [(ExecParam.md, true), (ExecParam.ms, true)] - } - var parameters: [Parameter] = vregs.map { ($0.reg.label, $0.reg.type, $0.isInout) } - if let immediate = self.immediate { - parameters += [(immediate.label, immediate.type, false)] - } - return parameters - } - } - - struct OpInstruction { - let op: String - let inputType: String - let resultType: String - let base: Instruction - - static func binop(op: String, type: String) -> OpInstruction { - let base = Instruction( - name: "\(type)\(op)", immediate: Immediate(name: nil, type: "Instruction.BinaryOperand") - ) - return OpInstruction(op: op, inputType: type, resultType: type, base: base) - } - static func binop(op: String, inputType: String, resultType: String) -> OpInstruction { - let base = Instruction( - name: "\(inputType)\(op)", immediate: Immediate(name: nil, type: "Instruction.BinaryOperand") - ) - return OpInstruction(op: op, inputType: inputType, resultType: resultType, base: base) - } - static func unop(op: String, type: String, resultType: String) -> OpInstruction { - let base = Instruction(name: "\(type)\(op)", immediate: Immediate(name: nil, type: "Instruction.UnaryOperand")) - return OpInstruction(op: op, inputType: type, resultType: resultType, base: base) - } - } - - struct BinOpInfo { - let op: String - let name: String - let lhsType: String - let rhsType: String - let resultType: String - var mayThrow: Bool = false - - var instruction: Instruction { - Instruction( - name: name, - mayThrow: mayThrow, - immediate: Immediate(name: nil, type: "Instruction.BinaryOperand") - ) - } - } - - struct UnOpInfo { - var op: String - var name: String - var inputType: String - var resultType: String - var mayThrow: Bool = false - - var instruction: Instruction { - Instruction(name: name, mayThrow: mayThrow, immediate: Immediate(name: nil, type: "Instruction.UnaryOperand")) - } - } - - static let intValueTypes = ["i32", "i64"] - static let floatValueTypes = ["f32", "f64"] - static let valueTypes = intValueTypes + floatValueTypes - - // MARK: - Int instructions - - static func buildIntBinOps() -> [BinOpInfo] { - var results: [BinOpInfo] = [] - // (T, T) -> T for all T in int types - results += [ - "Add", "Sub", "Mul", - "And", "Or", "Xor", "Shl", "ShrS", "ShrU", "Rotl", "Rotr", - ].flatMap { op -> [BinOpInfo] in - intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0) } - } - results += [ - "DivS", "DivU", "RemS", "RemU", - ].flatMap { op -> [BinOpInfo] in - intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0, mayThrow: true) } - } - // (T, T) -> i32 for all T in int types - results += [ - "Eq", "Ne", "LtS", "LtU", "GtS", "GtU", "LeS", "LeU", "GeS", "GeU", - ].flatMap { op -> [BinOpInfo] in - intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: "i32") } - } - return results - } - static let intBinOps: [BinOpInfo] = buildIntBinOps() - - static func buildIntUnaryInsts() -> [UnOpInfo] { - var results: [UnOpInfo] = [] - // (T) -> T for all T in int types - results += ["Clz", "Ctz", "Popcnt"].flatMap { op -> [UnOpInfo] in - intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } - } - // (T) -> i32 for all T in int types - results += ["Eqz"].flatMap { op -> [UnOpInfo] in - intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: "i32") } - } - // (i64) -> i32 - results += [UnOpInfo(op: "Wrap", name: "i32WrapI64", inputType: "i64", resultType: "i32")] - // (i32) -> i64 - results += ["ExtendI32S", "ExtendI32U"].map { op -> UnOpInfo in - UnOpInfo(op: op, name: "i64\(op)", inputType: "i32", resultType: "i64") - } - // (T) -> T for all T in int types - results += ["Extend8S", "Extend16S"].flatMap { op -> [UnOpInfo] in - intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } - } - // (i64) -> i64 - results += ["Extend32S"].map { op -> UnOpInfo in - UnOpInfo(op: op, name: "i64\(op)", inputType: "i64", resultType: "i64") - } - // Truncation - let truncInOut: [(source: String, result: String)] = [ - ("f32", "i32"), ("f64", "i32"), ("f32", "i64"), ("f64", "i64") - ] - results += truncInOut.flatMap { source, result in - [ - UnOpInfo(op: "TruncTo\(result.uppercased())S", name: "\(result)Trunc\(source.uppercased())S", inputType: source, resultType: result, mayThrow: true), - UnOpInfo(op: "TruncTo\(result.uppercased())U", name: "\(result)Trunc\(source.uppercased())U", inputType: source, resultType: result, mayThrow: true), - UnOpInfo(op: "TruncSatTo\(result.uppercased())S", name: "\(result)TruncSat\(source.uppercased())S", inputType: source, resultType: result, mayThrow: true), - UnOpInfo(op: "TruncSatTo\(result.uppercased())U", name: "\(result)TruncSat\(source.uppercased())U", inputType: source, resultType: result, mayThrow: true) - ] - } - // Conversion - let convInOut: [(source: String, result: String)] = [ - ("i32", "f32"), ("i64", "f32"), ("i32", "f64"), ("i64", "f64") - ] - results += convInOut.flatMap { source, result in - [ - UnOpInfo(op: "ConvertTo\(result.uppercased())S", name: "\(result)Convert\(source.uppercased())S", inputType: source, resultType: result), - UnOpInfo(op: "ConvertTo\(result.uppercased())U", name: "\(result)Convert\(source.uppercased())U", inputType: source, resultType: result), - ] - } - // Reinterpret - let reinterpretInOut: [(source: String, result: String)] = [ - ("i32", "f32"), ("i64", "f64"), ("f32", "i32"), ("f64", "i64") - ] - results += reinterpretInOut.flatMap { source, result in - [ - UnOpInfo(op: "ReinterpretTo\(result.uppercased())", name: "\(result)Reinterpret\(source.uppercased())", inputType: source, resultType: result), - ] - } - return results + static func camelCase(pascalCase: String) -> String { + let first = pascalCase.first!.lowercased() + return first + pascalCase.dropFirst() } - static let intUnaryInsts: [UnOpInfo] = buildIntUnaryInsts() + struct GeneratedFile { + let pathComponents: [String] + let content: String - // MARK: - Float instructions - - static func buildFloatBinOps() -> [BinOpInfo] { - var results: [BinOpInfo] = [] - // (T, T) -> T for all T in float types - results += [ - "Add", "Sub", "Mul", "Div", - "Min", "Max", "CopySign", - ].flatMap { op -> [BinOpInfo] in - floatValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0) } - } - // (T, T) -> i32 for all T in float types - results += [ - "Eq", "Ne", "Lt", "Gt", "Le", "Ge" - ].flatMap { op -> [BinOpInfo] in - floatValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: "i32") } + init(_ pathComponents: [String], _ content: String) { + self.pathComponents = pathComponents + self.content = content } - return results - } - static let floatBinOps: [BinOpInfo] = buildFloatBinOps() - - static func buildFloatUnaryOps() -> [UnOpInfo] { - var results: [UnOpInfo] = [] - // (T) -> T for all T in float types - results += ["Abs", "Neg", "Ceil", "Floor", "Trunc", "Nearest", "Sqrt"].flatMap { op -> [UnOpInfo] in - floatValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } - } - // (f32) -> f64 - results += ["PromoteF32"].map { op -> UnOpInfo in - UnOpInfo(op: op, name: "f64\(op)", inputType: "f32", resultType: "f64") - } - // (f64) -> f32 - results += ["DemoteF64"].map { op -> UnOpInfo in - UnOpInfo(op: op, name: "f32\(op)", inputType: "f64", resultType: "f32") - } - return results - } - static let floatUnaryOps: [UnOpInfo] = buildFloatUnaryOps() - - // MARK: - Minor numeric instructions - - static let numericOtherInsts: [Instruction] = [ - // Numeric - Instruction(name: "const32", immediate: Immediate(name: nil, type: "Instruction.Const32Operand")), - Instruction(name: "const64", immediate: Immediate(name: nil, type: "Instruction.Const64Operand")), - ] - - // MARK: - Memory instructions - - struct LoadInstruction { - let loadAs: String - let castToValue: String - let base: Instruction - } - - static let memoryLoadInsts: [LoadInstruction] = [ - ("i32Load", "UInt32", ".i32($0)"), - ("i64Load", "UInt64", ".i64($0)"), - ("f32Load", "UInt32", ".rawF32($0)"), - ("f64Load", "UInt64", ".rawF64($0)"), - ("i32Load8S", "Int8", ".init(signed: Int32($0))"), - ("i32Load8U", "UInt8", ".i32(UInt32($0))"), - ("i32Load16S", "Int16", ".init(signed: Int32($0))"), - ("i32Load16U", "UInt16", ".i32(UInt32($0))"), - ("i64Load8S", "Int8", ".init(signed: Int64($0))"), - ("i64Load8U", "UInt8", ".i64(UInt64($0))"), - ("i64Load16S", "Int16", ".init(signed: Int64($0))"), - ("i64Load16U", "UInt16", ".i64(UInt64($0))"), - ("i64Load32S", "Int32", ".init(signed: Int64($0))"), - ("i64Load32U", "UInt32", ".i64(UInt64($0))"), - ].map { (name, loadAs, castToValue) in - let base = Instruction(name: name, mayThrow: true, useCurrentMemory: .read, immediate: Immediate(name: nil, type: "Instruction.LoadOperand")) - return LoadInstruction(loadAs: loadAs, castToValue: castToValue, base: base) - } - - struct StoreInstruction { - let castFromValue: String - let base: Instruction - } - static let memoryStoreInsts: [StoreInstruction] = [ - ("i32Store", "$0.i32"), - ("i64Store", "$0.i64"), - ("f32Store", "$0.rawF32"), - ("f64Store", "$0.rawF64"), - ("i32Store8", "UInt8(truncatingIfNeeded: $0.i32)"), - ("i32Store16", "UInt16(truncatingIfNeeded: $0.i32)"), - ("i64Store8", "UInt8(truncatingIfNeeded: $0.i64)"), - ("i64Store16", "UInt16(truncatingIfNeeded: $0.i64)"), - ("i64Store32", "UInt32(truncatingIfNeeded: $0.i64)"), - ].map { (name, castFromValue) in - let base = Instruction(name: name, mayThrow: true, useCurrentMemory: .read, immediate: Immediate(name: nil, type: "Instruction.StoreOperand")) - return StoreInstruction(castFromValue: castFromValue, base: base) - } - static let memoryLoadStoreInsts: [Instruction] = memoryLoadInsts.map(\.base) + memoryStoreInsts.map(\.base) - static let memoryOpInsts: [Instruction] = [ - Instruction(name: "memorySize", immediate: Immediate(name: nil, type: "Instruction.MemorySizeOperand")), - Instruction(name: "memoryGrow", mayThrow: true, useCurrentMemory: .write, immediate: Immediate(name: nil, type: "Instruction.MemoryGrowOperand")), - Instruction(name: "memoryInit", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.MemoryInitOperand")), - Instruction(name: "memoryDataDrop", immediate: Immediate(name: nil, type: "DataIndex")), - Instruction(name: "memoryCopy", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.MemoryCopyOperand")), - Instruction(name: "memoryFill", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.MemoryFillOperand")), - ] - - // MARK: - Misc instructions - - static let miscInsts: [Instruction] = [ - // Parametric - Instruction(name: "select", immediate: Immediate(name: nil, type: "Instruction.SelectOperand")), - // Reference - Instruction(name: "refNull", immediate: Immediate(name: nil, type: "Instruction.RefNullOperand")), - Instruction(name: "refIsNull", immediate: Immediate(name: nil, type: "Instruction.RefIsNullOperand")), - Instruction(name: "refFunc", immediate: Immediate(name: nil, type: "Instruction.RefFuncOperand")), - // Table - Instruction(name: "tableGet", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableGetOperand")), - Instruction(name: "tableSet", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableSetOperand")), - Instruction(name: "tableSize", immediate: Immediate(name: nil, type: "Instruction.TableSizeOperand")), - Instruction(name: "tableGrow", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableGrowOperand")), - Instruction(name: "tableFill", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableFillOperand")), - Instruction(name: "tableCopy", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableCopyOperand")), - Instruction(name: "tableInit", mayThrow: true, immediate: Immediate(name: nil, type: "Instruction.TableInitOperand")), - Instruction(name: "tableElementDrop", immediate: Immediate(name: nil, type: "ElementIndex")), - // Profiling - Instruction(name: "onEnter", immediate: Immediate(name: nil, type: "Instruction.OnEnterOperand")), - Instruction(name: "onExit", immediate: Immediate(name: nil, type: "Instruction.OnExitOperand")), - ] - - // MARK: - Instruction generation - - static func buildInstructions() -> [Instruction] { - var instructions: [Instruction] = [ - // Variable - Instruction(name: "copyStack", immediate: Immediate(name: nil, type: "Instruction.CopyStackOperand")), - Instruction(name: "globalGet", immediate: Immediate(name: nil, type: "Instruction.GlobalGetOperand")), - Instruction(name: "globalSet", immediate: Immediate(name: nil, type: "Instruction.GlobalSetOperand")), - // Controls - Instruction( - name: "call", isControl: true, mayThrow: true, mayUpdateFrame: true, useCurrentMemory: .write, - immediate: Immediate(name: nil, type: "Instruction.CallOperand") - ), - Instruction( - name: "compilingCall", isControl: true, mayThrow: true, mayUpdateFrame: true, - immediate: Immediate(name: nil, type: "Instruction.CompilingCallOperand") - ), - Instruction( - name: "internalCall", isControl: true, mayThrow: true, mayUpdateFrame: true, - immediate: - Immediate(name: nil, type: "Instruction.InternalCallOperand") - ), - Instruction( - name: "callIndirect", isControl: true, mayThrow: true, mayUpdateFrame: true, useCurrentMemory: .write, - immediate: - Immediate(name: nil, type: "Instruction.CallIndirectOperand") - ), - Instruction(name: "unreachable", isControl: true, mayThrow: true), - Instruction(name: "nop"), - Instruction( - name: "br", isControl: true, mayUpdateFrame: false, - immediate: - Immediate(name: "offset", type: "Int32") - ), - Instruction( - name: "brIf", isControl: true, mayUpdateFrame: false, - immediate: - Immediate(name: nil, type: "Instruction.BrIfOperand") - ), - Instruction( - name: "brIfNot", isControl: true, mayUpdateFrame: false, - immediate: - Immediate(name: nil, type: "Instruction.BrIfOperand") - ), - Instruction( - name: "brTable", isControl: true, mayUpdateFrame: false, - immediate: - Immediate(name: nil, type: "Instruction.BrTable") - ), - Instruction(name: "_return", isControl: true, mayUpdateFrame: true, useCurrentMemory: .write), - Instruction(name: "endOfExecution", isControl: true, mayThrow: true, mayUpdateFrame: true), - ] - instructions += memoryLoadStoreInsts - instructions += memoryOpInsts - instructions += numericOtherInsts - instructions += intBinOps.map(\.instruction) - instructions += intUnaryInsts.map(\.instruction) - instructions += floatBinOps.map(\.instruction) - instructions += floatUnaryOps.map(\.instruction) - instructions += miscInsts - return instructions - } - - static let instructions: [Instruction] = buildInstructions() - - static func camelCase(pascalCase: String) -> String { - let first = pascalCase.first!.lowercased() - return first + pascalCase.dropFirst() } static func generateDispatcher(instructions: [Instruction]) -> String { let doExecuteParams: [Instruction.Parameter] = [("instruction", "UInt64", false)] - + ExecParam.allCases.map { ($0.label, $0.type, true) } + + ExecutionParameter.allCases.map { ($0.label, $0.type, true) } var output = """ - extension ExecutionState { + extension Execution { @inline(__always) mutating func doExecute(_ \(doExecuteParams.map { "\($0.label): \($0.isInout ? "inout " : "")\($0.type)" }.joined(separator: ", "))) throws { switch instruction { @@ -437,7 +31,7 @@ enum VMGen { for (index, inst) in instructions.enumerated() { let tryPrefix = inst.mayThrow ? "try " : "" - let args = ExecParam.allCases.map { "\($0.label): &\($0.label)" } + let args = ExecutionParameter.allCases.map { "\($0.label): &\($0.label)" } output += """ case \(index): \(tryPrefix)self.execute_\(inst.name)(\(args.joined(separator: ", "))) @@ -456,7 +50,7 @@ enum VMGen { static func generateBasicInstImplementations() -> String { var output = """ - extension ExecutionState { + extension Execution { """ for op in intBinOps + floatBinOps { @@ -511,7 +105,7 @@ enum VMGen { static func generatePrototype(instructions: [Instruction]) -> String { var output = """ - extension ExecutionState { + extension Execution { """ for inst in instructions { output += """ @@ -531,7 +125,8 @@ enum VMGen { static func replaceInstMethodSignature(_ inst: Instruction, sourceRoot: URL) throws { func tryReplace(file: URL) throws -> Bool { - var contents = try String(contentsOf: file) + let originalContent = try String(contentsOf: file) + var contents = originalContent guard contents.contains("func \(inst.name)(") else { return false } @@ -544,7 +139,11 @@ enum VMGen { } } contents = lines.joined(separator: "\n") + if contents == originalContent { + return true + } try contents.write(to: file, atomically: true, encoding: .utf8) + print("Replaced \(inst.name) in \(file.lastPathComponent)") return true } @@ -554,7 +153,6 @@ enum VMGen { continue } if try tryReplace(file: file) { - print("Replaced \(inst.name) in \(file.lastPathComponent)") return } } @@ -670,11 +268,11 @@ enum VMGen { static func generateDirectThreadedCode(instructions: [Instruction]) -> String { var output = """ - extension ExecutionState { + extension Execution { """ for inst in instructions { let args = inst.parameters.map { label, _, isInout in - let isExecParam = ExecParam.allCases.contains { $0.label == label } + let isExecParam = ExecutionParameter.allCases.contains { $0.label == label } if isExecParam { return "\(label): \(isInout ? "&" : "")\(label).pointee" } else { @@ -687,7 +285,7 @@ enum VMGen { output += """ @_silgen_name("wasmkit_execute_\(inst.name)") @inline(__always) - mutating func execute_\(inst.name)(\(ExecParam.allCases.map { "\($0.label): UnsafeMutablePointer<\($0.type)>" }.joined(separator: ", ")))\(throwsKwd) { + mutating func execute_\(inst.name)(\(ExecutionParameter.allCases.map { "\($0.label): UnsafeMutablePointer<\($0.type)>" }.joined(separator: ", ")))\(throwsKwd) { """ if let immediate = inst.immediate { @@ -717,7 +315,7 @@ enum VMGen { } for inst in instructions { - let params = ExecParam.allCases + let params = ExecutionParameter.allCases output += """ SWIFT_CC(swiftasync) static inline void \(handlerName(inst))(\(params.map { "\($0.type) \($0.label)" }.joined(separator: ", ")), SWIFT_CONTEXT void *state) { SWIFT_CC(swift) void wasmkit_execute_\(inst.name)(\(params.map { "\($0.type) *\($0.label)" }.joined(separator: ", ")), SWIFT_CONTEXT void *state, SWIFT_ERROR_RESULT void **error); @@ -765,56 +363,73 @@ enum VMGen { } } - do { - var output = """ - // This file is generated by Utilities/generate_inst_dispatch.swift - - """ - - output += generateDispatcher(instructions: instructions) - output += "\n\n" - output += generateInstName(instructions: instructions) - output += "\n\n" - output += generateBasicInstImplementations() - output += "\n\n" - output += generateDirectThreadedCode(instructions: instructions) - - output += """ - + let header = """ + //// Automatically generated by Utilities/Sources/VMGen.swift + //// DO NOT EDIT DIRECTLY - import _CWasmKit.InlineCode + """ - extension Instruction { - private static let handlers: [UInt64] = withUnsafePointer(to: wasmkit_tc_exec_handlers) { - let count = MemoryLayout.size(ofValue: wasmkit_tc_exec_handlers) / MemoryLayout.size - return $0.withMemoryRebound(to: UInt64.self, capacity: count) { - Array(UnsafeBufferPointer(start: $0, count: count)) + let projectSources = ["Sources"] + + let generatedFiles = [ + GeneratedFile( + projectSources + ["WasmKit", "Execution", "DispatchInstruction.swift"], + header + generateDispatcher(instructions: instructions) + + "\n\n" + + generateInstName(instructions: instructions) + + "\n\n" + + generateBasicInstImplementations() + + "\n\n" + + generateDirectThreadedCode(instructions: instructions) + + """ + + + import _CWasmKit.InlineCode + + extension Instruction { + private static let handlers: [UInt64] = withUnsafePointer(to: wasmkit_tc_exec_handlers) { + let count = MemoryLayout.size(ofValue: wasmkit_tc_exec_handlers) / MemoryLayout.size + return $0.withMemoryRebound(to: UInt64.self, capacity: count) { + Array(UnsafeBufferPointer(start: $0, count: count)) + } } - } - @inline(never) - var handler: UInt64 { - return Self.handlers[rawIndex] + @inline(never) + var handler: UInt64 { + return Self.handlers[rawIndex] + } } - } - """ + """ + ), + GeneratedFile( + projectSources + ["_CWasmKit", "include", "DirectThreadedCode.inc"], + header + generateDirectThreadedCodeOfCPart(instructions: instructions) + ), + GeneratedFile( + projectSources + ["WasmKit", "Execution", "Instructions", "Instruction.swift"], + header + generateEnumDefinition(instructions: instructions) + ), + ] - let outputFile = sourceRoot.appending(path: "Sources/WasmKit/Execution/Runtime/InstDispatch.swift") - try output.write(to: outputFile, atomically: true, encoding: .utf8) - } + for file in generatedFiles { + let subPath = file.pathComponents.joined(separator: "/") + let path = sourceRoot.appendingPathComponent(subPath) + // Write the content only if the file does not exist or the content is different + let shouldWrite: Bool + if !FileManager.default.fileExists(atPath: path.path) { + shouldWrite = true + } else { + let existingContent = try String(contentsOf: path) + shouldWrite = existingContent != file.content + } - do { - let outputFile = sourceRoot.appending(path: "Sources/_CWasmKit/include/DirectThreadedCode.inc") - let output = generateDirectThreadedCodeOfCPart(instructions: instructions) - try output.write(to: outputFile, atomically: true, encoding: .utf8) + if shouldWrite { + try file.content.write(to: path, atomically: true, encoding: .utf8) + print("\u{001B}[1;33mUpdated\u{001B}[0;0m \(subPath)") + } } - do { - let outputFile = sourceRoot.appending(path: "Sources/WasmKit/Execution/Instructions/Instruction.swift") - let output = generateEnumDefinition(instructions: instructions) - try output.write(to: outputFile, atomically: true, encoding: .utf8) - } try replaceMethodSignature(instructions: instructions, sourceRoot: sourceRoot) } } diff --git a/Utilities/Sources/VMSpec.swift b/Utilities/Sources/VMSpec.swift new file mode 100644 index 00000000..7e9401fd --- /dev/null +++ b/Utilities/Sources/VMSpec.swift @@ -0,0 +1,421 @@ +/// This file defines the instruction set of the internal VM. +/// +/// Most of the instructions are directly mapped from the WebAssembly instruction set. +/// Some instructions are added for internal use or for performance reasons. + +extension VMGen { + + /// A parameter passed to an instruction execution function. + /// Expected to be bound to a physical register. + struct ExecutionParameter: CaseIterable { + /// The label of the parameter. + let label: String + /// The type of the parameter. + let type: String + + static let sp = Self(label: "sp", type: "Sp") + static let pc = Self(label: "pc", type: "Pc") + static let md = Self(label: "md", type: "Md") + static let ms = Self(label: "ms", type: "Ms") + + /// All cases of `ExecParam`. + static var allCases = [sp, pc, md, ms] + } + + /// An immediate operand of an instruction. + struct Immediate: ExpressibleByStringLiteral { + let name: String? + let type: String + + init(name: String? = nil, type: String) { + self.name = name + self.type = type + } + + init(stringLiteral value: String) { + self.name = nil + self.type = value + } + + var label: String { + name ?? VMGen.camelCase(pascalCase: String(type.split(separator: ".").last!)) + } + } + + /// The use of a register in an instruction. + enum RegisterUse { + case none + case read + case write + } + + /// An instruction definition for the internal VM. + struct Instruction { + var name: String + var isControl: Bool + var mayThrow: Bool + var mayUpdateFrame: Bool + var mayUpdateSp: Bool = false + var useCurrentMemory: RegisterUse + var immediate: Immediate? + + var mayUpdatePc: Bool { + self.isControl + } + + init( + name: String, isControl: Bool = false, + mayThrow: Bool = false, mayUpdateFrame: Bool = false, + useCurrentMemory: RegisterUse = .none, + immediate: Immediate? = nil + ) { + self.name = name + self.isControl = isControl + self.mayThrow = mayThrow + self.mayUpdateFrame = mayUpdateFrame + self.useCurrentMemory = useCurrentMemory + self.immediate = immediate + assert(isControl || !mayUpdateFrame, "non-control instruction should not update frame") + } + + typealias Parameter = (label: String, type: String, isInout: Bool) + + /// The parameters of the execution function of this instruction. + var parameters: [Parameter] { + var vregs: [(reg: ExecutionParameter, isInout: Bool)] = [] + if self.mayUpdateFrame { + vregs += [(.sp, true)] + } else { + vregs += [(.sp, false)] + } + if self.mayUpdatePc { + vregs += [(.pc, false)] + } + switch useCurrentMemory { + case .none: break + case .read: + vregs += [(.md, false), (.ms, false)] + case .write: + vregs += [(.md, true), (.ms, true)] + } + var parameters: [Parameter] = vregs.map { ($0.reg.label, $0.reg.type, $0.isInout) } + if let immediate = self.immediate { + parameters += [(immediate.label, immediate.type, false)] + } + return parameters + } + } + + /// A binary operation information. + struct BinOpInfo { + let op: String + let name: String + let lhsType: String + let rhsType: String + let resultType: String + var mayThrow: Bool = false + + /// The instruction definition of this binary operation. + var instruction: Instruction { + Instruction( + name: name, + mayThrow: mayThrow, + immediate: "Instruction.BinaryOperand" + ) + } + } + + /// A unary operation information. + struct UnOpInfo { + var op: String + var name: String + var inputType: String + var resultType: String + var mayThrow: Bool = false + + /// The instruction definition of this unary operation. + var instruction: Instruction { + Instruction(name: name, mayThrow: mayThrow, immediate: Immediate(type: "Instruction.UnaryOperand")) + } + } + + static let intValueTypes = ["i32", "i64"] + static let floatValueTypes = ["f32", "f64"] + + // MARK: - Int instructions + + static func buildIntBinOps() -> [BinOpInfo] { + var results: [BinOpInfo] = [] + // (T, T) -> T for all T in int types + results += [ + "Add", "Sub", "Mul", + "And", "Or", "Xor", "Shl", "ShrS", "ShrU", "Rotl", "Rotr", + ].flatMap { op -> [BinOpInfo] in + intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0) } + } + results += [ + "DivS", "DivU", "RemS", "RemU", + ].flatMap { op -> [BinOpInfo] in + intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0, mayThrow: true) } + } + // (T, T) -> i32 for all T in int types + results += [ + "Eq", "Ne", "LtS", "LtU", "GtS", "GtU", "LeS", "LeU", "GeS", "GeU", + ].flatMap { op -> [BinOpInfo] in + intValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: "i32") } + } + return results + } + static let intBinOps: [BinOpInfo] = buildIntBinOps() + + static func buildIntUnaryInsts() -> [UnOpInfo] { + var results: [UnOpInfo] = [] + // (T) -> T for all T in int types + results += ["Clz", "Ctz", "Popcnt"].flatMap { op -> [UnOpInfo] in + intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } + } + // (T) -> i32 for all T in int types + results += ["Eqz"].flatMap { op -> [UnOpInfo] in + intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: "i32") } + } + // (i64) -> i32 + results += [UnOpInfo(op: "Wrap", name: "i32WrapI64", inputType: "i64", resultType: "i32")] + // (i32) -> i64 + results += ["ExtendI32S", "ExtendI32U"].map { op -> UnOpInfo in + UnOpInfo(op: op, name: "i64\(op)", inputType: "i32", resultType: "i64") + } + // (T) -> T for all T in int types + results += ["Extend8S", "Extend16S"].flatMap { op -> [UnOpInfo] in + intValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } + } + // (i64) -> i64 + results += ["Extend32S"].map { op -> UnOpInfo in + UnOpInfo(op: op, name: "i64\(op)", inputType: "i64", resultType: "i64") + } + // Truncation + let truncInOut: [(source: String, result: String)] = [ + ("f32", "i32"), ("f64", "i32"), ("f32", "i64"), ("f64", "i64") + ] + results += truncInOut.flatMap { source, result in + [ + UnOpInfo(op: "TruncTo\(result.uppercased())S", name: "\(result)Trunc\(source.uppercased())S", inputType: source, resultType: result, mayThrow: true), + UnOpInfo(op: "TruncTo\(result.uppercased())U", name: "\(result)Trunc\(source.uppercased())U", inputType: source, resultType: result, mayThrow: true), + UnOpInfo(op: "TruncSatTo\(result.uppercased())S", name: "\(result)TruncSat\(source.uppercased())S", inputType: source, resultType: result, mayThrow: true), + UnOpInfo(op: "TruncSatTo\(result.uppercased())U", name: "\(result)TruncSat\(source.uppercased())U", inputType: source, resultType: result, mayThrow: true) + ] + } + // Conversion + let convInOut: [(source: String, result: String)] = [ + ("i32", "f32"), ("i64", "f32"), ("i32", "f64"), ("i64", "f64") + ] + results += convInOut.flatMap { source, result in + [ + UnOpInfo(op: "ConvertTo\(result.uppercased())S", name: "\(result)Convert\(source.uppercased())S", inputType: source, resultType: result), + UnOpInfo(op: "ConvertTo\(result.uppercased())U", name: "\(result)Convert\(source.uppercased())U", inputType: source, resultType: result), + ] + } + // Reinterpret + let reinterpretInOut: [(source: String, result: String)] = [ + ("i32", "f32"), ("i64", "f64"), ("f32", "i32"), ("f64", "i64") + ] + results += reinterpretInOut.flatMap { source, result in + [ + UnOpInfo(op: "ReinterpretTo\(result.uppercased())", name: "\(result)Reinterpret\(source.uppercased())", inputType: source, resultType: result), + ] + } + return results + } + static let intUnaryInsts: [UnOpInfo] = buildIntUnaryInsts() + + + // MARK: - Float instructions + + static func buildFloatBinOps() -> [BinOpInfo] { + var results: [BinOpInfo] = [] + // (T, T) -> T for all T in float types + results += [ + "Add", "Sub", "Mul", "Div", + "Min", "Max", "CopySign", + ].flatMap { op -> [BinOpInfo] in + floatValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: $0) } + } + // (T, T) -> i32 for all T in float types + results += [ + "Eq", "Ne", "Lt", "Gt", "Le", "Ge" + ].flatMap { op -> [BinOpInfo] in + floatValueTypes.map { BinOpInfo(op: op, name: "\($0)\(op)", lhsType: $0, rhsType: $0, resultType: "i32") } + } + return results + } + static let floatBinOps: [BinOpInfo] = buildFloatBinOps() + + static func buildFloatUnaryOps() -> [UnOpInfo] { + var results: [UnOpInfo] = [] + // (T) -> T for all T in float types + results += ["Abs", "Neg", "Ceil", "Floor", "Trunc", "Nearest", "Sqrt"].flatMap { op -> [UnOpInfo] in + floatValueTypes.map { UnOpInfo(op: op, name: "\($0)\(op)", inputType: $0, resultType: $0) } + } + // (f32) -> f64 + results += ["PromoteF32"].map { op -> UnOpInfo in + UnOpInfo(op: op, name: "f64\(op)", inputType: "f32", resultType: "f64") + } + // (f64) -> f32 + results += ["DemoteF64"].map { op -> UnOpInfo in + UnOpInfo(op: op, name: "f32\(op)", inputType: "f64", resultType: "f32") + } + return results + } + static let floatUnaryOps: [UnOpInfo] = buildFloatUnaryOps() + + // MARK: - Minor numeric instructions + + static let numericOtherInsts: [Instruction] = [ + // Numeric + Instruction(name: "const32", immediate: "Instruction.Const32Operand"), + Instruction(name: "const64", immediate: "Instruction.Const64Operand"), + ] + + // MARK: - Memory instructions + + struct LoadInstruction { + let loadAs: String + let castToValue: String + let base: Instruction + } + + static let memoryLoadInsts: [LoadInstruction] = [ + ("i32Load", "UInt32", ".i32($0)"), + ("i64Load", "UInt64", ".i64($0)"), + ("f32Load", "UInt32", ".rawF32($0)"), + ("f64Load", "UInt64", ".rawF64($0)"), + ("i32Load8S", "Int8", ".init(signed: Int32($0))"), + ("i32Load8U", "UInt8", ".i32(UInt32($0))"), + ("i32Load16S", "Int16", ".init(signed: Int32($0))"), + ("i32Load16U", "UInt16", ".i32(UInt32($0))"), + ("i64Load8S", "Int8", ".init(signed: Int64($0))"), + ("i64Load8U", "UInt8", ".i64(UInt64($0))"), + ("i64Load16S", "Int16", ".init(signed: Int64($0))"), + ("i64Load16U", "UInt16", ".i64(UInt64($0))"), + ("i64Load32S", "Int32", ".init(signed: Int64($0))"), + ("i64Load32U", "UInt32", ".i64(UInt64($0))"), + ].map { (name, loadAs, castToValue) in + let base = Instruction(name: name, mayThrow: true, useCurrentMemory: .read, immediate: "Instruction.LoadOperand") + return LoadInstruction(loadAs: loadAs, castToValue: castToValue, base: base) + } + + struct StoreInstruction { + let castFromValue: String + let base: Instruction + } + static let memoryStoreInsts: [StoreInstruction] = [ + ("i32Store", "$0.i32"), + ("i64Store", "$0.i64"), + ("f32Store", "$0.rawF32"), + ("f64Store", "$0.rawF64"), + ("i32Store8", "UInt8(truncatingIfNeeded: $0.i32)"), + ("i32Store16", "UInt16(truncatingIfNeeded: $0.i32)"), + ("i64Store8", "UInt8(truncatingIfNeeded: $0.i64)"), + ("i64Store16", "UInt16(truncatingIfNeeded: $0.i64)"), + ("i64Store32", "UInt32(truncatingIfNeeded: $0.i64)"), + ].map { (name, castFromValue) in + let base = Instruction(name: name, mayThrow: true, useCurrentMemory: .read, immediate: "Instruction.StoreOperand") + return StoreInstruction(castFromValue: castFromValue, base: base) + } + static let memoryLoadStoreInsts: [Instruction] = memoryLoadInsts.map(\.base) + memoryStoreInsts.map(\.base) + static let memoryOpInsts: [Instruction] = [ + Instruction(name: "memorySize", immediate: "Instruction.MemorySizeOperand"), + Instruction(name: "memoryGrow", mayThrow: true, useCurrentMemory: .write, immediate: "Instruction.MemoryGrowOperand"), + Instruction(name: "memoryInit", mayThrow: true, immediate: "Instruction.MemoryInitOperand"), + Instruction(name: "memoryDataDrop", immediate: "DataIndex"), + Instruction(name: "memoryCopy", mayThrow: true, immediate: "Instruction.MemoryCopyOperand"), + Instruction(name: "memoryFill", mayThrow: true, immediate: "Instruction.MemoryFillOperand"), + ] + + // MARK: - Misc instructions + + static let miscInsts: [Instruction] = [ + // Parametric + Instruction(name: "select", immediate: "Instruction.SelectOperand"), + // Reference + Instruction(name: "refNull", immediate: "Instruction.RefNullOperand"), + Instruction(name: "refIsNull", immediate: "Instruction.RefIsNullOperand"), + Instruction(name: "refFunc", immediate: "Instruction.RefFuncOperand"), + // Table + Instruction(name: "tableGet", mayThrow: true, immediate: "Instruction.TableGetOperand"), + Instruction(name: "tableSet", mayThrow: true, immediate: "Instruction.TableSetOperand"), + Instruction(name: "tableSize", immediate: "Instruction.TableSizeOperand"), + Instruction(name: "tableGrow", mayThrow: true, immediate: "Instruction.TableGrowOperand"), + Instruction(name: "tableFill", mayThrow: true, immediate: "Instruction.TableFillOperand"), + Instruction(name: "tableCopy", mayThrow: true, immediate: "Instruction.TableCopyOperand"), + Instruction(name: "tableInit", mayThrow: true, immediate: "Instruction.TableInitOperand"), + Instruction(name: "tableElementDrop", immediate: "ElementIndex"), + // Profiling + Instruction(name: "onEnter", immediate: "Instruction.OnEnterOperand"), + Instruction(name: "onExit", immediate: "Instruction.OnExitOperand"), + ] + + // MARK: - Instruction generation + + static func buildInstructions() -> [Instruction] { + var instructions: [Instruction] = [ + // Variable + Instruction(name: "copyStack", immediate: "Instruction.CopyStackOperand"), + Instruction(name: "globalGet", immediate: "Instruction.GlobalGetOperand"), + Instruction(name: "globalSet", immediate: "Instruction.GlobalSetOperand"), + // Controls + Instruction( + name: "call", isControl: true, mayThrow: true, mayUpdateFrame: true, useCurrentMemory: .write, + immediate: "Instruction.CallOperand" + ), + Instruction( + name: "compilingCall", isControl: true, mayThrow: true, mayUpdateFrame: true, + immediate: "Instruction.CompilingCallOperand" + ), + Instruction( + name: "internalCall", isControl: true, mayThrow: true, mayUpdateFrame: true, + immediate: + "Instruction.InternalCallOperand" + ), + Instruction( + name: "callIndirect", isControl: true, mayThrow: true, mayUpdateFrame: true, useCurrentMemory: .write, + immediate: + "Instruction.CallIndirectOperand" + ), + Instruction(name: "unreachable", isControl: true, mayThrow: true), + Instruction(name: "nop"), + Instruction( + name: "br", isControl: true, mayUpdateFrame: false, + immediate: + Immediate(name: "offset", type: "Int32") + ), + Instruction( + name: "brIf", isControl: true, mayUpdateFrame: false, + immediate: + "Instruction.BrIfOperand" + ), + Instruction( + name: "brIfNot", isControl: true, mayUpdateFrame: false, + immediate: + "Instruction.BrIfOperand" + ), + Instruction( + name: "brTable", isControl: true, mayUpdateFrame: false, + immediate: + "Instruction.BrTable" + ), + Instruction(name: "_return", isControl: true, mayUpdateFrame: true, useCurrentMemory: .write), + Instruction(name: "endOfExecution", isControl: true, mayThrow: true, mayUpdateFrame: true), + ] + instructions += memoryLoadStoreInsts + instructions += memoryOpInsts + instructions += numericOtherInsts + instructions += intBinOps.map(\.instruction) + instructions += intUnaryInsts.map(\.instruction) + instructions += floatBinOps.map(\.instruction) + instructions += floatUnaryOps.map(\.instruction) + instructions += miscInsts + return instructions + } + + static let instructions: [Instruction] = buildInstructions() +} \ No newline at end of file