10
BlockId,
11
HIRFunction,
12
Identifier,
13
+ IdentifierId,
14
Instruction,
15
+ InstructionKind,
16
InstructionValue,
17
ObjectPattern,
18
} from "../HIR";
50
for (let i = block.instructions.length - 1; i >= 0; i--) {
51
const instr = block.instructions[i]!;
52
if (
51
- !state.used(instr.lvalue.identifier) &&
53
+ !state.isIdOrNameUsed(instr.lvalue.identifier) &&
54
pruneableValue(instr.value, state) &&
55
// Can't prune the last value of a value block, that's its value!
56
!(block.kind !== "block" && i === block.instructions.length - 1)
58
continue;
59
}
60
state.reference(instr.lvalue.identifier);
61
+
62
+ // For the last value of a value block, if it's not pruneable we can't
63
+ // rewrite it. This is necessary to preserve unused value blocks
64
+ if (block.kind !== "block" && i === block.instructions.length - 1) {
65
+ for (const place of eachInstructionValueOperand(instr.value)) {
66
+ state.reference(place.identifier);
67
+ }
68
+ continue;
69
+ }
70
+ // Otherwise rewrite instructions to remove unused parts of them
71
visitInstruction(instr, state);
72
}
73
for (const phi of block.phis) {
62
- if (state.used(phi.id)) {
74
+ if (state.isIdOrNameUsed(phi.id)) {
75
for (const [_pred, operand] of phi.operands) {
76
state.reference(operand);
77
}
81
} while (state.count > size && hasLoop);
82
for (const [, block] of fn.body.blocks) {
83
for (const phi of block.phis) {
72
- if (!state.used(phi.id)) {
84
+ if (!state.isIdOrNameUsed(phi.id)) {
85
block.phis.delete(phi);
86
}
87
}
88
retainWhere(block.instructions, (instr) =>
77
- state.used(instr.lvalue.identifier)
89
+ state.isIdOrNameUsed(instr.lvalue.identifier)
90
);
91
}
92
}
93
94
class State {
83
- identifiers: Set<Identifier> = new Set();
95
+ named: Set<string> = new Set();
96
+ identifiers: Set<IdentifierId> = new Set();
97
98
+ // Mark the identifier as being referenced (not dead code)
99
reference(identifier: Identifier): void {
86
- this.identifiers.add(identifier);
100
+ this.identifiers.add(identifier.id);
101
+ if (identifier.name !== null) {
102
+ this.named.add(identifier.name);
103
+ }
104
+ }
105
+
106
+ // Check if any version of the given identifier is used somewhere.
107
+ // This checks both for usage of this specific identifer id (ssa id)
108
+ // and (for named identifiers) for any usages of that identifier name.
109
+ isIdOrNameUsed(identifier: Identifier): boolean {
110
+ return (
111
+ this.identifiers.has(identifier.id) ||
112
+ (identifier.name !== null && this.named.has(identifier.name))
113
+ );
114
}
115
89
- used(identifier: Identifier): boolean {
90
- return this.identifiers.has(identifier);
116
+ // Like `used()`, but only checks for usages of this specific identifier id
117
+ // (ssa id).
118
+ isIdUsed(identifier: Identifier): boolean {
119
+ return this.identifiers.has(identifier.id);
120
}
121
122
get count(): number {
139
for (let i = originalItems.length - 1; i >= 0; i--) {
140
const item = originalItems[i];
141
if (item.kind === "Identifier") {
113
- if (state.used(item.identifier)) {
142
+ if (state.isIdOrNameUsed(item.identifier)) {
143
nextItems = originalItems.slice(0, i + 1);
144
break;
145
}
146
} else if (item.kind === "Spread") {
118
- if (state.used(item.place.identifier)) {
147
+ if (state.isIdOrNameUsed(item.place.identifier)) {
148
nextItems = originalItems.slice(0, i + 1);
149
break;
150
}
164
let nextProperties: ObjectPattern["properties"] | null = null;
165
for (const property of instr.value.lvalue.pattern.properties) {
166
if (property.kind === "ObjectProperty") {
138
- if (state.used(property.place.identifier)) {
167
+ if (state.isIdOrNameUsed(property.place.identifier)) {
168
nextProperties ??= [];
169
nextProperties.push(property);
170
}
171
} else {
143
- if (state.used(property.place.identifier)) {
172
+ if (state.isIdOrNameUsed(property.place.identifier)) {
173
nextProperties = null;
174
break;
175
}
189
);
190
}
191
}
192
+ } else if (instr.value.kind === "StoreLocal") {
193
+ if (
194
+ instr.value.lvalue.kind !== InstructionKind.Reassign &&
195
+ !state.isIdUsed(instr.value.lvalue.place.identifier)
196
+ ) {
197
+ // This is a const/let declaration where the variable is accessed later,
198
+ // but where the value is always overwritten before being read. Ie the
199
+ // initializer value is never read. We rewrite to a DeclareLocal so
200
+ // that the initializer value can be DCE'd
201
+ instr.value = {
202
+ kind: "DeclareLocal",
203
+ lvalue: instr.value.lvalue,
204
+ loc: instr.value.loc,
205
+ };
206
+ } else {
207
+ // Else we mark the initializer as referenced, since the variable itself is
208
+ // referenced
209
+ state.reference(instr.value.value.identifier);
210
+ }
211
} else {
212
for (const operand of eachInstructionValueOperand(instr.value)) {
213
state.reference(operand.identifier);
222
function pruneableValue(value: InstructionValue, state: State): boolean {
223
switch (value.kind) {
224
case "DeclareLocal": {
177
- return !state.used(value.lvalue.place.identifier);
225
+ // Declarations are pruneable only if the named variable is never read later
226
+ return !state.isIdOrNameUsed(value.lvalue.place.identifier);
227
}
228
case "StoreLocal": {
180
- // Stores are pruneable only if the identifier being stored to is never read later
181
- return !state.used(value.lvalue.place.identifier);
229
+ if (value.lvalue.kind === InstructionKind.Reassign) {
230
+ // Reassignments can be pruned if the specific instance being assigned is never read
231
+ return !state.isIdUsed(value.lvalue.place.identifier);
232
+ }
233
+ // Declarations are pruneable only if the named variable is never read later
234
+ return !state.isIdOrNameUsed(value.lvalue.place.identifier);
235
}
236
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
237
+ let isIdOrNameUsed = false;
238
+ let isIdUsed = false;
239
for (const place of eachPatternOperand(value.lvalue.pattern)) {
187
- if (state.used(place.identifier)) {
188
- return false;
240
+ if (state.isIdUsed(place.identifier)) {
241
+ isIdOrNameUsed = true;
242
+ isIdUsed = true;
243
+ } else if (state.isIdOrNameUsed(place.identifier)) {
244
+ isIdOrNameUsed = true;
245
}
246
}
191
- return true;
247
+ if (value.lvalue.kind === InstructionKind.Reassign) {
248
+ // Reassignments can be pruned if the specific instance being assigned is never read
249
+ return !isIdUsed;
250
+ } else {
251
+ // Otherwise pruneable only if none of the identifiers are read from later
252
+ return !isIdOrNameUsed;
253
+ }
254
}
255
case "PostfixUpdate":
256
case "PrefixUpdate": {
195
- // Updates are pruneable only if the identifier being stored to is never read later
196
- return !state.used(value.lvalue.identifier);
257
+ // Updates are pruneable if the specific instance instance being assigned is never read
258
+ return !state.isIdUsed(value.lvalue.identifier);
259
}
260
case "Debugger": {
261
// explicitly retain debugger statements to not break debugging workflows