16
ReactiveFunction,
17
ReactiveInstruction,
18
ReactiveScope,
19
+ ReactiveScopeBlock,
20
ReactiveScopeDependency,
21
ReactiveValue,
22
} from "../HIR/HIR";
30
ReactiveScopeDependencyTree,
31
ReactiveScopePropertyDependency,
32
} from "./DeriveMinimalDependencies";
33
+import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
34
35
/**
36
* Infers the dependencies of each scope to include variables whose values
39
* their direct dependencies and those of their child scopes.
40
*/
41
export function propagateScopeDependencies(fn: ReactiveFunction): void {
40
- const context = new Context();
42
+ const promotedTemporaries: PromotedTemporaries = {
43
+ declarations: new Map(),
44
+ used: new Set(),
45
+ };
46
+ visitReactiveFunction(fn, new FindPromotedTemporaries(), promotedTemporaries);
47
+
48
+ const context = new Context(promotedTemporaries.used);
49
if (fn.id !== null) {
50
context.declare(fn.id, {
51
id: makeInstructionId(0),
61
visit(context, fn.body);
62
}
63
64
+type PromotedTemporaries = {
65
+ declarations: Map<IdentifierId, ReactiveScope>;
66
+ used: Set<IdentifierId>;
67
+};
68
+class FindPromotedTemporaries extends ReactiveFunctionVisitor<PromotedTemporaries> {
69
+ scopes: Array<ReactiveScope> = [];
70
+
71
+ override visitScope(
72
+ scope: ReactiveScopeBlock,
73
+ state: PromotedTemporaries
74
+ ): void {
75
+ this.scopes.push(scope.scope);
76
+ this.traverseScope(scope, state);
77
+ this.scopes.pop();
78
+ }
79
+
80
+ override visitInstruction(
81
+ instruction: ReactiveInstruction,
82
+ state: PromotedTemporaries
83
+ ): void {
84
+ const scope = this.scopes.at(-1);
85
+ if (instruction.lvalue === null || scope === undefined) {
86
+ return;
87
+ }
88
+ switch (instruction.value.kind) {
89
+ case "LoadLocal":
90
+ case "PropertyLoad": {
91
+ state.declarations.set(instruction.lvalue.identifier.id, scope);
92
+ break;
93
+ }
94
+ default: {
95
+ break;
96
+ }
97
+ }
98
+ this.traverseInstruction(instruction, state);
99
+ }
100
+
101
+ override visitPlace(
102
+ _id: InstructionId,
103
+ place: Place,
104
+ state: PromotedTemporaries
105
+ ): void {
106
+ const declaringScope = state.declarations.get(place.identifier.id);
107
+ if (this.scopes.length === 0 || declaringScope === undefined) {
108
+ return;
109
+ }
110
+ if (this.scopes.indexOf(declaringScope) === -1) {
111
+ // Declaring scope is not active === used outside declaring scope
112
+ state.used.add(place.identifier.id);
113
+ }
114
+ }
115
+}
116
+
117
type DeclMap = Map<IdentifierId, Decl>;
118
type Decl = {
119
id: InstructionId;
121
};
122
123
class Context {
124
+ #temporariesUsedOutsideScope: Set<IdentifierId>;
125
#declarations: DeclMap = new Map();
126
#reassignments: Map<Identifier, Decl> = new Map();
127
// Reactive dependencies used in the current reactive scope.
133
// ReactiveScope (B) that uses the produced temporary.
134
// - codegen will inline these PropertyLoads back into scope (B)
135
#properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
74
- #temporaries: Map<Identifier, { place: Place; scope: ReactiveScope | null }> =
75
- new Map();
136
+ #temporaries: Map<Identifier, Place> = new Map();
137
#inConditionalWithinScope: boolean = false;
138
// Reactive dependencies used unconditionally in the current conditional.
139
// Composed of dependencies:
143
new ReactiveScopeDependencyTree();
144
#scopes: Stack<ReactiveScope> = empty();
145
146
+ constructor(temporariesUsedOutsideScope: Set<IdentifierId>) {
147
+ this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
148
+ }
149
+
150
enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
151
// Save context of previous scope
152
const prevInConditional = this.#inConditionalWithinScope;
184
return minInnerScopeDependencies;
185
}
186
187
+ isUsedOutsideDeclaringScope(place: Place): boolean {
188
+ return this.#temporariesUsedOutsideScope.has(place.identifier.id);
189
+ }
190
+
191
/**
192
* Prints dependency tree to string for debugging.
193
* @param includeAccesses
255
}
256
257
declareTemporary(lvalue: Place, place: Place): void {
189
- this.#temporaries.set(lvalue.identifier, {
190
- place,
191
- scope: this.currentScope.value,
192
- });
258
+ this.#temporaries.set(lvalue.identifier, place);
259
}
260
261
resolveTemporary(place: Place): Place {
196
- const temporary = this.#temporaries.get(place.identifier);
197
- if (
198
- temporary !== undefined &&
199
- (temporary.scope === null || this.#isScopeActive(temporary.scope))
200
- ) {
201
- return temporary.place;
202
- }
203
- return place;
262
+ return this.#temporaries.get(place.identifier) ?? place;
263
}
264
265
#getProperty(
598
if (value.kind === "LoadLocal" && lvalue !== null) {
599
if (
600
value.place.identifier.name !== null &&
542
- lvalue.identifier.name === null
601
+ lvalue.identifier.name === null &&
602
+ !context.isUsedOutsideDeclaringScope(lvalue)
603
) {
604
context.declareTemporary(lvalue, value.place);
605
} else {
606
context.visitOperand(value.place);
607
}
608
} else if (value.kind === "PropertyLoad") {
549
- if (lvalue !== null) {
609
+ if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) {
610
context.declareProperty(
611
lvalue,
612
value.object,