@samitouri / QOS-React-2 / commits / 2358d9f01f

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,