Refactor DCE internal state into a class
Tidying up the implementation to make the next PR's changes easier to follow.
Joe Savona committed
Mar 15, 2023 at 13:20 UTC
c6d1181ba3344707186c19ce0fbaaf08d3fc2d9e
1 file changed
+40
-27
compiler/forget/src/Optimization/DeadCodeElimination.ts
+40
-27
@@ -27,62 +27,78 @@ import { assertExhaustive, retainWhere } from "../Utils/utils";
27
* Note that unreachable blocks are already pruned during HIR construction.
28
*/
29
export function deadCodeElimination(fn: HIRFunction): void {
30
- const used = new Set<Identifier>();
30
+ const state = new State();
31
32
// If there are no back-edges the algorithm can terminate after a single iteration
33
// of the blocks
34
const hasLoop = hasBackEdge(fn);
35
36
- let size = used.size;
36
+ let size = state.count;
37
do {
38
- size = used.size;
38
+ size = state.count;
39
40
// Iterate blocks in postorder (successors before predecessors, excepting loops)
41
// to find usages before declarations
42
const reversedBlocks = [...fn.body.blocks.values()].reverse();
43
for (const block of reversedBlocks) {
44
for (const operand of eachTerminalOperand(block.terminal)) {
45
- used.add(operand.identifier);
45
+ state.reference(operand.identifier);
46
}
47
48
for (let i = block.instructions.length - 1; i >= 0; i--) {
49
const instr = block.instructions[i]!;
50
if (
51
- !used.has(instr.lvalue.identifier) &&
52
- pruneableValue(instr.value, used) &&
51
+ !state.used(instr.lvalue.identifier) &&
52
+ pruneableValue(instr.value, state) &&
53
// Can't prune the last value of a value block, that's its value!
54
!(block.kind !== "block" && i === block.instructions.length - 1)
55
) {
56
continue;
57
}
58
- used.add(instr.lvalue.identifier);
59
- visitInstruction(instr, used);
58
+ state.reference(instr.lvalue.identifier);
59
+ visitInstruction(instr, state);
60
}
61
for (const phi of block.phis) {
62
- if (used.has(phi.id)) {
62
+ if (state.used(phi.id)) {
63
for (const [_pred, operand] of phi.operands) {
64
- used.add(operand);
64
+ state.reference(operand);
65
}
66
}
67
}
68
}
69
- } while (used.size > size && hasLoop);
69
+ } while (state.count > size && hasLoop);
70
for (const [, block] of fn.body.blocks) {
71
for (const phi of block.phis) {
72
- if (!used.has(phi.id)) {
72
+ if (!state.used(phi.id)) {
73
block.phis.delete(phi);
74
}
75
}
76
retainWhere(block.instructions, (instr) =>
77
- used.has(instr.lvalue.identifier)
77
+ state.used(instr.lvalue.identifier)
78
);
79
}
80
}
81
82
-function visitInstruction(instr: Instruction, used: Set<Identifier>): void {
82
+class State {
83
+ identifiers: Set<Identifier> = new Set();
84
+
85
+ reference(identifier: Identifier): void {
86
+ this.identifiers.add(identifier);
87
+ }
88
+
89
+ used(identifier: Identifier): boolean {
90
+ return this.identifiers.has(identifier);
91
+ }
92
+
93
+ get count(): number {
94
+ return this.identifiers.size;
95
+ }
96
+}
97
+
98
+function visitInstruction(instr: Instruction, state: State): void {
99
if (instr.value.kind === "Destructure") {
100
// Mark the value as used, not the lvalues
85
- used.add(instr.value.value.identifier);
101
+ state.reference(instr.value.value.identifier);
102
// Remove unused lvalues
103
switch (instr.value.lvalue.pattern.kind) {
104
case "ArrayPattern": {
@@ -94,12 +110,12 @@ function visitInstruction(instr: Instruction, used: Set<Identifier>): void {
110
for (let i = originalItems.length - 1; i >= 0; i--) {
111
const item = originalItems[i];
112
if (item.kind === "Identifier") {
97
- if (used.has(item.identifier)) {
113
+ if (state.used(item.identifier)) {
114
nextItems = originalItems.slice(0, i + 1);
115
break;
116
}
117
} else {
102
- if (used.has(item.place.identifier)) {
118
+ if (state.used(item.place.identifier)) {
119
nextItems = originalItems.slice(0, i + 1);
120
break;
121
}
@@ -119,12 +135,12 @@ function visitInstruction(instr: Instruction, used: Set<Identifier>): void {
135
let nextProperties: ObjectPattern["properties"] | null = null;
136
for (const property of instr.value.lvalue.pattern.properties) {
137
if (property.kind === "ObjectProperty") {
122
- if (used.has(property.place.identifier)) {
138
+ if (state.used(property.place.identifier)) {
139
nextProperties ??= [];
140
nextProperties.push(property);
141
}
142
} else {
127
- if (used.has(property.place.identifier)) {
143
+ if (state.used(property.place.identifier)) {
144
nextProperties = null;
145
break;
146
}
@@ -146,7 +162,7 @@ function visitInstruction(instr: Instruction, used: Set<Identifier>): void {
162
}
163
} else {
164
for (const operand of eachInstructionValueOperand(instr.value)) {
149
- used.add(operand.identifier);
165
+ state.reference(operand.identifier);
166
}
167
}
168
}
@@ -155,23 +171,20 @@ function visitInstruction(instr: Instruction, used: Set<Identifier>): void {
171
* Returns true if it is safe to prune an instruction with the given value.
172
* Functions which may have side-
173
*/
158
-function pruneableValue(
159
- value: InstructionValue,
160
- used: Set<Identifier>
161
-): boolean {
174
+function pruneableValue(value: InstructionValue, state: State): boolean {
175
switch (value.kind) {
176
case "DeclareLocal": {
164
- return !used.has(value.lvalue.place.identifier);
177
+ return !state.used(value.lvalue.place.identifier);
178
}
179
case "StoreLocal": {
180
// Stores are pruneable only if the identifier being stored to is never read later
168
- return !used.has(value.lvalue.place.identifier);
181
+ return !state.used(value.lvalue.place.identifier);
182
}
183
case "Destructure": {
184
// Destructure is pruneable only if none of the identifiers are read from later
185
// TODO: as an optimization, prune unused properties where safe
186
for (const place of eachPatternOperand(value.lvalue.pattern)) {
174
- if (used.has(place.identifier)) {
187
+ if (state.used(place.identifier)) {
188
return false;
189
}
190
}