From 405b020b70e90b49fedd5ab9f4a2874f3a98ee9c Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Thu, 23 Jan 2025 18:36:21 +0800 Subject: [PATCH 01/10] invoke pvm unit tests wip --- PolkaVM/Sources/PolkaVM/Engine.swift | 4 +- PolkaVM/Sources/PolkaVM/Memory.swift | 16 ++--- PolkaVM/Sources/PolkaVM/ProgramCode.swift | 2 +- PolkaVM/Sources/PolkaVM/invokePVM.swift | 4 +- .../Tests/PolkaVMTests/InvokePVMTest.swift | 61 +++++++++++++++++++ .../Tests/PolkaVMTests/ProgramCodeTests.swift | 23 ++++++- 6 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift diff --git a/PolkaVM/Sources/PolkaVM/Engine.swift b/PolkaVM/Sources/PolkaVM/Engine.swift index be53f1f7..6bb331ad 100644 --- a/PolkaVM/Sources/PolkaVM/Engine.swift +++ b/PolkaVM/Sources/PolkaVM/Engine.swift @@ -13,13 +13,13 @@ public class Engine { self.invocationContext = invocationContext } - public func execute(program: ProgramCode, state: VMState) async -> ExitReason { + public func execute(state: VMState) async -> ExitReason { let context = ExecutionContext(state: state, config: config) while true { guard state.getGas() > GasInt(0) else { return .outOfGas } - if case let .exit(reason) = step(program: program, context: context) { + if case let .exit(reason) = step(program: state.program, context: context) { switch reason { case let .hostCall(callIndex): if case let .exit(hostExitReason) = await hostCall(state: state, callIndex: callIndex) { diff --git a/PolkaVM/Sources/PolkaVM/Memory.swift b/PolkaVM/Sources/PolkaVM/Memory.swift index ec06e116..bcee1d8d 100644 --- a/PolkaVM/Sources/PolkaVM/Memory.swift +++ b/PolkaVM/Sources/PolkaVM/Memory.swift @@ -186,13 +186,13 @@ public class MemoryChunk { guard startAddress <= address, address + UInt32(length) <= endAddress else { throw .exceedChunkBoundary(address) } - let startIndex = address - startAddress + let startIndex = Int(address - startAddress) + data.startIndex if startIndex >= data.count { return Data(repeating: 0, count: length) } else { let validCount = min(length, data.count - Int(startIndex)) - let dataToRead = data.count > 0 ? data[startIndex ..< startIndex + UInt32(validCount)] : Data() + let dataToRead = data.count > 0 ? data[startIndex ..< startIndex + validCount] : Data() let zeroCount = max(0, length - validCount) let zeros = Data(repeating: 0, count: zeroCount) @@ -207,12 +207,12 @@ public class MemoryChunk { throw .exceedChunkBoundary(address) } - let startIndex = address - startAddress - let endIndex = startIndex + UInt32(valuesData.count) + let startIndex = Int(address - startAddress) + data.startIndex + let endIndex = startIndex + valuesData.count - try zeroPad(until: startAddress + endIndex) + try zeroPad(until: startAddress + UInt32(valuesData.count)) - data[startIndex ..< endIndex] = valuesData + data.replaceSubrange(startIndex ..< endIndex, with: valuesData) } public func incrementEnd(size increment: UInt32) throws(MemoryError) { @@ -339,7 +339,7 @@ public class StandardMemory: Memory { } public func write(address: UInt32, values: some Sequence) throws(MemoryError) { - guard isWritable(address: address, length: values.underestimatedCount) else { + guard isWritable(address: address, length: Data(values).count) else { throw .notWritable(address) } try getChunk(address: address).write(address: address, values: values) @@ -492,7 +492,7 @@ public class GeneralMemory: Memory { } public func write(address: UInt32, values: some Sequence) throws(MemoryError) { - guard isWritable(address: address, length: values.underestimatedCount) else { + guard isWritable(address: address, length: Data(values).count) else { throw .notWritable(address) } try getChunk(address: address).write(address: address, values: values) diff --git a/PolkaVM/Sources/PolkaVM/ProgramCode.swift b/PolkaVM/Sources/PolkaVM/ProgramCode.swift index e91c8d66..4069c029 100644 --- a/PolkaVM/Sources/PolkaVM/ProgramCode.swift +++ b/PolkaVM/Sources/PolkaVM/ProgramCode.swift @@ -146,7 +146,7 @@ public class ProgramCode { var value: UInt32 = 0 if (beginIndex + 4) < bitmask.endIndex { // if enough bytes - value = bitmask.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: beginIndex, as: UInt32.self) } + value = bitmask.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: beginIndex - bitmask.startIndex, as: UInt32.self) } } else { let byte1 = UInt32(bitmask[beginIndex]) let byte2 = UInt32(bitmask[safe: beginIndex + 1] ?? 0xFF) diff --git a/PolkaVM/Sources/PolkaVM/invokePVM.swift b/PolkaVM/Sources/PolkaVM/invokePVM.swift index a57d8326..bfdbce73 100644 --- a/PolkaVM/Sources/PolkaVM/invokePVM.swift +++ b/PolkaVM/Sources/PolkaVM/invokePVM.swift @@ -11,12 +11,12 @@ public func invokePVM( pc: UInt32, gas: Gas, argumentData: Data?, - ctx: any InvocationContext + ctx: (any InvocationContext)? ) async -> (ExitReason, Gas, Data?) { do { let state = try VMState(standardProgramBlob: blob, pc: pc, gas: gas, argumentData: argumentData) let engine = Engine(config: config, invocationContext: ctx) - let exitReason = await engine.execute(program: state.program, state: state) + let exitReason = await engine.execute(state: state) switch exitReason { case .outOfGas: diff --git a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift new file mode 100644 index 00000000..b68ebe31 --- /dev/null +++ b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift @@ -0,0 +1,61 @@ +import Foundation +import Testing +import TracingUtils +import Utils + +@testable import PolkaVM + +// standard programs +let empty = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +// swiftformat:disable wrap wrapArguments +let fibonacci = Data( + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 51, 128, 119, 0, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3] +) +// let sumThree + +struct InvokePVMTests { + init() { + setupTestLogger() + } + + @Test func testEmpty() async throws {} + + @Test(arguments: [ + // (0, 0), + // (1, 1), + // (2, 1), + // (3, 2), + // (4, 3), + // (5, 5), + // (6, 8), + // (7, 13), + // (8, 21), + (9, 34, 999_938), + // (10, 55, 999935), + ]) + func testFibonacci(testCase: (input: UInt8, output: UInt8, gas: UInt64)) async throws { + let config = DefaultPvmConfig() + let (exitReason, gas, output) = await invokePVM( + config: config, + blob: fibonacci, + pc: 0, + gas: Gas(1_000_000), + argumentData: Data([testCase.input]), + ctx: nil + ) + + let value = output?.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self) } ?? 0 + + switch exitReason { + case .halt: + #expect(value == testCase.output) + #expect(gas == Gas(testCase.gas)) + default: + Issue.record("Expected halt, got \(exitReason)") + } + } + + @Test func testSumThree() async throws {} + + // TODO: add tests with a fake InvocationContext +} diff --git a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift index f949eba6..58e2d890 100644 --- a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift +++ b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift @@ -1,10 +1,15 @@ import Foundation import Testing +import TracingUtils import Utils @testable import PolkaVM struct ProgramTests { + init() { + setupTestLogger() + } + @Test func empty() { let blob = Data() #expect(throws: ProgramCode.Error.invalidJumpTableEntriesCount) { try ProgramCode(blob) } @@ -47,8 +52,6 @@ struct ProgramTests { _ = try ProgramCode(data) } - // TODO: add more Program parsing tests - @Test(arguments: [ (Data(), 0, 0), (Data([0]), 0, 7), @@ -66,4 +69,20 @@ struct ProgramTests { func skip(testCase: (Data, UInt32, UInt32)) { #expect(ProgramCode.skip(start: testCase.1, bitmask: testCase.0) == testCase.2) } + + @Test(arguments: [ + // inst_branch_eq_imm_nok + Data([0, 0, 16, 51, 7, 210, 4, 81, 39, 211, 4, 6, 0, 51, 7, 239, 190, 173, 222, 17, 6]), + // inst_branch_greater_unsigned_imm_ok + Data([0, 0, 14, 51, 7, 246, 86, 23, 10, 5, 0, 51, 7, 239, 190, 173, 222, 137, 1]), + // fibonacci general program + // swiftformat:disable wrap wrapArguments + Data([0, 0, 33, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 1, 50, 0, 73, 147, 82, 213, 0]) + ]) + func parseProgramCode(testCase: Data) throws { + let program = try ProgramCode(testCase) + #expect(program.jumpTableEntrySize == 0) + #expect(program.jumpTable == Data()) + #expect(program.code == testCase[3 ..< testCase[2] + 3]) + } } From 83e1f8b1f129fe23ff7acd0c624e616ac4586441 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Thu, 23 Jan 2025 19:37:42 +0800 Subject: [PATCH 02/10] fix memory read --- PolkaVM/Sources/PolkaVM/ExecOutcome.swift | 32 ++++++++++++++++++ PolkaVM/Sources/PolkaVM/Memory.swift | 4 +-- .../Tests/PolkaVMTests/InvokePVMTest.swift | 33 +++++++++++-------- .../Tests/PolkaVMTests/ProgramCodeTests.swift | 2 +- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/PolkaVM/Sources/PolkaVM/ExecOutcome.swift b/PolkaVM/Sources/PolkaVM/ExecOutcome.swift index 474990f0..50b0b205 100644 --- a/PolkaVM/Sources/PolkaVM/ExecOutcome.swift +++ b/PolkaVM/Sources/PolkaVM/ExecOutcome.swift @@ -17,3 +17,35 @@ public enum ExecOutcome { case continued // continue is a reserved keyword case exit(ExitReason) } + +extension ExitReason: Equatable { + public static func == (lhs: ExitReason, rhs: ExitReason) -> Bool { + switch (lhs, rhs) { + case (.halt, .halt): + true + case (.outOfGas, .outOfGas): + true + case let (.panic(l), .panic(r)): + l == r + case let (.hostCall(l), .hostCall(r)): + l == r + case let (.pageFault(l), .pageFault(r)): + l == r + default: + false + } + } +} + +extension ExecOutcome: Equatable { + public static func == (lhs: ExecOutcome, rhs: ExecOutcome) -> Bool { + switch (lhs, rhs) { + case (.continued, .continued): + true + case let (.exit(l), .exit(r)): + l == r + default: + false + } + } +} diff --git a/PolkaVM/Sources/PolkaVM/Memory.swift b/PolkaVM/Sources/PolkaVM/Memory.swift index bcee1d8d..95158776 100644 --- a/PolkaVM/Sources/PolkaVM/Memory.swift +++ b/PolkaVM/Sources/PolkaVM/Memory.swift @@ -188,10 +188,10 @@ public class MemoryChunk { } let startIndex = Int(address - startAddress) + data.startIndex - if startIndex >= data.count { + if startIndex >= data.endIndex { return Data(repeating: 0, count: length) } else { - let validCount = min(length, data.count - Int(startIndex)) + let validCount = min(length, data.endIndex - startIndex) let dataToRead = data.count > 0 ? data[startIndex ..< startIndex + validCount] : Data() let zeroCount = max(0, length - validCount) diff --git a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift index b68ebe31..158edc76 100644 --- a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift +++ b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift @@ -6,7 +6,7 @@ import Utils @testable import PolkaVM // standard programs -let empty = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +let empty = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0]) // swiftformat:disable wrap wrapArguments let fibonacci = Data( [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 51, 128, 119, 0, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3] @@ -15,23 +15,28 @@ let fibonacci = Data( struct InvokePVMTests { init() { - setupTestLogger() + // setupTestLogger() } - @Test func testEmpty() async throws {} + @Test func testEmptyProgram() async throws { + let config = DefaultPvmConfig() + let (exitReason, gas, output) = await invokePVM( + config: config, + blob: empty, + pc: 0, + gas: Gas(1_000_000), + argumentData: Data(), + ctx: nil + ) + #expect(exitReason == .panic(.trap)) + #expect(gas == Gas(0)) + #expect(output == nil) + } @Test(arguments: [ - // (0, 0), - // (1, 1), - // (2, 1), - // (3, 2), - // (4, 3), - // (5, 5), - // (6, 8), - // (7, 13), - // (8, 21), - (9, 34, 999_938), - // (10, 55, 999935), + (2, 2, 999_980), + (8, 34, 999_944), + (9, 55, 999_938), ]) func testFibonacci(testCase: (input: UInt8, output: UInt8, gas: UInt64)) async throws { let config = DefaultPvmConfig() diff --git a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift index 58e2d890..1e94966f 100644 --- a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift +++ b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift @@ -75,7 +75,7 @@ struct ProgramTests { Data([0, 0, 16, 51, 7, 210, 4, 81, 39, 211, 4, 6, 0, 51, 7, 239, 190, 173, 222, 17, 6]), // inst_branch_greater_unsigned_imm_ok Data([0, 0, 14, 51, 7, 246, 86, 23, 10, 5, 0, 51, 7, 239, 190, 173, 222, 137, 1]), - // fibonacci general program + // fibonacci general program (from pvm debuger example_) // swiftformat:disable wrap wrapArguments Data([0, 0, 33, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 1, 50, 0, 73, 147, 82, 213, 0]) ]) From 9398f98347a0af4c19ed17e16de0d15392003af5 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 09:59:53 +0800 Subject: [PATCH 03/10] fix memory write --- PolkaVM/Sources/PolkaVM/Memory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PolkaVM/Sources/PolkaVM/Memory.swift b/PolkaVM/Sources/PolkaVM/Memory.swift index 95158776..76df3ed0 100644 --- a/PolkaVM/Sources/PolkaVM/Memory.swift +++ b/PolkaVM/Sources/PolkaVM/Memory.swift @@ -210,7 +210,7 @@ public class MemoryChunk { let startIndex = Int(address - startAddress) + data.startIndex let endIndex = startIndex + valuesData.count - try zeroPad(until: startAddress + UInt32(valuesData.count)) + try zeroPad(until: startAddress + UInt32(endIndex)) data.replaceSubrange(startIndex ..< endIndex, with: valuesData) } From 8acf7233a977dd8a4723b1cda02fc567c502cea7 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 10:19:40 +0800 Subject: [PATCH 04/10] fix format --- PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift | 10 ++++++---- PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift index 158edc76..c10ed0fe 100644 --- a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift +++ b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift @@ -7,10 +7,12 @@ import Utils // standard programs let empty = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0]) -// swiftformat:disable wrap wrapArguments -let fibonacci = Data( - [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 51, 128, 119, 0, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3] -) +let fibonacci = Data([ + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 51, 128, 119, 0, + 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, + 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, 51, 8, 4, + 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3, +]) // let sumThree struct InvokePVMTests { diff --git a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift index 1e94966f..643ba864 100644 --- a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift +++ b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift @@ -76,8 +76,10 @@ struct ProgramTests { // inst_branch_greater_unsigned_imm_ok Data([0, 0, 14, 51, 7, 246, 86, 23, 10, 5, 0, 51, 7, 239, 190, 173, 222, 137, 1]), // fibonacci general program (from pvm debuger example_) - // swiftformat:disable wrap wrapArguments - Data([0, 0, 33, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 1, 50, 0, 73, 147, 82, 213, 0]) + Data([ + 0, 0, 33, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, + 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 1, 50, 0, 73, 147, 82, 213, 0, + ]) ]) func parseProgramCode(testCase: Data) throws { let program = try ProgramCode(testCase) From 5760c4aae6cc75001b8b1caeddc2b45b19f6384e Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 10:21:19 +0800 Subject: [PATCH 05/10] fix vm execute param --- .../Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift index 9cae7709..71751d82 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift @@ -1003,7 +1003,7 @@ public class Invoke: HostCall { let program = try ProgramCode(innerPvm.code) let vm = VMState(program: program, pc: innerPvm.pc, registers: Registers(registers), gas: Gas(gas), memory: innerPvm.memory) let engine = Engine(config: DefaultPvmConfig()) - let exitReason = await engine.execute(program: program, state: vm) + let exitReason = await engine.execute(state: vm) try state.writeMemory(address: startAddr, values: JamEncoder.encode(vm.getGas(), vm.getRegisters())) context.pvms[pvmIndex]?.memory = vm.getMemoryUnsafe() From a0ae7c6a2e72f290ec03fb111b741bec26ac4787 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 14:21:04 +0800 Subject: [PATCH 06/10] add sumToN test --- .../Tests/PolkaVMTests/InvokePVMTest.swift | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift index c10ed0fe..0b240942 100644 --- a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift +++ b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift @@ -1,6 +1,5 @@ import Foundation import Testing -import TracingUtils import Utils @testable import PolkaVM @@ -9,17 +8,17 @@ import Utils let empty = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0]) let fibonacci = Data([ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 51, 128, 119, 0, - 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, - 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, 51, 8, 4, - 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3, + 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, + 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 61, 7, 0, 0, 2, 0, + 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 154, 148, 170, 130, 4, 3, +]) +let sumToN = Data([ + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 38, 128, 119, 0, + 51, 8, 0, 100, 121, 40, 3, 0, 200, 137, 8, 149, 153, 255, 86, 9, 250, + 61, 8, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 77, 18, + 36, 24, ]) -// let sumThree - struct InvokePVMTests { - init() { - // setupTestLogger() - } - @Test func testEmptyProgram() async throws { let config = DefaultPvmConfig() let (exitReason, gas, output) = await invokePVM( @@ -62,7 +61,33 @@ struct InvokePVMTests { } } - @Test func testSumThree() async throws {} + @Test(arguments: [ + (1, 1, 999_988), + (4, 10, 999_979), + (5, 15, 999_976), + ]) + func testSumToN(testCase: (input: UInt8, output: UInt8, gas: UInt64)) async throws { + let config = DefaultPvmConfig() + let (exitReason, gas, output) = await invokePVM( + config: config, + blob: sumToN, + pc: 0, + gas: Gas(1_000_000), + argumentData: Data([testCase.input]), + ctx: nil + ) + + let value = output?.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self) } ?? 0 + + switch exitReason { + case .halt: + #expect(value == testCase.output) + #expect(gas == Gas(testCase.gas)) + default: + Issue.record("Expected halt, got \(exitReason)") + } + } // TODO: add tests with a fake InvocationContext + @Test func testInvocationContext() async throws {} } From 88d35a94d55635ed0c67e95228db586b19dc2b39 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 15:48:24 +0800 Subject: [PATCH 07/10] add basic invocation context test --- PolkaVM/Sources/PolkaVM/Engine.swift | 6 +-- .../Tests/PolkaVMTests/InvokePVMTest.swift | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/PolkaVM/Sources/PolkaVM/Engine.swift b/PolkaVM/Sources/PolkaVM/Engine.swift index 6bb331ad..d3d92889 100644 --- a/PolkaVM/Sources/PolkaVM/Engine.swift +++ b/PolkaVM/Sources/PolkaVM/Engine.swift @@ -44,14 +44,14 @@ public class Engine { case let .pageFault(address): return .exit(.pageFault(address)) case let .hostCall(callIndexInner): - let pc = state.pc - let skip = state.program.skip(pc) - state.increasePC(skip + 1) return await hostCall(state: state, callIndex: callIndexInner) default: return .exit(reason) } case .continued: + let pc = state.pc + let skip = state.program.skip(pc) + state.increasePC(skip + 1) return .continued } } diff --git a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift index 0b240942..3cdd8bd6 100644 --- a/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift +++ b/PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift @@ -18,6 +18,13 @@ let sumToN = Data([ 61, 8, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 1, 50, 0, 73, 77, 18, 36, 24, ]) +let sumToNWithHostCall = Data([ + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 40, 128, 119, 0, + 51, 8, 0, 100, 121, 40, 3, 0, 200, 137, 8, 149, 153, 255, 86, 9, 250, + 61, 8, 0, 0, 2, 0, 51, 8, 4, 51, 7, 0, 0, 2, 0, 10, 1, 1, 50, 0, 73, + 77, 18, 36, 104, +]) + struct InvokePVMTests { @Test func testEmptyProgram() async throws { let config = DefaultPvmConfig() @@ -88,6 +95,45 @@ struct InvokePVMTests { } } - // TODO: add tests with a fake InvocationContext - @Test func testInvocationContext() async throws {} + @Test func testInvocationContext() async throws { + let config = DefaultPvmConfig() + + struct TestInvocationContext: InvocationContext { + public typealias ContextType = Void + + public var context: ContextType = () + + public func dispatch(index _: UInt32, state: VMState) async -> ExecOutcome { + // perform output * 2 + do { + let (ouputAddr, len): (UInt32, UInt32) = state.readRegister(Registers.Index(raw: 7), Registers.Index(raw: 8)) + let output = try state.readMemory(address: ouputAddr, length: Int(len)) + let value = output.withUnsafeBytes { $0.load(as: UInt32.self) } + let newOutput = withUnsafeBytes(of: value << 1) { Data($0) } + try state.writeMemory(address: ouputAddr, values: newOutput) + return .continued + } catch { + return .exit(.panic(.trap)) + } + } + } + + let (exitReason, _, output) = await invokePVM( + config: config, + blob: sumToNWithHostCall, + pc: 0, + gas: Gas(1_000_000), + argumentData: Data([5]), + ctx: TestInvocationContext() + ) + + let value = output?.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self) } ?? 0 + + switch exitReason { + case .halt: + #expect(value == 30) + default: + Issue.record("Expected halt, got \(exitReason)") + } + } } From 200263fadabadf5fe24f4a1dea9011dd389ccfa9 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 15:51:47 +0800 Subject: [PATCH 08/10] remove log --- PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift index 643ba864..ec21a9ec 100644 --- a/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift +++ b/PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift @@ -1,15 +1,10 @@ import Foundation import Testing -import TracingUtils import Utils @testable import PolkaVM struct ProgramTests { - init() { - setupTestLogger() - } - @Test func empty() { let blob = Data() #expect(throws: ProgramCode.Error.invalidJumpTableEntriesCount) { try ProgramCode(blob) } @@ -75,7 +70,7 @@ struct ProgramTests { Data([0, 0, 16, 51, 7, 210, 4, 81, 39, 211, 4, 6, 0, 51, 7, 239, 190, 173, 222, 17, 6]), // inst_branch_greater_unsigned_imm_ok Data([0, 0, 14, 51, 7, 246, 86, 23, 10, 5, 0, 51, 7, 239, 190, 173, 222, 137, 1]), - // fibonacci general program (from pvm debuger example_) + // fibonacci general program (from pvm debuger example) Data([ 0, 0, 33, 51, 8, 1, 51, 9, 1, 40, 3, 0, 149, 119, 255, 81, 7, 12, 100, 138, 200, 152, 8, 100, 169, 40, 243, 100, 135, 51, 8, 51, 9, 1, 50, 0, 73, 147, 82, 213, 0, From ba9a6a118c5863b0490f835144b93cd0722e4ed4 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 17:46:37 +0800 Subject: [PATCH 09/10] remove extra equatable impl --- PolkaVM/Sources/PolkaVM/ExecOutcome.swift | 34 +---------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/PolkaVM/Sources/PolkaVM/ExecOutcome.swift b/PolkaVM/Sources/PolkaVM/ExecOutcome.swift index 50b0b205..2a70eecc 100644 --- a/PolkaVM/Sources/PolkaVM/ExecOutcome.swift +++ b/PolkaVM/Sources/PolkaVM/ExecOutcome.swift @@ -1,4 +1,4 @@ -public enum ExitReason { +public enum ExitReason: Equatable { public enum PanicReason { case trap case invalidInstructionIndex @@ -17,35 +17,3 @@ public enum ExecOutcome { case continued // continue is a reserved keyword case exit(ExitReason) } - -extension ExitReason: Equatable { - public static func == (lhs: ExitReason, rhs: ExitReason) -> Bool { - switch (lhs, rhs) { - case (.halt, .halt): - true - case (.outOfGas, .outOfGas): - true - case let (.panic(l), .panic(r)): - l == r - case let (.hostCall(l), .hostCall(r)): - l == r - case let (.pageFault(l), .pageFault(r)): - l == r - default: - false - } - } -} - -extension ExecOutcome: Equatable { - public static func == (lhs: ExecOutcome, rhs: ExecOutcome) -> Bool { - switch (lhs, rhs) { - case (.continued, .continued): - true - case let (.exit(l), .exit(r)): - l == r - default: - false - } - } -} From 0b005dc95f74e846456fcfe937ff89f4afde2b8a Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Fri, 24 Jan 2025 18:07:28 +0800 Subject: [PATCH 10/10] fix extra Data init --- PolkaVM/Sources/PolkaVM/Memory.swift | 23 ++++++++++---------- PolkaVM/Sources/PolkaVM/VMState.swift | 2 +- PolkaVM/Tests/PolkaVMTests/MemoryTests.swift | 10 ++++----- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/PolkaVM/Sources/PolkaVM/Memory.swift b/PolkaVM/Sources/PolkaVM/Memory.swift index 76df3ed0..5ff9e6c3 100644 --- a/PolkaVM/Sources/PolkaVM/Memory.swift +++ b/PolkaVM/Sources/PolkaVM/Memory.swift @@ -67,7 +67,7 @@ public protocol Memory { func read(address: UInt32) throws -> UInt8 func read(address: UInt32, length: Int) throws -> Data func write(address: UInt32, value: UInt8) throws - func write(address: UInt32, values: some Sequence) throws + func write(address: UInt32, values: Data) throws func zero(pageIndex: UInt32, pages: Int) throws func void(pageIndex: UInt32, pages: Int) throws @@ -201,18 +201,17 @@ public class MemoryChunk { } } - public func write(address: UInt32, values: some Sequence) throws(MemoryError) { - let valuesData = Data(values) - guard startAddress <= address, address + UInt32(valuesData.count) <= endAddress else { + public func write(address: UInt32, values: Data) throws(MemoryError) { + guard startAddress <= address, address + UInt32(values.count) <= endAddress else { throw .exceedChunkBoundary(address) } let startIndex = Int(address - startAddress) + data.startIndex - let endIndex = startIndex + valuesData.count + let endIndex = startIndex + values.count try zeroPad(until: startAddress + UInt32(endIndex)) - data.replaceSubrange(startIndex ..< endIndex, with: valuesData) + data.replaceSubrange(startIndex ..< endIndex, with: values) } public func incrementEnd(size increment: UInt32) throws(MemoryError) { @@ -335,11 +334,11 @@ public class StandardMemory: Memory { guard isWritable(address: address, length: 1) else { throw .notWritable(address) } - try getChunk(address: address).write(address: address, values: [value]) + try getChunk(address: address).write(address: address, values: Data([value])) } - public func write(address: UInt32, values: some Sequence) throws(MemoryError) { - guard isWritable(address: address, length: Data(values).count) else { + public func write(address: UInt32, values: Data) throws(MemoryError) { + guard isWritable(address: address, length: values.count) else { throw .notWritable(address) } try getChunk(address: address).write(address: address, values: values) @@ -488,11 +487,11 @@ public class GeneralMemory: Memory { guard isWritable(address: address, length: 1) else { throw .notWritable(address) } - try getChunk(address: address).write(address: address, values: [value]) + try getChunk(address: address).write(address: address, values: Data([value])) } - public func write(address: UInt32, values: some Sequence) throws(MemoryError) { - guard isWritable(address: address, length: Data(values).count) else { + public func write(address: UInt32, values: Data) throws(MemoryError) { + guard isWritable(address: address, length: values.count) else { throw .notWritable(address) } try getChunk(address: address).write(address: address, values: values) diff --git a/PolkaVM/Sources/PolkaVM/VMState.swift b/PolkaVM/Sources/PolkaVM/VMState.swift index 78d1de5a..e43e3d99 100644 --- a/PolkaVM/Sources/PolkaVM/VMState.swift +++ b/PolkaVM/Sources/PolkaVM/VMState.swift @@ -65,7 +65,7 @@ public class VMState { } public func writeMemory(address: some FixedWidthInteger, values: some Sequence) throws { - try memory.write(address: UInt32(truncatingIfNeeded: address), values: values) + try memory.write(address: UInt32(truncatingIfNeeded: address), values: Data(values)) } public func sbrk(_ increment: UInt32) throws -> UInt32 { diff --git a/PolkaVM/Tests/PolkaVMTests/MemoryTests.swift b/PolkaVM/Tests/PolkaVMTests/MemoryTests.swift index 9f7e4469..9431dd51 100644 --- a/PolkaVM/Tests/PolkaVMTests/MemoryTests.swift +++ b/PolkaVM/Tests/PolkaVMTests/MemoryTests.swift @@ -80,7 +80,7 @@ enum MemoryTests { @Test func write() throws { let chunk = try MemoryChunk(startAddress: 0, endAddress: 10, data: Data()) - try chunk.write(address: 0, values: [1]) + try chunk.write(address: 0, values: Data([1])) #expect(chunk.data == Data([1])) try chunk.write(address: 1, values: Data([2])) #expect(chunk.data == Data([1, 2])) @@ -199,7 +199,7 @@ enum MemoryTests { #expect(memory.isWritable(address: stackStart, length: Int(stackEnd - stackStart)) == true) try memory.write(address: stackStart, value: 1) #expect(try memory.read(address: stackStart, length: 2) == Data([1, 0])) - try memory.write(address: stackEnd - 2, values: [1, 2]) + try memory.write(address: stackEnd - 2, values: Data([1, 2])) #expect(try memory.read(address: stackEnd - 4, length: 4) == Data([0, 0, 1, 2])) // argument @@ -260,9 +260,9 @@ enum MemoryTests { } @Test func write() throws { - try memory.write(address: 2, values: [9, 8]) + try memory.write(address: 2, values: Data([9, 8])) #expect(try memory.read(address: 0, length: 4) == Data([1, 2, 9, 8])) - #expect(throws: MemoryError.notWritable(4096)) { try memory.write(address: 4096, values: [0]) } + #expect(throws: MemoryError.notWritable(4096)) { try memory.write(address: 4096, values: Data([0])) } } @Test func sbrk() throws { @@ -271,7 +271,7 @@ enum MemoryTests { #expect(memory.isWritable(address: oldEnd, length: 512) == true) #expect(memory.isWritable(address: 0, length: Int(oldEnd)) == true) - try memory.write(address: oldEnd, values: [1, 2, 3]) + try memory.write(address: oldEnd, values: Data([1, 2, 3])) #expect(try memory.read(address: oldEnd - 1, length: 5) == Data([7, 1, 2, 3, 0])) }