@samitouri / QOS-React-2 / commits / 144e856264

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 }