@samitouri / QOS-React-2 / commits / c6d1181ba3

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 }