Skip to content

Commit

Permalink
Add convenience methods for reading from and writing to the heap (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
atdrendel authored Feb 16, 2021
1 parent 91e4046 commit dd5edda
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 28 deletions.
52 changes: 51 additions & 1 deletion Sources/WasmInterpreter/WasmInterpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,56 @@ public final class WasmInterpreter {
}
return string
}

public func valueFromHeap<T: WasmTypeProtocol>(offset: Int) throws -> T {
let values = try valuesFromHeap(offset: offset, length: 1) as [T]
guard let value = values.first
else { throw WasmInterpreterError.couldNotLoadMemory }
return value
}

public func valuesFromHeap<T: WasmTypeProtocol>(offset: Int, length: Int) throws -> [T] {
let heap = try self.heap()
guard offset + length < heap.size else { throw WasmInterpreterError.invalidMemoryAccess }

return heap.pointer
.advanced(by: offset)
.withMemoryRebound(
to: T.self,
capacity: length
) { (pointer: UnsafeMutablePointer<T>) -> [T] in
return (0..<length).map { pointer.advanced(by: $0).pointee }
}
}

public func writeToHeap(data: Data, offset: Int) throws {
let heap = try self.heap()
guard offset + data.count < heap.size else { throw WasmInterpreterError.invalidMemoryAccess }

try data.withUnsafeBytes { (rawPointer: UnsafeRawBufferPointer) -> Void in
guard let pointer = rawPointer.bindMemory(to: UInt8.self).baseAddress
else { throw WasmInterpreterError.couldNotBindMemory }
heap.pointer
.advanced(by: offset)
.initialize(from: pointer, count: data.count)
}
}

public func writeToHeap(string: String, offset: Int) throws {
try writeToHeap(data: Data(string.utf8), offset: offset)
}

public func writeToHeap<T: WasmTypeProtocol>(value: T, offset: Int) throws {
try writeToHeap(values: [value], offset: offset)
}

public func writeToHeap<T: WasmTypeProtocol>(values: Array<T>, offset: Int) throws {
var values = values
try writeToHeap(
data: Data(bytes: &values, count: values.count * MemoryLayout<T>.size),
offset: offset
)
}
}

typealias ImportedFunctionSignature = (UnsafeMutablePointer<UInt64>?, UnsafeMutableRawPointer?) -> UnsafeRawPointer?
Expand Down Expand Up @@ -168,7 +218,7 @@ extension WasmInterpreter {
}
}

func _call<T>(_ function: IM3Function, args: [String]) throws -> T {
func _call<T: WasmTypeProtocol>(_ function: IM3Function, args: [String]) throws -> T {
try args.withCStrings { (cStrings) throws -> T in
var mutableCStrings = cStrings
let size = UnsafeMutablePointer<Int>.allocate(capacity: 1)
Expand Down
2 changes: 2 additions & 0 deletions Sources/WasmInterpreter/WasmInterpreterError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public enum WasmInterpreterError: Error {
case couldNotGenerateFunctionContext
case incorrectArguments
case missingHeap
case couldNotLoadMemory
case couldNotBindMemory
case unsupportedWasmType(String)
case wasm3Error(String)
}
11 changes: 1 addition & 10 deletions Tests/WasmInterpreterTests/Resources/memory.wat
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
(module
(import "native" "write" (func $write (param i32 i32)))
(memory 1) ;; at least 64 KB
(func (export "write_utf8")
i32.const 0 ;; offset of 0
i32.const 13 ;; length of 13
(call $write))
(func (export "modify_utf8") (param $offset i32)
(i32.store
(local.get $offset)
(i32.add (i32.load (local.get $offset)) (i32.const 1)))))
(memory 1)) ;; at least 64 KB
27 changes: 12 additions & 15 deletions Tests/WasmInterpreterTests/Wasm Modules/MemoryModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,28 @@ public struct MemoryModule {

init() throws {
_vm = try WasmInterpreter(module: MemoryModule.wasm)
try _vm.addImportHandler(named: "write", namespace: "native", block: self.write)
}

func callWrite() throws -> String {
try _vm.call("write_utf8") as ()
return try _vm.stringFromHeap(offset: 0, length: 13) // offset and length hardcoded in memory.wasm
func string(at offset: Int, length: Int) throws -> String {
try _vm.stringFromHeap(offset: offset, length: length)
}

func callModify() throws -> String {
try _vm.call("modify_utf8", Int32(6))
return try _vm.stringFromHeap(offset: 0, length: 13) // offset and length hardcoded in memory.wasm
func integers(at offset: Int, length: Int) throws -> [Int] {
(try _vm.valuesFromHeap(offset: offset, length: length) as [Int32])
.map(Int.init)
}

private var write: (Int32, Int32, UnsafeMutableRawPointer?) -> Void {
return { (offset: Int32, length: Int32, heap: UnsafeMutableRawPointer?) -> Void in
heap?
.advanced(by: Int(offset))
.initializeMemory(as: CChar.self, repeating: CChar(bitPattern: 0x0041), count: Int(length))
}
func write(_ string: String, to offset: Int) throws {
try _vm.writeToHeap(string: string, offset: offset)
}

func write(_ integers: [Int], to offset: Int) throws {
try _vm.writeToHeap(values: integers.map(Int32.init), offset: offset)
}

// `wat2wasm -o >(base64) Tests/WasmInterpreterTests/Resources/memory.wat | pbcopy`
private static var wasm: [UInt8] {
let base64 =
"AGFzbQEAAAABDQNgAn9/AGAAAGABfwACEAEGbmF0aXZlBXdyaXRlAAADAwIBAgUDAQABBxwCCndyaXRlX3V0ZjgAAQttb2RpZnlfdXRmOAACChoCCABBAEENEAALDwAgACAAKAIAQQFqNgIACw=="
let base64 = "AGFzbQEAAAAFAwEAAQ=="
return Array<UInt8>(Data(base64Encoded: base64)!)
}
}
20 changes: 18 additions & 2 deletions Tests/WasmInterpreterTests/WasmInterpreterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,24 @@ final class WasmInterpreterTests: XCTestCase {

func testAccessingAndModifyingHeapMemory() throws {
let mod = try MemoryModule()
try XCTAssertEqual("AAAAAAAAAAAAA", mod.callWrite())
try XCTAssertEqual("AAAAAABAAAAAA", mod.callModify())

XCTAssertEqual("\u{0}\u{0}\u{0}\u{0}", try mod.string(at: 0, length: 4))

let hello = "Hello, everyone! 👋"
try mod.write(hello, to: 0)
XCTAssertEqual(hello, try mod.string(at: 0, length: hello.utf8.count))

let numbers = [1, 2, 3, 4]
try mod.write(numbers, to: 0)
XCTAssertEqual(numbers, try mod.integers(at: 0, length: 4))

let fortyTwo = [42]
try mod.write(fortyTwo, to: 1)
XCTAssertEqual(42, try mod.integers(at: 1, length: 1).first)

XCTAssertEqual(10753, try mod.integers(at: 0, length: 1).first)

XCTAssertEqual("👋", try mod.string(at: 17, length: "👋".utf8.count))
}

static var allTests = [
Expand Down

0 comments on commit dd5edda

Please sign in to comment.