29
* Note that unreachable blocks are already pruned during HIR construction.
30
*/
31
export function deadCodeElimination(fn: HIRFunction): void {
32
- const state = new State();
33
-
34
- /*
35
- * If there are no back-edges the algorithm can terminate after a single iteration
36
- * of the blocks
32
+ /**
33
+ * Phase 1: Find/mark all referenced identifiers
34
+ * Usages may be visited AFTER declarations if there are circular phi / data dependencies
35
+ * between blocks, so we wait to sweep until after fixed point iteration is complete
36
*/
38
- const hasLoop = hasBackEdge(fn);
39
-
40
- const reversedBlocks = [...fn.body.blocks.values()].reverse();
41
- let size = state.count;
42
- do {
43
- size = state.count;
44
-
45
- /*
46
- * Iterate blocks in postorder (successors before predecessors, excepting loops)
47
- * to find usages before declarations
48
- */
49
- for (const block of reversedBlocks) {
50
- for (const operand of eachTerminalOperand(block.terminal)) {
51
- state.reference(operand.identifier);
52
- }
53
-
54
- for (let i = block.instructions.length - 1; i >= 0; i--) {
55
- const instr = block.instructions[i]!;
56
- if (
57
- !state.isIdOrNameUsed(instr.lvalue.identifier) &&
58
- pruneableValue(instr.value, state) &&
59
- // Can't prune the last value of a value block, that's its value!
60
- !(block.kind !== "block" && i === block.instructions.length - 1)
61
- ) {
62
- continue;
63
- }
64
- state.reference(instr.lvalue.identifier);
37
+ const state = findReferencedIdentifiers(fn);
38
66
- /*
67
- * For the last value of a value block, if it's not pruneable we can't
68
- * rewrite it. This is necessary to preserve unused value blocks
69
- */
70
- if (block.kind !== "block" && i === block.instructions.length - 1) {
71
- for (const place of eachInstructionValueOperand(instr.value)) {
72
- state.reference(place.identifier);
73
- }
74
- continue;
75
- }
76
- // Otherwise rewrite instructions to remove unused parts of them
77
- visitInstruction(instr, state);
78
- }
79
- for (const phi of block.phis) {
80
- if (state.isIdOrNameUsed(phi.id)) {
81
- for (const [_pred, operand] of phi.operands) {
82
- state.reference(operand);
83
- }
84
- }
85
- }
86
- }
87
- } while (state.count > size && hasLoop);
39
+ /**
40
+ * Phase 2: Prune / sweep unreferenced identifiers and instructions
41
+ * as possible (subject to HIR structural constraints)
42
+ */
43
for (const [, block] of fn.body.blocks) {
44
for (const phi of block.phis) {
45
if (!state.isIdOrNameUsed(phi.id)) {
49
retainWhere(block.instructions, (instr) =>
50
state.isIdOrNameUsed(instr.lvalue.identifier)
51
);
52
+ // Rewrite retained instructions
53
+ for (let i = 0; i < block.instructions.length; i++) {
54
+ const isBlockValue =
55
+ block.kind !== "block" && i === block.instructions.length - 1;
56
+ if (!isBlockValue) {
57
+ rewriteInstruction(block.instructions[i], state);
58
+ }
59
+ }
60
}
61
}
62
97
}
98
}
99
137
-function visitInstruction(instr: Instruction, state: State): void {
100
+function findReferencedIdentifiers(fn: HIRFunction): State {
101
+ /*
102
+ * If there are no back-edges the algorithm can terminate after a single iteration
103
+ * of the blocks
104
+ */
105
+ const hasLoop = hasBackEdge(fn);
106
+ const reversedBlocks = [...fn.body.blocks.values()].reverse();
107
+
108
+ const state = new State();
109
+ let size = state.count;
110
+ do {
111
+ size = state.count;
112
+
113
+ /*
114
+ * Iterate blocks in postorder (successors before predecessors, excepting loops)
115
+ * to visit usages before declarations
116
+ */
117
+ for (const block of reversedBlocks) {
118
+ for (const operand of eachTerminalOperand(block.terminal)) {
119
+ state.reference(operand.identifier);
120
+ }
121
+
122
+ for (let i = block.instructions.length - 1; i >= 0; i--) {
123
+ const instr = block.instructions[i]!;
124
+ const isBlockValue =
125
+ block.kind !== "block" && i === block.instructions.length - 1;
126
+
127
+ if (isBlockValue) {
128
+ /**
129
+ * The last instr of a value block is never eligible for pruning,
130
+ * as that's the block's value. Pessimistically consider all operands
131
+ * as used to avoid rewriting the last instruction
132
+ */
133
+ state.reference(instr.lvalue.identifier);
134
+ for (const place of eachInstructionValueOperand(instr.value)) {
135
+ state.reference(place.identifier);
136
+ }
137
+ } else if (
138
+ state.isIdOrNameUsed(instr.lvalue.identifier) ||
139
+ !pruneableValue(instr.value, state)
140
+ ) {
141
+ state.reference(instr.lvalue.identifier);
142
+
143
+ if (instr.value.kind === "StoreLocal") {
144
+ /*
145
+ * If this is a Let/Const declaration, mark the initializer as referenced
146
+ * only if the ssa'ed lval is also referenced
147
+ */
148
+ if (
149
+ instr.value.lvalue.kind === InstructionKind.Reassign ||
150
+ state.isIdUsed(instr.value.lvalue.place.identifier)
151
+ ) {
152
+ state.reference(instr.value.value.identifier);
153
+ }
154
+ } else {
155
+ for (const operand of eachInstructionValueOperand(instr.value)) {
156
+ state.reference(operand.identifier);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ for (const phi of block.phis) {
162
+ if (state.isIdOrNameUsed(phi.id)) {
163
+ for (const [_pred, operand] of phi.operands) {
164
+ state.reference(operand);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ } while (state.count > size && hasLoop);
170
+ return state;
171
+}
172
+
173
+function rewriteInstruction(instr: Instruction, state: State): void {
174
if (instr.value.kind === "Destructure") {
139
- // Mark the value as used, not the lvalues
140
- state.reference(instr.value.value.identifier);
175
// Remove unused lvalues
176
switch (instr.value.lvalue.pattern.kind) {
177
case "ArrayPattern": {
253
lvalue: instr.value.lvalue,
254
loc: instr.value.loc,
255
};
222
- } else {
223
- /*
224
- * Else we mark the initializer as referenced, since the variable itself is
225
- * referenced
226
- */
227
- state.reference(instr.value.value.identifier);
228
- }
229
- } else {
230
- for (const operand of eachInstructionValueOperand(instr.value)) {
231
- state.reference(operand.identifier);
256
}
257
}
258
}