Skip to content

Commit e970735

Browse files
committed
Add internal VM breakpoint instruction, clean up visitUnknown
1 parent ab876e6 commit e970735

File tree

11 files changed

+123
-55
lines changed

11 files changed

+123
-55
lines changed

Sources/WasmKit/Execution/DispatchInstruction.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ extension Execution {
212212
case 196: return self.execute_tableElementDrop(sp: &sp, pc: &pc, md: &md, ms: &ms)
213213
case 197: return self.execute_onEnter(sp: &sp, pc: &pc, md: &md, ms: &ms)
214214
case 198: return self.execute_onExit(sp: &sp, pc: &pc, md: &md, ms: &ms)
215+
case 199: return try self.execute_breakpoint(sp: &sp, pc: &pc, md: &md, ms: &ms)
215216
default: preconditionFailure("Unknown instruction!?")
216217

217218
}
@@ -1795,6 +1796,12 @@ extension Execution {
17951796
pc.pointee = pc.pointee.advanced(by: 1)
17961797
return next
17971798
}
1799+
@_silgen_name("wasmkit_execute_breakpoint") @inline(__always)
1800+
mutating func execute_breakpoint(sp: UnsafeMutablePointer<Sp>, pc: UnsafeMutablePointer<Pc>, md: UnsafeMutablePointer<Md>, ms: UnsafeMutablePointer<Ms>) throws -> CodeSlot {
1801+
let next: CodeSlot
1802+
(pc.pointee, next) = try self.breakpoint(sp: &sp.pointee, pc: pc.pointee)
1803+
return next
1804+
}
17981805
}
17991806

18001807
extension Instruction {

Sources/WasmKit/Execution/Execution.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,12 @@ extension Execution {
361361
}
362362
}
363363

364-
/// A ``Error`` thrown when the execution normally ends.
364+
/// An ``Error`` thrown when the execution normally ends.
365365
struct EndOfExecution: Error {}
366366

367+
/// An ``Error`` thrown when a breakpoint is triggered.
368+
struct Breakpoint: Error {}
369+
367370
/// The entry point for the execution of the WebAssembly function.
368371
@inline(never)
369372
mutating func execute(

Sources/WasmKit/Execution/Instructions/Control.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,8 @@ extension Execution {
223223
Function(handle: function, store: store.value)
224224
)
225225
}
226+
227+
mutating func breakpoint(sp: inout Sp, pc: Pc) throws -> (Pc, CodeSlot) {
228+
throw Breakpoint()
229+
}
226230
}

Sources/WasmKit/Execution/Instructions/Instruction.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ enum Instruction: Equatable {
413413
case onEnter(Instruction.OnEnterOperand)
414414
/// Intercept the exit of a function
415415
case onExit(Instruction.OnExitOperand)
416+
/// Stop the VM on this instruction as a breakpoint
417+
///
418+
/// This instruction is used in debugging scenarios.
419+
case breakpoint
416420
}
417421

418422
extension Instruction {
@@ -1271,6 +1275,7 @@ extension Instruction {
12711275
case .tableElementDrop: return 196
12721276
case .onEnter: return 197
12731277
case .onExit: return 198
1278+
case .breakpoint: return 199
12741279
}
12751280
}
12761281
}
@@ -1481,6 +1486,7 @@ extension Instruction {
14811486
case 196: return .tableElementDrop(Instruction.TableElementDropOperand.load(from: &pc))
14821487
case 197: return .onEnter(Instruction.OnEnterOperand.load(from: &pc))
14831488
case 198: return .onExit(Instruction.OnExitOperand.load(from: &pc))
1489+
case 199: return .breakpoint
14841490
default: fatalError("Unknown instruction opcode: \(opcode)")
14851491
}
14861492
}
@@ -1694,6 +1700,7 @@ extension Instruction {
16941700
case 196: return "tableElementDrop"
16951701
case 197: return "onEnter"
16961702
case 198: return "onExit"
1703+
case 199: return "breakpoint"
16971704
default: fatalError("Unknown instruction index: \(opcode)")
16981705
}
16991706
}

Sources/WasmKit/Translator.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,16 @@ struct InstructionTranslator<Context: TranslatorContext>: InstructionVisitor {
22572257
return .tableSize(Instruction.TableSizeOperand(tableIndex: table, result: LVReg(result)))
22582258
}
22592259
}
2260+
2261+
mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
2262+
guard opcode.count == 1 && opcode[0] == 0xFF else {
2263+
return false
2264+
}
2265+
2266+
emit(.breakpoint)
2267+
2268+
return true
2269+
}
22602270
}
22612271

22622272
struct TranslationError: Error, CustomStringConvertible {

Sources/WasmParser/BinaryInstructionDecoder.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
import WasmTypes
66

77
@usableFromInline
8-
package protocol BinaryInstructionDecoder {
8+
protocol BinaryInstructionDecoder {
99
/// Claim the next byte to be decoded
1010
@inlinable func claimNextByte() throws -> UInt8
11-
/// Visit unknown instruction
12-
@inlinable func visitUnknown(_ opcode: [UInt8]) throws
11+
12+
func throwUnknown(_ opcode: [UInt8]) throws -> Never
1313
/// Decode `block` immediates
1414
@inlinable mutating func visitBlock() throws -> BlockType
1515
/// Decode `loop` immediates
@@ -87,8 +87,9 @@ package protocol BinaryInstructionDecoder {
8787
/// Decode `table.size` immediates
8888
@inlinable mutating func visitTableSize() throws -> UInt32
8989
}
90+
9091
@inlinable
91-
func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(visitor: inout V, decoder: inout D) throws -> Bool {
92+
func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool {
9293
let opcode0 = try decoder.claimNextByte()
9394
switch opcode0 {
9495
case 0x00:
@@ -562,10 +563,10 @@ func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(
562563
let (table) = try decoder.visitTableFill()
563564
try visitor.visitTableFill(table: table)
564565
default:
565-
try decoder.visitUnknown([opcode0, opcode1])
566+
if try !visitor.visitUnknown([opcode0, opcode1]) { try decoder.throwUnknown([opcode0, opcode1]) }
566567
}
567568
default:
568-
try decoder.visitUnknown([opcode0])
569+
if try !visitor.visitUnknown([opcode0]) { try decoder.throwUnknown([opcode0]) }
569570
}
570571
return false
571572
}

Sources/WasmParser/InstructionVisitor.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ public protocol InstructionVisitor {
398398
mutating func visitTableGrow(table: UInt32) throws
399399
/// Visiting `table.size` instruction.
400400
mutating func visitTableSize(table: UInt32) throws
401+
/// Returns: `true` if the parser should silently proceed parsing.
402+
mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool
401403
}
402404

403405
extension InstructionVisitor {
@@ -514,5 +516,6 @@ extension InstructionVisitor {
514516
public mutating func visitTableSet(table: UInt32) throws {}
515517
public mutating func visitTableGrow(table: UInt32) throws {}
516518
public mutating func visitTableSize(table: UInt32) throws {}
519+
public mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool { false }
517520
}
518521

Sources/WasmParser/WasmParser.swift

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -606,23 +606,27 @@ extension Parser: BinaryInstructionDecoder {
606606
return 0
607607
}
608608

609-
@inlinable package func visitUnknown(_ opcode: [UInt8]) throws {
609+
@inlinable func throwUnknown(_ opcode: [UInt8]) throws -> Never {
610610
throw makeError(.illegalOpcode(opcode))
611611
}
612612

613-
@inlinable package mutating func visitBlock() throws -> BlockType { try parseResultType() }
614-
@inlinable package mutating func visitLoop() throws -> BlockType { try parseResultType() }
615-
@inlinable package mutating func visitIf() throws -> BlockType { try parseResultType() }
616-
@inlinable package mutating func visitBr() throws -> UInt32 { try parseUnsigned() }
617-
@inlinable package mutating func visitBrIf() throws -> UInt32 { try parseUnsigned() }
618-
@inlinable package mutating func visitBrTable() throws -> BrTable {
613+
@inlinable func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
614+
try throwUnknown(opcode)
615+
}
616+
617+
@inlinable mutating func visitBlock() throws -> BlockType { try parseResultType() }
618+
@inlinable mutating func visitLoop() throws -> BlockType { try parseResultType() }
619+
@inlinable mutating func visitIf() throws -> BlockType { try parseResultType() }
620+
@inlinable mutating func visitBr() throws -> UInt32 { try parseUnsigned() }
621+
@inlinable mutating func visitBrIf() throws -> UInt32 { try parseUnsigned() }
622+
@inlinable mutating func visitBrTable() throws -> BrTable {
619623
let labelIndices: [UInt32] = try parseVector { try parseUnsigned() }
620624
let labelIndex: UInt32 = try parseUnsigned()
621625
return BrTable(labelIndices: labelIndices, defaultIndex: labelIndex)
622626
}
623-
@inlinable package mutating func visitCall() throws -> UInt32 { try parseUnsigned() }
627+
@inlinable mutating func visitCall() throws -> UInt32 { try parseUnsigned() }
624628

625-
@inlinable package mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) {
629+
@inlinable mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) {
626630
let typeIndex: TypeIndex = try parseUnsigned()
627631
if try !features.contains(.referenceTypes) && stream.peek() != 0 {
628632
// Check that reserved byte is zero when reference-types is disabled
@@ -632,118 +636,119 @@ extension Parser: BinaryInstructionDecoder {
632636
return (typeIndex, tableIndex)
633637
}
634638

635-
@inlinable package mutating func visitReturnCall() throws -> UInt32 {
639+
@inlinable mutating func visitReturnCall() throws -> UInt32 {
636640
try parseUnsigned()
637641
}
638642

639-
@inlinable package mutating func visitReturnCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) {
643+
@inlinable mutating func visitReturnCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) {
640644
let typeIndex: TypeIndex = try parseUnsigned()
641645
let tableIndex: TableIndex = try parseUnsigned()
642646
return (typeIndex, tableIndex)
643647
}
644648

645-
@inlinable package mutating func visitTypedSelect() throws -> WasmTypes.ValueType {
649+
@inlinable mutating func visitTypedSelect() throws -> WasmTypes.ValueType {
646650
let results = try parseVector { try parseValueType() }
647651
guard results.count == 1 else {
648652
throw makeError(.invalidResultArity(expected: 1, actual: results.count))
649653
}
650654
return results[0]
651655
}
652656

653-
@inlinable package mutating func visitLocalGet() throws -> UInt32 { try parseUnsigned() }
654-
@inlinable package mutating func visitLocalSet() throws -> UInt32 { try parseUnsigned() }
655-
@inlinable package mutating func visitLocalTee() throws -> UInt32 { try parseUnsigned() }
656-
@inlinable package mutating func visitGlobalGet() throws -> UInt32 { try parseUnsigned() }
657-
@inlinable package mutating func visitGlobalSet() throws -> UInt32 { try parseUnsigned() }
658-
@inlinable package mutating func visitLoad(_: Instruction.Load) throws -> MemArg { try parseMemarg() }
659-
@inlinable package mutating func visitStore(_: Instruction.Store) throws -> MemArg { try parseMemarg() }
660-
@inlinable package mutating func visitMemorySize() throws -> UInt32 {
657+
@inlinable mutating func visitLocalGet() throws -> UInt32 { try parseUnsigned() }
658+
@inlinable mutating func visitLocalSet() throws -> UInt32 { try parseUnsigned() }
659+
@inlinable mutating func visitLocalTee() throws -> UInt32 { try parseUnsigned() }
660+
@inlinable mutating func visitGlobalGet() throws -> UInt32 { try parseUnsigned() }
661+
@inlinable mutating func visitGlobalSet() throws -> UInt32 { try parseUnsigned() }
662+
@inlinable mutating func visitLoad(_: Instruction.Load) throws -> MemArg { try parseMemarg() }
663+
@inlinable mutating func visitStore(_: Instruction.Store) throws -> MemArg { try parseMemarg() }
664+
@inlinable mutating func visitMemorySize() throws -> UInt32 {
661665
try parseMemoryIndex()
662666
}
663-
@inlinable package mutating func visitMemoryGrow() throws -> UInt32 {
667+
@inlinable mutating func visitMemoryGrow() throws -> UInt32 {
664668
try parseMemoryIndex()
665669
}
666-
@inlinable package mutating func visitI32Const() throws -> Int32 {
670+
@inlinable mutating func visitI32Const() throws -> Int32 {
667671
let n: UInt32 = try parseInteger()
668672
return Int32(bitPattern: n)
669673
}
670-
@inlinable package mutating func visitI64Const() throws -> Int64 {
674+
@inlinable mutating func visitI64Const() throws -> Int64 {
671675
let n: UInt64 = try parseInteger()
672676
return Int64(bitPattern: n)
673677
}
674-
@inlinable package mutating func visitF32Const() throws -> IEEE754.Float32 {
678+
@inlinable mutating func visitF32Const() throws -> IEEE754.Float32 {
675679
let n = try parseFloat()
676680
return IEEE754.Float32(bitPattern: n)
677681
}
678-
@inlinable package mutating func visitF64Const() throws -> IEEE754.Float64 {
682+
@inlinable mutating func visitF64Const() throws -> IEEE754.Float64 {
679683
let n = try parseDouble()
680684
return IEEE754.Float64(bitPattern: n)
681685
}
682-
@inlinable package mutating func visitRefNull() throws -> WasmTypes.ReferenceType {
686+
@inlinable mutating func visitRefNull() throws -> WasmTypes.ReferenceType {
683687
let type = try parseValueType()
684688
guard case let .ref(refType) = type else {
685689
throw makeError(.expectedRefType(actual: type))
686690
}
687691
return refType
688692
}
689693

690-
@inlinable package mutating func visitRefFunc() throws -> UInt32 { try parseUnsigned() }
691-
@inlinable package mutating func visitMemoryInit() throws -> UInt32 {
694+
@inlinable mutating func visitRefFunc() throws -> UInt32 { try parseUnsigned() }
695+
@inlinable mutating func visitMemoryInit() throws -> UInt32 {
692696
let dataIndex: DataIndex = try parseUnsigned()
693697
_ = try parseMemoryIndex()
694698
return dataIndex
695699
}
696700

697-
@inlinable package mutating func visitDataDrop() throws -> UInt32 {
701+
@inlinable mutating func visitDataDrop() throws -> UInt32 {
698702
try parseUnsigned()
699703
}
700704

701-
@inlinable package mutating func visitMemoryCopy() throws -> (dstMem: UInt32, srcMem: UInt32) {
705+
@inlinable mutating func visitMemoryCopy() throws -> (dstMem: UInt32, srcMem: UInt32) {
702706
_ = try parseMemoryIndex()
703707
_ = try parseMemoryIndex()
704708
return (0, 0)
705709
}
706710

707-
@inlinable package mutating func visitMemoryFill() throws -> UInt32 {
711+
@inlinable mutating func visitMemoryFill() throws -> UInt32 {
708712
let zero = try stream.consumeAny()
709713
guard zero == 0x00 else {
710714
throw makeError(.zeroExpected(actual: zero))
711715
}
712716
return 0
713717
}
714718

715-
@inlinable package mutating func visitTableInit() throws -> (elemIndex: UInt32, table: UInt32) {
719+
@inlinable mutating func visitTableInit() throws -> (elemIndex: UInt32, table: UInt32) {
716720
let elementIndex: ElementIndex = try parseUnsigned()
717721
let tableIndex: TableIndex = try parseUnsigned()
718722
return (elementIndex, tableIndex)
719723
}
720-
@inlinable package mutating func visitElemDrop() throws -> UInt32 {
724+
@inlinable mutating func visitElemDrop() throws -> UInt32 {
721725
try parseUnsigned()
722726
}
723-
@inlinable package mutating func visitTableCopy() throws -> (dstTable: UInt32, srcTable: UInt32) {
727+
@inlinable mutating func visitTableCopy() throws -> (dstTable: UInt32, srcTable: UInt32) {
724728
let destination: TableIndex = try parseUnsigned()
725729
let source: TableIndex = try parseUnsigned()
726730
return (destination, source)
727731
}
728-
@inlinable package mutating func visitTableFill() throws -> UInt32 {
732+
@inlinable mutating func visitTableFill() throws -> UInt32 {
729733
try parseUnsigned()
730734
}
731-
@inlinable package mutating func visitTableGet() throws -> UInt32 {
735+
@inlinable mutating func visitTableGet() throws -> UInt32 {
732736
try parseUnsigned()
733737
}
734-
@inlinable package mutating func visitTableSet() throws -> UInt32 {
738+
@inlinable mutating func visitTableSet() throws -> UInt32 {
735739
try parseUnsigned()
736740
}
737-
@inlinable package mutating func visitTableGrow() throws -> UInt32 {
741+
@inlinable mutating func visitTableGrow() throws -> UInt32 {
738742
try parseUnsigned()
739743
}
740-
@inlinable package mutating func visitTableSize() throws -> UInt32 {
744+
@inlinable mutating func visitTableSize() throws -> UInt32 {
741745
try parseUnsigned()
742746
}
743747
@inlinable package func claimNextByte() throws -> UInt8 {
744748
return try stream.consumeAny()
745749
}
746750

751+
/// Returns: `true` if the parsed instruction is the block end instruction.
747752
@inline(__always)
748753
@inlinable
749754
mutating func parseInstruction<V: InstructionVisitor>(visitor v: inout V) throws -> Bool {

Sources/_CWasmKit/include/DirectThreadedCode.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,13 @@ SWIFT_CC(swiftasync) static inline void wasmkit_tc_onExit(Sp sp, Pc pc, Md md, M
12621262
INLINE_CALL next = wasmkit_execute_onExit(&sp, &pc, &md, &ms, state, &error);
12631263
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
12641264
}
1265+
SWIFT_CC(swiftasync) static inline void wasmkit_tc_breakpoint(Sp sp, Pc pc, Md md, Ms ms, SWIFT_CONTEXT void *state) {
1266+
SWIFT_CC(swift) uint64_t wasmkit_execute_breakpoint(Sp *sp, Pc *pc, Md *md, Ms *ms, SWIFT_CONTEXT void *state, SWIFT_ERROR_RESULT void **error);
1267+
void * _Nullable error = NULL; uint64_t next;
1268+
INLINE_CALL next = wasmkit_execute_breakpoint(&sp, &pc, &md, &ms, state, &error);
1269+
if (error) return wasmkit_execution_state_set_error(error, sp, state);
1270+
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
1271+
}
12651272
static const uintptr_t wasmkit_tc_exec_handlers[] = {
12661273
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_copyStack),
12671274
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_globalGet),
@@ -1462,4 +1469,5 @@ static const uintptr_t wasmkit_tc_exec_handlers[] = {
14621469
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_tableElementDrop),
14631470
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_onEnter),
14641471
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_onExit),
1472+
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_breakpoint),
14651473
};

Utilities/Sources/VMSpec/Instruction.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,16 @@ extension VMGen {
502502
// Profiling
503503
Instruction(name: "onEnter", documentation: "Intercept the entry of a function", immediate: "OnEnterOperand"),
504504
Instruction(name: "onExit", documentation: "Intercept the exit of a function", immediate: "OnExitOperand"),
505+
506+
// Debugging
507+
Instruction(name: "breakpoint",
508+
documentation: """
509+
Stop the VM on this instruction as a breakpoint
510+
511+
This instruction is used in debugging scenarios.
512+
""",
513+
isControl: true, mayThrow: true, mayUpdateFrame: true
514+
),
505515
]
506516

507517
// MARK: - Instruction generation

0 commit comments

Comments
 (0)