Skip to content

Commit

Permalink
PolkaVM (#41)
Browse files Browse the repository at this point in the history
* PolkVM Package and integer codec

* Program parsing

* program parse

* more tests

* memory wip

* fix

* pvm wip

* pvm

* fix

* fix lint
  • Loading branch information
xlc authored Jul 26, 2024
1 parent 2d85e15 commit ebb7a84
Show file tree
Hide file tree
Showing 22 changed files with 1,009 additions and 3 deletions.
2 changes: 1 addition & 1 deletion JAMTests/jamtestvectors
69 changes: 69 additions & 0 deletions PolkaVM/Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"originHash" : "83027e22d1c1e6e15dd98a1719eec3b0aa70fd540013d36d77223e0931d6f7a8",
"pins" : [
{
"identity" : "blake2.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tesseract-one/Blake2.swift.git",
"state" : {
"revision" : "29c55c8fe42d6661e5a32cc5bbbad1fff64fd01e",
"version" : "0.2.0"
}
},
{
"identity" : "scalecodec.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/AcalaNetwork/ScaleCodec.swift.git",
"state" : {
"branch" : "main",
"revision" : "dac3e7161de34c60c82794d031de0231b5a5746e"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "46072478ca365fe48370993833cb22de9b41567f",
"version" : "3.5.2"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c",
"version" : "600.0.0-prerelease-2024-06-12"
}
},
{
"identity" : "swift-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-testing.git",
"state" : {
"branch" : "0.10.0",
"revision" : "69d59cfc76e5daf498ca61f5af409f594768eef9"
}
},
{
"identity" : "tuples.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tesseract-one/Tuples.swift.git",
"state" : {
"revision" : "4d2cf7c64443cdf4df833d0bedd767bf9dbc49d9",
"version" : "0.1.3"
}
}
],
"version" : 3
}
39 changes: 39 additions & 0 deletions PolkaVM/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "PolkaVM",
platforms: [
.macOS(.v14),
],
products: [
.library(
name: "PolkaVM",
targets: ["PolkaVM"]
),
],
dependencies: [
.package(path: "../Utils"),
.package(url: "https://github.com/apple/swift-testing.git", branch: "0.10.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
],
targets: [
.target(
name: "PolkaVM",
dependencies: [
"Utils",
.product(name: "Logging", package: "swift-log"),
]
),
.testTarget(
name: "PolkaVMTests",
dependencies: [
"PolkaVM",
.product(name: "Testing", package: "swift-testing"),
]
),
],
swiftLanguageVersions: [.version("6")]
)
26 changes: 26 additions & 0 deletions PolkaVM/Sources/PolkaVM/Engine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public class Engine {
public init() {}

public func execute(program: ProgramCode, state: VMState) -> ExitReason {
while true {
guard state.gas > 0 else {
return .outOfGas
}
if let exitReason = step(program: program, state: state) {
return exitReason
}
}
}

public func step(program: ProgramCode, state: VMState) -> ExitReason? {
let pc = state.pc
guard let skip = program.skip(state.pc) else {
return .halt(.invalidInstruction)
}
guard let inst = InstructionTable.parse(program.code[Int(pc) ..< Int(pc + 1 + skip)]) else {
return .halt(.invalidInstruction)
}

return inst.execute(state: state, skip: skip)
}
}
12 changes: 12 additions & 0 deletions PolkaVM/Sources/PolkaVM/ExitReason.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
public enum ExitReason {
public enum HaltReason {
case trap
case invalidInstruction
}

case halt(HaltReason)
case panic
case outOfGas
case hostCall(UInt32)
case pageFault(UInt32)
}
32 changes: 32 additions & 0 deletions PolkaVM/Sources/PolkaVM/Instruction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

public protocol Instruction {
static var opcode: UInt8 { get }

init?(data: Data)

func execute(state: VMState, skip: UInt32) -> ExitReason?
func executeImpl(state: VMState) -> ExitReason?

func gasCost() -> UInt64
func updatePC(state: VMState, skip: UInt32)
}

extension Instruction {
public func execute(state: VMState, skip: UInt32) -> ExitReason? {
state.consumeGas(gasCost())
let res = executeImpl(state: state)
if res == nil {
state.updatePC(state.pc + skip + 1)
}
return res
}

public func gasCost() -> UInt64 {
1
}

public func updatePC(state: VMState, skip: UInt32) {
state.increasePC(skip + 1)
}
}
30 changes: 30 additions & 0 deletions PolkaVM/Sources/PolkaVM/InstructionTable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation

public class InstructionTable {
public static let table: [Instruction.Type?] = {
let insts: [Instruction.Type] = [
Instructions.Trap.self,
Instructions.Fallthrough.self,
Instructions.Ecalli.self,
Instructions.StoreImmU8.self,
Instructions.StoreImmU16.self,
Instructions.StoreImmU32.self,
]
var table: [Instruction.Type?] = Array(repeating: nil, count: 256)
for i in 0 ..< insts.count {
table[Int(insts[i].opcode)] = insts[i]
}
return table
}()

public static func parse(_ data: Data) -> (any Instruction)? {
guard data.count >= 1 else {
return nil
}
let opcode = data[data.startIndex]
guard let instType = table[Int(opcode)] else {
return nil
}
return instType.init(data: data)
}
}
134 changes: 134 additions & 0 deletions PolkaVM/Sources/PolkaVM/Instructions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import Foundation
import Utils

public enum Instructions {
static func decodeImmidate(_ data: Data) -> UInt32 {
let len = min(data.count, 4)
if len == 0 {
return 0
}
var value: UInt32 = 0
for i in 0 ..< len {
value = value | (UInt32(data[i]) << (8 * i))
}
let shift = (4 - len) * 8
// shift left so that the MSB is the sign bit
// and then do signed shift right to fill the empty bits using the sign bit
// and then convert back to UInt32
return UInt32(bitPattern: Int32(bitPattern: value << shift) >> shift)
}

static func decodeImmidate2(_ data: Data) -> (UInt32, UInt32)? {
do {
let lA = try Int(data.at(0) & 0b111)
let lX = min(4, lA)
let lY1 = min(4, max(0, data.count - Int(lA) - 1))
let lY2 = min(lY1, 8 - lA)
let vX = try decodeImmidate(data.at(1 ..< lX))
let vY = try decodeImmidate(data.at((1 + lA) ..< lY2))
return (vX, vY)
} catch {
return nil
}
}

// MARK: Instructions without Arguments

public struct Trap: Instruction {
public static var opcode: UInt8 { 0 }

public init(data _: Data) {}

public func executeImpl(state _: VMState) -> ExitReason? {
.halt(.trap)
}
}

public struct Fallthrough: Instruction {
public static var opcode: UInt8 { 1 }

public init(data _: Data) {}

public func executeImpl(state _: VMState) -> ExitReason? {
nil
}
}

// MARK: Instructions with Arguments of One Immediate

public struct Ecalli: Instruction {
public static var opcode: UInt8 { 78 }

public let callIndex: UInt32

public init(data: Data) {
callIndex = Instructions.decodeImmidate(data)
}

public func executeImpl(state _: VMState) -> ExitReason? {
.hostCall(callIndex)
}
}

// MARK: Instructions with Arguments of Two Immediates

public struct StoreImmU8: Instruction {
public static var opcode: UInt8 { 62 }

public let address: UInt32
public let value: UInt8

public init(data: Data) {
let (x, y) = Instructions.decodeImmidate2(data)!
address = x
value = UInt8(truncatingIfNeeded: y)
}

public func executeImpl(state: VMState) -> ExitReason? {
if (try? state.memory.write(address: address, value: value)) != nil {
return nil
}
return .pageFault(address)
}
}

public struct StoreImmU16: Instruction {
public static var opcode: UInt8 { 79 }

public let address: UInt32
public let value: UInt16

public init(data: Data) {
let (x, y) = Instructions.decodeImmidate2(data)!
address = x
value = UInt16(truncatingIfNeeded: y)
}

public func executeImpl(state: VMState) -> ExitReason? {
if (try? state.memory.write(address: address, values: value.encode(method: .fixedWidth(2)))) != nil {
return nil
}
return .pageFault(address)
}
}

public struct StoreImmU32: Instruction {
public static var opcode: UInt8 { 38 }

public let address: UInt32
public let value: UInt32

public init(data: Data) {
let (x, y) = Instructions.decodeImmidate2(data)!
address = x
value = y
}

public func executeImpl(state: VMState) -> ExitReason? {
if (try? state.memory.write(address: address, values: value.encode(method: .fixedWidth(4)))) != nil {
return nil
}
return .pageFault(address)
}
}
}
Loading

0 comments on commit ebb7a84

Please sign in to comment.