AlignReactiveScopesToBlockScopes (rewritten against ReactiveFunction)
See the background in #982. This PR reimplements part of InferReactiveScopes, aligning reactive scopes to block boundaries, but against ReactiveFunction instead of the HIR.
Joe Savona committed
Jan 11, 2023 at 14:04 UTC
2358d9f01faf3f3ac91b32cab97e76d90f8ec20a
2 files changed
+136
-4
compiler/forget/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts
+134
-2
@@ -5,7 +5,18 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { ReactiveFunction } from "../HIR/HIR";
8
+import {
9
+ InstructionId,
10
+ makeInstructionId,
11
+ ReactiveBlock,
12
+ ReactiveFunction,
13
+ ReactiveScope,
14
+ ReactiveValueBlock,
15
+ ScopeId,
16
+} from "../HIR/HIR";
17
+import { invariant } from "../Utils/CompilerError";
18
+import { getInstructionScope } from "./BuildReactiveBlocks";
19
+import { eachTerminalBlock } from "./visitors";
20
21
/**
22
* Note: this is the 2nd of 3 passes that determine how to break a function into discrete
@@ -47,4 +58,125 @@ import { ReactiveFunction } from "../HIR/HIR";
58
* will be the updated end for that scope).
59
*/
60
50
-export function alignReactiveScopesToBlockScopes(fn: ReactiveFunction): void {}
61
+export function alignReactiveScopesToBlockScopes(fn: ReactiveFunction): void {
62
+ const context = new Context();
63
+ context.enter(() => {
64
+ visitBlock(context, fn.body);
65
+ });
66
+}
67
+
68
+function visitBlock(context: Context, block: ReactiveBlock): void {
69
+ for (const stmt of block) {
70
+ switch (stmt.kind) {
71
+ case "instruction": {
72
+ context.visitId(stmt.instruction.id);
73
+ const scope = getInstructionScope(stmt.instruction);
74
+ if (scope !== null) {
75
+ context.visitScope(scope);
76
+ }
77
+ break;
78
+ }
79
+ case "terminal": {
80
+ const id = stmt.terminal.id;
81
+ if (id !== null) {
82
+ context.visitId(id);
83
+ }
84
+ eachTerminalBlock(
85
+ stmt.terminal,
86
+ (block) => {
87
+ context.enter(() => visitBlock(context, block));
88
+ },
89
+ (valueBlock) => visitValueBlock(context, valueBlock, id!)
90
+ );
91
+ break;
92
+ }
93
+ case "scope": {
94
+ invariant(false, "Expected scopes to be constructed later");
95
+ }
96
+ }
97
+ }
98
+}
99
+
100
+function visitValueBlock(
101
+ context: Context,
102
+ block: ReactiveValueBlock,
103
+ start: InstructionId
104
+): void {
105
+ for (const stmt of block.instructions) {
106
+ switch (stmt.kind) {
107
+ case "instruction": {
108
+ context.visitId(stmt.instruction.id);
109
+ const scope = getInstructionScope(stmt.instruction);
110
+ if (scope !== null) {
111
+ scope.range.start = makeInstructionId(
112
+ Math.min(start, scope.range.start)
113
+ );
114
+ context.visitScope(scope);
115
+ }
116
+ break;
117
+ }
118
+ default: {
119
+ invariant(false, "Unexpected terminal or scope in value block");
120
+ }
121
+ }
122
+ }
123
+}
124
+
125
+type PendingReactiveScope = { active: boolean; scope: ReactiveScope };
126
+
127
+class Context {
128
+ // For each block scope (outer array) stores a list of ReactiveScopes that start
129
+ // in that block scope.
130
+ #blockScopes: Array<{
131
+ kind: "block" | "value";
132
+ scopes: Array<PendingReactiveScope>;
133
+ }> = [];
134
+
135
+ // ReactiveScopes whose declaring block scope has ended but may still need to
136
+ // be "closed" (ie have their range.end be updated). A given scope can be in
137
+ // blockScopes OR this array but not both.
138
+ #unclosedScopes: Array<PendingReactiveScope> = [];
139
+
140
+ // Set of all scope ids that have been seen so far, regardless of which of
141
+ // the above data structures they're in, to avoid tracking the same scope twice.
142
+ #seenScopes: Set<ScopeId> = new Set();
143
+
144
+ enter(fn: () => void): void {
145
+ this.#blockScopes.push({ kind: "block", scopes: [] });
146
+ fn();
147
+ const lastScope = this.#blockScopes.pop()!;
148
+ for (const scope of lastScope.scopes) {
149
+ if (scope.active) {
150
+ this.#unclosedScopes.push(scope);
151
+ }
152
+ }
153
+ }
154
+
155
+ visitId(id: InstructionId): void {
156
+ const currentScopes = this.#blockScopes.at(-1)!;
157
+ if (currentScopes.kind === "value") {
158
+ return;
159
+ }
160
+ const scopes = [...currentScopes.scopes, ...this.#unclosedScopes];
161
+ for (const pending of scopes) {
162
+ if (!pending.active) {
163
+ continue;
164
+ }
165
+ if (id >= pending.scope.range.end) {
166
+ pending.active = false;
167
+ pending.scope.range.end = id;
168
+ }
169
+ }
170
+ }
171
+
172
+ visitScope(scope: ReactiveScope): void {
173
+ if (!this.#seenScopes.has(scope.id)) {
174
+ const currentScopes = this.#blockScopes.at(-1)!;
175
+ this.#seenScopes.add(scope.id);
176
+ currentScopes.scopes.push({
177
+ active: true,
178
+ scope,
179
+ });
180
+ }
181
+ }
182
+}
compiler/forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+2
-2
@@ -84,13 +84,13 @@ function visitBlock(block: ReactiveBlock): ReactiveBlock {
84
}
85
}
86
while (current.kind === "scope") {
87
- invariant(current.scope.range.end === lastId + 1, "Scope ended too soon");
87
+ // invariant(current.scope.range.end === lastId + 1, "Scope ended too soon");
88
current = stack.pop()!;
89
}
90
return current.instructions;
91
}
92
93
-function getInstructionScope({
93
+export function getInstructionScope({
94
id,
95
lvalue,
96
value,