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 {
9
+ BlockId,
10
+ HIRFunction,
11
+ Identifier,
12
+ Instruction,
13
+ InstructionValue,
14
+} from "../HIR";
15
+import {
16
+ eachInstructionValueOperand,
17
+ eachTerminalOperand,
18
+} from "../HIR/visitors";
19
+import { assertExhaustive } from "../Utils/utils";
20
+
21
+/**
22
+ * Implements dead-code elimination, eliminating instructions whose values are unused.
23
+ *
24
+ * Note that unreachable blocks are already pruned during HIR construction.
25
+ */
26
+export function deadCodeElimination(fn: HIRFunction): void {
27
+ const used = new Set<Identifier>();
28
+
29
+ // Find any phi operands involved in a loop, which might otherwise appear as dead
30
+ // code when using a reverse iteration.
31
+ //
32
+ // A more advanced algorithm could still prune some of these operands
33
+ // let's keep it simple for now
34
+ const seen = new Set<BlockId>();
35
+ for (const [, block] of fn.body.blocks) {
36
+ for (const phi of block.phis) {
37
+ for (const [pred, operand] of phi.operands) {
38
+ if (!seen.has(pred)) {
39
+ used.add(operand);
40
+ }
41
+ }
42
+ }
43
+ seen.add(block.id);
44
+ }
45
+
46
+ // Iterate blocks in postorder (successors before predecessors, excepting loops)
47
+ // to find usages before declarations
48
+ const reversedBlocks = [...fn.body.blocks.values()].reverse();
49
+ for (const block of reversedBlocks) {
50
+ for (const operand of eachTerminalOperand(block.terminal)) {
51
+ used.add(operand.identifier);
52
+ }
53
+
54
+ let nextInstructions: Array<Instruction> | null = null;
55
+ for (let i = block.instructions.length - 1; i >= 0; i--) {
56
+ const instr = block.instructions[i]!;
57
+ if (
58
+ !used.has(instr.lvalue.place.identifier) &&
59
+ pruneableValue(instr.value) &&
60
+ // Can't prune the last value of a value block, that's its value!
61
+ !(block.kind !== "block" && i === block.instructions.length - 1)
62
+ ) {
63
+ continue;
64
+ }
65
+ used.add(instr.lvalue.place.identifier);
66
+ nextInstructions ??= [];
67
+ nextInstructions.push(instr);
68
+ for (const operand of eachInstructionValueOperand(instr.value)) {
69
+ used.add(operand.identifier);
70
+ }
71
+ }
72
+ if (nextInstructions !== null) {
73
+ nextInstructions.reverse();
74
+ block.instructions = nextInstructions;
75
+ }
76
+ for (const phi of block.phis) {
77
+ if (used.has(phi.id)) {
78
+ for (const [, operand] of phi.operands) {
79
+ used.add(operand);
80
+ }
81
+ } else {
82
+ for (const [, operand] of phi.operands) {
83
+ if (used.has(operand)) {
84
+ used.add(phi.id);
85
+ for (const [, operand] of phi.operands) {
86
+ used.add(operand);
87
+ }
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+}
95
+
96
+/**
97
+ * Returns true if it is safe to prune an instruction with the given value.
98
+ * Functions which may have side-
99
+ */
100
+function pruneableValue(value: InstructionValue): boolean {
101
+ switch (value.kind) {
102
+ case "CallExpression":
103
+ case "ComputedCall":
104
+ case "ComputedStore":
105
+ case "PropertyCall":
106
+ case "PropertyStore": {
107
+ // Mutating instructions are not safe to prune.
108
+ // TODO: we could be more precise and make this conditional on whether
109
+ // any arguments are actually modified
110
+ return false;
111
+ }
112
+ case "NewExpression":
113
+ case "UnsupportedNode":
114
+ case "TaggedTemplateExpression": {
115
+ // Potentially safe to prune, since they should just be creating new values
116
+ return false;
117
+ }
118
+ case "ArrayExpression":
119
+ case "BinaryExpression":
120
+ case "ComputedLoad":
121
+ case "ComputedStore":
122
+ case "FunctionExpression":
123
+ case "Identifier":
124
+ case "JsxExpression":
125
+ case "JsxFragment":
126
+ case "JSXText":
127
+ case "ObjectExpression":
128
+ case "Primitive":
129
+ case "PropertyLoad":
130
+ case "TemplateLiteral":
131
+ case "TypeCastExpression":
132
+ case "UnaryExpression": {
133
+ // Definitely safe to prune since they are read-only
134
+ return true;
135
+ }
136
+ default: {
137
+ assertExhaustive(value, `Unexepcted value kind '${(value as any).kind}'`);
138
+ }
139
+ }
140
+}