Flatten scopes within loops
We can't independently memoize values created within a loop, so this pass flattens scopes within loops. Right now this just flattens the scope away without propagating any dependency (or output) information, follow-ups will extend it to do that.
Joe Savona committed
Dec 14, 2022 at 08:42 UTC
5f3dc0b6c7691c5213f29fd6bd52a29dec1c3e4c
3 files changed
+147
-3
compiler/forget/src/HIR/FlattenReactiveLoops.ts
new
+144
@@ -0,0 +1,144 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import { assertExhaustive } from "../Common/utils";
9
+import { ReactiveBasicBlock, ReactiveBlock, ReactiveFunction } from "./HIR";
10
+
11
+/**
12
+ * Given a reactive function, flattens any scopes contained within a loop construct.
13
+ * We won't initially support memoization within loops though this is possible in the future.
14
+ */
15
+export function flattenReactiveLoops(fn: ReactiveFunction): void {
16
+ visit(fn.body, false);
17
+}
18
+
19
+function visit(block: ReactiveBasicBlock, shouldFlatten: boolean): void {
20
+ let i = 0;
21
+ while (i < block.length) {
22
+ const item = block[i]!;
23
+ switch (item.kind) {
24
+ case "block": {
25
+ if (shouldFlatten) {
26
+ const successors = block.splice(i + 1);
27
+ block.pop(); // remove the current element
28
+ flatten(item, block);
29
+ i = block.length;
30
+ block.push(...successors);
31
+ } else {
32
+ visit(item.instructions, false);
33
+ i++;
34
+ }
35
+ break;
36
+ }
37
+ case "instruction": {
38
+ i++;
39
+ break;
40
+ }
41
+ case "terminal": {
42
+ const terminal = item.terminal;
43
+ switch (terminal.kind) {
44
+ case "break":
45
+ case "continue":
46
+ case "return":
47
+ case "throw": {
48
+ break;
49
+ }
50
+ case "for": {
51
+ visit(terminal.loop, true);
52
+ break;
53
+ }
54
+ case "while": {
55
+ visit(terminal.loop, true);
56
+ break;
57
+ }
58
+ case "if": {
59
+ visit(terminal.consequent, shouldFlatten);
60
+ if (terminal.alternate !== null) {
61
+ visit(terminal.alternate, shouldFlatten);
62
+ }
63
+ break;
64
+ }
65
+ case "switch": {
66
+ for (const case_ of terminal.cases) {
67
+ if (case_.block !== undefined) {
68
+ visit(case_.block, shouldFlatten);
69
+ }
70
+ }
71
+ break;
72
+ }
73
+ default: {
74
+ assertExhaustive(
75
+ terminal,
76
+ `Unexpected terminal kind '${(terminal as any).kind}'`
77
+ );
78
+ }
79
+ }
80
+ i++;
81
+ break;
82
+ }
83
+ default: {
84
+ assertExhaustive(item, `Unexpected item`);
85
+ }
86
+ }
87
+ }
88
+}
89
+
90
+function flatten(scope: ReactiveBlock, block: ReactiveBasicBlock): void {
91
+ for (const item of scope.instructions) {
92
+ switch (item.kind) {
93
+ case "block": {
94
+ flatten(item, block);
95
+ break;
96
+ }
97
+ case "terminal": {
98
+ const terminal = item.terminal;
99
+ switch (terminal.kind) {
100
+ case "break":
101
+ case "continue":
102
+ case "return":
103
+ case "throw": {
104
+ break;
105
+ }
106
+ case "for": {
107
+ visit(terminal.loop, true);
108
+ break;
109
+ }
110
+ case "while": {
111
+ visit(terminal.loop, true);
112
+ break;
113
+ }
114
+ case "if": {
115
+ visit(terminal.consequent, true);
116
+ if (terminal.alternate !== null) {
117
+ visit(terminal.alternate, true);
118
+ }
119
+ break;
120
+ }
121
+ case "switch": {
122
+ for (const case_ of terminal.cases) {
123
+ if (case_.block !== undefined) {
124
+ visit(case_.block, true);
125
+ }
126
+ }
127
+ break;
128
+ }
129
+ default: {
130
+ assertExhaustive(
131
+ terminal,
132
+ `Unexpected terminal kind '${(terminal as any).kind}'`
133
+ );
134
+ }
135
+ }
136
+ block.push(item);
137
+ break;
138
+ }
139
+ default: {
140
+ block.push(item);
141
+ }
142
+ }
143
+ }
144
+}
compiler/forget/src/__tests__/fixtures/hir/component.expect.md
+1
-3
@@ -157,9 +157,7 @@ function Component(
157
}
158
[22] Call mutate seen$33_@0.add(mutate item$40_@0:TPrimitive)
159
[23] Const mutate $49:TPrimitive = "div"
160
- scope @3 [24:25] deps=[read $49:TPrimitive] {
161
- [24] Const mutate $50_@3 = JSX <read $49:TPrimitive>{freeze item$40_@0:TPrimitive}</read $49:TPrimitive>
162
- }
160
+ [24] Const mutate $50_@3 = JSX <read $49:TPrimitive>{freeze item$40_@0:TPrimitive}</read $49:TPrimitive>
161
[25] Call mutate renderedItems$32_@0.push(read $50_@3)
162
[26] Const mutate $55:TPrimitive = Binary read renderedItems$32_@0.length >= read max$35_@2:TPrimitive
163
if (read $55:TPrimitive) {
compiler/forget/src/__tests__/hir-test.ts
+2
@@ -15,6 +15,7 @@ import invariant from "invariant";
15
import path from "path";
16
import prettier from "prettier";
17
import { buildReactiveFunction } from "../HIR/BuildReactiveFunction";
18
+import { flattenReactiveLoops } from "../HIR/FlattenReactiveLoops";
19
import { toggleLogging } from "../HIR/logger";
20
import run from "../HIR/Pipeline";
21
import { printFunction } from "../HIR/PrintHIR";
@@ -148,6 +149,7 @@ function transform(text: string, file: string): Array<TestOutput> {
149
});
150
151
const reactiveFunction = buildReactiveFunction(ir);
152
+ flattenReactiveLoops(reactiveFunction);
153
const scopes = printReactiveFunction(reactiveFunction);
154
155
const textHIR = printFunction(ir);