Skip to content

Commit 3e510c7

Browse files
Add backtrace info to Trap
1 parent 8ef2e77 commit 3e510c7

File tree

7 files changed

+151
-109
lines changed

7 files changed

+151
-109
lines changed

Sources/WasmKit/Execution/Errors.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@ import WasmTypes
22

33
import struct WasmParser.Import
44

5+
/// The backtrace of the trap.
6+
struct Backtrace: CustomStringConvertible {
7+
/// A symbol in the backtrace.
8+
struct Symbol {
9+
/// The function that the symbol represents.
10+
let function: Function
11+
12+
/// The name of the symbol.
13+
let name: String?
14+
}
15+
16+
/// The symbols in the backtrace.
17+
let symbols: [Symbol?]
18+
19+
/// Textual description of the backtrace.
20+
var description: String {
21+
symbols.enumerated().map { (index, symbol) in
22+
let name = symbol?.name ?? "unknown"
23+
return " \(index): \(name)"
24+
}.joined(separator: "\n")
25+
}
26+
}
27+
528
/// An error that occurs during execution of a WebAssembly module.
629
public struct Trap: Error, CustomStringConvertible {
730
/// The reason for the trap.
831
var reason: TrapReason
932

10-
/// The backtrace of the trap.
11-
struct Backtrace {
12-
}
13-
1433
/// The backtrace of the trap.
1534
private(set) var backtrace: Backtrace?
1635

Sources/WasmKit/Execution/Execution.swift

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct Execution {
1212
/// The error trap thrown during execution.
1313
/// This property must not be assigned to be non-nil more than once.
1414
/// - Note: If the trap is set, it must be released manually.
15-
private var trap: UnsafeRawPointer? = nil
15+
private var trap: (error: UnsafeRawPointer, sp: Sp)? = nil
1616

1717
/// Executes the given closure with a new execution state associated with
1818
/// the given ``Store`` instance.
@@ -26,13 +26,6 @@ struct Execution {
2626
valueStack.deallocate()
2727
}
2828
var context = Execution(store: store, stackEnd: valueStack.advanced(by: limit))
29-
defer {
30-
if let trap = context.trap {
31-
// Manually release the error object because the trap is caught in C and
32-
// held as a raw pointer.
33-
wasmkit_swift_errorRelease(trap)
34-
}
35-
}
3629
return try body(&context, valueStack)
3730
}
3831

@@ -46,6 +39,7 @@ struct Execution {
4639
struct FrameIterator: IteratorProtocol {
4740
struct Element {
4841
let pc: Pc
42+
let function: EntityHandle<WasmFunctionEntity>?
4943
}
5044

5145
/// The stack pointer currently traversed.
@@ -56,29 +50,39 @@ struct Execution {
5650
}
5751

5852
mutating func next() -> Element? {
59-
guard let sp = self.sp else {
53+
guard let sp = self.sp, let pc = sp.returnPC else {
6054
// Reached the root frame, whose stack pointer is nil.
6155
return nil
6256
}
63-
let pc = sp.returnPC
6457
self.sp = sp.previousSP
65-
return Element(pc: pc)
58+
return Element(pc: pc, function: sp.currentFunction)
6659
}
6760
}
6861

69-
/// Returns an iterator for the call frames in the VM stack.
70-
///
71-
/// - Parameter sp: The stack pointer of the current frame.
72-
/// - Returns: An iterator for the call frames in the VM stack.
73-
static func frames(sp: Sp) -> FrameIterator {
74-
return FrameIterator(sp: sp)
62+
static func captureBacktrace(sp: Sp, store: Store) -> Backtrace {
63+
var frames = FrameIterator(sp: sp)
64+
var symbols: [Backtrace.Symbol?] = []
65+
while let frame = frames.next() {
66+
guard let function = frame.function else {
67+
symbols.append(nil)
68+
continue
69+
}
70+
let symbolName = store.nameRegistry.symbolicate(.wasm(function))
71+
symbols.append(
72+
Backtrace.Symbol(
73+
function: Function(handle: .wasm(function), store: store),
74+
name: symbolName
75+
)
76+
)
77+
}
78+
return Backtrace(symbols: symbols)
7579
}
7680

7781
/// Pushes a new call frame to the VM stack.
7882
@inline(__always)
7983
mutating func pushFrame(
8084
iseq: InstructionSequence,
81-
instance: InternalInstance,
85+
function: EntityHandle<WasmFunctionEntity>,
8286
numberOfNonParameterLocals: Int,
8387
sp: Sp, returnPC: Pc,
8488
spAddend: VReg
@@ -97,7 +101,7 @@ struct Execution {
97101
}
98102
newSp.previousSP = sp
99103
newSp.returnPC = returnPC
100-
newSp.currentInstance = instance
104+
newSp.currentFunction = function
101105
return newSp
102106
}
103107

@@ -106,7 +110,7 @@ struct Execution {
106110
mutating func popFrame(sp: inout Sp, pc: inout Pc, md: inout Md, ms: inout Ms) {
107111
let oldSp = sp
108112
sp = oldSp.previousSP.unsafelyUnwrapped
109-
pc = oldSp.returnPC
113+
pc = oldSp.returnPC.unsafelyUnwrapped
110114
let toInstance = oldSp.currentInstance.unsafelyUnwrapped
111115
let fromInstance = sp.currentInstance
112116
CurrentMemory.mayUpdateCurrentInstance(instance: toInstance, from: fromInstance, md: &md, ms: &ms)
@@ -222,15 +226,15 @@ extension Sp {
222226

223227
// MARK: - Special slots
224228

225-
/// The current instance of the execution context.
226-
fileprivate var currentInstance: InternalInstance? {
227-
get { return InternalInstance(bitPattern: UInt(self[-3].i64)) }
229+
/// The current executing function.
230+
fileprivate var currentFunction: EntityHandle<WasmFunctionEntity>? {
231+
get { return EntityHandle<WasmFunctionEntity>(bitPattern: UInt(self[-3].i64)) }
228232
nonmutating set { self[-3] = UInt64(UInt(bitPattern: newValue?.bitPattern ?? 0)) }
229233
}
230234

231235
/// The return program counter of the current frame.
232-
fileprivate var returnPC: Pc {
233-
get { return Pc(bitPattern: UInt(self[-2]))! }
236+
fileprivate var returnPC: Pc? {
237+
get { return Pc(bitPattern: UInt(self[-2])) }
234238
nonmutating set { self[-2] = UInt64(UInt(bitPattern: newValue)) }
235239
}
236240

@@ -239,6 +243,10 @@ extension Sp {
239243
get { return Sp(bitPattern: UInt(self[-1])) }
240244
nonmutating set { self[-1] = UInt64(UInt(bitPattern: newValue)) }
241245
}
246+
247+
fileprivate var currentInstance: InternalInstance? {
248+
currentFunction?.instance
249+
}
242250
}
243251

244252
extension Pc {
@@ -278,7 +286,9 @@ func executeWasm(
278286
// Advance the stack pointer to be able to reference negative indices
279287
// for saving slots.
280288
let sp = sp.advanced(by: FrameHeaderLayout.numberOfSavingSlots)
281-
sp.previousSP = nil // Mark root stack pointer as nil.
289+
// Mark root stack pointer and current function as nil.
290+
sp.previousSP = nil
291+
sp.currentFunction = nil
282292
for (index, argument) in arguments.enumerated() {
283293
sp[VReg(index)] = UntypedValue(argument)
284294
}
@@ -386,8 +396,17 @@ extension Execution {
386396
var pc = pc
387397
let handler = pc.read(wasmkit_tc_exec.self)
388398
wasmkit_tc_start(handler, sp, pc, md, ms, &self)
389-
if let error = self.trap {
390-
throw unsafeBitCast(error, to: Error.self)
399+
if let (rawError, trappingSp) = self.trap {
400+
let error = unsafeBitCast(rawError, to: Error.self)
401+
// Manually release the error object because the trap is caught in C and
402+
// held as a raw pointer.
403+
wasmkit_swift_errorRelease(rawError)
404+
405+
guard let trap = error as? Trap else {
406+
throw error
407+
}
408+
// Attach backtrace if the thrown error is a trap
409+
throw trap.withBacktrace(Self.captureBacktrace(sp: trappingSp, store: store.value))
391410
}
392411
}
393412

@@ -463,11 +482,15 @@ extension Execution {
463482
defer { stats.dump() }
464483
#endif
465484
var opcode = pc.read(OpcodeID.self)
466-
while true {
467-
#if EngineStats
468-
stats.track(inst)
469-
#endif
470-
opcode = try doExecute(opcode, sp: &sp, pc: &pc, md: &md, ms: &ms)
485+
do {
486+
while true {
487+
#if EngineStats
488+
stats.track(inst)
489+
#endif
490+
opcode = try doExecute(opcode, sp: &sp, pc: &pc, md: &md, ms: &ms)
491+
}
492+
} catch let trap as Trap {
493+
throw trap.withBacktrace(Self.captureBacktrace(sp: sp, store: store.value))
471494
}
472495
}
473496

@@ -477,9 +500,9 @@ extension Execution {
477500
/// It's used only when direct threading is enabled.
478501
/// - Parameter trap: The error trap thrown during execution.
479502
@_silgen_name("wasmkit_execution_state_set_error")
480-
mutating func setError(_ trap: UnsafeRawPointer) {
503+
mutating func setError(_ rawError: UnsafeRawPointer, sp: Sp) {
481504
precondition(self.trap == nil)
482-
self.trap = trap
505+
self.trap = (rawError, sp)
483506
}
484507

485508
/// Returns the new program counter and stack pointer.
@@ -496,7 +519,7 @@ extension Execution {
496519

497520
let newSp = try pushFrame(
498521
iseq: iseq,
499-
instance: function.instance,
522+
function: function,
500523
numberOfNonParameterLocals: function.numberOfNonParameterLocals,
501524
sp: sp,
502525
returnPC: pc,

Sources/WasmKit/Execution/Function.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,13 @@ extension InternalFunction {
212212
func assumeCompiled() -> (
213213
InstructionSequence,
214214
locals: Int,
215-
instance: InternalInstance
215+
function: EntityHandle<WasmFunctionEntity>
216216
) {
217217
let entity = self.wasm
218218
guard case let .compiled(iseq) = entity.code else {
219219
preconditionFailure()
220220
}
221-
return (iseq, entity.numberOfNonParameterLocals, entity.instance)
221+
return (iseq, entity.numberOfNonParameterLocals, entity)
222222
}
223223
}
224224

Sources/WasmKit/Execution/Instructions/Control.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ extension Execution {
8080
let (iseq, locals, instance) = internalCallOperand.callee.assumeCompiled()
8181
sp = try pushFrame(
8282
iseq: iseq,
83-
instance: instance,
83+
function: instance,
8484
numberOfNonParameterLocals: locals,
8585
sp: sp, returnPC: pc,
8686
spAddend: internalCallOperand.spAddend

0 commit comments

Comments
 (0)