Skip to content

Commit

Permalink
Merge branch 'master' into dev_networking
Browse files Browse the repository at this point in the history
* master:
  fix lint (#106)
  standard program initialization (#101)
  • Loading branch information
MacOMNI committed Sep 9, 2024
2 parents ea5e47d + b3af474 commit 77f5bb2
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 24 deletions.
10 changes: 5 additions & 5 deletions Codec/Sources/Codec/IntegerCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ public enum IntegerCodec {
return 0
}

let byteLengh = (~firstByte).leadingZeroBitCount
let byteLength = (~firstByte).leadingZeroBitCount
var res: UInt64 = 0
if byteLengh > 0 {
guard let rest: UInt64 = try decode(length: byteLengh, next: next) else {
if byteLength > 0 {
guard let rest: UInt64 = try decode(length: byteLength, next: next) else {
return nil
}
res = rest
}

let mask = UInt8(UInt(1) << (8 - byteLengh) - 1)
let mask = UInt8(UInt(1) << (8 - byteLength) - 1)
let topBits = firstByte & mask

return res + UInt64(topBits) << (8 * byteLengh)
return res + UInt64(topBits) << (8 * byteLength)
}
}
14 changes: 11 additions & 3 deletions Codec/Sources/Codec/JamDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ private struct JamKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerPr

func decode(_: String.Type, forKey key: K) throws -> String {
let data: Data = try decoder.decodeData(codingPath: codingPath + [key])
return String(decoding: data, as: UTF8.self)
guard let string = String(data: data, encoding: .utf8) else {
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Invalid UTF8 string")
}
return string
}

func decode(_: Double.Type, forKey key: K) throws -> Double {
Expand Down Expand Up @@ -352,7 +355,9 @@ private struct JamUnkeyedDecodingContainer: UnkeyedDecodingContainer {

mutating func decode(_: String.Type) throws -> String {
let data: Data = try decoder.decodeData(codingPath: codingPath)
let value = String(decoding: data, as: UTF8.self)
guard let value = String(data: data, encoding: .utf8) else {
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Invalid UTF8 string")
}
currentIndex += 1
return value
}
Expand Down Expand Up @@ -465,7 +470,10 @@ private struct JamSingleValueDecodingContainer: SingleValueDecodingContainer {

func decode(_: String.Type) throws -> String {
let data: Data = try decoder.decodeData(codingPath: codingPath)
return String(decoding: data, as: UTF8.self)
guard let string = String(data: data, encoding: .utf8) else {
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Invalid UTF8 string")
}
return string
}

func decode(_: Double.Type) throws -> Double {
Expand Down
3 changes: 0 additions & 3 deletions JAMTests/Tests/JAMTests/PVMTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ private let logger = Logger(label: "PVMTests")

// TODO: pass these
let knownFailedTestCases = [
"inst_load_u8_trap",
"inst_store_u8_trap_read_only",
"inst_store_u8_trap_inaccessible",
"inst_ret_halt",
"inst_ret_invalid",
]
Expand Down
2 changes: 2 additions & 0 deletions PolkaVM/Sources/PolkaVM/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ extension Instruction {
logger.debug("execution success! updating pc...")
return updatePC(context: context, skip: skip)
} catch let e as Memory.Error {
// this passes test vector
context.state.consumeGas(gasCost())
return .exit(.pageFault(e.address))
} catch let e {
// other unknown errors
Expand Down
87 changes: 74 additions & 13 deletions PolkaVM/Sources/PolkaVM/Memory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,67 @@ public class Memory {

private let pageMap: [(address: UInt32, length: UInt32, writable: Bool)]
private var chunks: [(address: UInt32, data: Data)]
private let heapStart: UInt32
private var heapEnd: UInt32 // start idx of unallocated heap
private let heapLimit: UInt32

public init(pageMap: [(address: UInt32, length: UInt32, writable: Bool)], chunks: [(address: UInt32, data: Data)]) {
self.pageMap = pageMap
self.chunks = chunks
heapStart = pageMap.first(where: { $0.writable })?.address ?? 0
heapLimit = UInt32.max
heapEnd = chunks.reduce(0) { max($0, $1.address + UInt32($1.data.count)) }
}

/// Standard Program init
///
/// Init with some read only data, writable data and argument data
public init(readOnlyData: Data, readWriteData: Data, argumentData: Data, heapEmptyPagesSize: UInt32, stackSize: UInt32) {
let config = DefaultPvmConfig()
let P = StandardProgram.alignToPageSize
let Q = StandardProgram.alignToSegmentSize
let ZQ = UInt32(config.pvmProgramInitSegmentSize)
let readOnlyLen = UInt32(readOnlyData.count)
let readWriteLen = UInt32(readWriteData.count)
let argumentDataLen = UInt32(argumentData.count)

let heapStart = 2 * ZQ + Q(readOnlyLen, config)

pageMap = [
(ZQ, readOnlyLen, false),
(ZQ + readOnlyLen, P(readOnlyLen, config) - readOnlyLen, false),
(heapStart, readWriteLen, true), // heap
(heapStart + readWriteLen, P(readWriteLen, config) + heapEmptyPagesSize - readWriteLen, true), // heap
(UInt32(config.pvmProgramInitStackBaseAddress) - P(stackSize, config), stackSize, true), // stack
(UInt32(config.pvmProgramInitInputStartAddress), argumentDataLen, false), // argument
(UInt32(config.pvmProgramInitInputStartAddress) + argumentDataLen, P(argumentDataLen, config) - argumentDataLen, false),
]

chunks = [
(ZQ, readOnlyData),
(heapStart, readWriteData),
(UInt32(config.pvmProgramInitInputStartAddress), argumentData),
]

self.heapStart = heapStart
heapLimit = heapStart + P(readWriteLen, config) + heapEmptyPagesSize
heapEnd = heapStart + readWriteLen
}

public func isWritable(address: UInt32) -> Bool {
// check heap range
guard heapStart <= address, address < heapLimit else {
return false
}

// TODO: optimize
for page in pageMap {
if page.address <= address, address < page.address + page.length {
return page.writable
}
}

return false
}

public func read(address: UInt32) throws(Error) -> UInt8 {
Expand Down Expand Up @@ -72,6 +129,10 @@ public class Memory {
}

public func write(address: UInt32, value: UInt8) throws(Error) {
guard isWritable(address: address) else {
throw Error.notWritable(address)
}

// TODO: optimize this
// check for chunks
for i in 0 ..< chunks.count {
Expand All @@ -88,13 +149,18 @@ public class Memory {
var newChunk = (address: address, data: Data(repeating: 0, count: Int(page.length)))
newChunk.data[Int(address - page.address)] = value
chunks.append(newChunk)
heapEnd = max(heapEnd, address + 1)
return
}
}
throw Error.notWritable(address)
}

public func write(address: UInt32, values: some Sequence<UInt8>) throws(Error) {
guard isWritable(address: address) else {
throw Error.notWritable(address)
}

// TODO: optimize this
// check for chunks
for i in 0 ..< chunks.count {
Expand All @@ -120,38 +186,33 @@ public class Memory {
var idx = Int(address - page.address)
for v in values {
if idx == newChunk.data.endIndex {
newChunk.data.append(v)
throw Error.notWritable(address)
} else {
newChunk.data[idx] = v
}
idx += 1
}
chunks.append(newChunk)
heapEnd = max(heapEnd, UInt32(idx))
return
}
}
throw Error.notWritable(address)
}

public func sbrk(_ increment: UInt32) throws -> UInt32 {
// TODO: update after memory layout details are implemented
var curHeapEnd: UInt32 = 0

if let lastChunk = chunks.last {
curHeapEnd = lastChunk.address + UInt32(lastChunk.data.count)
}

// TODO: optimize
for page in pageMap {
let pageEnd = page.address + page.length
if page.writable, curHeapEnd >= page.address, curHeapEnd + increment < pageEnd {
let newChunk = (address: curHeapEnd, data: Data(repeating: 0, count: Int(increment)))
if page.writable, heapEnd >= page.address, heapEnd + increment < pageEnd {
let newChunk = (address: heapEnd, data: Data(repeating: 0, count: Int(increment)))
chunks.append(newChunk)

return curHeapEnd
heapEnd += increment
return heapEnd
}
}

throw Error.outOfMemory(curHeapEnd)
throw Error.outOfMemory(heapEnd)
}
}

Expand Down
8 changes: 8 additions & 0 deletions PolkaVM/Sources/PolkaVM/PvmConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ public struct DefaultPvmConfig: PvmConfig {
public let pvmProgramInitPageSize: Int
public let pvmProgramInitSegmentSize: Int

public let pvmProgramInitRegister1Value: Int
public let pvmProgramInitStackBaseAddress: Int
public let pvmProgramInitInputStartAddress: Int

public init() {
pvmDynamicAddressAlignmentFactor = 2
pvmProgramInitInputDataSize = 1 << 24
pvmProgramInitPageSize = 1 << 14
pvmProgramInitSegmentSize = 1 << 16

pvmProgramInitRegister1Value = (1 << 32) - (1 << 16)
pvmProgramInitStackBaseAddress = (1 << 32) - (2 * pvmProgramInitSegmentSize) - pvmProgramInitInputDataSize
pvmProgramInitInputStartAddress = pvmProgramInitStackBaseAddress + pvmProgramInitSegmentSize
}
}
10 changes: 10 additions & 0 deletions PolkaVM/Sources/PolkaVM/Registers.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Foundation

public struct Registers: Equatable {
public struct Index {
public let value: UInt8
Expand Down Expand Up @@ -47,6 +49,14 @@ public struct Registers: Equatable {
reg13 = values[12]
}

/// standard program init registers
public init(config: DefaultPvmConfig, argumentData: Data?) {
reg1 = UInt32(config.pvmProgramInitRegister1Value)
reg2 = UInt32(config.pvmProgramInitStackBaseAddress)
reg10 = UInt32(config.pvmProgramInitInputStartAddress)
reg11 = UInt32(argumentData?.count ?? 0)
}

public subscript(index: Index) -> UInt32 {
get {
switch index.value {
Expand Down
80 changes: 80 additions & 0 deletions PolkaVM/Sources/PolkaVM/StandardProgram.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Foundation

/// Standard Program defined in GP.
///
/// It includes some metadata for memory and registers initialization
/// other than the program code
public class StandardProgram {
public enum Error: Swift.Error {
case invalidReadOnlyLength
case invalidReadWriteLength
case invalidHeapPages
case invalidStackSize
case invalidDataLength
case invalidCodeLength
case invalidTotalMemorySize
}

public let code: ProgramCode
public let initialMemory: Memory
public let initialRegisters: Registers

public init(blob: Data, argumentData: Data?) throws {
var slice = Slice(base: blob, bounds: blob.startIndex ..< blob.endIndex)
guard let readOnlyLen: UInt32 = slice.decode(length: 3) else { throw Error.invalidReadOnlyLength }
guard let readWriteLen: UInt32 = slice.decode(length: 3) else { throw Error.invalidReadWriteLength }
guard let heapPages: UInt16 = slice.decode(length: 2) else { throw Error.invalidHeapPages }
guard let stackSize: UInt32 = slice.decode(length: 3) else { throw Error.invalidStackSize }

let readOnlyEndIdx = slice.startIndex + Int(readOnlyLen)
guard readOnlyEndIdx <= slice.endIndex else { throw Error.invalidDataLength }
let readOnlyData = blob[slice.startIndex ..< readOnlyEndIdx]

let readWriteEndIdx = readOnlyEndIdx + Int(readWriteLen)
guard readWriteEndIdx <= slice.endIndex else { throw Error.invalidDataLength }
let readWriteData = blob[readOnlyEndIdx ..< readWriteEndIdx]

slice = slice.dropFirst(Int(readOnlyLen + readWriteLen))
guard let codeLength: UInt32 = slice.decode(length: 4), slice.startIndex + Int(codeLength) <= slice.endIndex else {
throw Error.invalidCodeLength
}

let config = DefaultPvmConfig()

let Q = StandardProgram.alignToSegmentSize
let ZP = config.pvmProgramInitPageSize
let ZQ = config.pvmProgramInitSegmentSize
let ZI = config.pvmProgramInitInputDataSize
let readOnlyAlignedSize = Int(Q(readOnlyLen, config))
let heapEmptyPagesSize = Int(heapPages) * ZP
let readWriteAlignedSize = Int(Q(readWriteLen + UInt32(heapEmptyPagesSize), config))
let stackAlignedSize = Int(Q(stackSize, config))

let totalSize = 5 * ZQ + readOnlyAlignedSize + readWriteAlignedSize + stackAlignedSize + ZI
guard totalSize <= Int32.max else {
throw Error.invalidTotalMemorySize
}

code = try ProgramCode(blob[relative: slice.startIndex ..< slice.startIndex + Int(codeLength)])

initialRegisters = Registers(config: config, argumentData: argumentData)

initialMemory = Memory(
readOnlyData: readOnlyData,
readWriteData: readWriteData,
argumentData: argumentData ?? Data(),
heapEmptyPagesSize: UInt32(heapEmptyPagesSize),
stackSize: UInt32(stackSize)
)
}

static func alignToPageSize(size: UInt32, config: PvmConfig) -> UInt32 {
let pageSize = UInt32(config.pvmProgramInitPageSize)
return (size + pageSize - 1) / pageSize * pageSize
}

static func alignToSegmentSize(size: UInt32, config: PvmConfig) -> UInt32 {
let segmentSize = UInt32(config.pvmProgramInitSegmentSize)
return (size + segmentSize - 1) / segmentSize * segmentSize
}
}
10 changes: 10 additions & 0 deletions PolkaVM/Sources/PolkaVM/VMState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ public class VMState {
self.memory = memory
}

/// Initialize from a standard program blob
public init(standardProgramBlob blob: Data, pc: UInt32, gas: UInt64, argumentData: Data?) throws {
let program = try StandardProgram(blob: blob, argumentData: argumentData)
self.program = program.code
registers = program.initialRegisters
memory = program.initialMemory
self.pc = pc
self.gas = Int64(gas)
}

public func getRegisters() -> Registers {
registers
}
Expand Down

0 comments on commit 77f5bb2

Please sign in to comment.