Skip to content

Commit 4049a4e

Browse files
Merge pull request #70 from swiftwasm/yt/stack-opt
Optimize VM Engine (Part 1)
2 parents 5c2f24a + c2357e9 commit 4049a4e

26 files changed

+2304
-1887
lines changed

Sources/CLI/Run/Run.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ struct Run: ParsableCommand {
5454
}
5555

5656
let interceptor = try deriveInterceptor()
57+
#if !DEBUG
58+
guard interceptor == nil else {
59+
fatalError("Internal Error: Interceptor API is unavailable with Release build due to performance reasons")
60+
}
61+
#endif
5762
defer { interceptor?.finalize() }
5863

5964
let invoke: () throws -> Void
Lines changed: 146 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,164 @@
11
/// > Note:
22
/// <https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions>
3-
enum ControlInstruction: Equatable {
4-
case unreachable
5-
case nop
6-
case block(expression: Expression, type: ResultType)
7-
case loop(expression: Expression, type: ResultType)
8-
case `if`(then: Expression, else: Expression, type: ResultType)
9-
case br(_ labelIndex: LabelIndex)
10-
case brIf(_ labelIndex: LabelIndex)
11-
case brTable(_ labelIndices: [LabelIndex], default: LabelIndex)
12-
case `return`
13-
case call(functionIndex: UInt32)
14-
case callIndirect(tableIndex: TableIndex, typeIndex: TypeIndex)
15-
16-
func execute(runtime: Runtime, execution: inout ExecutionState) throws {
17-
switch self {
18-
case .unreachable:
19-
throw Trap.unreachable
20-
21-
case .nop:
22-
execution.programCounter += 1
23-
24-
case let .block(expression, type):
25-
let (paramSize, resultSize) = type.arity(typeSection: { execution.stack.currentFrame.module.types })
26-
let values = try execution.stack.popValues(count: paramSize)
27-
execution.enter(expression, continuation: execution.programCounter + 1, arity: resultSize)
28-
execution.stack.push(values: values)
29-
30-
case let .loop(expression, type):
31-
let (paramSize, _) = type.arity(typeSection: { execution.stack.currentFrame.module.types })
32-
let values = try execution.stack.popValues(count: paramSize)
33-
execution.enter(expression, continuation: execution.programCounter, arity: paramSize)
34-
execution.stack.push(values: values)
35-
36-
case let .if(then, `else`, type):
37-
let isTrue = try execution.stack.popValue().i32 != 0
38-
39-
let expression: Expression
40-
if isTrue {
41-
expression = then
42-
} else {
43-
expression = `else`
44-
}
45-
46-
if !expression.instructions.isEmpty {
47-
let derived = ControlInstruction.block(expression: expression, type: type)
48-
try derived.execute(runtime: runtime, execution: &execution)
49-
} else {
50-
execution.programCounter += 1
51-
}
52-
53-
case let .brIf(labelIndex):
54-
guard try execution.stack.popValue().i32 != 0 else {
55-
execution.programCounter += 1
56-
return
57-
}
58-
59-
fallthrough
60-
61-
case let .br(labelIndex):
62-
try execution.branch(labelIndex: Int(labelIndex))
63-
64-
case let .brTable(labelIndices, defaultLabelIndex):
65-
let value = try execution.stack.popValue().i32
66-
let labelIndex: LabelIndex
67-
if labelIndices.indices.contains(Int(value)) {
68-
labelIndex = labelIndices[Int(value)]
69-
} else {
70-
labelIndex = defaultLabelIndex
71-
}
72-
73-
try execution.branch(labelIndex: Int(labelIndex))
74-
75-
case .return:
76-
let values = try execution.stack.popValues(count: execution.stack.currentFrame.arity)
77-
78-
let currentFrame = Stack.Element.frame(execution.stack.currentFrame)
79-
var lastLabel: Label?
80-
while execution.stack.top != currentFrame {
81-
execution.stack.discardTopValues()
82-
lastLabel = try execution.stack.popLabel()
83-
}
84-
if let lastLabel {
85-
execution.programCounter = lastLabel.continuation
86-
}
87-
execution.stack.push(values: values)
88-
89-
case let .call(functionIndex):
90-
let functionAddresses = execution.stack.currentFrame.module.functionAddresses
91-
92-
guard functionAddresses.indices.contains(Int(functionIndex)) else {
93-
throw Trap.invalidFunctionIndex(functionIndex)
94-
}
95-
96-
try execution.invoke(functionAddress: functionAddresses[Int(functionIndex)], runtime: runtime)
97-
98-
case let .callIndirect(tableIndex, typeIndex):
99-
let moduleInstance = execution.stack.currentFrame.module
100-
let tableAddresses = moduleInstance.tableAddresses[Int(tableIndex)]
101-
let tableInstance = runtime.store.tables[tableAddresses]
102-
let expectedType = moduleInstance.types[Int(typeIndex)]
103-
let value = try execution.stack.popValue().i32
104-
let elementIndex = Int(value)
105-
guard elementIndex < tableInstance.elements.count else {
106-
throw Trap.undefinedElement
107-
}
108-
guard case let .function(functionAddress?) = tableInstance.elements[elementIndex]
109-
else {
110-
throw Trap.tableUninitialized(ElementIndex(elementIndex))
111-
}
112-
let function = runtime.store.functions[functionAddress]
113-
guard function.type == expectedType else {
114-
throw Trap.callIndirectFunctionTypeMismatch(actual: function.type, expected: expectedType)
115-
}
116-
117-
try execution.invoke(functionAddress: functionAddress, runtime: runtime)
3+
extension ExecutionState {
4+
func unreachable(runtime: Runtime) throws {
5+
throw Trap.unreachable
6+
}
7+
mutating func nop(runtime: Runtime) throws {
8+
programCounter += 1
9+
}
10+
private func getTypeSection(store: Store) -> [FunctionType] {
11+
store.module(address: stack.currentFrame.module).types
12+
}
13+
14+
typealias BlockType = Instruction.BlockType
15+
16+
mutating func block(runtime: Runtime, endRef: ExpressionRef, type: BlockType) {
17+
enter(
18+
jumpTo: programCounter + 1,
19+
continuation: programCounter + endRef.relativeOffset,
20+
arity: Int(type.results),
21+
pushPopValues: Int(type.parameters)
22+
)
23+
}
24+
mutating func loop(runtime: Runtime, type: BlockType) {
25+
let paramSize = Int(type.parameters)
26+
enter(jumpTo: programCounter + 1, continuation: programCounter, arity: paramSize, pushPopValues: paramSize)
27+
}
28+
29+
mutating func ifThen(runtime: Runtime, endRef: ExpressionRef, type: BlockType) {
30+
let isTrue = stack.popValue().i32 != 0
31+
if isTrue {
32+
enter(
33+
jumpTo: programCounter + 1,
34+
continuation: programCounter.advanced(by: endRef.relativeOffset),
35+
arity: Int(type.results),
36+
pushPopValues: Int(type.parameters)
37+
)
38+
} else {
39+
programCounter += endRef.relativeOffset
11840
}
11941
}
120-
}
12142

122-
extension ControlInstruction: CustomStringConvertible {
123-
public var description: String {
124-
switch self {
125-
case .loop:
126-
return "loop"
43+
mutating func ifThenElse(runtime: Runtime, elseRef: ExpressionRef, endRef: ExpressionRef, type: BlockType) {
44+
let isTrue = stack.popValue().i32 != 0
45+
let addendToPC: Int
46+
if isTrue {
47+
addendToPC = 1
48+
} else {
49+
addendToPC = elseRef.relativeOffset
50+
}
51+
enter(
52+
jumpTo: programCounter + addendToPC,
53+
continuation: programCounter + endRef.relativeOffset,
54+
arity: Int(type.results),
55+
pushPopValues: Int(type.parameters)
56+
)
57+
}
58+
mutating func end(runtime: Runtime) {
59+
if let currentLabel = self.stack.currentLabel {
60+
stack.exit(label: currentLabel)
61+
}
62+
programCounter += 1
63+
}
64+
mutating func `else`(runtime: Runtime) {
65+
let label = self.stack.currentLabel!
66+
stack.exit(label: label)
67+
programCounter = label.continuation // if-then-else's continuation points the "end"
68+
}
69+
70+
private mutating func branch(labelIndex: Int, runtime: Runtime) throws {
71+
if stack.numberOfLabelsInCurrentFrame() == labelIndex {
72+
try self.return(runtime: runtime)
73+
return
74+
}
75+
let label = stack.getLabel(index: Int(labelIndex))
76+
let values = stack.popValues(count: label.arity)
12777

128-
case .block:
129-
return "block"
78+
stack.unwindLabels(upto: labelIndex)
13079

131-
case let .br(i):
132-
return "br \(i)"
80+
stack.push(values: values)
81+
programCounter = label.continuation
82+
}
83+
mutating func br(runtime: Runtime, labelIndex: LabelIndex) throws {
84+
try branch(labelIndex: Int(labelIndex), runtime: runtime)
85+
}
86+
mutating func brIf(runtime: Runtime, labelIndex: LabelIndex) throws {
87+
guard stack.popValue().i32 != 0 else {
88+
programCounter += 1
89+
return
90+
}
91+
try br(runtime: runtime, labelIndex: labelIndex)
92+
}
93+
mutating func brTable(runtime: Runtime, brTable: Instruction.BrTable) throws {
94+
let labelIndices = brTable.labelIndices
95+
let defaultIndex = brTable.defaultIndex
96+
let value = stack.popValue().i32
97+
let labelIndex: LabelIndex
98+
if labelIndices.indices.contains(Int(value)) {
99+
labelIndex = labelIndices[Int(value)]
100+
} else {
101+
labelIndex = defaultIndex
102+
}
133103

134-
case let .brIf(i):
135-
return "br_if \(i)"
104+
try branch(labelIndex: Int(labelIndex), runtime: runtime)
105+
}
106+
mutating func `return`(runtime: Runtime) throws {
107+
let currentFrame = stack.currentFrame!
108+
_ = stack.exit(frame: currentFrame)
109+
try endOfFunction(runtime: runtime, currentFrame: currentFrame)
110+
}
136111

137-
case let .brTable(i, d):
138-
return "br_if \(i.map(\.description).joined(separator: " ")) \(d)"
112+
mutating func endOfFunction(runtime: Runtime) throws {
113+
try self.endOfFunction(runtime: runtime, currentFrame: stack.currentFrame)
114+
}
139115

140-
case let .call(functionIndex):
141-
return "call \(functionIndex)"
116+
mutating func endOfExecution(runtime: Runtime) throws {
117+
reachedEndOfExecution = true
118+
}
142119

143-
case let .callIndirect(tableIndex, typeIndex):
144-
return "call_indirect \(tableIndex) \(typeIndex)"
120+
private mutating func endOfFunction(runtime: Runtime, currentFrame: Frame) throws {
121+
// When reached at "end" of function
122+
#if DEBUG
123+
if let address = currentFrame.address {
124+
runtime.interceptor?.onExitFunction(address, store: runtime.store)
125+
}
126+
#endif
127+
let values = stack.popValues(count: currentFrame.arity)
128+
stack.popFrame()
129+
stack.push(values: values)
130+
programCounter = currentFrame.returnPC
131+
}
145132

146-
case let .if(type, then, `else`):
147-
return """
148-
if \(type)\n \(then)
149-
else\n \(`else`)
150-
end
151-
"""
133+
mutating func call(runtime: Runtime, functionIndex: UInt32) throws {
134+
let functionAddresses = runtime.store.module(address: stack.currentFrame.module).functionAddresses
152135

153-
case .unreachable:
154-
return "unreachable"
136+
guard functionAddresses.indices.contains(Int(functionIndex)) else {
137+
throw Trap.invalidFunctionIndex(functionIndex)
138+
}
155139

156-
case .nop:
157-
return "nop"
140+
try invoke(functionAddress: functionAddresses[Int(functionIndex)], runtime: runtime)
141+
}
158142

159-
case .return:
160-
return "return"
143+
mutating func callIndirect(runtime: Runtime, tableIndex: TableIndex, typeIndex: TypeIndex) throws {
144+
let moduleInstance = runtime.store.module(address: stack.currentFrame.module)
145+
let tableAddresses = moduleInstance.tableAddresses[Int(tableIndex)]
146+
let tableInstance = runtime.store.tables[tableAddresses]
147+
let expectedType = moduleInstance.types[Int(typeIndex)]
148+
let value = stack.popValue().i32
149+
let elementIndex = Int(value)
150+
guard elementIndex < tableInstance.elements.count else {
151+
throw Trap.undefinedElement
152+
}
153+
guard case let .function(functionAddress?) = tableInstance.elements[elementIndex]
154+
else {
155+
throw Trap.tableUninitialized(ElementIndex(elementIndex))
161156
}
157+
let function = runtime.store.functions[functionAddress]
158+
guard function.type == expectedType else {
159+
throw Trap.callIndirectFunctionTypeMismatch(actual: function.type, expected: expectedType)
160+
}
161+
162+
try invoke(functionAddress: functionAddress, runtime: runtime)
162163
}
163164
}

Sources/WasmKit/Execution/Instructions/Expression.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,50 @@ enum PseudoInstruction {
1313
case end
1414
}
1515

16-
/// > Note:
17-
/// <https://webassembly.github.io/spec/core/syntax/instructions.html#expressions>
18-
struct Expression: Equatable {
19-
/// Note that `end` or `else` pseudo instructions are omitted in this array
20-
let instructions: [Instruction]
16+
struct InstructionSequence: Equatable {
17+
let instructions: UnsafeBufferPointer<Instruction>
2118

2219
init(instructions: [Instruction]) {
23-
self.instructions = instructions
20+
assert(_isPOD(Instruction.self))
21+
let buffer = UnsafeMutableBufferPointer<Instruction>.allocate(capacity: instructions.count + 1)
22+
for (idx, instruction) in instructions.enumerated() {
23+
buffer[idx] = instruction
24+
}
25+
buffer[instructions.count] = .endOfFunction
26+
self.instructions = UnsafeBufferPointer(buffer)
27+
}
28+
29+
func deallocate() {
30+
instructions.deallocate()
31+
}
32+
33+
var baseAddress: UnsafePointer<Instruction> {
34+
self.instructions.baseAddress!
35+
}
36+
37+
static func == (lhs: InstructionSequence, rhs: InstructionSequence) -> Bool {
38+
lhs.instructions.baseAddress == rhs.instructions.baseAddress
2439
}
2540
}
2641

27-
extension Expression: ExpressibleByArrayLiteral {
42+
extension InstructionSequence: ExpressibleByArrayLiteral {
2843
init(arrayLiteral elements: Instruction...) {
2944
self.init(instructions: elements)
3045
}
3146
}
47+
48+
struct ExpressionRef: Equatable {
49+
let _relativeOffset: UInt32
50+
var relativeOffset: Int {
51+
Int(_relativeOffset)
52+
}
53+
54+
init(_ relativeOffset: Int) {
55+
self._relativeOffset = UInt32(relativeOffset)
56+
}
57+
}
58+
59+
/// > Note:
60+
/// <https://webassembly.github.io/spec/core/syntax/instructions.html#expressions>
61+
62+
typealias Expression = [Instruction]

0 commit comments

Comments
 (0)