@samitouri / QOS-React-2 / commits / b8d78a94a8

Improve "value block" handling

This is a pre-req to construct reactive scopes in #857. "Value blocks" such as `for` init/test/update and `while` test need to be consistently wrapped in enter/leave calls so that we can extend the range of values properly. We also need to handle `for` init blocks a bit differently, since they allow variable declarations but not other types of statements. This PR ensures that we use consistent methods for handling value blocks (`for` test/update and `while` test) and treats `for` init as a new type with its own enter/append/leave visitor functions.

Joseph Savona committed Dec 12, 2022 at 11:20 UTC b8d78a94a8b2ad913be3bd6538c3ce34c07071df
6 files changed +319 -151
compiler/forget/src/HIR/Codegen.ts
+95 -47
@@ -91,7 +91,14 @@ type Temporaries = Map<IdentifierId, t.Expression>;
91
92 class CodegenVisitor
93 implements
94 - Visitor<Array<t.Statement>, t.Expression, t.Statement, t.SwitchCase>
94 + Visitor<
95 + Array<t.Statement>,
96 + Array<t.Statement>,
97 + Array<t.Statement>,
98 + t.Expression,
99 + t.Statement,
100 + t.SwitchCase
101 + >
102 {
103 depth: number = 0;
104 temp: Map<IdentifierId, t.Expression> = new Map();
@@ -100,10 +107,77 @@ class CodegenVisitor
107 this.depth++;
108 return [];
109 }
110 + appendBlock(
111 + block: t.Statement[],
112 + item: t.Statement,
113 + blockId?: BlockId | undefined
114 + ): void {
115 + if (item.type === "EmptyStatement") {
116 + return;
117 + }
118 + if (blockId !== undefined) {
119 + block.push(
120 + createLabelledStatement(
121 + item.loc,
122 + t.identifier(codegenLabel(blockId)),
123 + item
124 + )
125 + );
126 + } else {
127 + block.push(item);
128 + }
129 + }
130 + leaveBlock(block: t.Statement[]): t.Statement {
131 + this.depth--;
132 + return t.blockStatement(block);
133 + }
134 enterValueBlock(): t.Statement[] {
104 - this.depth++;
105 - return [];
135 + return this.enterBlock();
136 + }
137 + appendValueBlock(block: t.Statement[], item: t.Statement): void {
138 + this.appendBlock(block, item);
139 + }
140 + leaveValueBlock(block: t.Statement[], place: t.Expression): t.Expression {
141 + this.depth--;
142 + if (block.length === 0) {
143 + return place;
144 + }
145 + const expressions = block.map((stmt) => {
146 + switch (stmt.type) {
147 + case "ExpressionStatement":
148 + return stmt.expression;
149 + default:
150 + todoInvariant(
151 + false,
152 + `Handle conversion of ${stmt.type} to expression`
153 + );
154 + }
155 + });
156 + expressions.push(place);
157 + return t.sequenceExpression(expressions);
158 + }
159 +
160 + enterInitBlock(block: t.Statement[]): t.Statement[] {
161 + return this.enterBlock();
162 + }
163 +
164 + appendInitBlock(block: t.Statement[], item: t.Statement): void {
165 + this.appendBlock(block, item);
166 + }
167 + leaveInitBlock(block: t.Statement[]): t.Statement[] {
168 + switch (block.length) {
169 + case 0: {
170 + return [t.emptyStatement()];
171 + }
172 + case 1: {
173 + return [block[0]];
174 + }
175 + default: {
176 + return [t.blockStatement(block)];
177 + }
178 + }
179 }
180 +
181 visitValue(value: InstructionValue): t.Expression {
182 return codegenInstructionValue(this.temp, value);
183 }
@@ -191,8 +265,25 @@ class CodegenVisitor
265 return createWhileStatement(terminal.loc, terminal.test, terminal.loop);
266 }
267 case "for": {
268 + const initBlock = terminal.init;
269 + invariant(
270 + initBlock.length === 1,
271 + "Expected for init to be a single expression or statement"
272 + );
273 + const initStatement = initBlock[0]!;
274 + let init;
275 + if (initStatement.type === "VariableDeclaration") {
276 + init = initStatement;
277 + } else if (initStatement.type === "ExpressionStatement") {
278 + init = initStatement.expression;
279 + } else {
280 + invariant(
281 + false,
282 + `Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.`
283 + );
284 + }
285 return t.forStatement(
195 - terminal.init as any, // TODO: make sure it's a variable declaration
286 + init,
287 terminal.test,
288 terminal.update,
289 terminal.loop
@@ -225,49 +316,6 @@ class CodegenVisitor
316 visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase {
317 return t.switchCase(test, [block]);
318 }
228 - appendBlock(
229 - block: t.Statement[],
230 - item: t.Statement,
231 - blockId?: BlockId | undefined
232 - ): void {
233 - if (item.type === "EmptyStatement") {
234 - return;
235 - }
236 - if (blockId !== undefined) {
237 - block.push(
238 - createLabelledStatement(
239 - item.loc,
240 - t.identifier(codegenLabel(blockId)),
241 - item
242 - )
243 - );
244 - } else {
245 - block.push(item);
246 - }
247 - }
248 - leaveBlock(block: t.Statement[]): t.Statement {
249 - this.depth--;
250 - return t.blockStatement(block);
251 - }
252 - leaveValueBlock(block: t.Statement[], place: t.Expression): t.Expression {
253 - this.depth--;
254 - if (block.length === 0) {
255 - return place;
256 - }
257 - const expressions = block.map((stmt) => {
258 - switch (stmt.type) {
259 - case "ExpressionStatement":
260 - return stmt.expression;
261 - default:
262 - todoInvariant(
263 - false,
264 - `Handle conversion of ${stmt.type} to expression`
265 - );
266 - }
267 - });
268 - expressions.push(place);
269 - return t.sequenceExpression(expressions);
270 - }
319 }
320
321 function codegenLabel(id: BlockId): string {
compiler/forget/src/HIR/HIRTreeVisitor.ts
+143 -89
@@ -27,25 +27,35 @@ import {
27 *
28 * See the {@link Visitor} interface for more about implementing a visitor.
29 */
30 -export function visitTree<TBlock, TValue, TItem, TCase>(
30 +export function visitTree<
31 + TBlock,
32 + TInit,
33 + TValueBlock,
34 + TValue,
35 + TStatement,
36 + TCase
37 +>(
38 fn: HIRFunction,
32 - visitor: Visitor<TBlock, TValue, TItem, TCase>
33 -): TItem {
39 + visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>
40 +): TStatement {
41 const cx = new Context(fn.body);
42 const driver = new Driver(cx, visitor);
43 return driver.traverseBlock(cx.block(fn.body.entry));
44 }
45
39 -class Driver<TBlock, TValue, TItem, TCase> {
46 +class Driver<TBlock, TInit, TValueBlock, TValue, TStatement, TCase> {
47 cx: Context;
41 - visitor: Visitor<TBlock, TValue, TItem, TCase>;
48 + visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>;
49
43 - constructor(cx: Context, visitor: Visitor<TBlock, TValue, TItem, TCase>) {
50 + constructor(
51 + cx: Context,
52 + visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>
53 + ) {
54 this.cx = cx;
55 this.visitor = visitor;
56 }
57
48 - traverseBlock(block: BasicBlock): TItem {
58 + traverseBlock(block: BasicBlock): TStatement {
59 const blockValue = this.visitor.enterBlock();
60 this.visitBlock(block, blockValue);
61 return this.visitor.leaveBlock(blockValue);
@@ -110,7 +120,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
120 }
121
122 this.visitor.visitTerminalId(terminal.id);
113 - let consequent: TItem | null = null;
123 + let consequent: TStatement | null = null;
124 if (this.cx.isScheduled(terminal.consequent)) {
125 consequent = this.visitBreak(terminal.consequent);
126 } else {
@@ -119,7 +129,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
129 );
130 }
131
122 - let alternate: TItem | null = null;
132 + let alternate: TStatement | null = null;
133 if (alternateId !== null) {
134 if (this.cx.isScheduled(alternateId)) {
135 alternate = this.visitBreak(alternateId);
@@ -233,26 +243,6 @@ class Driver<TBlock, TValue, TItem, TCase> {
243 break;
244 }
245 case "while": {
236 - const testBlock = this.cx.ir.blocks.get(terminal.test)!;
237 - const testTerminal = testBlock.terminal;
238 - invariant(
239 - testTerminal.kind === "if",
240 - "Expected while loop test block to end in an if"
241 - );
242 - const testValueBlock = this.visitor.enterValueBlock();
243 - for (const instr of testBlock.instructions) {
244 - const value = this.visitor.visitValue(instr.value, instr.id);
245 - const item = this.visitor.visitInstruction(instr, value);
246 - this.visitor.appendBlock(testValueBlock, item);
247 - }
248 - const testValueLast = this.visitor.visitValue(
249 - testTerminal.test,
250 - testTerminal.id
251 - );
252 - const testValue = this.visitor.leaveValueBlock(
253 - testValueBlock,
254 - testValueLast
255 - );
246 const fallthroughId =
247 terminal.fallthrough !== null &&
248 !this.cx.isScheduled(terminal.fallthrough)
@@ -271,7 +261,17 @@ class Driver<TBlock, TValue, TItem, TCase> {
261 scheduleIds.push(scheduleId);
262
263 this.visitor.visitTerminalId(terminal.id);
274 - let loopBody: TItem;
264 + const testBlock = this.cx.ir.blocks.get(terminal.test)!;
265 + const testTerminal = testBlock.terminal;
266 + invariant(
267 + testTerminal.kind === "if",
268 + "Expected while loop test block to end in an if"
269 + );
270 + const testValue = this.visitValueBlock(blockValue, testBlock, {
271 + value: testTerminal.test,
272 + id: testTerminal.id,
273 + });
274 + let loopBody: TStatement;
275 if (loopId) {
276 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
277 } else {
@@ -331,18 +331,15 @@ class Driver<TBlock, TValue, TItem, TCase> {
331 );
332 scheduleIds.push(scheduleId);
333
334 + this.visitor.visitTerminalId(terminal.id);
335 +
336 const initBlock = this.cx.ir.blocks.get(terminal.init)!;
337 const initTerminal = initBlock.terminal;
338 invariant(
339 initTerminal.kind === "goto",
340 "Expected for loop init block to end in a goto"
341 );
340 -
341 - let initItem;
342 - for (const instr of initBlock.instructions) {
343 - const value = this.visitor.visitValue(instr.value, instr.id);
344 - initItem = this.visitor.visitInstruction(instr, value);
345 - }
342 + const initValue = this.visitInitBlock(blockValue, initBlock);
343
344 const testBlock = this.cx.ir.blocks.get(terminal.test)!;
345 const testTerminal = testBlock.terminal;
@@ -350,20 +347,10 @@ class Driver<TBlock, TValue, TItem, TCase> {
347 testTerminal.kind === "if",
348 "Expected for loop test block to end in an if"
349 );
353 - const testValueBlock = this.visitor.enterValueBlock();
354 - for (const instr of testBlock.instructions) {
355 - const value = this.visitor.visitValue(instr.value, instr.id);
356 - const item = this.visitor.visitInstruction(instr, value);
357 - this.visitor.appendBlock(testValueBlock, item);
358 - }
359 - const testValueLast = this.visitor.visitValue(
360 - testTerminal.test,
361 - testTerminal.id
362 - );
363 - const testValue = this.visitor.leaveValueBlock(
364 - testValueBlock,
365 - testValueLast
366 - );
350 + const testValue = this.visitValueBlock(blockValue, testBlock, {
351 + value: testTerminal.test,
352 + id: testTerminal.id,
353 + });
354
355 const updateBlock = this.cx.ir.blocks.get(terminal.update)!;
356 const updateTerminal = updateBlock.terminal;
@@ -371,12 +358,9 @@ class Driver<TBlock, TValue, TItem, TCase> {
358 updateTerminal.kind === "goto",
359 "Expected for loop update block to end in a goto"
360 );
374 - let updateValue;
375 - for (const instr of updateBlock.instructions) {
376 - updateValue = this.visitor.visitValue(instr.value, instr.id);
377 - }
361 + const updateValue = this.visitValueBlock(blockValue, updateBlock);
362
379 - let loopBody: TItem;
363 + let loopBody: TStatement;
364 if (loopId) {
365 loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
366 } else {
@@ -396,9 +380,9 @@ class Driver<TBlock, TValue, TItem, TCase> {
380 blockValue,
381 this.visitor.visitTerminal({
382 kind: "for",
399 - init: initItem as any,
383 + init: initValue,
384 test: testValue,
401 - update: updateValue as any,
385 + update: updateValue,
386 loop: loopBody,
387 }),
388 fallthroughId
@@ -409,9 +393,9 @@ class Driver<TBlock, TValue, TItem, TCase> {
393 blockValue,
394 this.visitor.visitTerminal({
395 kind: "for",
412 - init: initItem as any,
396 + init: initValue,
397 test: testValue,
414 - update: updateValue as any,
398 + update: updateValue,
399 loop: loopBody,
400 })
401 );
@@ -450,12 +434,50 @@ class Driver<TBlock, TValue, TItem, TCase> {
434 }
435 }
436
453 - emptyBlock(): TItem {
437 + visitInitBlock(parent: TBlock, block: BasicBlock): TInit {
438 + const initBlock = this.visitor.enterInitBlock(parent);
439 + for (const instr of block.instructions) {
440 + const value = this.visitor.visitValue(instr.value, instr.id);
441 + const item = this.visitor.visitInstruction(instr, value);
442 + this.visitor.appendInitBlock(initBlock, item);
443 + }
444 + return this.visitor.leaveInitBlock(initBlock);
445 + }
446 +
447 + visitValueBlock(
448 + parent: TBlock,
449 + block: BasicBlock,
450 + terminalValue?: { value: InstructionValue; id: InstructionId }
451 + ): TValue {
452 + const valueBlock = this.visitor.enterValueBlock(parent);
453 + const instructions = [...block.instructions];
454 + let lastValue: { value: InstructionValue; id: InstructionId };
455 + if (terminalValue != null) {
456 + lastValue = terminalValue;
457 + } else {
458 + invariant(instructions.length > 0, "Value block may not be empty");
459 + const last = instructions.pop()!;
460 + invariant(
461 + last.lvalue === null,
462 + "Expected value block to end in a value, not an assignment"
463 + );
464 + lastValue = { value: last.value, id: last.id };
465 + }
466 + for (const instr of instructions) {
467 + const value = this.visitor.visitValue(instr.value, instr.id);
468 + const item = this.visitor.visitInstruction(instr, value);
469 + this.visitor.appendValueBlock(valueBlock, item);
470 + }
471 + const value = this.visitor.visitValue(lastValue.value, lastValue.id);
472 + return this.visitor.leaveValueBlock(valueBlock, value);
473 + }
474 +
475 + emptyBlock(): TStatement {
476 const block = this.visitor.enterBlock();
477 return this.visitor.leaveBlock(block);
478 }
479
458 - visitBreak(block: BlockId): TItem | null {
480 + visitBreak(block: BlockId): TStatement | null {
481 const target = this.cx.getBreakTarget(block);
482 if (target === null) {
483 // TODO: we should always have a target
@@ -477,7 +499,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
499 }
500 }
501
480 - visitContinue(block: BlockId): TItem | null {
502 + visitContinue(block: BlockId): TStatement | null {
503 const target = this.cx.getContinueTarget(block);
504 invariant(
505 target !== null,
@@ -746,19 +768,62 @@ type ControlFlowTarget =
768 *
769 * TBlock = representation of a list of statements
770 * TValue = represenation of an InstructionValue
749 - * TItem = representation of an Instruction
771 + * TStatement = representation of an Instruction
772 * TCase = representation of a switch case
773 */
752 -export interface Visitor<TBlock, TValue, TItem, TCase> {
774 +export interface Visitor<
775 + TBlock,
776 + TInit,
777 + TValueBlock,
778 + TValue,
779 + TStatement,
780 + TCase
781 +> {
782 /**
783 * Must create an "empty" instance of the visitor's represenation for
784 * the contents of a block.
785 */
786 enterBlock(): TBlock;
787
759 - enterValueBlock(): TBlock;
788 + /**
789 + * Appends an item onto the given block, with an optional label. The label
790 + * indicates that a break/continue will proceed to code *after* the given item.
791 + */
792 + appendBlock(block: TBlock, item: TStatement, label?: BlockId): void;
793
761 - leaveValueBlock(block: TBlock, value: TValue): TValue;
794 + /**
795 + * Converts the visitor's block representation into the representation of a
796 + * block item, simultaneously "closing" the given block. The block will no
797 + * longer be modified by the visitor driver.
798 + */
799 + leaveBlock(block: TBlock): TStatement;
800 +
801 + /**
802 + * Must create an "empty" instance of the visitor's representation for a value
803 + * block, which can generally contain only expressions but not statements. The
804 + * currently active parent block is passed as an argument.
805 + *
806 + * The exception is the for initializer, which may contain declarations but not
807 + * other statement types.
808 + */
809 + enterValueBlock(block: TBlock): TValueBlock;
810 +
811 + /**
812 + * Appends an item onto the given value block.
813 + */
814 + appendValueBlock(block: TValueBlock, item: TStatement): void;
815 +
816 + /**
817 + * Converts the visitor's value block (and final value) to the visitor's
818 + * value representation.
819 + */
820 + leaveValueBlock(block: TValueBlock, value: TValue): TValue;
821 +
822 + enterInitBlock(block: TBlock): TValueBlock;
823 +
824 + appendInitBlock(block: TValueBlock, item: TStatement): void;
825 +
826 + leaveInitBlock(block: TValueBlock): TInit;
827
828 /**
829 * Convert an InstructionValue into the visitor's own representation
@@ -770,7 +835,7 @@ export interface Visitor<TBlock, TValue, TItem, TCase> {
835 * Convert an Instruction into the visitor's own representation of
836 * a block item.
837 */
773 - visitInstruction(instruction: Instruction, value: TValue): TItem;
838 + visitInstruction(instruction: Instruction, value: TValue): TStatement;
839
840 /**
841 * Called when a terminal is reached, before processing any of its
@@ -783,7 +848,7 @@ export interface Visitor<TBlock, TValue, TItem, TCase> {
848 * have to be emitted — to the visitor's representation. The visitor
849 * can choose to return null if this does not need to be represented.
850 */
786 - visitImplicitTerminal(): TItem | null;
851 + visitImplicitTerminal(): TStatement | null;
852
853 /**
854 * Converts a terminal into the visitor's own representation of a block
@@ -791,50 +856,39 @@ export interface Visitor<TBlock, TValue, TItem, TCase> {
856 * values and block ids will have already been converted into the visitor's
857 * own representations.
858 */
794 - visitTerminal(terminal: BlockTerminal<TBlock, TValue, TItem, TCase>): TItem;
859 + visitTerminal(
860 + terminal: BlockTerminal<TInit, TValue, TStatement, TCase>
861 + ): TStatement;
862
863 /**
864 * Visits a switch case statement, which is collected into a switch terminal
865 * variant.
866 */
800 - visitCase(test: TValue | null, block: TItem): TCase;
801 -
802 - /**
803 - * Appends an item onto the given block, with an optional label. The label
804 - * indicates that a break/continue will proceed to code *after* the given item.
805 - */
806 - appendBlock(block: TBlock, item: TItem, label?: BlockId): void;
807 -
808 - /**
809 - * Converts the visitor's block representation into the representation of a
810 - * block item, simultaneously "closing" the given block. The block will no
811 - * longer be modified by the visitor driver.
812 - */
813 - leaveBlock(block: TBlock): TItem;
867 + visitCase(test: TValue | null, block: TStatement): TCase;
868 }
869
816 -export type BlockTerminal<TBlock, TValue, TItem, TCase> =
870 +export type BlockTerminal<TInit, TValue, TStatement, TCase> =
871 | { kind: "return"; loc: SourceLocation; value: TValue | null }
872 | { kind: "throw"; value: TValue }
873 | {
874 kind: "if";
875 test: TValue;
822 - consequent: TItem;
823 - alternate: TItem | null;
876 + consequent: TStatement;
877 + alternate: TStatement | null;
878 }
879 | { kind: "switch"; test: TValue; cases: Array<TCase> }
880 | {
881 kind: "while";
882 loc: SourceLocation;
883 test: TValue;
830 - loop: TItem;
884 + loop: TStatement;
885 }
886 | {
887 kind: "for";
834 - init: TItem;
888 + init: TInit;
889 test: TValue;
890 update: TValue;
837 - loop: TItem;
891 + loop: TStatement;
892 }
893 | { kind: "break"; label: BlockId | null }
894 | { kind: "continue"; label: BlockId | null };
compiler/forget/src/HIR/InferReactiveScopeDependencies.ts
+8 -1
@@ -35,7 +35,7 @@ export function instructionInScope(
35 }
36
37 class ScopeDependenciesVisitor
38 - implements Visitor<void, InstructionValue, void, void>
38 + implements Visitor<void, void, void, InstructionValue, void, void>
39 {
40 #identifiers: Map<Identifier, InstructionId> = new Map();
41 // Scopes that are currently active at this point in the traversal
@@ -89,6 +89,9 @@ class ScopeDependenciesVisitor
89 case "switch":
90 case "for":
91 case "while": {
92 + if (typeof terminal?.test?.kind !== "string") {
93 + console.log(terminal);
94 + }
95 for (const operand of eachInstructionValueOperand(terminal.test)) {
96 this.#addTerminalDependency(operand);
97 }
@@ -176,11 +179,15 @@ class ScopeDependenciesVisitor
179
180 enterBlock(): void {}
181 enterValueBlock(): void {}
182 + enterInitBlock(block: void): void {}
183 visitImplicitTerminal(): void | null {}
184 visitCase(test: InstructionValue, block: void): void {}
185 appendBlock(block: void, item: void, label?: BlockId | undefined): void {}
186 + appendValueBlock(block: void, item: void): void {}
187 + appendInitBlock(block: void, item: void): void {}
188 leaveBlock(block: void): void {}
189 leaveValueBlock(block: void, value: InstructionValue): InstructionValue {
190 return value;
191 }
192 + leaveInitBlock(block: void): void {}
193 }
compiler/forget/src/HIR/InferReactiveScopes.ts
+47 -11
@@ -15,10 +15,13 @@ import {
15 InstructionId,
16 InstructionValue,
17 makeInstructionId,
18 + MutableRange,
19 ReactiveScope,
20 ScopeId,
21 } from "./HIR";
22 import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor";
23 +import { log } from "./logger";
24 +import { printFunction } from "./PrintHIR";
25 import {
26 eachInstructionOperand,
27 eachInstructionValueOperand,
@@ -120,6 +123,10 @@ export function inferReactiveScopes(fn: HIRFunction) {
123 mergeScopesWithIdenticalRanges(fn);
124
125 visitTree(fn, new AlignReactiveScopesToBlockScopeRangeVisitor());
126 + log(
127 + () =>
128 + `AlignReactiveScopesToBlockScopeRangeVisitor:\n${printFunction(fn)}\n\n`
129 + );
130 visitTree(fn, new MergeOverlappingReactiveScopesVisitor());
131 }
132
@@ -158,10 +165,8 @@ type ShadowableReactiveScope = {
165 shadowedBy: ReactiveScope | null;
166 };
167
161 -// maybe we just merge cases of interleaving when at the same block scope - eh fine?
162 -// then we can look at scopes that start at the same block scope and overlap
168 class MergeOverlappingReactiveScopesVisitor
164 - implements Visitor<void, void, void, void>
169 + implements Visitor<void, void, void, void, void, void>
170 {
171 scopes: Array<BlockScope> = [];
172 seenScopes: Set<ScopeId> = new Set();
@@ -257,6 +262,12 @@ class MergeOverlappingReactiveScopesVisitor
262 enterValueBlock(): void {
263 this.enterBlock();
264 }
265 + enterInitBlock(block: void): void {
266 + this.enterBlock();
267 + }
268 + leaveInitBlock(block: void): void {
269 + this.leaveBlock();
270 + }
271 leaveValueBlock(block: void, value: void): void {
272 this.leaveBlock();
273 }
@@ -290,6 +301,8 @@ class MergeOverlappingReactiveScopesVisitor
301 visitTerminal(terminal: BlockTerminal<void, void, void, void>): void {}
302 visitCase(test: void | null, block: void): void {}
303 appendBlock(block: void, item: void, label?: BlockId | undefined): void {}
304 + appendValueBlock(block: void, item: void): void {}
305 + appendInitBlock(block: void, item: void): void {}
306 leaveBlock(block: void): void {
307 this.scopes.pop();
308 if (this.scopes.length === 0) {
@@ -313,9 +326,13 @@ type PendingReactiveScope = { active: boolean; scope: ReactiveScope };
326
327 /**
328 * Aligns scopes to block scope boundaries.
329 + *
330 + * TODO @josephsavona this algorithm isn't quite right. we need to ensure that
331 + * reactive scopes can only be closed (end updated) at the same block scope as they
332 + * were opened (start encountered).
333 */
334 class AlignReactiveScopesToBlockScopeRangeVisitor
318 - implements Visitor<void, void, void, void>
335 + implements Visitor<void, void, void, void, void, void>
336 {
337 // For each block scope (outer array) stores a list of ReactiveScopes that start
338 // in that block scope.
@@ -348,6 +365,8 @@ class AlignReactiveScopesToBlockScopeRangeVisitor
365 this.blockScopes.push([]);
366 }
367
368 + appendBlock(block: void, item: void, label?: BlockId | undefined): void {}
369 +
370 leaveBlock(block: void): void {
371 const lastScope = this.blockScopes.pop();
372 invariant(
@@ -364,12 +383,21 @@ class AlignReactiveScopesToBlockScopeRangeVisitor
383 enterValueBlock(): void {
384 this.enterBlock();
385 }
367 -
386 + appendValueBlock(block: void, item: void): void {}
387 leaveValueBlock(block: void, value: void): void {
369 - this.leaveBlock();
388 + this.leaveBlock(block);
389 + }
390 +
391 + enterInitBlock(block: void): void {
392 + this.enterBlock();
393 + }
394 + appendInitBlock(block: void, item: void): void {}
395 + leaveInitBlock(block: void): void {
396 + this.leaveBlock(block);
397 }
398
399 visitInstruction(instruction: Instruction, value: void): void {
400 + this.visitId(instruction.id);
401 const scope = getInstructionScope(instruction);
402 if (scope !== null) {
403 if (!this.seenScopes.has(scope.id)) {
@@ -381,8 +409,6 @@ class AlignReactiveScopesToBlockScopeRangeVisitor
409 });
410 }
411 }
384 -
385 - this.visitId(instruction.id);
412 }
413
414 visitTerminalId(id: InstructionId): void {
@@ -396,18 +422,28 @@ class AlignReactiveScopesToBlockScopeRangeVisitor
422 // no-ops
423 visitValue(value: InstructionValue): void {}
424 visitCase(test: void | null, block: void): void {}
399 - appendBlock(block: void, item: void, label?: BlockId | undefined): void {}
425 }
426
427 function getInstructionScope(instr: Instruction): ReactiveScope | null {
403 - if (instr.lvalue !== null && instr.lvalue.place.identifier.scope !== null) {
428 + if (
429 + instr.lvalue !== null &&
430 + instr.lvalue.place.identifier.scope !== null &&
431 + isActive(instr, instr.lvalue.place.identifier.scope.range)
432 + ) {
433 return instr.lvalue.place.identifier.scope;
434 } else {
435 for (const operand of eachInstructionOperand(instr)) {
407 - if (operand.identifier.scope !== null) {
436 + if (
437 + operand.identifier.scope !== null &&
438 + isActive(instr, operand.identifier.scope.range)
439 + ) {
440 return operand.identifier.scope;
441 }
442 }
443 }
444 return null;
445 }
446 +
447 +function isActive(instr: Instruction, range: MutableRange): boolean {
448 + return instr.id >= range.start && instr.id < range.end;
449 +}
compiler/forget/src/HIR/PrintHIRTree.ts
+23 -1
@@ -25,7 +25,17 @@ export function printHIRTree(fn: HIRFunction): string {
25 return visitTree(fn, new PrintVisitor());
26 }
27
28 -class PrintVisitor implements Visitor<Array<string>, string, string, string> {
28 +class PrintVisitor
29 + implements
30 + Visitor<
31 + Array<string>,
32 + Array<string>,
33 + Array<string>,
34 + string,
35 + string,
36 + string
37 + >
38 +{
39 depth: number = 0; // for indentation
40
41 enterBlock(): string[] {
@@ -38,6 +48,12 @@ class PrintVisitor implements Visitor<Array<string>, string, string, string> {
48 leaveValueBlock(block: string[], value: string): string {
49 return this.leaveBlock(block);
50 }
51 + enterInitBlock(block: string[]): string[] {
52 + return this.enterBlock();
53 + }
54 + leaveInitBlock(block: string[]): string[] {
55 + return block;
56 + }
57 visitValue(value: InstructionValue): string {
58 return printMixedHIR(value);
59 }
@@ -142,6 +158,12 @@ class PrintVisitor implements Visitor<Array<string>, string, string, string> {
158 block.push(`${prefix}bb${label}:`);
159 }
160 }
161 + appendValueBlock(block: string[], item: string): void {
162 + this.appendBlock(block, item);
163 + }
164 + appendInitBlock(block: string[], item: string): void {
165 + this.appendBlock(block, item);
166 + }
167 leaveBlock(block: string[]): string {
168 this.depth--;
169 const prefix = " ".repeat(this.depth);
compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md
+3 -2
@@ -20,7 +20,7 @@ bb0:
20 [2] For init=bb3 test=bb1 loop=bb5 update=bb4 fallthrough=bb2
21 bb3:
22 predecessor blocks: bb0
23 - [3] Const mutate i$8_@1[3:13] = 0
23 + [3] Const mutate i$8_@1[3:5] = 0
24 [4] Goto bb1
25 bb1:
26 predecessor blocks: bb3 bb4
@@ -40,6 +40,7 @@ bb2:
40 predecessor blocks: bb1
41 [13] Return read x$7_@0
42 scope3 [6:8]:
43 + - dependency: read i$8_@1
44 - dependency: read $9_@2
45 ```
46
@@ -56,7 +57,7 @@ flowchart TB
57 end
58 subgraph bb3
59 bb3_instrs["
59 - [3] Const mutate i$8_@1[3:13] = 0
60 + [3] Const mutate i$8_@1[3:5] = 0
61 "]
62 bb3_instrs --> bb3_terminal(["Goto"])
63 end