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

Support reentrant function call between host/guest #69

Merged
merged 1 commit into from
Nov 15, 2023
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 Sources/WasmKit/Execution/Runtime/ExecutionState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ extension ExecutionState {
switch try runtime.store.function(at: address) {
case let .host(function):
let parameters = try stack.popValues(count: function.type.parameters.count)
let caller = Caller(store: runtime.store, instance: stack.currentFrame.module)
let caller = Caller(runtime: runtime, instance: stack.currentFrame.module)
stack.push(values: try function.implementation(caller, parameters))

programCounter += 1
Expand Down
3 changes: 2 additions & 1 deletion Sources/WasmKit/Execution/Runtime/Function.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// A WebAssembly guest function or host function
public struct Function: Equatable {
internal let address: FunctionAddress

Expand All @@ -16,7 +17,7 @@ public struct Function: Equatable {

let parameters = try execution.stack.popValues(count: function.type.parameters.count)

let caller = Caller(store: runtime.store, instance: execution.stack.currentFrame.module)
let caller = Caller(runtime: runtime, instance: execution.stack.currentFrame.module)
let results = try function.implementation(caller, parameters)
try check(functionType: function.type, results: results)
execution.stack.push(values: results)
Expand Down
5 changes: 4 additions & 1 deletion Sources/WasmKit/Execution/Runtime/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ public final class Store {

/// A caller context passed to host functions
public struct Caller {
public let store: Store
public let runtime: Runtime
public let instance: ModuleInstance
public var store: Store {
runtime.store
}
}

/// A host-defined function which can be imported by a WebAssembly module instance.
Expand Down
60 changes: 60 additions & 0 deletions Tests/WasmKitTests/Execution/HostModuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,64 @@ final class HostModuleTests: XCTestCase {
// Ensure the allocated address is valid
_ = runtime.store.memory(at: memoryAddr)
}

func testReentrancy() throws {
let runtime = Runtime()
let voidSignature = FunctionType(parameters: [], results: [])
let module = Module(
types: [voidSignature],
functions: [
// [0] (import "env" "bar" func)
// [1] (import "env" "qux" func)
// [2] "foo"
GuestFunction(
type: 0, locals: [],
body: [
.control(.call(functionIndex: 0)),
.control(.call(functionIndex: 0)),
.control(.call(functionIndex: 0)),
]),
// [3] "bar"
GuestFunction(
type: 0, locals: [],
body: [
.control(.call(functionIndex: 1))
]),
],
imports: [
Import(module: "env", name: "bar", descriptor: .function(0)),
Import(module: "env", name: "qux", descriptor: .function(0)),
],
exports: [
Export(name: "foo", descriptor: .function(2)),
Export(name: "baz", descriptor: .function(3)),
]
)

var isExecutingFoo = false
var isQuxCalled = false
let hostModule = HostModule(
functions: [
"bar": HostFunction(type: voidSignature) { caller, _ in
// Ensure "invoke" executes instructions under the current call
XCTAssertFalse(isExecutingFoo, "bar should not be called recursively")
isExecutingFoo = true
defer { isExecutingFoo = false }
let foo = try XCTUnwrap(caller.instance.exportedFunction(name: "baz"))
_ = try foo.invoke([], runtime: caller.runtime)
return []
},
"qux": HostFunction(type: voidSignature) { _, _ in
XCTAssertTrue(isExecutingFoo)
isQuxCalled = true
return []
},
]
)
try runtime.store.register(hostModule, as: "env")
let instance = try runtime.instantiate(module: module)
// Check foo(wasm) -> bar(host) -> baz(wasm) -> qux(host)
_ = try runtime.invoke(instance, function: "foo")
XCTAssertTrue(isQuxCalled)
}
}