Skip to content

Generalize WASI to other Wasm engines #79

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

Merged
merged 8 commits into from
Apr 17, 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
27 changes: 21 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import class Foundation.ProcessInfo

let package = Package(
name: "WasmKit",
platforms: [.macOS(.v12)],
platforms: [.macOS(.v12), .iOS(.v14)],
products: [
.library(
name: "WasmKit",
targets: ["WasmKit"]
),
.library(
name: "WasmKitWASI",
targets: ["WasmKitWASI"]
),
.library(
name: "WASI",
targets: ["WASI"]
Expand All @@ -31,23 +35,34 @@ let package = Package(
name: "CLI",
dependencies: [
"WasmKit",
"WASI",
"WasmKitWASI",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
]
),
.target(
name: "WasmTypes"
),
.target(
name: "WASI",
dependencies: ["WasmTypes", "SystemExtras"]
),
.target(
name: "WasmKit",
dependencies: [
"WasmParser",
"SystemExtras",
"WasmTypes",
.product(name: "SystemPackage", package: "swift-system"),
]
),
.target(name: "WasmParser"),
.target(
name: "WASI",
dependencies: ["WasmKit", "SystemExtras"]
name: "WasmParser",
dependencies: ["WasmTypes"]
),
.target(
name: "WasmKitWASI",
dependencies: ["WasmKit", "WASI"]
),
.target(
name: "SystemExtras",
Expand All @@ -70,7 +85,7 @@ let package = Package(
.plugin(name: "GenerateOverlayForTesting", capability: .buildTool(), dependencies: ["WITTool"]),
.testTarget(
name: "WITOverlayGeneratorTests",
dependencies: ["WITOverlayGenerator", "WasmKit", "WASI"],
dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"],
exclude: ["Fixtures", "Compiled", "Generated"],
plugins: [.plugin(name: "GenerateOverlayForTesting")]
),
Expand Down
2 changes: 1 addition & 1 deletion Sources/CLI/Run/Run.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ArgumentParser
import Foundation
import SystemPackage
import WASI
import WasmKitWASI
import WasmKit

struct Run: ParsableCommand {
Expand Down
2 changes: 1 addition & 1 deletion Sources/WASI/GuestMemorySupport.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import WasmKit
import WasmTypes

extension GuestPointee {
static func readFromGuest(_ pointer: inout UnsafeGuestRawPointer) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions Sources/WASI/Platform/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ extension FdWASIFile {
let handle = FileHandle(fileDescriptor: fd.rawValue)
var nread: UInt32 = 0
for iovec in buffer {
try iovec.buffer.withHostPointer { rawBufferStart in
var bufferStart = rawBufferStart.bindMemory(
try iovec.buffer.withHostPointer(count: Int(iovec.length)) { rawBufferStart in
var bufferStart = rawBufferStart.baseAddress!.bindMemory(
to: UInt8.self, capacity: Int(iovec.length)
)
let bufferEnd = bufferStart + Int(iovec.length)
Expand Down
78 changes: 36 additions & 42 deletions Sources/WASI/WASI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import Foundation
import SwiftShims // For swift_stdlib_random
import SystemExtras
import SystemPackage
import WasmKit
import WasmParser
import WasmTypes

protocol WASI {
/// Reads command-line argument data.
Expand Down Expand Up @@ -379,8 +378,8 @@ enum WASIAbi {
let length: WASIAbi.Size

func withHostBufferPointer<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
try buffer.withHostPointer { hostPointer in
try body(UnsafeRawBufferPointer(start: hostPointer, count: Int(length)))
try buffer.withHostPointer(count: Int(length)) { hostPointer in
try body(UnsafeRawBufferPointer(hostPointer))
}
}

Expand Down Expand Up @@ -782,16 +781,29 @@ enum WASIAbi {
}
}

struct WASIError: Error, CustomStringConvertible {
let description: String
public struct WASIError: Error, CustomStringConvertible {
public let description: String

public init(description: String) {
self.description = description
}
}

public struct WASIExitCode: Error {
public let code: UInt32
}

public struct WASIHostFunction {
public let type: FunctionType
public let implementation: (GuestMemory, [Value]) throws -> [Value]
}

struct WASIExitCode: Error {
let code: UInt32
public struct WASIHostModule {
public let functions: [String: WASIHostFunction]
}

extension WASI {
var _hostModules: [String: HostModule] {
var _hostModules: [String: WASIHostModule] {
let unimplementedFunctionTypes: [String: FunctionType] = [
"poll_oneoff": .init(parameters: [.i32, .i32, .i32, .i32], results: [.i32]),
"proc_raise": .init(parameters: [.i32], results: [.i32]),
Expand All @@ -803,23 +815,19 @@ extension WASI {

]

var preview1: [String: HostFunction] = unimplementedFunctionTypes.reduce(into: [:]) { functions, entry in
var preview1: [String: WASIHostFunction] = unimplementedFunctionTypes.reduce(into: [:]) { functions, entry in
let (name, type) = entry
functions[name] = HostFunction(type: type) { _, _ in
functions[name] = WASIHostFunction(type: type) { _, _ in
print("\"\(name)\" not implemented yet")
return [.i32(WASIAbi.Errno.ENOSYS.rawValue)]
}
}

func withMemoryBuffer<T>(
Copy link
Contributor Author

@kabiroberai kabiroberai Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should note that this function is basically now a no-op since this work is offloaded to WasmKitWASI. I wanted to minimize the diff to begin with for ease of review but it might make sense to remove this now. @kateinoigakukun would you prefer that I do it in this PR or in a follow-up?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for minimizing diff! I prefer a following up way

caller: Caller,
caller: GuestMemory,
body: (GuestMemory) throws -> T
) throws -> T {
guard case let .memory(memoryAddr) = caller.instance.exports["memory"] else {
throw WASIError(description: "Missing required \"memory\" export")
}
let memory = GuestMemory(store: caller.store, address: memoryAddr)
return try body(memory)
return try body(caller)
}

func readString(pointer: UInt32, length: UInt32, buffer: GuestMemory) throws -> String {
Expand All @@ -839,8 +847,8 @@ extension WASI {
}
}

func wasiFunction(type: FunctionType, implementation: @escaping (Caller, [Value]) throws -> [Value]) -> HostFunction {
return HostFunction(type: type) { caller, arguments in
func wasiFunction(type: FunctionType, implementation: @escaping (GuestMemory, [Value]) throws -> [Value]) -> WASIHostFunction {
return WASIHostFunction(type: type) { caller, arguments in
do {
return try implementation(caller, arguments)
} catch let errno as WASIAbi.Errno {
Expand Down Expand Up @@ -1339,7 +1347,7 @@ extension WASI {
}

return [
"wasi_snapshot_preview1": HostModule(globals: [:], functions: preview1)
"wasi_snapshot_preview1": WASIHostModule(functions: preview1)
]
}
}
Expand Down Expand Up @@ -1380,16 +1388,7 @@ public class WASIBridgeToHost: WASI {
self.fdTable = fdTable
}

public var hostModules: [String: HostModule] { _hostModules }

public func start(_ instance: ModuleInstance, runtime: Runtime) throws -> UInt32 {
do {
_ = try runtime.invoke(instance, function: "_start")
} catch let code as WASIExitCode {
return code.code
}
return 0
}
public var wasiHostModules: [String: WASIHostModule] { _hostModules }

func args_get(
argv: UnsafeGuestPointer<UnsafeGuestPointer<UInt8>>,
Expand All @@ -1402,8 +1401,7 @@ public class WASIBridgeToHost: WASI {
offsets += 1
let count = arg.utf8CString.withUnsafeBytes { bytes in
let count = UInt32(bytes.count)
buffer.raw.withHostPointer { hostRawPointer in
let hostDestBuffer = UnsafeMutableRawBufferPointer(start: hostRawPointer, count: bytes.count)
_ = buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
bytes.copyBytes(to: hostDestBuffer)
}
return count
Expand All @@ -1428,8 +1426,7 @@ public class WASIBridgeToHost: WASI {
offsets += 1
let count = "\(key)=\(value)".utf8CString.withUnsafeBytes { bytes in
let count = UInt32(bytes.count)
buffer.raw.withHostPointer { hostRawPointer in
let hostDestBuffer = UnsafeMutableRawBufferPointer(start: hostRawPointer, count: bytes.count)
_ = buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
bytes.copyBytes(to: hostDestBuffer)
}
return count
Expand Down Expand Up @@ -1630,8 +1627,7 @@ public class WASIBridgeToHost: WASI {
guard bytes.count <= maxPathLength else {
throw WASIAbi.Errno.ENAMETOOLONG
}
path.withHostPointer {
let buffer = UnsafeMutableRawBufferPointer(start: $0, count: Int(maxPathLength))
_ = path.withHostPointer(count: Int(maxPathLength)) { buffer in
bytes.copyBytes(to: buffer)
}
}
Expand Down Expand Up @@ -1692,10 +1688,7 @@ public class WASIBridgeToHost: WASI {
let copyingBytes = min(entry.dirNameLen, totalBufferSize - bufferUsed)
let rangeStart = buffer.baseAddress.raw.advanced(by: bufferUsed)
name.withUTF8 { bytes in
rangeStart.withHostPointer { rangeStart in
let hostBuffer = UnsafeMutableRawBufferPointer(
start: rangeStart, count: Int(copyingBytes)
)
_ = rangeStart.withHostPointer(count: Int(copyingBytes)) { hostBuffer in
bytes.copyBytes(to: hostBuffer, count: Int(copyingBytes))
}
}
Expand Down Expand Up @@ -1865,8 +1858,9 @@ public class WASIBridgeToHost: WASI {
}

func random_get(buffer: UnsafeGuestPointer<UInt8>, length: WASIAbi.Size) {
buffer.withHostPointer {
swift_stdlib_random(UnsafeMutableRawPointer($0), Int(length))
guard length > 0 else { return }
buffer.withHostPointer(count: Int(length)) {
swift_stdlib_random($0.baseAddress!, Int(length))
}
}
}
26 changes: 24 additions & 2 deletions Sources/WasmKit/Component/CanonicalCall.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@_exported import WasmTypes

struct CanonicalABIError: Error, CustomStringConvertible {
let description: String
}
Expand All @@ -15,8 +17,8 @@ public struct CanonicalCallContext {
/// The executing `Runtime` instance
public let runtime: Runtime
/// A reference to the guest memory.
public var guestMemory: GuestMemory {
GuestMemory(store: runtime.store, address: options.memory)
public var guestMemory: WasmKitGuestMemory {
WasmKitGuestMemory(store: runtime.store, address: options.memory)
}

public init(options: CanonicalOptions, moduleInstance: ModuleInstance, runtime: Runtime) {
Expand Down Expand Up @@ -47,3 +49,23 @@ public struct CanonicalCallContext {
return UnsafeGuestRawPointer(memorySpace: guestMemory, offset: new)
}
}

public struct WasmKitGuestMemory: GuestMemory {
private let store: Store
private let address: MemoryAddress

/// Creates a new memory instance from the given store and address
public init(store: Store, address: MemoryAddress) {
self.store = store
self.address = address
}

/// Executes the given closure with a mutable buffer pointer to the host memory region mapped as guest memory.
public func withUnsafeMutableBufferPointer<T>(offset: UInt, count: Int, _ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
try store.withMemory(at: address) { memory in
try memory.data.withUnsafeMutableBufferPointer { buffer in
try body(UnsafeMutableRawBufferPointer(start: buffer.baseAddress! + Int(offset), count: count))
}
}
}
}
4 changes: 2 additions & 2 deletions Sources/WasmKit/Component/CanonicalLowering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public enum CanonicalLowering {
let newBuffer = try context.realloc(
old: 0, oldSize: 0, oldAlign: 1, newSize: UInt32(bytes.count)
)
newBuffer.withHostPointer { newBuffer in
UnsafeMutableRawBufferPointer(start: newBuffer, count: bytes.count).copyBytes(from: bytes)
newBuffer.withHostPointer(count: bytes.count) { newBuffer in
newBuffer.copyBytes(from: bytes)
}
return (newBuffer.offset, UInt32(bytes.count))
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/WasmKit/Docs.docc/Docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ This example shows how to run WASI application on WasmKit.

```swift
import WasmKit
import WASI
import WasmKitWASI
import Foundation

let bytes = try Data(contentsOf: URL(filePath: "./main.wasm"))
Expand Down
9 changes: 0 additions & 9 deletions Sources/WasmKit/Execution/Runtime/Store.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import WasmParser

/// > Note:
/// <https://webassembly.github.io/spec/core/exec/runtime.html#addresses>
public typealias FunctionAddress = Int
public typealias TableAddress = Int
public typealias MemoryAddress = Int
public typealias GlobalAddress = Int
public typealias ElementAddress = Int
public typealias DataAddress = Int
public typealias ExternAddress = Int
internal typealias ModuleAddress = Int

/// A collection of globals and functions that are exported from a host module.
Expand Down
Loading