Skip to content

Commit 16ebf17

Browse files
authored
Add Caller.captureBacktrace() (#328)
This allows a Wasm host function to capture a back trace, which can be useful for debugging.
1 parent 281c4c9 commit 16ebf17

7 files changed

Lines changed: 32 additions & 15 deletions

File tree

Sources/WasmKit/Execution/Errors.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ import WasmTypes
33
import struct WasmParser.Import
44

55
/// The backtrace of the trap.
6-
struct Backtrace: CustomStringConvertible, Sendable {
6+
public struct Backtrace: CustomStringConvertible, Sendable {
77
/// A symbol in the backtrace.
8-
struct Symbol: @unchecked Sendable {
8+
public struct Symbol: @unchecked Sendable {
99
/// The name of the symbol.
10-
let name: String?
10+
public let name: String?
1111
let address: Pc
1212
}
1313

1414
/// The symbols in the backtrace.
15-
let symbols: [Symbol]
15+
public let symbols: [Symbol]
1616

1717
/// Textual description of the backtrace.
18-
var description: String {
18+
public var description: String {
1919
symbols.enumerated().map { (index, symbol) in
2020
let name = symbol.name ?? "unknown"
2121
return " \(index): (\(symbol.address)) \(name)"

Sources/WasmKit/Execution/Execution.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,8 @@ extension Execution {
711711
let instance = self.currentInstance(sp: sp)
712712
let caller = Caller(
713713
instanceHandle: instance,
714-
store: store.value
714+
store: store.value,
715+
sp: sp
715716
)
716717
let results = try function.implementation(caller, Array(parameters))
717718
guard resolvedType.results.count == results.count else {

Sources/WasmKit/Execution/Function.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import struct WasmTypes.FunctionType
3434
/// }
3535
/// ```
3636
public struct Function: Equatable {
37+
/// The type of a host function implementation closure.
38+
public typealias Implementation = (borrowing Caller, [Value]) throws -> [Value]
39+
3740
internal let handle: InternalFunction
3841
let store: Store
3942

@@ -52,7 +55,7 @@ public struct Function: Equatable {
5255
public init(
5356
store: Store,
5457
parameters: [ValueType], results: [ValueType] = [],
55-
body: @escaping (Caller, [Value]) throws -> [Value]
58+
body: @escaping Implementation
5659
) {
5760
self.init(store: store, type: FunctionType(parameters: parameters, results: results), body: body)
5861
}
@@ -66,7 +69,7 @@ public struct Function: Equatable {
6669
public init(
6770
store: Store,
6871
type: FunctionType,
69-
body: @escaping (Caller, [Value]) throws -> [Value]
72+
body: @escaping Implementation
7073
) {
7174
self.init(handle: store.allocator.allocate(type: type, implementation: body, engine: store.engine), store: store)
7275
}

Sources/WasmKit/Execution/Runtime.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,13 @@ public final class Runtime {
181181
@available(*, deprecated, renamed: "Function", message: "`HostFunction` is now unified with `Function`")
182182
public struct HostFunction {
183183
@available(*, deprecated, renamed: "Function.init(store:type:implementation:)", message: "Use `Engine`-based API instead")
184-
public init(type: FunctionType, implementation: @escaping (Caller, [Value]) throws -> [Value]) {
184+
public init(type: FunctionType, implementation: @escaping Function.Implementation) {
185185
self.type = type
186186
self.implementation = implementation
187187
}
188188

189189
public let type: FunctionType
190-
public let implementation: (Caller, [Value]) throws -> [Value]
190+
public let implementation: Function.Implementation
191191
}
192192

193193
/// A collection of globals and functions that are exported from a host module.

Sources/WasmKit/Execution/Store.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ extension Store: Equatable {
3636
}
3737

3838
/// A caller context passed to host functions
39-
public struct Caller {
39+
/// Not copyable to avoid storing stale stack pointer.
40+
public struct Caller: ~Copyable {
4041
private let instanceHandle: InternalInstance?
42+
/// The stack pointer at the point of the host function call.
43+
private let sp: Sp?
4144
/// The instance that called the host function.
4245
/// - Note: This property is `nil` if a `Function` backed by a host function is called directly.
4346
public var instance: Instance? {
@@ -55,15 +58,25 @@ public struct Caller {
5558
@available(*, unavailable, message: "Use `engine` instead")
5659
public var runtime: Runtime { fatalError() }
5760

58-
init(instanceHandle: InternalInstance?, store: Store) {
61+
init(instanceHandle: InternalInstance?, store: Store, sp: Sp? = nil) {
5962
self.instanceHandle = instanceHandle
6063
self.store = store
64+
self.sp = sp
65+
}
66+
67+
/// Captures the current WebAssembly call stack backtrace.
68+
///
69+
/// Returns `nil` if the caller context does not have stack pointer information
70+
/// (e.g., when a host function is called directly rather than from WebAssembly).
71+
public func captureBacktrace() -> Backtrace? {
72+
guard let sp else { return nil }
73+
return Execution.captureBacktrace(sp: sp, store: store)
6174
}
6275
}
6376

6477
struct HostFunctionEntity {
6578
let type: InternedFuncType
66-
let implementation: (Caller, [Value]) throws -> [Value]
79+
let implementation: Function.Implementation
6780
}
6881

6982
extension Store {

Sources/WasmKit/Execution/StoreAllocator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ extension StoreAllocator {
510510

511511
internal func allocate(
512512
type: FunctionType,
513-
implementation: @escaping (Caller, [Value]) throws -> [Value],
513+
implementation: @escaping Function.Implementation,
514514
engine: Engine
515515
) -> InternalFunction {
516516
let pointer = hostFunctions.allocate(

Sources/WasmKitWASI/WASIBridgeToHost+WasmKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ extension WASIBridgeToHost {
3333
}
3434
}
3535

36-
private func makeHostFunction(_ function: WASIHostFunction) -> ((Caller, [Value]) throws -> [Value]) {
36+
private func makeHostFunction(_ function: WASIHostFunction) -> Function.Implementation {
3737
{ caller, values -> [Value] in
3838
guard case .memory(let memory) = caller.instance?.export("memory") else {
3939
throw WASIError(description: "Missing required \"memory\" export")

0 commit comments

Comments
 (0)