diff --git a/Blockchain/Package.swift b/Blockchain/Package.swift index 02c0abda..8263e990 100644 --- a/Blockchain/Package.swift +++ b/Blockchain/Package.swift @@ -19,6 +19,7 @@ let package = Package( .package(path: "../Codec"), .package(path: "../Utils"), .package(path: "../TracingUtils"), + .package(path: "../PolkaVM"), .package(url: "https://github.com/apple/swift-testing.git", branch: "0.10.0"), ], targets: [ @@ -29,6 +30,7 @@ let package = Package( dependencies: [ "Codec", "Utils", + "PolkaVM", "TracingUtils", ] ), diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift index dc249393..9fe079f8 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift @@ -33,7 +33,7 @@ extension Ref where T == ProtocolConfig { maxEncodedWorkReportSize: 96 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 500, - pvmDynamicAddressAlignmentFactor: 4, + pvmDynamicAddressAlignmentFactor: 2, pvmProgramInitInputDataSize: 1 << 24, pvmProgramInitPageSize: 1 << 14, pvmProgramInitSegmentSize: 1 << 16 @@ -70,7 +70,7 @@ extension Ref where T == ProtocolConfig { maxEncodedWorkReportSize: 96 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 500, - pvmDynamicAddressAlignmentFactor: 4, + pvmDynamicAddressAlignmentFactor: 2, pvmProgramInitInputDataSize: 1 << 24, pvmProgramInitPageSize: 1 << 14, pvmProgramInitSegmentSize: 1 << 16 diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift index c0ba1607..5f074c3b 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift @@ -1,3 +1,4 @@ +import PolkaVM import Utils // constants defined in the graypaper @@ -94,7 +95,7 @@ public struct ProtocolConfig: Sendable { // Y = 500: The number of slots into an epoch at which ticket-submission ends. public var ticketSubmissionEndSlot: Int - // ZA = 4: The pvm dynamic address alignment factor. + // ZA = 2: The pvm dynamic address alignment factor. public var pvmDynamicAddressAlignmentFactor: Int // ZI = 2^24: The standard pvm program initialization input data size. @@ -181,6 +182,8 @@ public struct ProtocolConfig: Sendable { public typealias ProtocolConfigRef = Ref +extension ProtocolConfig: PvmConfig {} + extension ProtocolConfig { public enum AuditTranchePeriod: ReadInt { public typealias TConfig = ProtocolConfigRef diff --git a/PolkaVM/Sources/PolkaVM/Config.swift b/PolkaVM/Sources/PolkaVM/Config.swift new file mode 100644 index 00000000..8828483d --- /dev/null +++ b/PolkaVM/Sources/PolkaVM/Config.swift @@ -0,0 +1,13 @@ +public protocol PvmConfig { + // ZA = 2: The pvm dynamic address alignment factor. + var pvmDynamicAddressAlignmentFactor: Int { get } + + // ZI = 2^24: The standard pvm program initialization input data size. + var pvmProgramInitInputDataSize: Int { get } + + // ZP = 2^14: The standard pvm program initialization page size. + var pvmProgramInitPageSize: Int { get } + + // ZQ = 2^16: The standard pvm program initialization segment size. + var pvmProgramInitSegmentSize: Int { get } +} diff --git a/PolkaVM/Sources/PolkaVM/Engine.swift b/PolkaVM/Sources/PolkaVM/Engine.swift index 5a247a0a..f9138f02 100644 --- a/PolkaVM/Sources/PolkaVM/Engine.swift +++ b/PolkaVM/Sources/PolkaVM/Engine.swift @@ -34,12 +34,6 @@ public class Engine { let res = inst.execute(state: state, skip: skip) - if state.pc == Constants.exitAddress { - // TODO: GP only defined this for `djump` but not `branch` - // so need to confirm this is correct - return .exit(.halt) - } - return res } } diff --git a/PolkaVM/Sources/PolkaVM/InstructionTable.swift b/PolkaVM/Sources/PolkaVM/InstructionTable.swift index 31673d04..7e5c830c 100644 --- a/PolkaVM/Sources/PolkaVM/InstructionTable.swift +++ b/PolkaVM/Sources/PolkaVM/InstructionTable.swift @@ -34,6 +34,63 @@ public class InstructionTable { Instructions.BranchLeSImm.self, Instructions.BranchGeSImm.self, Instructions.BranchGtSImm.self, + Instructions.MoveReg.self, + Instructions.Sbrk.self, + Instructions.StoreIndU8.self, + Instructions.StoreIndU16.self, + Instructions.StoreIndU32.self, + Instructions.LoadIndU8.self, + Instructions.LoadIndI8.self, + Instructions.LoadIndU16.self, + Instructions.LoadIndI16.self, + Instructions.LoadIndU32.self, + Instructions.AddImm.self, + Instructions.AndImm.self, + Instructions.XorImm.self, + Instructions.OrImm.self, + Instructions.MulImm.self, + Instructions.MulUpperSSImm.self, + Instructions.MulUpperUUImm.self, + Instructions.SetLtUImm.self, + Instructions.SetLtSImm.self, + Instructions.ShloLImm.self, + Instructions.ShloRImm.self, + Instructions.SharRImm.self, + Instructions.NegAddImm.self, + Instructions.SetGtUImm.self, + Instructions.SetGtSImm.self, + Instructions.ShloLImmAlt.self, + Instructions.ShloRImmAlt.self, + Instructions.SharRImmAlt.self, + Instructions.CmovIzImm.self, + Instructions.CmovNzImm.self, + Instructions.BranchEq.self, + Instructions.BranchNe.self, + Instructions.BranchLtU.self, + Instructions.BranchLtS.self, + Instructions.BranchGeU.self, + Instructions.BranchGeS.self, + Instructions.LoadImmJumpInd.self, + Instructions.Add.self, + Instructions.Sub.self, + Instructions.And.self, + Instructions.Xor.self, + Instructions.Or.self, + Instructions.Mul.self, + Instructions.MulUpperSS.self, + Instructions.MulUpperUU.self, + Instructions.MulUpperSU.self, + Instructions.DivU.self, + Instructions.DivS.self, + Instructions.RemU.self, + Instructions.RemS.self, + Instructions.SetLtU.self, + Instructions.SetLtS.self, + Instructions.ShloL.self, + Instructions.ShloR.self, + Instructions.SharR.self, + Instructions.CmovIz.self, + Instructions.CmovNz.self, ] var table: [Instruction.Type?] = Array(repeating: nil, count: 256) for i in 0 ..< insts.count { diff --git a/PolkaVM/Sources/PolkaVM/Instructions.swift b/PolkaVM/Sources/PolkaVM/Instructions.swift index 4d57f417..9fe0ebf2 100644 --- a/PolkaVM/Sources/PolkaVM/Instructions.swift +++ b/PolkaVM/Sources/PolkaVM/Instructions.swift @@ -7,14 +7,13 @@ public let BASIC_BLOCK_INSTRUCTIONS: Set = [ Instructions.Jump.opcode, Instructions.JumpInd.opcode, Instructions.LoadImmJump.opcode, - // TODO: uncomment after add more - // Instructions.LoadImmJumpInd.opcode, - // Instructions.BranchEq.opcode, - // Instructions.BranchNe.opcode, - // Instructions.BranchGeU.opcode, - // Instructions.BranchGeS.opcode, - // Instructions.BranchLtU.opcode, - // Instructions.BranchLtS.opcode, + Instructions.LoadImmJumpInd.opcode, + Instructions.BranchEq.opcode, + Instructions.BranchNe.opcode, + Instructions.BranchGeU.opcode, + Instructions.BranchGeS.opcode, + Instructions.BranchLtU.opcode, + Instructions.BranchLtS.opcode, Instructions.BranchEqImm.opcode, Instructions.BranchNeImm.opcode, Instructions.BranchLtUImm.opcode, @@ -28,6 +27,11 @@ public let BASIC_BLOCK_INSTRUCTIONS: Set = [ ] public enum Instructions { + public enum Constants { + public static let djumpHaltAddress: UInt32 = 0xFFFF_0000 + public static let djumpAddressAlignmentFactor: Int = 2 + } + static func decodeImmediate(_ data: Data) -> UInt32 { let len = min(data.count, 4) if len == 0 { @@ -44,10 +48,10 @@ public enum Instructions { return UInt32(bitPattern: Int32(bitPattern: value << shift) >> shift) } - static func decodeImmediate2(_ data: Data, divideBy: UInt8 = 1) throws -> (UInt32, UInt32) { + static func decodeImmediate2(_ data: Data, divideBy: UInt8 = 1, minus: Int = 1) throws -> (UInt32, UInt32) { let lX1 = try Int((data.at(relative: 0) / divideBy) & 0b111) let lX = min(4, lX1) - let lY = min(4, max(0, data.count - Int(lX) - 1)) + let lY = min(4, max(0, data.count - Int(lX) - minus)) let vX = try decodeImmediate(data.at(relative: 1 ..< 1 + lX)) let vY = try decodeImmediate(data.at(relative: (1 + lX) ..< (1 + lX + lY))) @@ -58,6 +62,35 @@ public enum Instructions { state.program.basicBlockIndices.contains(state.pc &+ offset) } + static func isDjumpValid(state: VMState, target a: UInt32, targetAligned: UInt32) -> Bool { + let za = Constants.djumpAddressAlignmentFactor + return !(a == 0 || + a > state.program.jumpTable.count * za || + Int(a) % za != 0 || + state.program.basicBlockIndices.contains(targetAligned)) + } + + static func djump(state: VMState, target: UInt32) -> ExecOutcome { + if target == Constants.djumpHaltAddress { + return .exit(.halt) + } + + let entrySize = Int(state.program.jumpTableEntrySize) + let start = ((Int(target) / Constants.djumpAddressAlignmentFactor) - 1) * entrySize + let end = start + entrySize + var targetAlignedData = state.program.jumpTable[relative: start ..< end] + guard let targetAligned = targetAlignedData.decode() else { + fatalError("unreachable: jump table entry should be valid") + } + + guard isDjumpValid(state: state, target: target, targetAligned: UInt32(truncatingIfNeeded: targetAligned)) else { + return .exit(.panic(.invalidDynamicJump)) + } + + state.updatePC(UInt32(targetAligned)) + return .continued + } + // MARK: Instructions without Arguments (5.1) public struct Trap: Instruction { @@ -195,8 +228,7 @@ public enum Instructions { public func updatePC(state: VMState, skip _: UInt32) -> ExecOutcome { let regVal = state.readRegister(register) - state.updatePC(regVal &+ offset) // wrapped add - return .continued + return Instructions.djump(state: state, target: regVal &+ offset) } } @@ -965,8 +997,8 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = value & 0x1F - state.writeRegister(ra, regVal << shift) + let shift = value & 0x20 + state.writeRegister(ra, UInt32(truncatingIfNeeded: regVal << shift)) return .continued } } @@ -986,7 +1018,7 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = value & 0x1F + let shift = value & 0x20 state.writeRegister(ra, regVal >> shift) return .continued } @@ -1007,7 +1039,7 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = value & 0x1F + let shift = value & 0x20 state.writeRegister(ra, UInt32(bitPattern: Int32(bitPattern: regVal) >> shift)) return .continued } @@ -1088,8 +1120,8 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = regVal & 0x1F - state.writeRegister(ra, value << shift) + let shift = regVal & 0x20 + state.writeRegister(ra, UInt32(truncatingIfNeeded: value << shift)) return .continued } } @@ -1109,7 +1141,7 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = regVal & 0x1F + let shift = regVal & 0x20 state.writeRegister(ra, value >> shift) return .continued } @@ -1130,13 +1162,13 @@ public enum Instructions { public func _executeImpl(state: VMState) -> ExecOutcome { let regVal = state.readRegister(rb) - let shift = regVal & 0x1F + let shift = regVal & 0x20 state.writeRegister(ra, UInt32(bitPattern: Int32(bitPattern: value) >> shift)) return .continued } } - public struct CmovIZImm: Instruction { + public struct CmovIzImm: Instruction { public static var opcode: UInt8 { 81 } public let ra: Registers.Index @@ -1156,7 +1188,7 @@ public enum Instructions { } } - public struct CmovNIImm: Instruction { + public struct CmovNzImm: Instruction { public static var opcode: UInt8 { 82 } public let ra: Registers.Index @@ -1175,6 +1207,544 @@ public enum Instructions { return .continued } } + + // MARK: Instructions with Arguments of Two Registers & One Offset (5.10) + + public struct BranchEq: BranchInstructionBase2 { + public static var opcode: UInt8 { 24 } + typealias Compare = CompareEq + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + public struct BranchNe: BranchInstructionBase2 { + public static var opcode: UInt8 { 30 } + typealias Compare = CompareNe + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + public struct BranchLtU: BranchInstructionBase2 { + public static var opcode: UInt8 { 47 } + typealias Compare = CompareLt + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + public struct BranchLtS: BranchInstructionBase2 { + public static var opcode: UInt8 { 48 } + typealias Compare = CompareLt + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + public struct BranchGeU: BranchInstructionBase2 { + public static var opcode: UInt8 { 41 } + typealias Compare = CompareGe + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + public struct BranchGeS: BranchInstructionBase2 { + public static var opcode: UInt8 { 43 } + typealias Compare = CompareGe + + var r1: Registers.Index + var r2: Registers.Index + var offset: UInt32 + public init(data: Data) throws { (r1, r2, offset) = try Self.parse(data: data) } + } + + // MARK: Instruction with Arguments of Two Registers and Two Immediates (5.11) + + public struct LoadImmJumpInd: Instruction { + public static var opcode: UInt8 { 10 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let value: UInt32 + public let offset: UInt32 + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + (value, offset) = try Instructions.decodeImmediate2(data[relative: 1...], divideBy: 1, minus: 2) + } + + public func _executeImpl(state: VMState) throws -> ExecOutcome { + state.writeRegister(ra, value) + return .continued + } + + public func updatePC(state: VMState, skip _: UInt32) -> ExecOutcome { + let rbVal = state.readRegister(rb) + return Instructions.djump(state: state, target: rbVal &+ offset) + } + } + + // MARK: Instructions with Arguments of Three Registers (5.12) + + public struct Add: Instruction { + public static var opcode: UInt8 { 8 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal &+ rbVal) + return .continued + } + } + + public struct Sub: Instruction { + public static var opcode: UInt8 { 20 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal &- rbVal) + return .continued + } + } + + public struct And: Instruction { + public static var opcode: UInt8 { 23 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal & rbVal) + return .continued + } + } + + public struct Xor: Instruction { + public static var opcode: UInt8 { 28 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal ^ rbVal) + return .continued + } + } + + public struct Or: Instruction { + public static var opcode: UInt8 { 12 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal | rbVal) + return .continued + } + } + + public struct Mul: Instruction { + public static var opcode: UInt8 { 34 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal &* rbVal) + return .continued + } + } + + public struct MulUpperSS: Instruction { + public static var opcode: UInt8 { 67 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, UInt32(bitPattern: Int32((Int64(Int32(bitPattern: raVal)) * Int64(Int32(bitPattern: rbVal))) >> 32))) + return .continued + } + } + + public struct MulUpperUU: Instruction { + public static var opcode: UInt8 { 57 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, UInt32((UInt64(raVal) * UInt64(rbVal)) >> 32)) + return .continued + } + } + + public struct MulUpperSU: Instruction { + public static var opcode: UInt8 { 81 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, UInt32(bitPattern: Int32((Int64(Int32(bitPattern: raVal)) * Int64(Int32(bitPattern: rbVal))) >> 32))) + return .continued + } + } + + public struct DivU: Instruction { + public static var opcode: UInt8 { 68 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal == 0 { + state.writeRegister(rd, UInt32.max) + } else { + state.writeRegister(rd, raVal / rbVal) + } + return .continued + } + } + + public struct DivS: Instruction { + public static var opcode: UInt8 { 64 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal == 0 { + state.writeRegister(rd, UInt32.max) + } else if Int32(bitPattern: raVal) == Int32.min, Int32(bitPattern: rbVal) == -1 { + state.writeRegister(rd, raVal) + } else { + state.writeRegister(rd, UInt32(bitPattern: Int32(bitPattern: raVal) / Int32(bitPattern: rbVal))) + } + return .continued + } + } + + public struct RemU: Instruction { + public static var opcode: UInt8 { 73 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal == 0 { + state.writeRegister(rd, raVal) + } else { + state.writeRegister(rd, raVal % rbVal) + } + return .continued + } + } + + public struct RemS: Instruction { + public static var opcode: UInt8 { 70 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal == 0 { + state.writeRegister(rd, raVal) + } else if Int32(bitPattern: raVal) == Int32.min, Int32(bitPattern: rbVal) == -1 { + state.writeRegister(rd, 0) + } else { + state.writeRegister(rd, UInt32(bitPattern: Int32(bitPattern: raVal) % Int32(bitPattern: rbVal))) + } + return .continued + } + } + + public struct SetLtU: Instruction { + public static var opcode: UInt8 { 36 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, raVal < rbVal ? 1 : 0) + return .continued + } + } + + public struct SetLtS: Instruction { + public static var opcode: UInt8 { 58 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + state.writeRegister(rd, Int32(bitPattern: raVal) < Int32(bitPattern: rbVal) ? 1 : 0) + return .continued + } + } + + public struct ShloL: Instruction { + public static var opcode: UInt8 { 55 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + let shift = rbVal & 0x20 + state.writeRegister(rd, UInt32(truncatingIfNeeded: raVal << shift)) + return .continued + } + } + + public struct ShloR: Instruction { + public static var opcode: UInt8 { 51 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + let shift = rbVal & 0x20 + state.writeRegister(rd, raVal >> shift) + return .continued + } + } + + public struct SharR: Instruction { + public static var opcode: UInt8 { 77 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + let shift = rbVal & 0x20 + state.writeRegister(rd, UInt32(bitPattern: Int32(bitPattern: raVal) >> shift)) + return .continued + } + } + + public struct CmovIz: Instruction { + public static var opcode: UInt8 { 83 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal == 0 { + state.writeRegister(rd, raVal) + } + return .continued + } + } + + public struct CmovNz: Instruction { + public static var opcode: UInt8 { 84 } + + public let ra: Registers.Index + public let rb: Registers.Index + public let rd: Registers.Index + + public init(data: Data) throws { + ra = try Registers.Index(ra: data.at(relative: 0)) + rb = try Registers.Index(rb: data.at(relative: 0)) + rd = try Registers.Index(rd: data.at(relative: 1)) + } + + public func _executeImpl(state: VMState) -> ExecOutcome { + let raVal = state.readRegister(ra) + let rbVal = state.readRegister(rb) + if rbVal != 0 { + state.writeRegister(rd, raVal) + } + return .continued + } + } } // MARK: Branch Helpers @@ -1192,9 +1762,7 @@ protocol BranchInstructionBase: Instruction { var offset: UInt32 { get set } func _executeImpl(state _: VMState) throws -> ExecOutcome - func updatePC(state: VMState, skip: UInt32) -> ExecOutcome - func condition(state: VMState) -> Bool } @@ -1225,6 +1793,48 @@ extension BranchInstructionBase { } } +// for branch in A.5.10 +protocol BranchInstructionBase2: Instruction { + associatedtype Compare: BranchCompare + + var r1: Registers.Index { get set } + var r2: Registers.Index { get set } + var offset: UInt32 { get set } + + func _executeImpl(state _: VMState) throws -> ExecOutcome + func updatePC(state: VMState, skip: UInt32) -> ExecOutcome + func condition(state: VMState) -> Bool +} + +extension BranchInstructionBase2 { + public static func parse(data: Data) throws -> (Registers.Index, Registers.Index, UInt32) { + let offset = try Instructions.decodeImmediate(data.at(relative: 1...)) + let r1 = try Registers.Index(ra: data.at(relative: 0)) + let r2 = try Registers.Index(rb: data.at(relative: 0)) + return (r1, r2, offset) + } + + public func _executeImpl(state _: VMState) throws -> ExecOutcome { .continued } + + public func updatePC(state: VMState, skip: UInt32) -> ExecOutcome { + guard Instructions.isBranchValid(state: state, offset: offset) else { + return .exit(.panic(.invalidBranch)) + } + if condition(state: state) { + state.increasePC(offset) + } else { + state.increasePC(skip + 1) + } + return .continued + } + + public func condition(state: VMState) -> Bool { + let r1Val = state.readRegister(r1) + let r2Val = state.readRegister(r2) + return Compare.compare(a: r1Val, b: r2Val) + } +} + public struct CompareEq: BranchCompare { public static func compare(a: UInt32, b: UInt32) -> Bool { Int32(bitPattern: a) == Int32(bitPattern: b) diff --git a/PolkaVM/Sources/PolkaVM/Registers.swift b/PolkaVM/Sources/PolkaVM/Registers.swift index dd9a047f..91180624 100644 --- a/PolkaVM/Sources/PolkaVM/Registers.swift +++ b/PolkaVM/Sources/PolkaVM/Registers.swift @@ -8,6 +8,10 @@ public struct Registers: Equatable { public init(rb: UInt8) { value = min(rb >> 4, 12) } + + public init(rd: UInt8) { + value = min(rd, 12) + } } public var reg1: UInt32 = 0