@samitouri / QOS-React / commits / 1976fd03b4

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