@samitouri / QOS-React-2 / commits / 57ea3b7e8a

Merge consecutive blocks

See the previous PR for context, this PR adds a new pass to merge consecutive blocks.

Joe Savona committed Jan 9, 2023 at 13:04 UTC 57ea3b7e8a04272282f303701d0b709981c96e39
4 files changed +155 -1
compiler/forget/src/CompilerPipeline.ts
+10 -1
@@ -6,8 +6,14 @@
6 */
7 import { NodePath } from "@babel/traverse";
8 import * as t from "@babel/types";
9 +import {
10 + Environment,
11 + HIRFunction,
12 + lower,
13 + mergeConsecutiveBlocks,
14 + ReactiveFunction,
15 +} from "./HIR";
16 import { inferMutableRanges, inferReferenceEffects } from "./Inference";
10 -import { Environment, HIRFunction, lower, ReactiveFunction } from "./HIR";
17 import {
18 buildReactiveFunction,
19 codegenReactiveFunction,
@@ -38,6 +44,9 @@ export default function (
44 const ir = lower(func, env);
45 logHIRFunction("HIR", ir);
46
47 + mergeConsecutiveBlocks(ir);
48 + logHIRFunction("mergeConsecutiveBlocks", ir);
49 +
50 enterSSA(ir, env);
51 logHIRFunction("SSA", ir);
52
compiler/forget/src/HIR/MergeConsecutiveBlocks.ts new
+143
@@ -0,0 +1,143 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import { invariant } from "../Utils/CompilerError";
9 +import { assertExhaustive } from "../Utils/utils";
10 +import {
11 + BlockId,
12 + Effect,
13 + GeneratedSource,
14 + HIRFunction,
15 + Instruction,
16 + InstructionKind,
17 +} from "./HIR";
18 +
19 +/**
20 + * Merges sequences of blocks that will always execute consecutively —
21 + * ie where the predecessor always transfers control to the successor
22 + * (ie ends in a goto) and where the predecessor is the only predecessor
23 + * for that successor (ie, there is no other way to reach the successor).
24 + *
25 + * Note that this pass leaves "value" blocks alone because they cannot
26 + * be merged without breaking the structure of the high-level terminals
27 + * that reference them.
28 + *
29 + * TODO @josephsavona make value blocks explicit (eg a `kind` on Block).
30 + */
31 +export function mergeConsecutiveBlocks(fn: HIRFunction): void {
32 + const merged = new MergedBlocks();
33 + const valueBlocks = new Set<BlockId>();
34 + for (const [, block] of fn.body.blocks) {
35 + // Before evaluating the block itself, determine which other blocks
36 + // are value blocks. See above TODO which would allow us to bypass this.
37 + const terminal = block.terminal;
38 + switch (terminal.kind) {
39 + case "while": {
40 + valueBlocks.add(terminal.test);
41 + break;
42 + }
43 + case "for": {
44 + valueBlocks.add(terminal.init);
45 + valueBlocks.add(terminal.test);
46 + valueBlocks.add(terminal.update);
47 + break;
48 + }
49 + case "if":
50 + case "goto":
51 + case "return":
52 + case "throw":
53 + case "switch": {
54 + break;
55 + }
56 + default: {
57 + assertExhaustive(
58 + terminal,
59 + `Unexpected terminal kind '${(terminal as any).kind}'`
60 + );
61 + }
62 + }
63 +
64 + // Can only merge blocks with a single predecessor, can't merge
65 + // value blocks
66 + if (block.preds.size !== 1 || valueBlocks.has(block.id)) {
67 + continue;
68 + }
69 + const originalPredecessorId = Array.from(block.preds)[0]!;
70 + const predecessorId = merged.get(originalPredecessorId);
71 + const predecessor = fn.body.blocks.get(predecessorId);
72 + invariant(
73 + predecessor !== undefined,
74 + "Expected predecessor %s to exist",
75 + predecessorId
76 + );
77 + if (predecessor.terminal.kind !== "goto") {
78 + // The predecessor is not guaranteed to transfer control to this block,
79 + // they aren't consecutive.
80 + continue;
81 + }
82 +
83 + // Replace phis in the merged block with canonical assignments to the single operand value
84 + for (const phi of block.phis) {
85 + invariant(
86 + phi.operands.size === 1,
87 + "Found a block with a single predecessor but where a phi has multiple (%s) operands",
88 + phi.operands.size
89 + );
90 + const operand = Array.from(phi.operands.values())[0]!;
91 + const instr: Instruction = {
92 + id: predecessor.terminal.id,
93 + lvalue: {
94 + place: {
95 + kind: "Identifier",
96 + identifier: phi.id,
97 + effect: Effect.Mutate,
98 + loc: GeneratedSource,
99 + },
100 + kind: InstructionKind.Const,
101 + },
102 + value: {
103 + kind: "Identifier",
104 + identifier: operand,
105 + effect: Effect.Read,
106 + loc: GeneratedSource,
107 + },
108 + loc: GeneratedSource,
109 + };
110 + predecessor.instructions.push(instr);
111 + }
112 +
113 + predecessor.instructions.push(...block.instructions);
114 + predecessor.terminal = block.terminal;
115 + merged.merge(block.id, predecessorId);
116 + fn.body.blocks.delete(block.id);
117 + }
118 +}
119 +
120 +class MergedBlocks {
121 + #map: Map<BlockId, BlockId> = new Map();
122 +
123 + /**
124 + * Record that @param block was merged into @param into.
125 + */
126 + merge(block: BlockId, into: BlockId): void {
127 + const target = this.get(into);
128 + this.#map.set(block, target);
129 + }
130 +
131 + /**
132 + * Get the id of the block that @param block has been merged into.
133 + * This is transitive, in the case that eg @param block was merged
134 + * into a block which later merged into another block.
135 + */
136 + get(block: BlockId): BlockId {
137 + let current = block;
138 + while (this.#map.has(current)) {
139 + current = this.#map.get(current) ?? current;
140 + }
141 + return current;
142 + }
143 +}
compiler/forget/src/HIR/index.ts
+1
@@ -8,4 +8,5 @@
8 export { lower } from "./BuildHIR";
9 export { HIRFunction, ReactiveFunction } from "./HIR";
10 export { Environment } from "./HIRBuilder";
11 +export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
12 export { printFunction } from "./PrintHIR";
compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md
+1
@@ -19,6 +19,7 @@ function foo(a) {
19 function foo(a) {
20 const x = 0;
21 const x$0 = 1;
22 + return a + x$0;
23 }
24
25 ```