[compiler] HIR-based FlattenReactiveLoops
Pre the title, this implements an HIR-based version of FlattenReactiveLoops. Another step on the way to HIR-everywhere. ghstack-source-id: e1d166352df6b0725e4c4915a19445437916251f Pull Request resolved: https://github.com/facebook/react/pull/29838
Joe Savona committed
Jun 10, 2024 at 13:22 UTC
43c17d13efc0c356a89a9036f173680ba1c115d2
2 files changed
+87
-8
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+16
-8
@@ -70,7 +70,9 @@ import {
70
} from "../ReactiveScopes";
71
import { alignMethodCallScopes } from "../ReactiveScopes/AlignMethodCallScopes";
72
import { alignReactiveScopesToBlockScopesHIR } from "../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR";
73
+import { flattenReactiveLoopsHIR } from "../ReactiveScopes/FlattenReactiveLoopsHIR";
74
import { pruneAlwaysInvalidatingScopes } from "../ReactiveScopes/PruneAlwaysInvalidatingScopes";
75
+import pruneInitializationDependencies from "../ReactiveScopes/PruneInitializationDependencies";
76
import { stabilizeBlockIds } from "../ReactiveScopes/StabilizeBlockIds";
77
import { eliminateRedundantPhi, enterSSA, leaveSSA } from "../SSA";
78
import { inferTypes } from "../TypeInference";
@@ -91,7 +93,6 @@ import {
93
validatePreservedManualMemoization,
94
validateUseMemo,
95
} from "../Validation";
94
-import pruneInitializationDependencies from "../ReactiveScopes/PruneInitializationDependencies";
96
97
export type CompilerPipelineValue =
98
| { kind: "ast"; name: string; value: CodegenFunction }
@@ -281,6 +282,13 @@ function* runWithEnvironment(
282
});
283
284
assertValidBlockNesting(hir);
285
+
286
+ flattenReactiveLoopsHIR(hir);
287
+ yield log({
288
+ kind: "hir",
289
+ name: "FlattenReactiveLoopsHIR",
290
+ value: hir,
291
+ });
292
}
293
294
const reactiveFunction = buildReactiveFunction(hir);
@@ -320,14 +328,14 @@ function* runWithEnvironment(
328
name: "BuildReactiveBlocks",
329
value: reactiveFunction,
330
});
323
- }
331
325
- flattenReactiveLoops(reactiveFunction);
326
- yield log({
327
- kind: "reactive",
328
- name: "FlattenReactiveLoops",
329
- value: reactiveFunction,
330
- });
332
+ flattenReactiveLoops(reactiveFunction);
333
+ yield log({
334
+ kind: "reactive",
335
+ name: "FlattenReactiveLoops",
336
+ value: reactiveFunction,
337
+ });
338
+ }
339
340
assertScopeInstructionsWithinScopes(reactiveFunction);
341
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoopsHIR.ts
new
+71
@@ -0,0 +1,71 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and 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 { BlockId, HIRFunction, PrunedScopeTerminal } from "../HIR";
9
+import { assertExhaustive, retainWhere } from "../Utils/utils";
10
+
11
+/**
12
+ * Prunes any reactive scopes that are within a loop (for, while, etc). We don't yet
13
+ * support memoization within loops because this would require an extra layer of reconciliation
14
+ * (plus a way to identify values across runs, similar to how we use `key` in JSX for lists).
15
+ * Eventually we may integrate more deeply into the runtime so that we can do a single level
16
+ * of reconciliation, but for now we've found it's sufficient to memoize *around* the loop.
17
+ */
18
+export function flattenReactiveLoopsHIR(fn: HIRFunction): void {
19
+ const activeLoops = Array<BlockId>();
20
+ for (const [, block] of fn.body.blocks) {
21
+ retainWhere(activeLoops, (id) => id !== block.id);
22
+ const { terminal } = block;
23
+ switch (terminal.kind) {
24
+ case "do-while":
25
+ case "for":
26
+ case "for-in":
27
+ case "for-of":
28
+ case "while": {
29
+ activeLoops.push(terminal.fallthrough);
30
+ break;
31
+ }
32
+ case "scope": {
33
+ if (activeLoops.length !== 0) {
34
+ block.terminal = {
35
+ kind: "pruned-scope",
36
+ block: terminal.block,
37
+ fallthrough: terminal.fallthrough,
38
+ id: terminal.id,
39
+ loc: terminal.loc,
40
+ scope: terminal.scope,
41
+ } as PrunedScopeTerminal;
42
+ }
43
+ break;
44
+ }
45
+ case "branch":
46
+ case "goto":
47
+ case "if":
48
+ case "label":
49
+ case "logical":
50
+ case "maybe-throw":
51
+ case "optional":
52
+ case "pruned-scope":
53
+ case "return":
54
+ case "sequence":
55
+ case "switch":
56
+ case "ternary":
57
+ case "throw":
58
+ case "try":
59
+ case "unreachable":
60
+ case "unsupported": {
61
+ break;
62
+ }
63
+ default: {
64
+ assertExhaustive(
65
+ terminal,
66
+ `Unexpected terminal kind \`${(terminal as any).kind}\``
67
+ );
68
+ }
69
+ }
70
+ }
71
+}