14
ReactiveScope,
15
ReactiveScopeBlock,
16
ReactiveScopeDependency,
17
+ makeInstructionId,
18
} from "../HIR";
18
-import { assertExhaustive } from "../Utils/utils";
19
import {
20
ReactiveFunctionTransform,
21
ReactiveFunctionVisitor,
52
*
53
*/
54
export function mergeConsecutiveScopes(fn: ReactiveFunction): void {
55
- visitReactiveFunction(fn, new Transform(), undefined);
55
+ const lastUsageVisitor = new FindLastUsageVisitor();
56
+ visitReactiveFunction(fn, lastUsageVisitor, undefined);
57
+ visitReactiveFunction(
58
+ fn,
59
+ new Transform(lastUsageVisitor.lastUsage),
60
+ undefined
61
+ );
62
+}
63
+
64
+class FindLastUsageVisitor extends ReactiveFunctionVisitor<void> {
65
+ lastUsage: Map<IdentifierId, InstructionId> = new Map();
66
+
67
+ override visitPlace(id: InstructionId, place: Place, _state: void): void {
68
+ const previousUsage = this.lastUsage.get(place.identifier.id);
69
+ const lastUsage =
70
+ previousUsage !== undefined
71
+ ? makeInstructionId(Math.max(previousUsage, id))
72
+ : id;
73
+ this.lastUsage.set(place.identifier.id, lastUsage);
74
+ }
75
}
76
77
class Transform extends ReactiveFunctionTransform<void> {
78
+ lastUsage: Map<IdentifierId, InstructionId>;
79
+
80
+ constructor(lastUsage: Map<IdentifierId, InstructionId>) {
81
+ super();
82
+ this.lastUsage = lastUsage;
83
+ }
84
+
85
override visitBlock(block: ReactiveBlock, state: void): void {
86
this.traverseBlock(block, state);
87
166
// if those intermediate instructions are all used by the second scope.
167
// if not, merging them would make those values unavailable to subsequent
168
// code by moving them inside a different block scope in the output.
143
- usesAllLvalues(instr.instructions, lvalues)
169
+ areLValuesLastUsedByScope(instr.scope, lvalues, this.lastUsage)
170
) {
171
const intermediateInstructions = block.slice(currentScope.to, i);
172
currentScope.scope.instructions.push(...intermediateInstructions);
199
}
200
}
201
176
-function usesAllLvalues(
177
- block: ReactiveBlock,
178
- lvalues: Set<IdentifierId>
202
+/**
203
+ * Returns whether the given @param scope is the last usage of all
204
+ * the given @param lvalues. Returns false if any of the lvalues
205
+ * are used again after the scope.
206
+ */
207
+function areLValuesLastUsedByScope(
208
+ scope: ReactiveScope,
209
+ lvalues: Set<IdentifierId>,
210
+ lastUsage: Map<IdentifierId, InstructionId>
211
): boolean {
180
- if (lvalues.size === 0) {
181
- return true;
182
- }
183
- const visitor = new OperandVisitor();
184
- visitor.traverseBlock(block, lvalues);
185
- return lvalues.size === 0;
186
-}
187
-
188
-class OperandVisitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
189
- override visitPlace(
190
- _id: InstructionId,
191
- place: Place,
192
- state: Set<IdentifierId>
193
- ): void {
194
- state.delete(place.identifier.id);
195
- }
196
-
197
- override traverseBlock(block: ReactiveBlock, state: Set<IdentifierId>): void {
198
- for (const instr of block) {
199
- if (state.size === 0) {
200
- return;
201
- }
202
- switch (instr.kind) {
203
- case "instruction": {
204
- this.visitInstruction(instr.instruction, state);
205
- break;
206
- }
207
- case "scope": {
208
- this.visitScope(instr, state);
209
- break;
210
- }
211
- case "terminal": {
212
- this.visitTerminal(instr, state);
213
- break;
214
- }
215
- default: {
216
- assertExhaustive(
217
- instr,
218
- `Unexpected instruction kind '${(instr as any).kind}'`
219
- );
220
- }
221
- }
212
+ for (const lvalue of lvalues) {
213
+ const lastUsedAt = lastUsage.get(lvalue)!;
214
+ if (lastUsedAt >= scope.range.end) {
215
+ return false;
216
}
217
}
218
+ return true;
219
}
220
221
function canMergeScopes(a: ReactiveScope, b: ReactiveScope): boolean {