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

PolkaVM #41

Merged
merged 12 commits into from
Jul 26, 2024
Merged
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
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