Adjust scope ranges to block boundaries
Visits the HIR as a tree and updates mutable ranges to ensure their range end is aligned with the block in which the scope is declared: ```javascript function foo(cond, a) { ⌵ original scope ⌵ expanded scope const x = []; ⌝ ⌝ if (cond) { ⎮ ⎮ ... ⎮ ⎮ x.push(a); ⌟ ⎮ ... ⎮ } ⎮ ... ⌟ } ``` The implementation tracks the block in which each scope "starts" (first instruction with an operand in that scope), and then finds the first instruction at that block (or a parent) which is after the scope's end.
Joe Savona committed
Nov 22, 2022 at 08:03 UTC
1976fd03b4ae71c97e375f0e74d094a0a9088d79
27 files changed
+450
-84
compiler/forget/src/HIR/HIRTreeVisitor.ts
+51
-26
@@ -14,6 +14,7 @@ import {
14
HIR,
15
HIRFunction,
16
Instruction,
17
+ InstructionId,
18
InstructionValue,
19
Place,
20
SourceLocation,
@@ -71,6 +72,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
72
this.visitor.visitTerminal({
73
kind: "return",
74
value,
75
+ id: terminal.id,
76
})
77
);
78
break;
@@ -82,6 +84,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
84
this.visitor.visitTerminal({
85
kind: "throw",
86
value,
87
+ id: terminal.id,
88
})
89
);
90
break;
@@ -105,7 +108,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
108
109
let consequent: TItem | null = null;
110
if (this.cx.isScheduled(terminal.consequent)) {
108
- consequent = this.visitBreak(terminal.consequent);
111
+ consequent = this.visitBreak(terminal.consequent, null);
112
} else {
113
consequent = this.traverseBlock(
114
this.cx.ir.blocks.get(terminal.consequent)!
@@ -115,7 +118,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
118
let alternate: TItem | null = null;
119
if (alternateId !== null) {
120
if (this.cx.isScheduled(alternateId)) {
118
- alternate = this.visitBreak(alternateId);
121
+ alternate = this.visitBreak(alternateId, null);
122
} else {
123
alternate = this.traverseBlock(this.cx.ir.blocks.get(alternateId)!);
124
}
@@ -130,6 +133,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
133
test,
134
consequent: consequent ?? this.emptyBlock(),
135
alternate: alternate,
136
+ id: terminal.id,
137
}),
138
`bb${fallthroughId}` //
139
);
@@ -142,6 +146,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
146
test,
147
consequent: consequent ?? this.emptyBlock(),
148
alternate: alternate,
149
+ id: terminal.id,
150
})
151
);
152
}
@@ -169,7 +174,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
174
// that are already scheduled. emit as follows:
175
// - if the block is for another case branch, don't emit a break and fall-through
176
// - else, emit an explicit break.
172
- const break_ = this.visitBreak(case_.block);
177
+ const break_ = this.visitBreak(case_.block, null);
178
if (
179
index === 0 &&
180
break_ === null &&
@@ -201,14 +206,24 @@ class Driver<TBlock, TValue, TItem, TCase> {
206
if (fallthroughId !== null) {
207
this.visitor.appendBlock(
208
blockValue,
204
- this.visitor.visitTerminal({ kind: "switch", test, cases }),
209
+ this.visitor.visitTerminal({
210
+ kind: "switch",
211
+ test,
212
+ cases,
213
+ id: terminal.id,
214
+ }),
215
`bb${fallthroughId}`
216
);
217
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
218
} else {
219
this.visitor.appendBlock(
220
blockValue,
211
- this.visitor.visitTerminal({ kind: "switch", test, cases })
221
+ this.visitor.visitTerminal({
222
+ kind: "switch",
223
+ test,
224
+ cases,
225
+ id: terminal.id,
226
+ })
227
);
228
}
229
break;
@@ -229,11 +244,6 @@ class Driver<TBlock, TValue, TItem, TCase> {
244
// "Expected test to produce only temporaries"
245
// );
246
const testValue = this.visitPlace(testTerminal.test);
232
- invariant(
233
- testValue != null,
234
- "Expected test to produce a temporary value"
235
- );
236
-
247
const fallthroughId =
248
terminal.fallthrough !== null &&
249
!this.cx.isScheduled(terminal.fallthrough)
@@ -255,7 +265,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
265
if (loopId) {
266
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
267
} else {
258
- const break_ = this.visitBreak(terminal.loop);
268
+ const break_ = this.visitBreak(terminal.loop, terminal.id);
269
invariant(
270
break_ !== null,
271
"If loop body is already scheduled it must be a break"
@@ -274,6 +284,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
284
loc: terminal.loc,
285
test: testValue,
286
loop: loopBody,
287
+ id: terminal.id,
288
}),
289
`bb${fallthroughId}`
290
);
@@ -286,6 +297,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
297
loc: terminal.loc,
298
test: testValue,
299
loop: loopBody,
300
+ id: terminal.id,
301
})
302
);
303
}
@@ -294,14 +306,14 @@ class Driver<TBlock, TValue, TItem, TCase> {
306
case "goto": {
307
switch (terminal.variant) {
308
case GotoVariant.Break: {
297
- const break_ = this.visitBreak(terminal.block);
309
+ const break_ = this.visitBreak(terminal.block, terminal.id);
310
if (break_ !== null) {
311
this.visitor.appendBlock(blockValue, break_);
312
}
313
break;
314
}
315
case GotoVariant.Continue: {
304
- const continue_ = this.visitContinue(terminal.block);
316
+ const continue_ = this.visitContinue(terminal.block, terminal.id);
317
if (continue_ !== null) {
318
this.visitor.appendBlock(blockValue, continue_);
319
}
@@ -327,7 +339,7 @@ class Driver<TBlock, TValue, TItem, TCase> {
339
return this.visitor.leaveBlock(block);
340
}
341
330
- visitBreak(block: BlockId): TItem | null {
342
+ visitBreak(block: BlockId, id: InstructionId | null): TItem | null {
343
const target = this.cx.getBreakTarget(block);
344
if (target === null) {
345
// TODO: we should always have a target
@@ -335,21 +347,22 @@ class Driver<TBlock, TValue, TItem, TCase> {
347
}
348
switch (target.type) {
349
case "implicit": {
338
- return this.visitor.visitImplicitTerminal();
350
+ return this.visitor.visitImplicitTerminal(id);
351
}
352
case "unlabeled": {
341
- return this.visitor.visitTerminal({ kind: "break", label: null });
353
+ return this.visitor.visitTerminal({ kind: "break", label: null, id });
354
}
355
case "labeled": {
356
return this.visitor.visitTerminal({
357
kind: "break",
358
label: `bb${target.block}`,
359
+ id,
360
});
361
}
362
}
363
}
364
352
- visitContinue(block: BlockId): TItem | null {
365
+ visitContinue(block: BlockId, id: InstructionId): TItem | null {
366
const target = this.cx.getContinueTarget(block);
367
invariant(
368
target !== null,
@@ -360,13 +373,18 @@ class Driver<TBlock, TValue, TItem, TCase> {
373
return this.visitor.visitTerminal({
374
kind: "continue",
375
label: `bb${target.block}`,
376
+ id,
377
});
378
}
379
case "unlabeled": {
366
- return this.visitor.visitTerminal({ kind: "continue", label: null });
380
+ return this.visitor.visitTerminal({
381
+ kind: "continue",
382
+ label: null,
383
+ id,
384
+ });
385
}
386
case "implicit": {
369
- return this.visitor.visitImplicitTerminal();
387
+ return this.visitor.visitImplicitTerminal(id);
388
}
389
default: {
390
assertExhaustive(
@@ -642,7 +660,7 @@ export interface Visitor<TBlock, TValue, TItem, TCase> {
660
* have to be emitted — to the visitor's representation. The visitor
661
* can choose to return null if this does not need to be represented.
662
*/
645
- visitImplicitTerminal(): TItem | null;
663
+ visitImplicitTerminal(id: InstructionId | null): TItem | null;
664
665
/**
666
* Converts a terminal into the visitor's own representation of a block
@@ -673,15 +691,22 @@ export interface Visitor<TBlock, TValue, TItem, TCase> {
691
}
692
693
export type BlockTerminal<TBlock, TValue, TItem, TCase> =
676
- | { kind: "return"; value: TValue | null }
677
- | { kind: "throw"; value: TValue }
694
+ | { kind: "return"; value: TValue | null; id: InstructionId }
695
+ | { kind: "throw"; value: TValue; id: InstructionId }
696
| {
697
kind: "if";
698
test: TValue;
699
consequent: TItem;
700
alternate: TItem | null;
701
+ id: InstructionId;
702
+ }
703
+ | { kind: "switch"; test: TValue; cases: Array<TCase>; id: InstructionId }
704
+ | {
705
+ kind: "while";
706
+ loc: SourceLocation;
707
+ test: TValue;
708
+ loop: TItem;
709
+ id: InstructionId;
710
}
684
- | { kind: "switch"; test: TValue; cases: Array<TCase> }
685
- | { kind: "while"; loc: SourceLocation; test: TValue; loop: TItem }
686
- | { kind: "break"; label: string | null }
687
- | { kind: "continue"; label: string | null };
711
+ | { kind: "break"; label: string | null; id: InstructionId | null }
712
+ | { kind: "continue"; label: string | null; id: InstructionId };
compiler/forget/src/HIR/InferReactiveScopes.ts
+111
-4
@@ -5,7 +5,17 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { HIRFunction } from "./HIR";
8
+import invariant from "invariant";
9
+import {
10
+ HIRFunction,
11
+ Instruction,
12
+ InstructionId,
13
+ InstructionValue,
14
+ ReactiveScope,
15
+ ScopeId,
16
+} from "./HIR";
17
+import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor";
18
+import { eachInstructionOperand } from "./visitors";
19
20
/**
21
* This is a second (final) stage of constructing reactive scopes. Prior to this pass,
@@ -19,7 +29,7 @@ import { HIRFunction } from "./HIR";
29
*
30
* This pass refines the reactive scopes as follows:
31
*
22
- * ## Expanding each reactive scope to align with control-flow boundaries
32
+ * ## Expanding each reactive scope to align with control-flow boundaries (DONE)
33
*
34
* This corresponds with the shape of the AST: a scope that extends into an if consequent
35
* would expand across the alternate branch, A scope that extends partway into an if
@@ -38,7 +48,7 @@ import { HIRFunction } from "./HIR";
48
* }
49
* ```
50
*
41
- * ## Merging (some) overlapping reactive scopes
51
+ * ## Merging (some) overlapping reactive scopes (TODO)
52
*
53
* Two scopes overlap if there is one or more instruction that is inside the range
54
* of both scopes. In general, overlapping scopes are merged togther. The only
@@ -96,4 +106,101 @@ import { HIRFunction } from "./HIR";
106
* Then we would see that 'x' is active, but that it is shadowed. The two scopes would have
107
* to be merged.
108
*/
99
-export function inferReactiveScopes(fn: HIRFunction) {}
109
+export function inferReactiveScopes(fn: HIRFunction) {
110
+ const visitor = new ScopeVisitor();
111
+ visitTree(fn, visitor);
112
+}
113
+
114
+type PendingReactiveScope = ReactiveScope & { active: boolean };
115
+
116
+class ScopeVisitor implements Visitor<void, void, void, void> {
117
+ // For each block scope (outer array) stores a list of ReactiveScopes that start
118
+ // in that block scope.
119
+ blockScopes: Array<Array<PendingReactiveScope>> = [];
120
+
121
+ // ReactiveScopes whose declaring block scope has ended but may still need to
122
+ // be "closed" (ie have their range.end be updated). A given scope can be in
123
+ // blockScopes OR this array but not both.
124
+ unclosedScopes: Array<PendingReactiveScope> = [];
125
+
126
+ // Set of all scope ids that have been seen so far, regardless of which of
127
+ // the above data structures they're in, to avoid tracking the same scope twice.
128
+ seenScopes: Set<ScopeId> = new Set();
129
+
130
+ visitId(id: InstructionId) {
131
+ const currentScopes = this.blockScopes[this.blockScopes.length - 1]!;
132
+ const scopes = [...currentScopes, ...this.unclosedScopes];
133
+ for (const scope of scopes) {
134
+ if (!scope.active) {
135
+ continue;
136
+ }
137
+ if (id >= scope.range.end) {
138
+ scope.active = false;
139
+ scope.range.end = id;
140
+ }
141
+ }
142
+ }
143
+
144
+ enterBlock(): void {
145
+ this.blockScopes.push([]);
146
+ }
147
+
148
+ leaveBlock(block: void): void {
149
+ const lastScope = this.blockScopes.pop();
150
+ invariant(
151
+ lastScope !== undefined,
152
+ "Expected enterBlock/leaveBlock to be called 1:1"
153
+ );
154
+ for (const scope of lastScope) {
155
+ if (scope.active) {
156
+ this.unclosedScopes.push(scope);
157
+ }
158
+ }
159
+ }
160
+
161
+ visitInstruction(instruction: Instruction, value: void): void {
162
+ const scope = getInstructionScope(instruction);
163
+ if (scope !== null) {
164
+ if (!this.seenScopes.has(scope.id)) {
165
+ const currentScopes = this.blockScopes[this.blockScopes.length - 1]!;
166
+ this.seenScopes.add(scope.id);
167
+ currentScopes.push({
168
+ id: scope.id,
169
+ active: true,
170
+ range: scope.range,
171
+ });
172
+ }
173
+ }
174
+
175
+ this.visitId(instruction.id);
176
+ }
177
+
178
+ visitTerminal(terminal: BlockTerminal<void, void, void, void>): void {
179
+ if (terminal.id !== null) {
180
+ this.visitId(terminal.id);
181
+ }
182
+ }
183
+
184
+ visitImplicitTerminal(id: InstructionId | null): void | null {
185
+ if (id !== null) {
186
+ this.visitId(id);
187
+ }
188
+ }
189
+
190
+ // no-ops
191
+ visitValue(value: InstructionValue): void {}
192
+ visitCase(test: void | null, block: void): void {}
193
+ appendBlock(block: void, item: void, label?: string | undefined): void {}
194
+}
195
+
196
+function getInstructionScope(instr: Instruction): ReactiveScope | null {
197
+ if (instr.lvalue !== null && instr.lvalue.place.identifier.scope !== null) {
198
+ return instr.lvalue.place.identifier.scope;
199
+ }
200
+ for (const operand of eachInstructionOperand(instr)) {
201
+ if (operand.identifier.scope !== null) {
202
+ return operand.identifier.scope;
203
+ }
204
+ }
205
+ return null;
206
+}
compiler/forget/src/HIR/Pipeline.ts
+5
@@ -15,6 +15,7 @@ import inferReferenceEffects from "../HIR/InferReferenceEffects";
15
import leaveSSA from "../HIR/LeaveSSA";
16
import codegen from "./Codegen";
17
import { HIRFunction } from "./HIR";
18
+import { inferReactiveScopes } from "./InferReactiveScopes";
19
import { inferReactiveScopeVariables } from "./InferReactiveScopeVariables";
20
21
export type CompilerFlags = {
@@ -22,6 +23,7 @@ export type CompilerFlags = {
23
inferReferenceEffects: boolean;
24
inferMutableRanges: boolean;
25
inferReactiveScopeVariables: boolean;
26
+ inferReactiveScopes: boolean;
27
leaveSSA: boolean;
28
codegen: boolean;
29
};
@@ -50,6 +52,9 @@ export default function (
52
if (flags.inferReactiveScopeVariables) {
53
inferReactiveScopeVariables(ir);
54
}
55
+ if (flags.inferReactiveScopes) {
56
+ inferReactiveScopes(ir);
57
+ }
58
if (flags.leaveSSA) {
59
leaveSSA(ir);
60
}
compiler/forget/src/__tests__/fixtures/hir/alias-while.expect.md
+2
-2
@@ -27,7 +27,7 @@ function mutate(x, y) {}
27
28
```
29
bb0:
30
- [1] Let mutate a$2_@0[0:11] = Object { }
30
+ [1] Let mutate a$2_@0[0:12] = Object { }
31
[2] Let mutate b$3_@0[0:11] = Object { }
32
[3] Let mutate c$4_@0[0:11] = Object { }
33
[4] While test=bb1 loop=bb3 fallthrough=bb2
@@ -57,7 +57,7 @@ flowchart TB
57
%% Basic Blocks
58
subgraph bb0
59
bb0_instrs["
60
- [1] Let mutate a$2_@0[0:11] = Object { }
60
+ [1] Let mutate a$2_@0[0:12] = Object { }
61
[2] Let mutate b$3_@0[0:11] = Object { }
62
[3] Let mutate c$4_@0[0:11] = Object { }
63
"]
compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md
+8
-8
@@ -67,8 +67,8 @@ function mayMutate() {}
67
68
```
69
bb0:
70
- [1] Const mutate a$2_@0[1:5] = Array []
71
- [2] Const mutate b$3_@1[2:8] = Array []
70
+ [1] Const mutate a$2_@0[1:6] = Array []
71
+ [2] Const mutate b$3_@1[2:9] = Array []
72
[3] If (read b$3_@1) then:bb2 else:bb1
73
bb2:
74
predecessor blocks: bb0
@@ -94,8 +94,8 @@ flowchart TB
94
%% Basic Blocks
95
subgraph bb0
96
bb0_instrs["
97
- [1] Const mutate a$2_@0[1:5] = Array []
98
- [2] Const mutate b$3_@1[2:8] = Array []
97
+ [1] Const mutate a$2_@0[1:6] = Array []
98
+ [2] Const mutate b$3_@1[2:9] = Array []
99
"]
100
bb0_instrs --> bb0_terminal(["If (read b$3_@1)"])
101
end
@@ -153,8 +153,8 @@ function Component$0(props$1) {
153
154
```
155
bb0:
156
- [1] Const mutate a$2_@0[1:6] = Array []
157
- [2] Const mutate b$3_@1[0:9] = Array []
156
+ [1] Const mutate a$2_@0[1:7] = Array []
157
+ [2] Const mutate b$3_@1[0:10] = Array []
158
[3] Const mutate $5_@1[0:9] = Call mutate mayMutate$4_@1(mutate b$3_@1)
159
[4] If (read $5_@1) then:bb2 else:bb1
160
bb2:
@@ -181,8 +181,8 @@ flowchart TB
181
%% Basic Blocks
182
subgraph bb0
183
bb0_instrs["
184
- [1] Const mutate a$2_@0[1:6] = Array []
185
- [2] Const mutate b$3_@1[0:9] = Array []
184
+ [1] Const mutate a$2_@0[1:7] = Array []
185
+ [2] Const mutate b$3_@1[0:10] = Array []
186
[3] Const mutate $5_@1[0:9] = Call mutate mayMutate$4_@1(mutate b$3_@1)
187
"]
188
bb0_instrs --> bb0_terminal(["If (read $5_@1)"])
compiler/forget/src/__tests__/fixtures/hir/extend-scopes-if.expect.md
new
+122
@@ -0,0 +1,122 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo(a, b, c) {
6
+ let x = [];
7
+ if (a) {
8
+ if (b) {
9
+ if (c) {
10
+ x.push(0);
11
+ }
12
+ }
13
+ }
14
+ if (a.length) {
15
+ return a;
16
+ }
17
+ return null;
18
+}
19
+
20
+```
21
+
22
+## HIR
23
+
24
+```
25
+bb0:
26
+ [1] Let mutate x$4_@0[1:8] = Array []
27
+ [2] If (read a$1) then:bb2 else:bb1
28
+bb2:
29
+ predecessor blocks: bb0
30
+ [3] If (read b$2) then:bb4 else:bb1
31
+bb4:
32
+ predecessor blocks: bb2
33
+ [4] If (read c$3) then:bb6 else:bb1
34
+bb6:
35
+ predecessor blocks: bb4
36
+ [5] Const mutate $5_@1 = 0
37
+ [6] Call mutate x$4_@0.push(read $5_@1)
38
+ [7] Goto bb1
39
+bb1:
40
+ predecessor blocks: bb6 bb4 bb2 bb0
41
+ [8] If (read a$1.length) then:bb8 else:bb7
42
+bb8:
43
+ predecessor blocks: bb1
44
+ [9] Return read a$1
45
+bb7:
46
+ predecessor blocks: bb1
47
+ [10] Const mutate $6_@2 = null
48
+ [11] Return read $6_@2
49
+```
50
+
51
+### CFG
52
+
53
+```mermaid
54
+flowchart TB
55
+ %% Basic Blocks
56
+ subgraph bb0
57
+ bb0_instrs["
58
+ [1] Let mutate x$4_@0[1:8] = Array []
59
+ "]
60
+ bb0_instrs --> bb0_terminal(["If (read a$1)"])
61
+ end
62
+ subgraph bb2
63
+ bb2_terminal(["If (read b$2)"])
64
+ end
65
+ subgraph bb4
66
+ bb4_terminal(["If (read c$3)"])
67
+ end
68
+ subgraph bb6
69
+ bb6_instrs["
70
+ [5] Const mutate $5_@1 = 0
71
+ [6] Call mutate x$4_@0.push(read $5_@1)
72
+ "]
73
+ bb6_instrs --> bb6_terminal(["Goto"])
74
+ end
75
+ subgraph bb1
76
+ bb1_terminal(["If (read a$1.length)"])
77
+ end
78
+ subgraph bb8
79
+ bb8_terminal(["Return read a$1"])
80
+ end
81
+ subgraph bb7
82
+ bb7_instrs["
83
+ [10] Const mutate $6_@2 = null
84
+ "]
85
+ bb7_instrs --> bb7_terminal(["Return read $6_@2"])
86
+ end
87
+
88
+ %% Jumps
89
+ bb0_terminal -- "then" --> bb2
90
+ bb0_terminal -- "else" --> bb1
91
+ bb2_terminal -- "then" --> bb4
92
+ bb2_terminal -- "else" --> bb1
93
+ bb4_terminal -- "then" --> bb6
94
+ bb4_terminal -- "else" --> bb1
95
+ bb6_terminal --> bb1
96
+ bb1_terminal -- "then" --> bb8
97
+ bb1_terminal -- "else" --> bb7
98
+
99
+```
100
+
101
+## Code
102
+
103
+```javascript
104
+function foo$0(a$1, b$2, c$3) {
105
+ let x$4 = [];
106
+ bb1: if (a$1) {
107
+ if (b$2) {
108
+ if (c$3) {
109
+ x$4.push(0);
110
+ }
111
+ }
112
+ }
113
+
114
+ bb7: if (a$1.length) {
115
+ return a$1;
116
+ }
117
+
118
+ return null;
119
+}
120
+
121
+```
122
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/extend-scopes-if.js
new
+14
@@ -0,0 +1,14 @@
1
+function foo(a, b, c) {
2
+ let x = [];
3
+ if (a) {
4
+ if (b) {
5
+ if (c) {
6
+ x.push(0);
7
+ }
8
+ }
9
+ }
10
+ if (a.length) {
11
+ return a;
12
+ }
13
+ return null;
14
+}
compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md
+2
-2
@@ -27,7 +27,7 @@ function call(x) {}
27
```
28
bb0:
29
[1] Const mutate cond$2_@0 = read props$1.cond
30
- [2] Const mutate x$3_@1[2:8] = read props$1.x
30
+ [2] Const mutate x$3_@1[2:9] = read props$1.x
31
[3] Let mutate a$4_@2 = undefined
32
[4] If (read cond$2_@0) then:bb2 else:bb3
33
bb2:
@@ -54,7 +54,7 @@ flowchart TB
54
subgraph bb0
55
bb0_instrs["
56
[1] Const mutate cond$2_@0 = read props$1.cond
57
- [2] Const mutate x$3_@1[2:8] = read props$1.x
57
+ [2] Const mutate x$3_@1[2:9] = read props$1.x
58
[3] Let mutate a$4_@2 = undefined
59
"]
60
bb0_instrs --> bb0_terminal(["If (read cond$2_@0)"])
compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md
+2
-2
@@ -149,7 +149,7 @@ function Foo$0() {
149
150
```
151
bb0:
152
- [1] Const mutate a$2_@0[0:6] = Call mutate compute$3_@0(read props$1.a)
152
+ [1] Const mutate a$2_@0[0:7] = Call mutate compute$3_@0(read props$1.a)
153
[2] Const mutate b$4_@0[0:6] = Call mutate compute$3_@0(read props$1.b)
154
[3] If (read props$1.c) then:bb2 else:bb1
155
bb2:
@@ -170,7 +170,7 @@ flowchart TB
170
%% Basic Blocks
171
subgraph bb0
172
bb0_instrs["
173
- [1] Const mutate a$2_@0[0:6] = Call mutate compute$3_@0(read props$1.a)
173
+ [1] Const mutate a$2_@0[0:7] = Call mutate compute$3_@0(read props$1.a)
174
[2] Const mutate b$4_@0[0:6] = Call mutate compute$3_@0(read props$1.b)
175
"]
176
bb0_instrs --> bb0_terminal(["If (read props$1.c)"])
compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md
+2
-2
@@ -115,7 +115,7 @@ function Foo$0() {
115
116
```
117
bb0:
118
- [1] Const mutate a$2_@0[0:5] = Call mutate compute$3_@0(read props$1.a)
118
+ [1] Const mutate a$2_@0[0:6] = Call mutate compute$3_@0(read props$1.a)
119
[2] Const mutate b$4_@0[0:5] = Call mutate compute$3_@0(read props$1.b)
120
[3] If (read props$1.c) then:bb2 else:bb1
121
bb2:
@@ -135,7 +135,7 @@ flowchart TB
135
%% Basic Blocks
136
subgraph bb0
137
bb0_instrs["
138
- [1] Const mutate a$2_@0[0:5] = Call mutate compute$3_@0(read props$1.a)
138
+ [1] Const mutate a$2_@0[0:6] = Call mutate compute$3_@0(read props$1.a)
139
[2] Const mutate b$4_@0[0:5] = Call mutate compute$3_@0(read props$1.b)
140
"]
141
bb0_instrs --> bb0_terminal(["If (read props$1.c)"])
compiler/forget/src/__tests__/fixtures/hir/inverted-if.expect.md
new
+83
@@ -0,0 +1,83 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo(a, b, c) {
6
+ let y = [];
7
+ label: if (a) {
8
+ if (b) {
9
+ y.push(c);
10
+ break label;
11
+ }
12
+ }
13
+}
14
+
15
+```
16
+
17
+## HIR
18
+
19
+```
20
+bb0:
21
+ [1] Let mutate y$4_@0[1:6] = Array []
22
+ [2] If (read a$1) then:bb3 else:bb1
23
+bb3:
24
+ predecessor blocks: bb0
25
+ [3] If (read b$2) then:bb5 else:bb1
26
+bb5:
27
+ predecessor blocks: bb3
28
+ [4] Call mutate y$4_@0.push(read c$3)
29
+ [5] Goto bb1
30
+bb1:
31
+ predecessor blocks: bb5 bb3 bb0
32
+ [6] Return
33
+```
34
+
35
+### CFG
36
+
37
+```mermaid
38
+flowchart TB
39
+ %% Basic Blocks
40
+ subgraph bb0
41
+ bb0_instrs["
42
+ [1] Let mutate y$4_@0[1:6] = Array []
43
+ "]
44
+ bb0_instrs --> bb0_terminal(["If (read a$1)"])
45
+ end
46
+ subgraph bb3
47
+ bb3_terminal(["If (read b$2)"])
48
+ end
49
+ subgraph bb5
50
+ bb5_instrs["
51
+ [4] Call mutate y$4_@0.push(read c$3)
52
+ "]
53
+ bb5_instrs --> bb5_terminal(["Goto"])
54
+ end
55
+ subgraph bb1
56
+ bb1_terminal(["Return"])
57
+ end
58
+
59
+ %% Jumps
60
+ bb0_terminal -- "then" --> bb3
61
+ bb0_terminal -- "else" --> bb1
62
+ bb3_terminal -- "then" --> bb5
63
+ bb3_terminal -- "else" --> bb1
64
+ bb5_terminal --> bb1
65
+
66
+```
67
+
68
+## Code
69
+
70
+```javascript
71
+function foo$0(a$1, b$2, c$3) {
72
+ let y$4 = [];
73
+ bb1: if (a$1) {
74
+ if (b$2) {
75
+ y$4.push(c$3);
76
+ }
77
+ }
78
+
79
+ return;
80
+}
81
+
82
+```
83
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/inverted-if.js
new
+9
@@ -0,0 +1,9 @@
1
+function foo(a, b, c) {
2
+ let y = [];
3
+ label: if (a) {
4
+ if (b) {
5
+ y.push(c);
6
+ break label;
7
+ }
8
+ }
9
+}
compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md
+6
-6
@@ -23,7 +23,7 @@ function g() {}
23
24
```
25
bb0:
26
- [1] Const mutate $2_@0[0:6] = Call mutate f$1_@0()
26
+ [1] Const mutate $2_@0[0:7] = Call mutate f$1_@0()
27
[2] If (read $2_@0) then:bb2 else:bb3
28
bb2:
29
predecessor blocks: bb0
@@ -45,7 +45,7 @@ flowchart TB
45
%% Basic Blocks
46
subgraph bb0
47
bb0_instrs["
48
- [1] Const mutate $2_@0[0:6] = Call mutate f$1_@0()
48
+ [1] Const mutate $2_@0[0:7] = Call mutate f$1_@0()
49
"]
50
bb0_instrs --> bb0_terminal(["If (read $2_@0)"])
51
end
@@ -89,7 +89,7 @@ function And$0() {
89
90
```
91
bb0:
92
- [1] Const mutate $2_@0[0:6] = Call mutate f$1_@0()
92
+ [1] Const mutate $2_@0[0:7] = Call mutate f$1_@0()
93
[2] If (read $2_@0) then:bb2 else:bb3
94
bb2:
95
predecessor blocks: bb0
@@ -111,7 +111,7 @@ flowchart TB
111
%% Basic Blocks
112
subgraph bb0
113
bb0_instrs["
114
- [1] Const mutate $2_@0[0:6] = Call mutate f$1_@0()
114
+ [1] Const mutate $2_@0[0:7] = Call mutate f$1_@0()
115
"]
116
bb0_instrs --> bb0_terminal(["If (read $2_@0)"])
117
end
@@ -155,7 +155,7 @@ function Or$0() {
155
156
```
157
bb0:
158
- [1] Const mutate $3_@0[0:8] = Call mutate f$2_@0()
158
+ [1] Const mutate $3_@0[0:9] = Call mutate f$2_@0()
159
[2] Const mutate $4_@1 = null
160
[3] Const mutate $5_@0[0:8] = Binary read $3_@0 != read $4_@1
161
[4] If (read $5_@0) then:bb2 else:bb3
@@ -179,7 +179,7 @@ flowchart TB
179
%% Basic Blocks
180
subgraph bb0
181
bb0_instrs["
182
- [1] Const mutate $3_@0[0:8] = Call mutate f$2_@0()
182
+ [1] Const mutate $3_@0[0:9] = Call mutate f$2_@0()
183
[2] Const mutate $4_@1 = null
184
[3] Const mutate $5_@0[0:8] = Binary read $3_@0 != read $4_@1
185
"]
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md
+2
-2
@@ -106,7 +106,7 @@ bb0:
106
[5] While test=bb1 loop=bb3 fallthrough=bb2
107
bb1:
108
predecessor blocks: bb0 bb4
109
- [6] Const mutate $10_@1 = true
109
+ [6] Const mutate $10_@1[6:17] = true
110
[7] If (read $10_@1) then:bb3 else:bb2
111
bb3:
112
predecessor blocks: bb1
@@ -156,7 +156,7 @@ flowchart TB
156
end
157
subgraph bb1
158
bb1_instrs["
159
- [6] Const mutate $10_@1 = true
159
+ [6] Const mutate $10_@1[6:17] = true
160
"]
161
bb1_instrs --> bb1_terminal(["If (read $10_@1)"])
162
end
compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md
+2
-2
@@ -101,7 +101,7 @@ bb0:
101
[5] While test=bb1 loop=bb3 fallthrough=bb2
102
bb1:
103
predecessor blocks: bb0 bb4
104
- [6] Const mutate $9_@2 = true
104
+ [6] Const mutate $9_@2[6:12] = true
105
[7] If (read $9_@2) then:bb3 else:bb2
106
bb3:
107
predecessor blocks: bb1
@@ -146,7 +146,7 @@ flowchart TB
146
end
147
subgraph bb1
148
bb1_instrs["
149
- [6] Const mutate $9_@2 = true
149
+ [6] Const mutate $9_@2[6:12] = true
150
"]
151
bb1_instrs --> bb1_terminal(["If (read $9_@2)"])
152
end
compiler/forget/src/__tests__/fixtures/hir/reactive-scopes.expect.md
+2
-2
@@ -19,7 +19,7 @@ function f(a, b) {
19
20
```
21
bb0:
22
- [1] Let mutate x$3_@0[1:7] = Array []
22
+ [1] Let mutate x$3_@0[1:8] = Array []
23
[2] Const mutate $4_@1 = 1
24
[3] Const mutate $5_@2 = Binary read a$1.length === read $4_@1
25
[4] If (read $5_@2) then:bb2 else:bb1
@@ -44,7 +44,7 @@ flowchart TB
44
%% Basic Blocks
45
subgraph bb0
46
bb0_instrs["
47
- [1] Let mutate x$3_@0[1:7] = Array []
47
+ [1] Let mutate x$3_@0[1:8] = Array []
48
[2] Const mutate $4_@1 = 1
49
[3] Const mutate $5_@2 = Binary read a$1.length === read $4_@1
50
"]
compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md
+2
-2
@@ -23,7 +23,7 @@ function Component(props) {
23
24
```
25
bb0:
26
- [1] Let mutate x$2_@0[1:6] = Array []
26
+ [1] Let mutate x$2_@0[1:7] = Array []
27
[2] Call mutate x$2_@0.push(read props$1.p0)
28
[3] Let mutate y$3_@0[1:6] = read x$2_@0
29
[4] If (read props$1.p1) then:bb2 else:bb1
@@ -46,7 +46,7 @@ flowchart TB
46
%% Basic Blocks
47
subgraph bb0
48
bb0_instrs["
49
- [1] Let mutate x$2_@0[1:6] = Array []
49
+ [1] Let mutate x$2_@0[1:7] = Array []
50
[2] Call mutate x$2_@0.push(read props$1.p0)
51
[3] Let mutate y$3_@0[1:6] = read x$2_@0
52
"]
compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md
+2
-2
@@ -19,7 +19,7 @@ function foo() {
19
20
```
21
bb0:
22
- [1] Let mutate x$1_@0[1:7] = 1
22
+ [1] Let mutate x$1_@0[1:8] = 1
23
[2] Let mutate y$2_@1 = 2
24
[3] If (read y$2_@1) then:bb2 else:bb3
25
bb2:
@@ -42,7 +42,7 @@ flowchart TB
42
%% Basic Blocks
43
subgraph bb0
44
bb0_instrs["
45
- [1] Let mutate x$1_@0[1:7] = 1
45
+ [1] Let mutate x$1_@0[1:8] = 1
46
[2] Let mutate y$2_@1 = 2
47
"]
48
bb0_instrs --> bb0_terminal(["If (read y$2_@1)"])
compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md
+4
-4
@@ -22,8 +22,8 @@ function foo() {
22
23
```
24
bb0:
25
- [1] Let mutate x$1_@0[1:7] = 1
26
- [2] Let mutate y$2_@1[2:9] = 2
25
+ [1] Let mutate x$1_@0[1:10] = 1
26
+ [2] Let mutate y$2_@1[2:10] = 2
27
[3] Const mutate $3_@2 = 1
28
[4] Const mutate $4_@3 = Binary read x$1_@0 > read $3_@2
29
[5] If (read $4_@3) then:bb2 else:bb3
@@ -48,8 +48,8 @@ flowchart TB
48
%% Basic Blocks
49
subgraph bb0
50
bb0_instrs["
51
- [1] Let mutate x$1_@0[1:7] = 1
52
- [2] Let mutate y$2_@1[2:9] = 2
51
+ [1] Let mutate x$1_@0[1:10] = 1
52
+ [2] Let mutate y$2_@1[2:10] = 2
53
[3] Const mutate $3_@2 = 1
54
[4] Const mutate $4_@3 = Binary read x$1_@0 > read $3_@2
55
"]
compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md
+2
-2
@@ -17,7 +17,7 @@ function foo() {
17
18
```
19
bb0:
20
- [1] Let mutate x$1_@0[1:6] = 1
20
+ [1] Let mutate x$1_@0[1:7] = 1
21
[2] Const mutate $2_@1 = 1
22
[3] Const mutate $3_@2 = Binary read x$1_@0 === read $2_@1
23
[4] If (read $3_@2) then:bb2 else:bb1
@@ -37,7 +37,7 @@ flowchart TB
37
%% Basic Blocks
38
subgraph bb0
39
bb0_instrs["
40
- [1] Let mutate x$1_@0[1:6] = 1
40
+ [1] Let mutate x$1_@0[1:7] = 1
41
[2] Const mutate $2_@1 = 1
42
[3] Const mutate $3_@2 = Binary read x$1_@0 === read $2_@1
43
"]
compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md
+2
-2
@@ -49,7 +49,7 @@ function log$0() {
49
50
```
51
bb0:
52
- [1] Let mutate str$2_@0[1:7] = ""
52
+ [1] Let mutate str$2_@0[1:8] = ""
53
[2] If (read cond$1) then:bb2 else:bb3
54
bb2:
55
predecessor blocks: bb0
@@ -73,7 +73,7 @@ flowchart TB
73
%% Basic Blocks
74
subgraph bb0
75
bb0_instrs["
76
- [1] Let mutate str$2_@0[1:7] = ''
76
+ [1] Let mutate str$2_@0[1:8] = ''
77
"]
78
bb0_instrs --> bb0_terminal(["If (read cond$1)"])
79
end
compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md
+2
-2
@@ -16,7 +16,7 @@ function foo() {
16
17
```
18
bb0:
19
- [1] Let mutate x$1_@0[1:6] = 1
19
+ [1] Let mutate x$1_@0[1:7] = 1
20
[2] Const mutate $2_@1 = 1
21
[3] Const mutate $3_@2 = Binary read x$1_@0 === read $2_@1
22
[4] If (read $3_@2) then:bb2 else:bb1
@@ -36,7 +36,7 @@ flowchart TB
36
%% Basic Blocks
37
subgraph bb0
38
bb0_instrs["
39
- [1] Let mutate x$1_@0[1:6] = 1
39
+ [1] Let mutate x$1_@0[1:7] = 1
40
[2] Const mutate $2_@1 = 1
41
[3] Const mutate $3_@2 = Binary read x$1_@0 === read $2_@1
42
"]
compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md
+4
-4
@@ -17,12 +17,12 @@ function foo() {
17
18
```
19
bb0:
20
- [1] Let mutate x$1_@0 = 1
20
+ [1] Let mutate x$1_@0[1:3] = 1
21
[2] While test=bb1 loop=bb3 fallthrough=bb2
22
bb1:
23
predecessor blocks: bb0 bb3
24
[3] Const mutate $3_@1 = 10
25
- [4] Const mutate $4_@2 = Binary read x$1_@0 < read $3_@1
25
+ [4] Const mutate $4_@2[4:9] = Binary read x$1_@0 < read $3_@1
26
[5] If (read $4_@2) then:bb3 else:bb2
27
bb3:
28
predecessor blocks: bb1
@@ -41,14 +41,14 @@ flowchart TB
41
%% Basic Blocks
42
subgraph bb0
43
bb0_instrs["
44
- [1] Let mutate x$1_@0 = 1
44
+ [1] Let mutate x$1_@0[1:3] = 1
45
"]
46
bb0_instrs --> bb0_terminal(["While"])
47
end
48
subgraph bb1
49
bb1_instrs["
50
[3] Const mutate $3_@1 = 10
51
- [4] Const mutate $4_@2 = Binary read x$1_@0 < read $3_@1
51
+ [4] Const mutate $4_@2[4:9] = Binary read x$1_@0 < read $3_@1
52
"]
53
bb1_instrs --> bb1_terminal(["If (read $4_@2)"])
54
end
compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md
+4
-4
@@ -17,12 +17,12 @@ function foo() {
17
18
```
19
bb0:
20
- [1] Let mutate x$1_@0[0:8] = 1
20
+ [1] Let mutate x$1_@0[0:9] = 1
21
[2] While test=bb1 loop=bb3 fallthrough=bb2
22
bb1:
23
predecessor blocks: bb0 bb3
24
[3] Const mutate $3_@1 = 10
25
- [4] Const mutate $4_@2 = Binary read x$1_@0 < read $3_@1
25
+ [4] Const mutate $4_@2[4:9] = Binary read x$1_@0 < read $3_@1
26
[5] If (read $4_@2) then:bb3 else:bb2
27
bb3:
28
predecessor blocks: bb1
@@ -41,14 +41,14 @@ flowchart TB
41
%% Basic Blocks
42
subgraph bb0
43
bb0_instrs["
44
- [1] Let mutate x$1_@0[0:8] = 1
44
+ [1] Let mutate x$1_@0[0:9] = 1
45
"]
46
bb0_instrs --> bb0_terminal(["While"])
47
end
48
subgraph bb1
49
bb1_instrs["
50
[3] Const mutate $3_@1 = 10
51
- [4] Const mutate $4_@2 = Binary read x$1_@0 < read $3_@1
51
+ [4] Const mutate $4_@2[4:9] = Binary read x$1_@0 < read $3_@1
52
"]
53
bb1_instrs --> bb1_terminal(["If (read $4_@2)"])
54
end
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md
+2
-2
@@ -32,7 +32,7 @@ function Component(props) {
32
33
```
34
bb0:
35
- [1] Let mutate x$2_@0[1:11] = Array []
35
+ [1] Let mutate x$2_@0[1:12] = Array []
36
[2] Let mutate y$3_@0[1:11] = undefined
37
[3] Const mutate $4_@1 = false
38
[4] Const mutate $5_@2 = true
@@ -66,7 +66,7 @@ flowchart TB
66
%% Basic Blocks
67
subgraph bb0
68
bb0_instrs["
69
- [1] Let mutate x$2_@0[1:11] = Array []
69
+ [1] Let mutate x$2_@0[1:12] = Array []
70
[2] Let mutate y$3_@0[1:11] = undefined
71
[3] Const mutate $4_@1 = false
72
[4] Const mutate $5_@2 = true
compiler/forget/src/__tests__/fixtures/hir/switch.expect.md
+2
-2
@@ -28,7 +28,7 @@ function Component(props) {
28
29
```
30
bb0:
31
- [1] Let mutate x$2_@0[1:11] = Array []
31
+ [1] Let mutate x$2_@0[1:12] = Array []
32
[2] Let mutate y$3_@0[1:11] = undefined
33
[3] Const mutate $4_@1 = false
34
[4] Const mutate $5_@2 = true
@@ -61,7 +61,7 @@ flowchart TB
61
%% Basic Blocks
62
subgraph bb0
63
bb0_instrs["
64
- [1] Let mutate x$2_@0[1:11] = Array []
64
+ [1] Let mutate x$2_@0[1:12] = Array []
65
[2] Let mutate y$3_@0[1:11] = undefined
66
[3] Const mutate $4_@1 = false
67
[4] Const mutate $5_@2 = true
compiler/forget/src/__tests__/hir-test.ts
+1
@@ -62,6 +62,7 @@ describe("React Forget (HIR version)", () => {
62
inferMutableRanges: true,
63
leaveSSA: true,
64
inferReactiveScopeVariables: true,
65
+ inferReactiveScopes: true,
66
codegen: true,
67
};
68