Skip to content

Commit ce6124c

Browse files
committed
Move mapping dictionaries to DebuggerInstructionMapping
1 parent da87018 commit ce6124c

File tree

5 files changed

+101
-89
lines changed

5 files changed

+101
-89
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
2+
/// Two-way mapping between Wasm and internal iseq bytecode instructions. The implementation of the mapping
3+
/// is private and is empty when `WasmDebuggingSupport` package trait is disabled.
4+
struct DebuggerInstructionMapping {
5+
#if WasmDebuggingSupport
6+
7+
/// Mapping from iseq Pc to instruction addresses in the original binary.
8+
/// Used for handling current call stack requests issued by a ``Debugger`` instance.
9+
private var iseqToWasm = [Pc: Int]()
10+
11+
/// Mapping from Wasm instruction addresses in the original binary to iseq instruction addresses.
12+
/// Used for handling breakpoint requests issued by a ``Debugger`` instance.
13+
private var wasmToIseq = [Int: Pc]()
14+
15+
/// Wasm addresses sorted in ascending order for binary search when of the next closest mapped
16+
/// instruction, when no key is found in `wasmToIseqMapping`.
17+
private var wasmMappings = [Int]()
18+
19+
mutating func add(wasm: Int, iseq: Pc) {
20+
// Don't override the existing mapping, only store a new pair if there's no mapping for a given key.
21+
if self.iseqToWasm[iseq] == nil {
22+
self.iseqToWasm[iseq] = wasm
23+
}
24+
if self.wasmToIseq[wasm] == nil {
25+
self.wasmToIseq[wasm] = iseq
26+
}
27+
self.wasmMappings.append(wasm)
28+
}
29+
30+
/// Computes an address of WasmKit's iseq bytecode instruction that matches a given Wasm instruction address.
31+
/// - Parameter address: the Wasm instruction to find a mapping for.
32+
/// - Returns: A tuple with an address of found iseq instruction and the original Wasm instruction or next
33+
/// closest match if no direct match was found.
34+
func findIseq(forWasmAddress address: Int) -> (iseq: Pc, wasm: Int)? {
35+
// Look in the main mapping
36+
if let iseq = self.wasmToIseq[address] {
37+
return (iseq, address)
38+
}
39+
40+
// If nothing found, find the closest Wasm address using binary search
41+
guard let nextAddress = self.wasmMappings.binarySearch(nextClosestTo: address),
42+
// Look in the main mapping again with the next closest address if binary search produced anything
43+
let iseq = self.wasmToIseq[nextAddress]
44+
else {
45+
return nil
46+
}
47+
48+
return (iseq, nextAddress)
49+
}
50+
51+
func findWasm(forIseqAddress pc: Pc) -> Int? {
52+
self.iseqToWasm[pc]
53+
}
54+
#endif
55+
}
56+
57+
58+
#if WasmDebuggingSupport
59+
extension [Int] {
60+
/// Uses binary search to find an element in `self` that's next closest to a given value.
61+
/// - Parameter value: the array element to search for or to use as a baseline when searching.
62+
/// - Returns: array element `result`, where `result - value` is the smallest possible, while
63+
/// `result > value` also holds.
64+
package func binarySearch(nextClosestTo value: Int) -> Int? {
65+
switch self.count {
66+
case 0:
67+
return nil
68+
default:
69+
var slice = self[0..<self.count]
70+
while slice.count > 1 {
71+
let middle = (slice.endIndex - slice.startIndex) / 2
72+
if slice[middle] < value {
73+
// Not found anything in the lower half, assigning higher half to `slice`.
74+
slice = slice[(middle + 1)..<slice.endIndex]
75+
} else {
76+
// Not found anything in the higher half, assigning lower half to `slice`.
77+
slice = slice[slice.startIndex..<middle]
78+
}
79+
}
80+
81+
return self[slice.startIndex]
82+
}
83+
}
84+
}
85+
86+
#endif

Sources/WasmKit/Execution/Debugger.swift

Lines changed: 8 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,4 @@
11
#if WasmDebuggingSupport
2-
3-
extension [Int] {
4-
/// Uses binary search to find an element in `self` that's next closest to a given value.
5-
/// - Parameter value: the array element to search for or to use as a baseline when searching.
6-
/// - Returns: array element `result`, where `result - value` is the smallest possible, while
7-
/// `result > value` also holds.
8-
package func binarySearch(nextClosestTo value: Int) -> Int? {
9-
switch self.count {
10-
case 0:
11-
return nil
12-
default:
13-
var slice = self[0..<self.count]
14-
while slice.count > 1 {
15-
let middle = (slice.endIndex - slice.startIndex) / 2
16-
if slice[middle] < value {
17-
// Not found anything in the lower half, assigning higher half to `slice`.
18-
slice = slice[(middle + 1)..<slice.endIndex]
19-
} else {
20-
// Not found anything in the higher half, assigning lower half to `slice`.
21-
slice = slice[slice.startIndex..<middle]
22-
}
23-
}
24-
25-
return self[slice.startIndex]
26-
}
27-
}
28-
}
29-
30-
extension Instance {
31-
/// Computes an address of WasmKit's iseq bytecode instruction that matches a given Wasm instruction address.
32-
/// - Parameter address: the Wasm instruction to find a mapping for.
33-
/// - Returns: A tuple with an address of found iseq instruction and the original Wasm instruction or next
34-
/// closest match if no direct match was found.
35-
fileprivate func findIseq(forWasmAddress address: Int) throws(Debugger.Error) -> (iseq: Pc, wasm: Int) {
36-
// Look in the main mapping
37-
if let iseq = handle.wasmToIseqMapping[address] {
38-
return (iseq, address)
39-
}
40-
41-
// If nothing found, find the closest Wasm address using binary search
42-
guard let nextAddress = handle.wasmMappings.binarySearch(nextClosestTo: address),
43-
// Look in the main mapping again with the next closest address if binary search produced anything
44-
let iseq = handle.wasmToIseqMapping[nextAddress]
45-
else {
46-
throw Debugger.Error.noInstructionMappingAvailable(address)
47-
}
48-
49-
return (iseq, nextAddress)
50-
}
51-
}
52-
532
/// User-facing debugger state driven by a debugger host. This implementation has no knowledge of the exact
543
/// debugger protocol, which allows any protocol implementation or direct API users to be layered on top if needed.
554
package struct Debugger: ~Copyable {
@@ -140,7 +89,9 @@
14089
return
14190
}
14291

143-
let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address)
92+
guard let (iseq, wasm) = try self.instance.handle.instructionMapping.findIseq(forWasmAddress: address) else {
93+
throw Error.noInstructionMappingAvailable(address)
94+
}
14495

14596
self.breakpoints[wasm] = iseq.pointee
14697
iseq.pointee = Instruction.breakpoint.headSlot(threadingModel: self.threadingModel)
@@ -156,7 +107,9 @@
156107
return
157108
}
158109

159-
let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address)
110+
guard let (iseq, wasm) = try self.instance.handle.instructionMapping.findIseq(forWasmAddress: address) else {
111+
throw Error.noInstructionMappingAvailable(address)
112+
}
160113

161114
self.breakpoints[wasm] = nil
162115
iseq.pointee = oldCodeSlot
@@ -216,7 +169,7 @@
216169
}
217170
} catch let breakpoint as Execution.Breakpoint {
218171
let pc = breakpoint.pc
219-
guard let wasmPc = self.instance.handle.iseqToWasmMapping[pc] else {
172+
guard let wasmPc = self.instance.handle.instructionMapping.findWasm(forIseqAddress: pc) else {
220173
throw Error.noReverseInstructionMappingAvailable(pc)
221174
}
222175

@@ -232,7 +185,7 @@
232185
}
233186

234187
var result = Execution.captureBacktrace(sp: currentBreakpoint.iseq.sp, store: self.store).symbols.compactMap {
235-
return self.instance.handle.iseqToWasmMapping[$0.address]
188+
return self.instance.handle.instructionMapping.findWasm(forIseqAddress: $0.address)
236189
}
237190
result.append(currentBreakpoint.wasmPc)
238191

Sources/WasmKit/Execution/Instances.swift

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,7 @@ struct InstanceEntity /* : ~Copyable */ {
8585
var dataCount: UInt32?
8686
var isDebuggable: Bool
8787

88-
/// Mapping from iseq Pc to instruction addresses in the original binary.
89-
/// Used for handling current call stack requests issued by a ``Debugger`` instance.
90-
var iseqToWasmMapping = [Pc: Int]()
91-
92-
/// Mapping from Wasm instruction addresses in the original binary to iseq instruction addresses.
93-
/// Used for handling breakpoint requests issued by a ``Debugger`` instance.
94-
var wasmToIseqMapping = [Int: Pc]()
95-
96-
/// Wasm addresses sorted in ascending order for binary search when of the next closest mapped
97-
/// instruction, when no key is found in `wasmToIseqMapping`.
98-
var wasmMappings = [Int]()
88+
var instructionMapping: DebuggerInstructionMapping
9989

10090
static var empty: InstanceEntity {
10191
InstanceEntity(
@@ -111,8 +101,7 @@ struct InstanceEntity /* : ~Copyable */ {
111101
features: [],
112102
dataCount: nil,
113103
isDebuggable: false,
114-
iseqToWasmMapping: [:],
115-
wasmToIseqMapping: [:]
104+
instructionMapping: .init()
116105
)
117106
}
118107

Sources/WasmKit/Execution/StoreAllocator.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,7 @@ extension StoreAllocator {
453453
features: module.features,
454454
dataCount: module.dataCount,
455455
isDebuggable: isDebuggable,
456-
iseqToWasmMapping: [:],
457-
wasmToIseqMapping: [:]
456+
instructionMapping: .init()
458457
)
459458
instancePointer.initialize(to: instanceEntity)
460459
instanceInitialized = true

Sources/WasmKit/Translator.swift

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,19 +1100,14 @@ struct InstructionTranslator: InstructionVisitor {
11001100
let initializedElementsIndex = buffer.initialize(fromContentsOf: instructions)
11011101
assert(initializedElementsIndex == instructions.endIndex)
11021102

1103+
#if WasmDebuggingSupport
11031104
for (iseq, wasm) in self.iseqToWasmMapping {
11041105
self.module.withValue {
1105-
let absoluteISeq = iseq + buffer.baseAddress.unsafelyUnwrapped
1106-
// Don't override the existing mapping, only store a new pair if there's no mapping for a given key.
1107-
if $0.iseqToWasmMapping[absoluteISeq] == nil {
1108-
$0.iseqToWasmMapping[absoluteISeq] = wasm
1109-
}
1110-
if $0.wasmToIseqMapping[wasm] == nil {
1111-
$0.wasmToIseqMapping[wasm] = absoluteISeq
1112-
}
1113-
$0.wasmMappings.append(wasm)
1106+
let absoluteIseq = iseq + buffer.baseAddress.unsafelyUnwrapped
1107+
$0.instructionMapping.add(wasm: wasm, iseq: absoluteIseq)
11141108
}
11151109
}
1110+
#endif
11161111

11171112
let constants = allocator.allocateConstants(self.constantSlots.values)
11181113
return InstructionSequence(
@@ -2305,16 +2300,6 @@ struct InstructionTranslator: InstructionVisitor {
23052300
return .tableSize(Instruction.TableSizeOperand(tableIndex: table, result: LVReg(result)))
23062301
}
23072302
}
2308-
2309-
mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
2310-
guard self.module.isDebuggable && opcode.count == 1 && opcode[0] == 0xFF else {
2311-
return false
2312-
}
2313-
2314-
emit(.breakpoint)
2315-
2316-
return true
2317-
}
23182303
}
23192304

23202305
struct TranslationError: Error, CustomStringConvertible {

0 commit comments

Comments
 (0)