main
ts 146 lines 4.66 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 {CompilerError} from '../CompilerError';
9 import {
10 BlockId,
11 Effect,
12 GeneratedSource,
13 HIRFunction,
14 Instruction,
15 Place,
16 } from './HIR';
17 import {markPredecessors} from './HIRBuilder';
18 import {terminalFallthrough, terminalHasFallthrough} from './visitors';
19
20 /*
21 * Merges sequences of blocks that will always execute consecutively —
22 * ie where the predecessor always transfers control to the successor
23 * (ie ends in a goto) and where the predecessor is the only predecessor
24 * for that successor (ie, there is no other way to reach the successor).
25 *
26 * Note that this pass leaves value/loop blocks alone because they cannot
27 * be merged without breaking the structure of the high-level terminals
28 * that reference them.
29 */
30 export function mergeConsecutiveBlocks(fn: HIRFunction): void {
31 const merged = new MergedBlocks();
32 const fallthroughBlocks = new Set<BlockId>();
33 for (const [, block] of fn.body.blocks) {
34 const fallthrough = terminalFallthrough(block.terminal);
35 if (fallthrough !== null) {
36 fallthroughBlocks.add(fallthrough);
37 }
38
39 for (const instr of block.instructions) {
40 if (
41 instr.value.kind === 'FunctionExpression' ||
42 instr.value.kind === 'ObjectMethod'
43 ) {
44 mergeConsecutiveBlocks(instr.value.loweredFunc.func);
45 }
46 }
47
48 if (
49 // Can only merge blocks with a single predecessor
50 block.preds.size !== 1 ||
51 // Value blocks cannot merge
52 block.kind !== 'block' ||
53 // Merging across fallthroughs could move the predecessor out of its block scope
54 fallthroughBlocks.has(block.id)
55 ) {
56 continue;
57 }
58 const originalPredecessorId = Array.from(block.preds)[0]!;
59 const predecessorId = merged.get(originalPredecessorId);
60 const predecessor = fn.body.blocks.get(predecessorId);
61 CompilerError.invariant(predecessor !== undefined, {
62 reason: `Expected predecessor ${predecessorId} to exist`,
63 loc: GeneratedSource,
64 });
65 if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
66 /*
67 * The predecessor is not guaranteed to transfer control to this block,
68 * they aren't consecutive.
69 */
70 continue;
71 }
72
73 // Replace phis in the merged block with canonical assignments to the single operand value
74 for (const phi of block.phis) {
75 CompilerError.invariant(phi.operands.size === 1, {
76 reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
77 loc: GeneratedSource,
78 });
79 const operand = Array.from(phi.operands.values())[0]!;
80 const lvalue: Place = {
81 kind: 'Identifier',
82 identifier: phi.place.identifier,
83 effect: Effect.ConditionallyMutate,
84 reactive: false,
85 loc: GeneratedSource,
86 };
87 const instr: Instruction = {
88 id: predecessor.terminal.id,
89 lvalue: {...lvalue},
90 value: {
91 kind: 'LoadLocal',
92 place: {...operand},
93 loc: GeneratedSource,
94 },
95 effects: [{kind: 'Alias', from: {...operand}, into: {...lvalue}}],
96 loc: GeneratedSource,
97 };
98 predecessor.instructions.push(instr);
99 }
100
101 predecessor.instructions.push(...block.instructions);
102 predecessor.terminal = block.terminal;
103 merged.merge(block.id, predecessorId);
104 fn.body.blocks.delete(block.id);
105 }
106 for (const [, block] of fn.body.blocks) {
107 for (const phi of block.phis) {
108 for (const [predecessorId, operand] of phi.operands) {
109 const mapped = merged.get(predecessorId);
110 if (mapped !== predecessorId) {
111 phi.operands.delete(predecessorId);
112 phi.operands.set(mapped, operand);
113 }
114 }
115 }
116 }
117 markPredecessors(fn.body);
118 for (const [, {terminal}] of fn.body.blocks) {
119 if (terminalHasFallthrough(terminal)) {
120 terminal.fallthrough = merged.get(terminal.fallthrough);
121 }
122 }
123 }
124
125 class MergedBlocks {
126 #map: Map<BlockId, BlockId> = new Map();
127
128 // Record that @param block was merged into @param into.
129 merge(block: BlockId, into: BlockId): void {
130 const target = this.get(into);
131 this.#map.set(block, target);
132 }
133
134 /*
135 * Get the id of the block that @param block has been merged into.
136 * This is transitive, in the case that eg @param block was merged
137 * into a block which later merged into another block.
138 */
139 get(block: BlockId): BlockId {
140 let current = block;
141 while (this.#map.has(current)) {
142 current = this.#map.get(current) ?? current;
143 }
144 return current;
145 }
146 }