@samitouri / QOS-React-2 / commits / 748992d508

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 }