|
1 | 1 | /// > Note:
|
2 | 2 | /// <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 |
118 | 40 | }
|
119 | 41 | }
|
120 |
| -} |
121 | 42 |
|
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) |
127 | 77 |
|
128 |
| - case .block: |
129 |
| - return "block" |
| 78 | + stack.unwindLabels(upto: labelIndex) |
130 | 79 |
|
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 | + } |
133 | 103 |
|
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 | + } |
136 | 111 |
|
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 | + } |
139 | 115 |
|
140 |
| - case let .call(functionIndex): |
141 |
| - return "call \(functionIndex)" |
| 116 | + mutating func endOfExecution(runtime: Runtime) throws { |
| 117 | + reachedEndOfExecution = true |
| 118 | + } |
142 | 119 |
|
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 | + } |
145 | 132 |
|
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 |
152 | 135 |
|
153 |
| - case .unreachable: |
154 |
| - return "unreachable" |
| 136 | + guard functionAddresses.indices.contains(Int(functionIndex)) else { |
| 137 | + throw Trap.invalidFunctionIndex(functionIndex) |
| 138 | + } |
155 | 139 |
|
156 |
| - case .nop: |
157 |
| - return "nop" |
| 140 | + try invoke(functionAddress: functionAddresses[Int(functionIndex)], runtime: runtime) |
| 141 | + } |
158 | 142 |
|
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)) |
161 | 156 | }
|
| 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) |
162 | 163 | }
|
163 | 164 | }
|
0 commit comments