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
```