9
import { CompilerError } from "../CompilerError";
10
import {
11
BasicBlock,
12
+ BlockId,
13
Effect,
14
GeneratedSource,
15
HIRFunction,
16
Identifier,
17
Instruction,
17
- InstructionId,
18
InstructionKind,
19
LValue,
20
LValuePattern,
108
phi: Phi;
109
block: BasicBlock;
110
};
111
- function pushPhis(arr: Array<PhiState>, block: BasicBlock): void {
111
+
112
+ const seen = new Set<BlockId>();
113
+ const backEdgePhis = new Set<Phi>();
114
+ for (const [, block] of fn.body.blocks) {
115
for (const phi of block.phis) {
113
- arr.push({ phi, block });
116
+ for (const [pred] of phi.operands) {
117
+ if (!seen.has(pred)) {
118
+ backEdgePhis.add(phi);
119
+ break;
120
+ }
121
+ }
122
}
123
+ seen.add(block.id);
124
}
125
126
for (const [, block] of fn.body.blocks) {
133
const originalLVal = declarations.get(
134
value.lvalue.place.identifier.name
135
);
127
- if (originalLVal === undefined) {
136
+ if (
137
+ originalLVal === undefined ||
138
+ originalLVal.lvalue === value.lvalue // in case this was pre-declared for the `for` initializer
139
+ ) {
140
declarations.set(value.lvalue.place.identifier.name, {
141
lvalue: value.lvalue,
142
place: value.lvalue.place,
146
// This is an instance of the original id, so we need to promote the original declaration
147
// to a `let` and the current lval to a `reassign`
148
originalLVal.lvalue.kind = InstructionKind.Let;
149
+ value.lvalue.kind = InstructionKind.Reassign;
150
}
151
} else if (rewrites.has(value.lvalue.place.identifier)) {
139
- value.lvalue.kind =
140
- rewrites.get(value.lvalue.place.identifier) ===
141
- value.lvalue.place.identifier
142
- ? InstructionKind.Let
143
- : InstructionKind.Reassign;
152
+ value.lvalue.kind = InstructionKind.Const;
153
}
154
} else if (value.kind === "Destructure") {
155
let kind: InstructionKind | null = null;
166
kind = InstructionKind.Const;
167
} else {
168
const originalLVal = declarations.get(place.identifier.name);
160
- if (originalLVal === undefined) {
169
+ if (
170
+ originalLVal === undefined ||
171
+ originalLVal.lvalue === value.lvalue
172
+ ) {
173
declarations.set(place.identifier.name, {
174
lvalue: value.lvalue,
175
place,
219
// such as for or while (and later if/switch).
220
const reassignmentPhis: Array<PhiState> = [];
221
const rewritePhis: Array<PhiState> = [];
222
+ function pushPhis(phiBlock: BasicBlock): void {
223
+ for (const phi of phiBlock.phis) {
224
+ if (phi.id.name === null) {
225
+ rewritePhis.push({ phi, block: phiBlock });
226
+ } else {
227
+ reassignmentPhis.push({ phi, block: phiBlock });
228
+ }
229
+ const hasBackEdge = backEdgePhis.has(phi);
230
+ const isPhiMutatedAfterCreation: boolean =
231
+ phi.id.mutableRange.end >
232
+ (phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
233
+
234
+ // Named variables whose phi doesn't have a back-edge can potentially be independenly
235
+ // memoized, depending on whether the phi is after its creation.
236
+ if (phi.id.name !== null && !hasBackEdge) {
237
+ if (!isPhiMutatedAfterCreation) {
238
+ // Simple case: predecesor-only values flowing into a phi, which is never modified:
239
+ // adjust the phi's range to clarify that the identifier does not mutate
240
+ phi.id.mutableRange.start = terminal.id;
241
+ phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
242
+ } else {
243
+ // Predecessor only values flow into a phi, which is modified later:
244
+ // all operands flow into the phi and can be modified, must extend their ranges
245
+ for (const [, operand] of phi.operands) {
246
+ operand.mutableRange.end = phi.id.mutableRange.end;
247
+ }
248
+ }
249
+ return;
250
+ }
251
+ // Otherwise this is a temporary phi (logical or ternary) or occurs in a loop. In either
252
+ // case we can't independently memoize any of the values: unify their ranges to span the
253
+ // min(start) to max(end) so that we create a single scope for all the computation.
254
+ let start = block.terminal.id as number;
255
+ let end = Number.MIN_SAFE_INTEGER;
256
+ const operands = [phi.id, ...phi.operands.values()];
257
+ for (const operand of operands) {
258
+ start = Math.min(start, operand.mutableRange.start);
259
+ end = Math.max(end, operand.mutableRange.end);
260
+ }
261
+ for (const operand of operands) {
262
+ operand.mutableRange.start = makeInstructionId(start);
263
+ operand.mutableRange.end = makeInstructionId(end);
264
+ }
265
+ }
266
+ }
267
if (
268
(terminal.kind === "if" ||
269
terminal.kind === "switch" ||
272
terminal.fallthrough !== null
273
) {
274
const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
218
- for (const phi of fallthrough.phis) {
219
- if (phi.id.name == null) {
220
- rewritePhis.push({ phi, block: fallthrough });
221
- } else {
222
- reassignmentPhis.push({ phi, block: fallthrough });
223
- }
224
- }
275
+ pushPhis(fallthrough);
276
}
277
if (terminal.kind === "while" || terminal.kind === "for") {
278
const test = fn.body.blocks.get(terminal.test)!;
228
- pushPhis(rewritePhis, test);
229
- test.phis.clear();
279
+ pushPhis(test);
280
281
const loop = fn.body.blocks.get(terminal.loop)!;
232
- pushPhis(rewritePhis, loop);
233
- loop.phis.clear();
282
+ pushPhis(loop);
283
}
284
if (terminal.kind === "for") {
285
const init = fn.body.blocks.get(terminal.init)!;
237
- pushPhis(rewritePhis, init);
238
- init.phis.clear();
286
+ pushPhis(init);
287
+
288
+ // To avoid generating a let binding for the initializer prior to the loop,
289
+ // check to see if the for declares an iterator variable
290
+ const initIdentifier = init.instructions.at(-1);
291
+ if (
292
+ initIdentifier !== undefined &&
293
+ initIdentifier.value.kind === "StoreLocal"
294
+ ) {
295
+ const value = initIdentifier.value;
296
+ if (value.lvalue.place.identifier.name !== null) {
297
+ const originalLVal = declarations.get(
298
+ value.lvalue.place.identifier.name
299
+ );
300
+ if (originalLVal === undefined) {
301
+ declarations.set(value.lvalue.place.identifier.name, {
302
+ lvalue: value.lvalue,
303
+ place: value.lvalue.place,
304
+ });
305
+ value.lvalue.kind = InstructionKind.Const;
306
+ }
307
+ }
308
+ }
309
310
const update = fn.body.blocks.get(terminal.update)!;
241
- pushPhis(rewritePhis, update);
242
- update.phis.clear();
311
+ pushPhis(update);
312
}
313
if (terminal.kind === "logical" || terminal.kind === "ternary") {
314
const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
246
- pushPhis(rewritePhis, fallthrough);
247
- fallthrough.phis.clear();
315
+ pushPhis(fallthrough);
316
}
317
318
for (const { phi, block: phiBlock } of reassignmentPhis) {
335
phi.id.mutableRange.end >
336
(phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
337
270
- // If a phi is never mutated after creation, reset its mutable range to be itself
271
- if (!isPhiMutatedAfterCreation) {
272
- phi.id.mutableRange.start = terminal.id;
273
- phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
274
- }
275
-
338
// If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
339
// a new Let binding
340
invariant(
423
block.instructions.push(instr);
424
declarations.set(phi.id.name, { lvalue, place: lvalue.place });
425
phi.id.mutableRange.start = terminal.id;
364
- if (!isPhiMutatedAfterCreation) {
365
- phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
366
- }
426
} else if (isPhiMutatedAfterCreation) {
427
// The declaration is not guaranteed to flow into the phi, for example in the case of a variable
428
// that is reassigned in all control flow paths to a given phi. The original declaration's range
448
canonicalId = canonicalOperand;
449
}
450
}
392
- canonicalId.mutableRange.start = Math.min(
393
- canonicalId.mutableRange.start,
394
- terminal.id
395
- ) as InstructionId;
451
rewrites.set(phi.id, canonicalId);
452
453
if (canonicalId.name !== null) {
459
}
460
461
// all versions of the variable need to be remapped to the canonical id
407
- // also extend the mutable range of the canonical id based on the min/max
408
- // of the ranges of its operands
409
- let start = canonicalId.mutableRange.start as number;
410
- let end = canonicalId.mutableRange.end as number;
462
for (const [, operand] of phi.operands) {
412
- start = Math.min(start, operand.mutableRange.start);
413
- end = Math.max(end, operand.mutableRange.end);
463
rewrites.set(operand, canonicalId);
464
}
416
- canonicalId.mutableRange.start = makeInstructionId(start);
417
- canonicalId.mutableRange.end = makeInstructionId(end);
465
}
466
}
467
}
479
480
if (nextIdentifier !== undefined) {
481
if (nextIdentifier === prevIdentifier) return;
435
- nextIdentifier.mutableRange.start = makeInstructionId(
436
- Math.min(
437
- nextIdentifier.mutableRange.start,
438
- prevIdentifier.mutableRange.start
439
- )
440
- );
441
- nextIdentifier.mutableRange.end = makeInstructionId(
442
- Math.max(nextIdentifier.mutableRange.end, prevIdentifier.mutableRange.end)
443
- );
482
place.identifier = nextIdentifier;
483
} else if (prevIdentifier.name != null) {
484
const declaration = declarations.get(prevIdentifier.name);
447
- if (declaration === undefined) return;
485
// Only rewrite identifiers that were declared within the function
486
+ if (declaration === undefined) return;
487
const originalIdentifier = declaration.place.identifier;
488
prevIdentifier.id = originalIdentifier.id;
489
}