LeaveSSA: handle phi as operand to subsequent phi
Fixes https://github.com/facebook/react-forget/pull/858#discussion_r1044626137. The case is ```javascript function foo() { let x$1 = 1; let y = 2; if (y === 2) { x$2 = 3; } x$3 = phi(x$1, x$2) if (y === 3) { x4 = 5; } x$5 = phi(x$3, x$4); y = x$5; } ``` What happens here is that there are two _sequential_ phis for `x`. Previously when we encountered the second phi we would find that there is no `let` declaration for the phi or any of its operands, and create a new one before the second `if`. That's incorrect, these should all merge into a single `x` declaration. We now look up the phi operands to see if they are part of a previous phi, and merge them correctly.
Joseph Savona committed
Dec 9, 2022 at 09:47 UTC
748992d508cefaa555c5465ce17d9e7a68941f55
6 files changed
+66
-19
compiler/forget/src/HIR/LeaveSSA.ts
+3
-2
@@ -78,8 +78,9 @@ export function leaveSSA(fn: HIRFunction) {
78
// options we'll choose it and can reuse the declaration.
79
canonicalId = phi.id;
80
for (const [, operand] of phi.operands) {
81
- if (operand.id < canonicalId.id) {
82
- canonicalId = operand;
81
+ let canonicalOperand = variableMapping.get(operand) ?? operand;
82
+ if (canonicalOperand.id < canonicalId.id) {
83
+ canonicalId = canonicalOperand;
84
}
85
}
86
canonicalId.mutableRange.start = Math.min(
compiler/forget/src/HIR/Pipeline.ts
+25
@@ -18,6 +18,8 @@ import { inferMutableRanges } from "./InferMutableRanges";
18
import { inferReactiveScopeDependencies } from "./InferReactiveScopeDependencies";
19
import { inferReactiveScopes } from "./InferReactiveScopes";
20
import { inferReactiveScopeVariables } from "./InferReactiveScopeVariables";
21
+import { log } from "./logger";
22
+import { printFunction } from "./PrintHIR";
23
24
export type CompilerFlags = {
25
eliminateRedundantPhi: boolean;
@@ -40,29 +42,48 @@ export default function (
42
flags: CompilerFlags
43
): CompilerResult {
44
const env = new Environment();
45
+
46
const ir = lower(func, env);
47
+ logStep("HIR", ir);
48
+
49
enterSSA(ir, env);
50
+ logStep("SSA", ir);
51
+
52
if (flags.eliminateRedundantPhi) {
53
eliminateRedundantPhi(ir);
54
+ logStep("eliminateRedundantPhi", ir);
55
}
56
+
57
if (flags.inferReferenceEffects) {
58
inferReferenceEffects(ir);
59
+ logStep("inferReferenceEffects", ir);
60
}
61
+
62
if (flags.inferMutableRanges) {
63
inferMutableRanges(ir);
64
+ logStep("inferMutableRanges", ir);
65
}
66
+
67
if (flags.leaveSSA) {
68
leaveSSA(ir);
69
+ logStep("leaveSSA", ir);
70
}
71
+
72
if (flags.inferReactiveScopeVariables) {
73
inferReactiveScopeVariables(ir);
74
+ logStep("inferReactiveScopeVariables", ir);
75
}
76
+
77
if (flags.inferReactiveScopes) {
78
inferReactiveScopes(ir);
79
+ logStep("inferReactiveScopes", ir);
80
}
81
+
82
if (flags.inferReactiveScopeDependencies) {
83
inferReactiveScopeDependencies(ir);
84
+ logStep("inferReactiveScopeDependencies", ir);
85
}
86
+
87
if (flags.codegen) {
88
return {
89
ast: codegen(ir),
@@ -72,3 +93,7 @@ export default function (
93
94
return { ast: null, ir: ir };
95
}
96
+
97
+function logStep(step: string, ir: HIRFunction) {
98
+ log(() => `${step}:\n${printFunction(ir)}`);
99
+}
compiler/forget/src/HIR/logger.ts
new
+19
@@ -0,0 +1,19 @@
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
+let ENABLED: boolean = false;
9
+
10
+export function toggleLogging(enabled: boolean) {
11
+ ENABLED = enabled;
12
+}
13
+
14
+export function log(fn: () => string) {
15
+ if (ENABLED) {
16
+ const message = fn();
17
+ process.stdout.write(message.trim() + "\n\n");
18
+ }
19
+}
compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md
+10
-14
@@ -21,28 +21,27 @@ function foo() {
21
22
```
23
bb0:
24
- [1] Let mutate x$7_@0[1:8] = 1
24
+ [1] Let mutate x$7_@0[1:14] = 1
25
[2] Const mutate y$8_@1 = 2
26
[3] Const mutate $9_@2 = 2
27
[4] Const mutate $10_@3 = Binary read y$8_@1 === read $9_@2
28
[5] If (read $10_@3) then:bb2 else:bb1 fallthrough=bb1
29
bb2:
30
predecessor blocks: bb0
31
- [6] Reassign mutate x$7_@0[1:8] = 3
31
+ [6] Reassign mutate x$7_@0[1:14] = 3
32
[7] Goto bb1
33
bb1:
34
predecessor blocks: bb2 bb0
35
[8] Const mutate $12_@4 = 3
36
[9] Const mutate $14_@5 = Binary read y$8_@1 === read $12_@4
37
- [10] Let mutate x$15_@6[1:14] = undefined
37
[10] If (read $14_@5) then:bb4 else:bb3 fallthrough=bb3
38
bb4:
39
predecessor blocks: bb1
41
- [11] Reassign mutate x$15_@6[1:14] = 5
40
+ [11] Reassign mutate x$7_@0[1:14] = 5
41
[12] Goto bb3
42
bb3:
43
predecessor blocks: bb4 bb1
45
- [13] Const mutate y$18_@6[1:14] = read x$15_@6
44
+ [13] Const mutate y$18_@0[1:14] = read x$7_@0
45
[14] Return
46
scope3 [4:5]:
47
- read y$8_@1
@@ -59,7 +58,7 @@ flowchart TB
58
%% Basic Blocks
59
subgraph bb0
60
bb0_instrs["
62
- [1] Let mutate x$7_@0[1:8] = 1
61
+ [1] Let mutate x$7_@0[1:14] = 1
62
[2] Const mutate y$8_@1 = 2
63
[3] Const mutate $9_@2 = 2
64
[4] Const mutate $10_@3 = Binary read y$8_@1 === read $9_@2
@@ -68,7 +67,7 @@ flowchart TB
67
end
68
subgraph bb2
69
bb2_instrs["
71
- [6] Reassign mutate x$7_@0[1:8] = 3
70
+ [6] Reassign mutate x$7_@0[1:14] = 3
71
"]
72
bb2_instrs --> bb2_terminal(["Goto"])
73
end
@@ -76,19 +75,18 @@ flowchart TB
75
bb1_instrs["
76
[8] Const mutate $12_@4 = 3
77
[9] Const mutate $14_@5 = Binary read y$8_@1 === read $12_@4
79
- [10] Let mutate x$15_@6[1:14] = undefined
78
"]
79
bb1_instrs --> bb1_terminal(["If (read $14_@5)"])
80
end
81
subgraph bb4
82
bb4_instrs["
85
- [11] Reassign mutate x$15_@6[1:14] = 5
83
+ [11] Reassign mutate x$7_@0[1:14] = 5
84
"]
85
bb4_instrs --> bb4_terminal(["Goto"])
86
end
87
subgraph bb3
88
bb3_instrs["
91
- [13] Const mutate y$18_@6[1:14] = read x$15_@6
89
+ [13] Const mutate y$18_@0[1:14] = read x$7_@0
90
"]
91
bb3_instrs --> bb3_terminal(["Return"])
92
end
@@ -113,13 +111,11 @@ function foo$0() {
111
x$7 = 3;
112
}
113
116
- let x$15 = undefined;
117
-
114
bb3: if (y$8 === 3) {
119
- x$15 = 5;
115
+ x$7 = 5;
116
}
117
122
- const y$18 = x$15;
118
+ const y$18 = x$7;
119
}
120
121
```
compiler/forget/src/__tests__/hir-test.ts
+5
-1
@@ -14,6 +14,7 @@ import { wasmFolder } from "@hpcc-js/wasm";
14
import invariant from "invariant";
15
import path from "path";
16
import prettier from "prettier";
17
+import { toggleLogging } from "../HIR/logger";
18
import run from "../HIR/Pipeline";
19
import { printFunction } from "../HIR/PrintHIR";
20
import visualizeHIRMermaid from "../HIR/VisualizeHIRMermaid";
@@ -34,7 +35,7 @@ const Pragma_RE = /\/\/\s*@enable\((\w+)\)$/gm;
35
describe("React Forget (HIR version)", () => {
36
generateTestsFromFixtures(
37
path.join(__dirname, "fixtures", "hir"),
37
- (input, file) => {
38
+ (input, file, options) => {
39
const matches = input.matchAll(Pragma_RE);
40
41
for (const match of matches) {
@@ -50,6 +51,9 @@ describe("React Forget (HIR version)", () => {
51
52
let items: Array<[string, string, string]> | null = null;
53
let error: Error | null = null;
54
+ if (options.debug) {
55
+ toggleLogging(options.debug);
56
+ }
57
try {
58
items = transform(input, file);
59
} catch (e) {
compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts
+4
-2
@@ -42,7 +42,7 @@ expect.extend({
42
43
export default function generateTestsFromFixtures(
44
fixturesPath: string,
45
- transform: (input: string, file: any) => string
45
+ transform: (input: string, file: any, options: { debug: boolean }) => string
46
) {
47
const files = fs.readdirSync(fixturesPath);
48
const fixtures = matchInputOutputFixtures(files, fixturesPath);
@@ -87,18 +87,20 @@ export default function generateTestsFromFixtures(
87
}
88
89
let input: string | null = null;
90
+ let debug = false;
91
if (inputFile != null) {
92
input = fs.readFileSync(inputFile, "utf8");
93
const lines = input.split("\n");
94
if (lines[0]!.indexOf("@only") !== -1) {
95
testCommand = test.only;
96
+ debug = true;
97
}
98
}
99
100
testCommand(basename, () => {
101
let receivedOutput;
102
if (input !== null) {
101
- receivedOutput = transform(input, basename);
103
+ receivedOutput = transform(input, basename, { debug });
104
} else {
105
receivedOutput = "<<input deleted>>";
106
}