Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invoke pvm unit tests #269

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@
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)

Check warning on line 1006 in Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift#L1006

Added line #L1006 was not covered by tests

try state.writeMemory(address: startAddr, values: JamEncoder.encode(vm.getGas(), vm.getRegisters()))
context.pvms[pvmIndex]?.memory = vm.getMemoryUnsafe()
Expand Down
10 changes: 5 additions & 5 deletions PolkaVM/Sources/PolkaVM/Engine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion PolkaVM/Sources/PolkaVM/ExecOutcome.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public enum ExitReason {
public enum ExitReason: Equatable {
public enum PanicReason {
case trap
case invalidInstructionIndex
Expand Down
35 changes: 17 additions & 18 deletions PolkaVM/Sources/PolkaVM/Memory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
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<UInt8>) throws
func write(address: UInt32, values: Data) throws

func zero(pageIndex: UInt32, pages: Int) throws
func void(pageIndex: UInt32, pages: Int) throws
Expand Down Expand Up @@ -186,13 +186,13 @@
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 {
if startIndex >= data.endIndex {
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 validCount = min(length, data.endIndex - startIndex)
let dataToRead = data.count > 0 ? data[startIndex ..< startIndex + validCount] : Data()

let zeroCount = max(0, length - validCount)
let zeros = Data(repeating: 0, count: zeroCount)
Expand All @@ -201,18 +201,17 @@
}
}

public func write(address: UInt32, values: some Sequence<UInt8>) 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 = address - startAddress
let endIndex = startIndex + UInt32(valuesData.count)
let startIndex = Int(address - startAddress) + data.startIndex
let endIndex = startIndex + values.count

try zeroPad(until: startAddress + endIndex)
try zeroPad(until: startAddress + UInt32(endIndex))

data[startIndex ..< endIndex] = valuesData
data.replaceSubrange(startIndex ..< endIndex, with: values)
}

public func incrementEnd(size increment: UInt32) throws(MemoryError) {
Expand Down Expand Up @@ -335,11 +334,11 @@
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<UInt8>) throws(MemoryError) {
guard isWritable(address: address, length: values.underestimatedCount) 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)
Expand Down Expand Up @@ -488,11 +487,11 @@
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]))

Check warning on line 490 in PolkaVM/Sources/PolkaVM/Memory.swift

View check run for this annotation

Codecov / codecov/patch

PolkaVM/Sources/PolkaVM/Memory.swift#L490

Added line #L490 was not covered by tests
}

public func write(address: UInt32, values: some Sequence<UInt8>) throws(MemoryError) {
guard isWritable(address: address, length: values.underestimatedCount) 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)
Expand Down
2 changes: 1 addition & 1 deletion PolkaVM/Sources/PolkaVM/ProgramCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion PolkaVM/Sources/PolkaVM/VMState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class VMState {
}

public func writeMemory(address: some FixedWidthInteger, values: some Sequence<UInt8>) 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 {
Expand Down
4 changes: 2 additions & 2 deletions PolkaVM/Sources/PolkaVM/invokePVM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
139 changes: 139 additions & 0 deletions PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Foundation
import Testing
import Utils

@testable import PolkaVM

// standard programs
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,
])
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 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()
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: [
(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()
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)")

Check warning on line 67 in PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift

View check run for this annotation

Codecov / codecov/patch

PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift#L67

Added line #L67 was not covered by tests
}
}

@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)")

Check warning on line 94 in PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift

View check run for this annotation

Codecov / codecov/patch

PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift#L94

Added line #L94 was not covered by tests
}
}

@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))
}

Check warning on line 117 in PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift

View check run for this annotation

Codecov / codecov/patch

PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift#L116-L117

Added lines #L116 - L117 were not covered by tests
}
}

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)")

Check warning on line 136 in PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift

View check run for this annotation

Codecov / codecov/patch

PolkaVM/Tests/PolkaVMTests/InvokePVMTest.swift#L136

Added line #L136 was not covered by tests
}
}
}
10 changes: 5 additions & 5 deletions PolkaVM/Tests/PolkaVMTests/MemoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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]))
}

Expand Down
20 changes: 18 additions & 2 deletions PolkaVM/Tests/PolkaVMTests/ProgramCodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ struct ProgramTests {
_ = try ProgramCode(data)
}

// TODO: add more Program parsing tests

@Test(arguments: [
(Data(), 0, 0),
(Data([0]), 0, 7),
Expand All @@ -66,4 +64,22 @@ 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 (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,
])
])
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])
}
}