main
ts 107 lines 3.52 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 '..';
9 import {
10 BlockId,
11 GeneratedSource,
12 HIRFunction,
13 Instruction,
14 assertConsistentIdentifiers,
15 assertTerminalSuccessorsExist,
16 mergeConsecutiveBlocks,
17 reversePostorderBlocks,
18 } from '../HIR';
19 import {
20 markInstructionIds,
21 removeDeadDoWhileStatements,
22 removeUnnecessaryTryCatch,
23 removeUnreachableForUpdates,
24 } from '../HIR/HIRBuilder';
25 import {printPlace} from '../HIR/PrintHIR';
26
27 /**
28 * This pass updates `maybe-throw` terminals for blocks that can provably *never* throw,
29 * nulling out the handler to indicate that control will always continue. Note that
30 * rewriting to a `goto` disrupts the structure of the HIR, making it more difficult to
31 * reconstruct an ast during BuildReactiveFunction. Preserving the maybe-throw makes the
32 * continuations clear, while nulling out the handler tells us that control cannot flow
33 * to the handler.
34 *
35 * For now the analysis is very conservative, and only affects blocks with primitives or
36 * array/object literals. Even a variable reference could throw bc of the TDZ.
37 */
38 export function pruneMaybeThrows(fn: HIRFunction): void {
39 const terminalMapping = pruneMaybeThrowsImpl(fn);
40 if (terminalMapping) {
41 /*
42 * If terminals have changed then blocks may have become newly unreachable.
43 * Re-run minification of the graph (incl reordering instruction ids)
44 */
45 reversePostorderBlocks(fn.body);
46 removeUnreachableForUpdates(fn.body);
47 removeDeadDoWhileStatements(fn.body);
48 removeUnnecessaryTryCatch(fn.body);
49 markInstructionIds(fn.body);
50 mergeConsecutiveBlocks(fn);
51
52 // Rewrite phi operands to reference the updated predecessor blocks
53 for (const [, block] of fn.body.blocks) {
54 for (const phi of block.phis) {
55 for (const [predecessor, operand] of phi.operands) {
56 if (!block.preds.has(predecessor)) {
57 const mappedTerminal = terminalMapping.get(predecessor);
58 CompilerError.invariant(mappedTerminal != null, {
59 reason: `Expected non-existing phi operand's predecessor to have been mapped to a new terminal`,
60 description: `Could not find mapping for predecessor bb${predecessor} in block bb${
61 block.id
62 } for phi ${printPlace(phi.place)}`,
63 loc: GeneratedSource,
64 });
65 phi.operands.delete(predecessor);
66 phi.operands.set(mappedTerminal, operand);
67 }
68 }
69 }
70 }
71
72 assertConsistentIdentifiers(fn);
73 assertTerminalSuccessorsExist(fn);
74 }
75 }
76
77 function pruneMaybeThrowsImpl(fn: HIRFunction): Map<BlockId, BlockId> | null {
78 const terminalMapping = new Map<BlockId, BlockId>();
79 for (const [_, block] of fn.body.blocks) {
80 const terminal = block.terminal;
81 if (terminal.kind !== 'maybe-throw') {
82 continue;
83 }
84 const canThrow = block.instructions.some(instr =>
85 instructionMayThrow(instr),
86 );
87 if (!canThrow) {
88 const source = terminalMapping.get(block.id) ?? block.id;
89 terminalMapping.set(terminal.continuation, source);
90 terminal.handler = null;
91 }
92 }
93 return terminalMapping.size > 0 ? terminalMapping : null;
94 }
95
96 function instructionMayThrow(instr: Instruction): boolean {
97 switch (instr.value.kind) {
98 case 'Primitive':
99 case 'ArrayExpression':
100 case 'ObjectExpression': {
101 return false;
102 }
103 default: {
104 return true;
105 }
106 }
107 }