@samitouri / QOS-React-2 / commits / 5f3dc0b6c7

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);