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