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 invariant from "invariant";
9
+import { assertExhaustive } from "../Common/utils";
10
+import { BlockId, HIRFunction, Identifier, Place } from "./HIR";
11
+import { collectInputs } from "./InferMutableLifetimes";
12
+
13
+/**
14
+ * Pass to eliminate redundant phi nodes:
15
+ * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`.
16
+ * - all operands are the same identifier *or* the output of the phi, ie `x2 = phi(x1, x2, x1, x2)`.
17
+ *
18
+ * In both these cases, the phi is eliminated and all usages of the phi identifier
19
+ * are replaced with the other operand (ie in both cases above, all usages of `x2` are replaced with `x1` .
20
+ *
21
+ * The algorithm is inspired by that in https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf
22
+ * but modified to reduce passes over the CFG. We visit the blocks in reverse postorder. Each time a redundant
23
+ * phi is encountered we add a mapping (eg x2 -> x1) to a rewrite table. Subsequent instructions, terminals,
24
+ * and phis rewrite all their identifiers based on this table. The algorithm loops over the CFG repeatedly
25
+ * until there are no new rewrites: for a CFG without back-edges it completes in a single pass.
26
+ */
27
+export function eliminateRedundantPhi(fn: HIRFunction) {
28
+ const ir = fn.body;
29
+ const rewrites: Map<Identifier, Identifier> = new Map();
30
+
31
+ // Whether or the CFG has a back-edge (a loop). We determine this dynamically
32
+ // during the first iteration over the CFG by recording which blocks were already
33
+ // visited, and checking if a block has any predecessors that weren't visited yet.
34
+ // Because blocks are in reverse postorder, the only time this can occur is a loop.
35
+ let hasBackEdge = false;
36
+ const visited: Set<BlockId> = new Set();
37
+
38
+ // size tracks the number of rewrites at the beginning of each iteration, so we can
39
+ // compare to see if any new rewrites were added in that iteration.
40
+ let size = rewrites.size;
41
+ do {
42
+ size = rewrites.size;
43
+ for (const [blockId, block] of ir.blocks) {
44
+ // On the first iteration of the loop check for any back-edges.
45
+ // if there aren't any then there won't be a second iteration
46
+ if (!hasBackEdge) {
47
+ for (const pred of block.preds) {
48
+ if (!visited.has(pred.id)) {
49
+ hasBackEdge = true;
50
+ }
51
+ }
52
+ }
53
+ visited.add(blockId);
54
+
55
+ // Find any redundant phis
56
+ phis: for (const phi of block.phis) {
57
+ let same: Identifier | null = null;
58
+ for (const [_, operand] of phi.operands) {
59
+ const ident = rewrites.get(operand) ?? operand;
60
+ if (
61
+ (same !== null && ident.id === same.id) ||
62
+ ident.id === phi.id.id
63
+ ) {
64
+ // This operand is the same as the phi or is the same as the
65
+ // previous non-phi operands
66
+ continue;
67
+ } else if (same !== null) {
68
+ // There are multiple operands not equal to the phi itself,
69
+ // this phi can't be eliminated.
70
+ continue phis;
71
+ } else {
72
+ // First non-phi operand
73
+ same = ident;
74
+ }
75
+ }
76
+ invariant(same !== null, "Expected phis to be non-empty");
77
+ rewrites.set(phi.id, same);
78
+ block.phis.delete(phi);
79
+ }
80
+
81
+ // Rewrite all instruction lvalues and operands
82
+ for (const instr of block.instructions) {
83
+ for (const place of collectInputs(instr)) {
84
+ rewritePlace(place, rewrites);
85
+ }
86
+ const { lvalue } = instr;
87
+ if (lvalue !== null) {
88
+ rewritePlace(lvalue.place, rewrites);
89
+ }
90
+ }
91
+
92
+ // Rewrite all terminal operands
93
+ const { terminal } = block;
94
+ switch (terminal.kind) {
95
+ case "if": {
96
+ rewritePlace(terminal.test, rewrites);
97
+ break;
98
+ }
99
+ case "switch": {
100
+ rewritePlace(terminal.test, rewrites);
101
+ for (const case_ of terminal.cases) {
102
+ if (case_.test === null) {
103
+ continue;
104
+ }
105
+ rewritePlace(case_.test, rewrites);
106
+ }
107
+ break;
108
+ }
109
+ case "return":
110
+ case "throw": {
111
+ if (terminal.value !== null) {
112
+ rewritePlace(terminal.value, rewrites);
113
+ }
114
+ break;
115
+ }
116
+ case "goto": {
117
+ // no-op
118
+ break;
119
+ }
120
+ default: {
121
+ assertExhaustive(
122
+ terminal,
123
+ `Unexpected terminal kind '${(terminal as any).kind}'`
124
+ );
125
+ }
126
+ }
127
+ }
128
+ // We only need to loop if there were newly eliminated phis in this iteration
129
+ // *and* the CFG has loops. If there are no loops, then all eliminated phis
130
+ // have already propagated forwards since we visit in reverse postorder.
131
+ } while (rewrites.size > size && hasBackEdge);
132
+}
133
+
134
+function rewritePlace(place: Place, rewrites: Map<Identifier, Identifier>) {
135
+ const rewrite = rewrites.get(place.identifier);
136
+ if (rewrite != null) {
137
+ place.identifier = rewrite;
138
+ }
139
+}