Skip to content
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
7 changes: 7 additions & 0 deletions Sources/WasmKit/Execution/DispatchInstruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ extension Execution {
case 196: return self.execute_tableElementDrop(sp: &sp, pc: &pc, md: &md, ms: &ms)
case 197: return self.execute_onEnter(sp: &sp, pc: &pc, md: &md, ms: &ms)
case 198: return self.execute_onExit(sp: &sp, pc: &pc, md: &md, ms: &ms)
case 199: return try self.execute_breakpoint(sp: &sp, pc: &pc, md: &md, ms: &ms)
default: preconditionFailure("Unknown instruction!?")

}
Expand Down Expand Up @@ -1795,6 +1796,12 @@ extension Execution {
pc.pointee = pc.pointee.advanced(by: 1)
return next
}
@_silgen_name("wasmkit_execute_breakpoint") @inline(__always)
mutating func execute_breakpoint(sp: UnsafeMutablePointer<Sp>, pc: UnsafeMutablePointer<Pc>, md: UnsafeMutablePointer<Md>, ms: UnsafeMutablePointer<Ms>) throws -> CodeSlot {
let next: CodeSlot
(pc.pointee, next) = try self.breakpoint(sp: &sp.pointee, pc: pc.pointee)
return next
}
}

extension Instruction {
Expand Down
5 changes: 4 additions & 1 deletion Sources/WasmKit/Execution/Execution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,12 @@ extension Execution {
}
}

/// A ``Error`` thrown when the execution normally ends.
/// An ``Error`` thrown when the execution normally ends.
struct EndOfExecution: Error {}

/// An ``Error`` thrown when a breakpoint is triggered.
struct Breakpoint: Error {}

/// The entry point for the execution of the WebAssembly function.
@inline(never)
mutating func execute(
Expand Down
10 changes: 7 additions & 3 deletions Sources/WasmKit/Execution/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ struct InternalFunction: Equatable, Hashable {
_storage = bitPattern
}

/// Returns `true` if the function is defined as a Wasm function in its original module.
/// Returns `false` if the function is implemented by the host.
var isWasm: Bool {
_storage & 0b1 == 0
}
Expand Down Expand Up @@ -241,7 +243,7 @@ struct WasmFunctionEntity {
switch code {
case .uncompiled(let code):
return try compile(store: store, code: code)
case .compiled(let iseq):
case .compiled(let iseq), .compiledAndPatchable(_, let iseq):
return iseq
}
}
Expand All @@ -260,7 +262,7 @@ struct WasmFunctionEntity {
locals: code.locals,
functionIndex: index,
codeSize: code.expression.count,
intercepting: engine.interceptor != nil
isIntercepting: engine.interceptor != nil
)
let iseq = try code.withValue { code in
try translator.translate(code: code)
Expand All @@ -281,7 +283,8 @@ extension EntityHandle<WasmFunctionEntity> {
$0.code = .compiled(iseq)
return iseq
}
case .compiled(let iseq): return iseq
case .compiled(let iseq), .compiledAndPatchable(_, let iseq):
return iseq
}
}
}
Expand Down Expand Up @@ -313,6 +316,7 @@ struct InstructionSequence {
enum CodeBody {
case uncompiled(InternalUncompiledCode)
case compiled(InstructionSequence)
case compiledAndPatchable(InternalUncompiledCode, InstructionSequence)
}

extension Reference {
Expand Down
4 changes: 4 additions & 0 deletions Sources/WasmKit/Execution/Instructions/Control.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,8 @@ extension Execution {
Function(handle: function, store: store.value)
)
}

mutating func breakpoint(sp: inout Sp, pc: Pc) throws -> (Pc, CodeSlot) {
throw Breakpoint()
}
}
7 changes: 7 additions & 0 deletions Sources/WasmKit/Execution/Instructions/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ enum Instruction: Equatable {
case onEnter(Instruction.OnEnterOperand)
/// Intercept the exit of a function
case onExit(Instruction.OnExitOperand)
/// Stop the VM on this instruction as a breakpoint
///
/// This instruction is used in debugging scenarios.
case breakpoint
}

extension Instruction {
Expand Down Expand Up @@ -1271,6 +1275,7 @@ extension Instruction {
case .tableElementDrop: return 196
case .onEnter: return 197
case .onExit: return 198
case .breakpoint: return 199
}
}
}
Expand Down Expand Up @@ -1481,6 +1486,7 @@ extension Instruction {
case 196: return .tableElementDrop(Instruction.TableElementDropOperand.load(from: &pc))
case 197: return .onEnter(Instruction.OnEnterOperand.load(from: &pc))
case 198: return .onExit(Instruction.OnExitOperand.load(from: &pc))
case 199: return .breakpoint
default: fatalError("Unknown instruction opcode: \(opcode)")
}
}
Expand Down Expand Up @@ -1694,6 +1700,7 @@ extension Instruction {
case 196: return "tableElementDrop"
case 197: return "onEnter"
case 198: return "onExit"
case 199: return "breakpoint"
default: fatalError("Unknown instruction index: \(opcode)")
}
}
Expand Down
24 changes: 19 additions & 5 deletions Sources/WasmKit/Translator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,9 @@ struct InstructionTranslator: InstructionVisitor {
/// The index of the function in the module
let functionIndex: FunctionIndex
/// Whether a call to this function should be intercepted
let intercepting: Bool
let isIntercepting: Bool
/// Whether Wasm debugging facilities are currently enabled.
let isDebugging: Bool
var constantSlots: ConstSlots
let validator: InstructionValidator

Expand All @@ -833,7 +835,8 @@ struct InstructionTranslator: InstructionVisitor {
locals: [WasmTypes.ValueType],
functionIndex: FunctionIndex,
codeSize: Int,
intercepting: Bool
isIntercepting: Bool,
isDebugging: Bool = false
) throws {
self.allocator = allocator
self.funcTypeInterner = funcTypeInterner
Expand All @@ -849,7 +852,8 @@ struct InstructionTranslator: InstructionVisitor {
self.valueStack = ValueStack(stackLayout: stackLayout)
self.locals = Locals(types: type.parameters + locals)
self.functionIndex = functionIndex
self.intercepting = intercepting
self.isIntercepting = isIntercepting
self.isDebugging = isDebugging
self.constantSlots = ConstSlots(stackLayout: stackLayout)
self.validator = InstructionValidator(context: module)

Expand Down Expand Up @@ -1059,7 +1063,7 @@ struct InstructionTranslator: InstructionVisitor {
return emittedCopy
}
private mutating func translateReturn() throws {
if intercepting {
if isIntercepting {
// Emit `onExit` instruction before every `return` instruction
emit(.onExit(functionIndex))
}
Expand Down Expand Up @@ -1098,7 +1102,7 @@ struct InstructionTranslator: InstructionVisitor {

/// Translate a Wasm expression into a sequence of instructions.
mutating func translate(code: Code) throws -> InstructionSequence {
if intercepting {
if isIntercepting {
// Emit `onEnter` instruction at the beginning of the function
emit(.onEnter(functionIndex))
}
Expand Down Expand Up @@ -2239,6 +2243,16 @@ struct InstructionTranslator: InstructionVisitor {
return .tableSize(Instruction.TableSizeOperand(tableIndex: table, result: LVReg(result)))
}
}

mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
guard self.isDebugging && opcode.count == 1 && opcode[0] == 0xFF else {
return false
}

emit(.breakpoint)

return true
}
}

struct TranslationError: Error, CustomStringConvertible {
Expand Down
11 changes: 6 additions & 5 deletions Sources/WasmParser/BinaryInstructionDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import WasmTypes
protocol BinaryInstructionDecoder {
/// Claim the next byte to be decoded
@inlinable func claimNextByte() throws -> UInt8
/// Visit unknown instruction
@inlinable func visitUnknown(_ opcode: [UInt8]) throws

func throwUnknown(_ opcode: [UInt8]) throws -> Never
/// Decode `block` immediates
@inlinable mutating func visitBlock() throws -> BlockType
/// Decode `loop` immediates
Expand Down Expand Up @@ -87,8 +87,9 @@ protocol BinaryInstructionDecoder {
/// Decode `table.size` immediates
@inlinable mutating func visitTableSize() throws -> UInt32
}

@inlinable
func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(visitor: inout V, decoder: inout D) throws -> Bool {
func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool {
let opcode0 = try decoder.claimNextByte()
switch opcode0 {
case 0x00:
Expand Down Expand Up @@ -562,10 +563,10 @@ func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(
let (table) = try decoder.visitTableFill()
try visitor.visitTableFill(table: table)
default:
try decoder.visitUnknown([opcode0, opcode1])
if try !visitor.visitUnknown([opcode0, opcode1]) { try decoder.throwUnknown([opcode0, opcode1]) }
}
default:
try decoder.visitUnknown([opcode0])
if try !visitor.visitUnknown([opcode0]) { try decoder.throwUnknown([opcode0]) }
}
return false
}
3 changes: 3 additions & 0 deletions Sources/WasmParser/InstructionVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ public protocol InstructionVisitor {
mutating func visitTableGrow(table: UInt32) throws
/// Visiting `table.size` instruction.
mutating func visitTableSize(table: UInt32) throws
/// Returns: `true` if the parser should silently proceed parsing.
mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool
}

extension InstructionVisitor {
Expand Down Expand Up @@ -514,5 +516,6 @@ extension InstructionVisitor {
public mutating func visitTableSet(table: UInt32) throws {}
public mutating func visitTableGrow(table: UInt32) throws {}
public mutating func visitTableSize(table: UInt32) throws {}
public mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool { false }
}

7 changes: 6 additions & 1 deletion Sources/WasmParser/WasmParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,14 @@ extension Parser: BinaryInstructionDecoder {
return 0
}

@inlinable func visitUnknown(_ opcode: [UInt8]) throws {
@inlinable func throwUnknown(_ opcode: [UInt8]) throws -> Never {
throw makeError(.illegalOpcode(opcode))
}

@inlinable func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
try throwUnknown(opcode)
}

@inlinable mutating func visitBlock() throws -> BlockType { try parseResultType() }
@inlinable mutating func visitLoop() throws -> BlockType { try parseResultType() }
@inlinable mutating func visitIf() throws -> BlockType { try parseResultType() }
Expand Down Expand Up @@ -744,6 +748,7 @@ extension Parser: BinaryInstructionDecoder {
return try stream.consumeAny()
}

/// Returns: `true` if the parsed instruction is the block end instruction.
@inline(__always)
@inlinable
mutating func parseInstruction<V: InstructionVisitor>(visitor v: inout V) throws -> Bool {
Expand Down
8 changes: 8 additions & 0 deletions Sources/_CWasmKit/include/DirectThreadedCode.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,13 @@ SWIFT_CC(swiftasync) static inline void wasmkit_tc_onExit(Sp sp, Pc pc, Md md, M
INLINE_CALL next = wasmkit_execute_onExit(&sp, &pc, &md, &ms, state, &error);
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
}
SWIFT_CC(swiftasync) static inline void wasmkit_tc_breakpoint(Sp sp, Pc pc, Md md, Ms ms, SWIFT_CONTEXT void *state) {
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);
void * _Nullable error = NULL; uint64_t next;
INLINE_CALL next = wasmkit_execute_breakpoint(&sp, &pc, &md, &ms, state, &error);
if (error) return wasmkit_execution_state_set_error(error, sp, state);
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
}
static const uintptr_t wasmkit_tc_exec_handlers[] = {
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_copyStack),
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_globalGet),
Expand Down Expand Up @@ -1462,4 +1469,5 @@ static const uintptr_t wasmkit_tc_exec_handlers[] = {
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_tableElementDrop),
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_onEnter),
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_onExit),
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_breakpoint),
};
10 changes: 10 additions & 0 deletions Utilities/Sources/VMSpec/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,16 @@ extension VMGen {
// Profiling
Instruction(name: "onEnter", documentation: "Intercept the entry of a function", immediate: "OnEnterOperand"),
Instruction(name: "onExit", documentation: "Intercept the exit of a function", immediate: "OnExitOperand"),

// Debugging
Instruction(name: "breakpoint",
documentation: """
Stop the VM on this instruction as a breakpoint

This instruction is used in debugging scenarios.
""",
isControl: true, mayThrow: true, mayUpdateFrame: true
),
]

// MARK: - Instruction generation
Expand Down
23 changes: 16 additions & 7 deletions Utilities/Sources/WasmGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ enum WasmGen {

code += """

/// Returns: `true` if the parser should silently proceed parsing.
mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool
}
"""

Expand Down Expand Up @@ -162,7 +164,11 @@ enum WasmGen {
}.joined(separator: ", ")
code += ") throws {}\n"
}
code += "}\n"
code += """
public mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool { false }
}

"""

return code
}
Expand Down Expand Up @@ -527,8 +533,9 @@ enum WasmGen {
protocol BinaryInstructionDecoder {
/// Claim the next byte to be decoded
@inlinable func claimNextByte() throws -> UInt8
/// Visit unknown instruction
@inlinable func visitUnknown(_ opcode: [UInt8]) throws

/// Throw an error due to unknown opcode.
func throwUnknown(_ opcode: [UInt8]) throws -> Never

"""
for instruction in instructions.categorized {
Expand All @@ -552,8 +559,9 @@ enum WasmGen {
"""

code += """

@inlinable
func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(visitor: inout V, decoder: inout D) throws -> Bool {
func parseBinaryInstruction(visitor: inout some InstructionVisitor, decoder: inout some BinaryInstructionDecoder) throws -> Bool {
"""

func renderSwitchCase(_ root: Trie, depth: Int = 0) {
Expand Down Expand Up @@ -603,9 +611,10 @@ enum WasmGen {
}
}
code += "\(indent)default:\n"
code += "\(indent) try decoder.visitUnknown("
code += "[" + (0...depth).map { opcodeByteName($0) }.joined(separator: ", ") + "]"
code += ")\n"
code += "\(indent) if try !visitor.visitUnknown("
let opcode = "[" + (0...depth).map { opcodeByteName($0) }.joined(separator: ", ") + "]"
code += opcode
code += ") { try decoder.throwUnknown(\(opcode)) }\n"
code += "\(indent)}\n"
}

Expand Down
Loading