12
InstructionKind,
13
InstructionValue,
14
makeInstructionId,
15
- MutableRange,
15
Place,
16
ReactiveBlock,
17
ReactiveFunction,
18
+ ReactiveScope,
19
ReactiveValueBlock,
20
} from "../HIR/HIR";
21
import { eachInstructionValueOperand } from "../HIR/visitors";
39
id: makeInstructionId(0),
40
});
41
}
42
- visit(fn.body, dependencies, declarations, null);
42
+ visit(fn.body, dependencies, declarations, []);
43
}
44
45
enum DeclKind {
49
50
type DeclMap = Map<Identifier, { kind: DeclKind; id: InstructionId }>;
51
52
+type Scopes = Array<ReactiveScope>;
53
+
54
function visit(
55
block: ReactiveBlock,
56
dependencies: Set<Place>,
57
declarations: DeclMap,
56
- scopeRange: MutableRange | null
58
+ scopes: Scopes
59
): void {
60
for (const item of block) {
61
switch (item.kind) {
63
const scopeDependencies: Set<Place> = new Set();
64
// TODO: it would be sufficient to use a single mapping of declarations
65
const scopeDeclarations: DeclMap = new Map(declarations);
64
- visit(
65
- item.instructions,
66
- scopeDependencies,
67
- scopeDeclarations,
68
- item.scope.range
69
- );
66
+ scopes.push(item.scope);
67
+ visit(item.instructions, scopeDependencies, scopeDeclarations, scopes);
68
+ scopes.pop();
69
item.scope.dependencies = scopeDependencies;
70
for (const dep of scopeDependencies) {
71
// propagate dependencies upward using the same rules as
72
// normal dependency collection. child scopes may have dependencies
73
// on values created within the outer scope, which necessarily cannot
74
// be dependencies of the outer scope
76
- visitOperand(dep, dependencies, declarations, scopeRange);
75
+ visitOperand(dep, dependencies, declarations, scopes);
76
}
77
for (const [ident, kind] of scopeDeclarations) {
78
declarations.set(ident, kind);
80
break;
81
}
82
case "instruction": {
84
- visitInstruction(
85
- item.instruction,
86
- dependencies,
87
- declarations,
88
- scopeRange
89
- );
83
+ visitInstruction(item.instruction, dependencies, declarations, scopes);
84
break;
85
}
86
case "terminal": {
92
}
93
case "return": {
94
if (terminal.value !== null) {
101
- visitOperand(
102
- terminal.value,
103
- dependencies,
104
- declarations,
105
- scopeRange
106
- );
95
+ visitOperand(terminal.value, dependencies, declarations, scopes);
96
}
97
break;
98
}
99
case "throw": {
111
- visitOperand(
112
- terminal.value,
113
- dependencies,
114
- declarations,
115
- scopeRange
116
- );
100
+ visitOperand(terminal.value, dependencies, declarations, scopes);
101
break;
102
}
103
case "for": {
120
- visitValueBlock(
121
- terminal.init,
122
- dependencies,
123
- declarations,
124
- scopeRange
125
- );
126
- visitValueBlock(
127
- terminal.test,
128
- dependencies,
129
- declarations,
130
- scopeRange
131
- );
104
+ visitValueBlock(terminal.init, dependencies, declarations, scopes);
105
+ visitValueBlock(terminal.test, dependencies, declarations, scopes);
106
visitValueBlock(
107
terminal.update,
108
dependencies,
109
declarations,
136
- scopeRange
110
+ scopes
111
);
138
- visit(terminal.loop, dependencies, declarations, scopeRange);
112
+ visit(terminal.loop, dependencies, declarations, scopes);
113
break;
114
}
115
case "while": {
142
- visitValueBlock(
143
- terminal.test,
144
- dependencies,
145
- declarations,
146
- scopeRange
147
- );
148
- visit(terminal.loop, dependencies, declarations, scopeRange);
116
+ visitValueBlock(terminal.test, dependencies, declarations, scopes);
117
+ visit(terminal.loop, dependencies, declarations, scopes);
118
break;
119
}
120
case "if": {
152
- visitOperand(terminal.test, dependencies, declarations, scopeRange);
153
- visit(terminal.consequent, dependencies, declarations, scopeRange);
121
+ visitOperand(terminal.test, dependencies, declarations, scopes);
122
+ visit(terminal.consequent, dependencies, declarations, scopes);
123
if (terminal.alternate !== null) {
155
- visit(terminal.alternate, dependencies, declarations, scopeRange);
124
+ visit(terminal.alternate, dependencies, declarations, scopes);
125
}
126
break;
127
}
128
case "switch": {
160
- visitOperand(terminal.test, dependencies, declarations, scopeRange);
129
+ visitOperand(terminal.test, dependencies, declarations, scopes);
130
for (const case_ of terminal.cases) {
131
if (case_.block !== undefined) {
163
- visit(case_.block, dependencies, declarations, scopeRange);
132
+ visit(case_.block, dependencies, declarations, scopes);
133
}
134
}
135
break;
154
block: ReactiveValueBlock,
155
dependencies: Set<Place>,
156
declarations: DeclMap,
188
- scopeRange: MutableRange | null
157
+ scopes: Scopes
158
): void {
159
for (const initItem of block.instructions) {
160
if (initItem.kind === "instruction") {
162
initItem.instruction,
163
dependencies,
164
declarations,
196
- scopeRange
165
+ scopes
166
);
167
}
168
}
169
if (block.value !== null) {
201
- visitInstructionValue(block.value, dependencies, declarations, scopeRange);
170
+ visitInstructionValue(block.value, dependencies, declarations, scopes);
171
}
172
}
173
175
maybeDependency: Place,
176
dependencies: Set<Place>,
177
declarations: DeclMap,
209
- scopeRange: MutableRange | null
178
+ scopes: Scopes
179
): void {
180
const decl = declarations.get(maybeDependency.identifier);
181
184
// some later code needs access to the value.
185
if (decl !== undefined) {
186
const operandScope = maybeDependency.identifier.scope;
218
- if (
219
- operandScope !== null &&
220
- ((scopeRange !== null && operandScope.range.end <= scopeRange.start) ||
221
- scopeRange === null)
222
- ) {
187
+ if (operandScope !== null && scopes.indexOf(operandScope) === -1) {
188
operandScope.outputs.add(maybeDependency.identifier);
189
}
190
}
191
192
// If this operand is used in a scope, has a dynamic value, and was defined
193
// before this scope, then its a dependency of the scope.
194
+ const currentScope = scopes.at(-1);
195
if (
196
decl !== undefined &&
197
decl.kind !== DeclKind.Const &&
232
- scopeRange !== null &&
233
- decl.id < scopeRange.start
198
+ currentScope !== undefined &&
199
+ decl.id < currentScope.range.start
200
) {
201
// Check if there is an existing dependency that describes this operand
202
for (const dep of dependencies) {
238
value: InstructionValue,
239
dependencies: Set<Place>,
240
declarations: DeclMap,
275
- scopeRange: MutableRange | null
241
+ scopes: Scopes
242
): void {
243
for (const operand of eachInstructionValueOperand(value)) {
244
// check for method invocation, we want to depend on the callee, not the method
251
...operand,
252
memberPath: operand.memberPath.slice(0, -1),
253
};
288
- visitOperand(callee, dependencies, declarations, scopeRange);
254
+ visitOperand(callee, dependencies, declarations, scopes);
255
} else {
290
- visitOperand(operand, dependencies, declarations, scopeRange);
256
+ visitOperand(operand, dependencies, declarations, scopes);
257
}
258
}
259
}
262
instr: Instruction,
263
dependencies: Set<Place>,
264
declarations: DeclMap,
299
- scopeRange: MutableRange | null
265
+ scopes: Scopes
266
): void {
301
- visitInstructionValue(instr.value, dependencies, declarations, scopeRange);
267
+ visitInstructionValue(instr.value, dependencies, declarations, scopes);
268
const { lvalue } = instr;
269
if (
270
lvalue !== null &&