Cleanup reverse postordering
Splits out the reverse postorder logic from `shrink` into a separate method.
Joseph Savona committed
Oct 13, 2022 at 07:58 UTC
144e856264d1326fc39d32ac4e2165cbf8a2904b
1 file changed
+112
-40
compiler/forget/src/HIR/HIRBuilder.ts
+112
-40
@@ -115,10 +115,13 @@ export default class HIRBuilder {
115
instructions,
116
terminal: { kind: "return", value: null },
117
});
118
- return shrink({
118
+ // First reduce indirections and prune unreachable blocks
119
+ let reduced = shrink({
120
blocks: this.#completed,
121
entry: this.#entry,
122
});
123
+ // then convert to reverse postorder
124
+ return reversePostorderBlocks(reduced);
125
}
126
127
/**
@@ -291,10 +294,7 @@ export default class HIRBuilder {
294
/**
295
* Helper to shrink a CFG to eliminate unreachable node and eliminate jump-only blocks.
296
*/
294
-function shrink(func: {
295
- blocks: Map<BlockId, BasicBlock>;
296
- entry: BlockId;
297
-}): HIR {
297
+function shrink(func: HIR): HIR {
298
const gotos = new Map();
299
/**
300
* Given a target block for some terminator, resolves the ideal block that should be
@@ -319,49 +319,121 @@ function shrink(func: {
319
}
320
}
321
322
- const visited: Set<BlockId> = new Set();
322
+ const queue = [func.entry];
323
const blocks: Map<BlockId, BasicBlock> = new Map();
324
-
325
- // Visit all the blocks and map their successors to remove indirections,
326
- // and eliminate unreachable blocks. Stores blocks in postorder,
327
- // successors before predecessors.
328
- function visit(blockId: BlockId) {
329
- visited.add(blockId);
330
- const block = func.blocks.get(blockId)!;
331
- const { instructions, terminal: prevTerminal } = block;
332
- const terminal = mapTerminalSuccessors(
333
- prevTerminal,
334
- (prevTarget, isFallthrough) => {
335
- const target = resolveBlockTarget(prevTarget);
336
- if (!visited.has(target) && !isFallthrough) {
337
- visit(target);
338
- }
339
- return target;
340
- }
341
- );
342
- block.terminal = terminal;
343
- blocks.set(blockId, block);
324
+ while (queue.length !== 0) {
325
+ const blockId = queue.shift()!;
326
+ if (blocks.has(blockId)) {
327
+ continue;
328
+ }
329
+ const { instructions, terminal: prevTerminal } = func.blocks.get(blockId)!;
330
+ const terminal = mapTerminalSuccessors(prevTerminal, (prevTarget) => {
331
+ const target = resolveBlockTarget(prevTarget);
332
+ queue.push(target);
333
+ return target;
334
+ });
335
+ blocks.set(blockId, {
336
+ id: blockId,
337
+ instructions,
338
+ terminal,
339
+ });
340
}
345
- visit(func.entry);
341
342
// Cleanup any fallthrough blocks that weren't visited
348
- // also store into reverse postorder.
349
- const reversedBlocks: Map<BlockId, BasicBlock> = new Map();
350
- for (const blockId of Array.from(blocks.keys()).reverse()) {
351
- const block = blocks.get(blockId)!;
343
+ for (const block of blocks.values()) {
344
if (block.terminal.kind === "if" || block.terminal.kind === "switch") {
345
if (
346
block.terminal.fallthrough !== null &&
355
- !visited.has(block.terminal.fallthrough)
347
+ !blocks.has(block.terminal.fallthrough)
348
) {
349
block.terminal.fallthrough = null;
350
}
351
}
360
- reversedBlocks.set(blockId, block);
352
}
353
+ return { blocks, entry: func.entry };
354
+}
355
356
+/**
357
+ * Converts the graph to reverse-postorder, with predecessor blocks appearing
358
+ * before successors except in the case of back links (ie loops).
359
+ */
360
+function reversePostorderBlocks(func: HIR): HIR {
361
+ const visited: Set<BlockId> = new Set();
362
+ const postorder: Array<BlockId> = [];
363
+ function visit(blockId: BlockId) {
364
+ if (visited.has(blockId)) {
365
+ return;
366
+ }
367
+ visited.add(blockId);
368
+ const block = func.blocks.get(blockId)!;
369
+ const { terminal } = block;
370
+
371
+ /**
372
+ * Note that we visit successors in reverse order. This ensures that when we
373
+ * reverse the list at the end, that "sibling" edges appear in-order. For example,
374
+ * ```
375
+ * // bb0
376
+ * let x;
377
+ * if (c) {
378
+ * // bb1
379
+ * x = 1;
380
+ * } else {
381
+ * // b2
382
+ * x = 2;
383
+ * }
384
+ * // bb3
385
+ * x;
386
+ * ```
387
+ *
388
+ * We want the output to be bb0, bb1, bb2, bb3 just to line up with the original
389
+ * program order for visual debugging. By visiting the successors in reverse order
390
+ * (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
391
+ */
392
+ switch (terminal.kind) {
393
+ case "return":
394
+ case "throw": {
395
+ // no-op, no successors
396
+ break;
397
+ }
398
+ case "goto": {
399
+ visit(terminal.block);
400
+ break;
401
+ }
402
+ case "if": {
403
+ // can ignore fallthrough, if its reachable it will be reached through
404
+ // consequent/alternate
405
+ const { consequent, alternate } = terminal;
406
+ visit(alternate);
407
+ visit(consequent);
408
+ break;
409
+ }
410
+ case "switch": {
411
+ // can ignore fallthrough, if its reachable it will be reached through
412
+ // a case
413
+ const { cases } = terminal;
414
+ for (const case_ of [...cases].reverse()) {
415
+ visit(case_.block);
416
+ }
417
+ break;
418
+ }
419
+ default: {
420
+ assertExhaustive(
421
+ terminal,
422
+ `Unexpected terminal kind '${(terminal as any).kind}'`
423
+ );
424
+ }
425
+ }
426
+
427
+ postorder.push(blockId);
428
+ }
429
+ visit(func.entry);
430
+
431
+ const blocks = new Map();
432
+ for (const blockId of postorder.reverse()) {
433
+ blocks.set(blockId, func.blocks.get(blockId)!);
434
+ }
435
return {
364
- blocks: reversedBlocks,
436
+ blocks,
437
entry: func.entry,
438
};
439
}
@@ -395,10 +467,10 @@ export function mapTerminalSuccessors(
467
};
468
}
469
case "if": {
470
+ const consequent = fn(terminal.consequent, false);
471
+ const alternate = fn(terminal.alternate, false);
472
const fallthrough =
473
terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
400
- const alternate = fn(terminal.alternate, false);
401
- const consequent = fn(terminal.consequent, false);
474
return {
475
kind: "if",
476
test: terminal.test,
@@ -408,19 +480,19 @@ export function mapTerminalSuccessors(
480
};
481
}
482
case "switch": {
411
- const fallthrough =
412
- terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
413
- const cases = [...terminal.cases].reverse().map((case_) => {
483
+ const cases = terminal.cases.map((case_) => {
484
const target = fn(case_.block, false);
485
return {
486
test: case_.test,
487
block: target,
488
};
489
});
490
+ const fallthrough =
491
+ terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
492
return {
493
kind: "switch",
494
test: terminal.test,
423
- cases: cases.reverse(),
495
+ cases,
496
fallthrough,
497
};
498
}