Fix PruneMaybeThrows to update phi operand predecessor ids
When PruneMaybeThrows removes maybe-throw terminals, it's possible that the block in question reassigned a value s.t. it appears as a later phi operand. That phi has to be rewritten to reflect the updated predecessor block. Here we track these rewrites (transitively) and rewrite phi operands accordingly.
Joe Savona committed
Mar 21, 2024 at 14:11 UTC
241b35463c662a9da230b511dc161fd9aa943634
4 files changed
+95
-47
compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts
+24
-21
@@ -5,7 +5,10 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
+import { CompilerError } from "..";
9
import {
10
+ BlockId,
11
+ GeneratedSource,
12
GotoVariant,
13
HIRFunction,
14
Instruction,
@@ -17,12 +20,11 @@ import {
20
} from "../HIR";
21
import {
22
markInstructionIds,
20
- markPredecessors,
23
removeDeadDoWhileStatements,
24
removeUnnecessaryTryCatch,
25
removeUnreachableForUpdates,
26
} from "../HIR/HIRBuilder";
25
-import { eliminateRedundantPhi } from "../SSA";
27
+import { printIdentifier } from "../HIR/PrintHIR";
28
29
/*
30
* This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
@@ -30,8 +32,8 @@ import { eliminateRedundantPhi } from "../SSA";
32
* array/object literals. Even a variable reference could throw bc of the TDZ.
33
*/
34
export function pruneMaybeThrows(fn: HIRFunction): void {
33
- const didPrune = pruneMaybeThrowsImpl(fn);
34
- if (didPrune) {
35
+ const terminalMapping = pruneMaybeThrowsImpl(fn);
36
+ if (terminalMapping) {
37
/*
38
* If terminals have changed then blocks may have become newly unreachable.
39
* Re-run minification of the graph (incl reordering instruction ids)
@@ -42,36 +44,36 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
44
removeDeadDoWhileStatements(fn.body);
45
removeUnnecessaryTryCatch(fn.body);
46
markInstructionIds(fn.body);
45
- markPredecessors(fn.body);
47
+ mergeConsecutiveBlocks(fn);
48
47
- // Now that predecessors are updated, prune phi operands that can never be reached
49
+ // Rewrite phi operands to reference the updated predecessor blocks
50
for (const [, block] of fn.body.blocks) {
51
for (const phi of block.phis) {
50
- for (const [predecessor] of phi.operands) {
52
+ for (const [predecessor, operand] of phi.operands) {
53
if (!block.preds.has(predecessor)) {
54
+ const mappedTerminal = terminalMapping.get(predecessor);
55
+ CompilerError.invariant(mappedTerminal != null, {
56
+ reason: `Expected non-existing phi operand's predecessor to have been mapped to a new terminal`,
57
+ loc: GeneratedSource,
58
+ description: `Could not find mapping for predecessor bb${predecessor} in block bb${
59
+ block.id
60
+ } for phi ${printIdentifier(phi.id)}`,
61
+ suggestions: null,
62
+ });
63
phi.operands.delete(predecessor);
64
+ phi.operands.set(mappedTerminal, operand);
65
}
66
}
67
}
68
}
57
- /*
58
- * By removing some phi operands, there may be phis that were not previously
59
- * redundant but now are
60
- */
61
- eliminateRedundantPhi(fn);
62
- /*
63
- * Finally, merge together any blocks that are now guaranteed to execute
64
- * consecutively
65
- */
66
- mergeConsecutiveBlocks(fn);
69
70
assertConsistentIdentifiers(fn);
71
assertTerminalSuccessorsExist(fn);
72
}
73
}
74
73
-function pruneMaybeThrowsImpl(fn: HIRFunction): boolean {
74
- let hasChanges = false;
75
+function pruneMaybeThrowsImpl(fn: HIRFunction): Map<BlockId, BlockId> | null {
76
+ const terminalMapping = new Map<BlockId, BlockId>();
77
for (const [_, block] of fn.body.blocks) {
78
const terminal = block.terminal;
79
if (terminal.kind !== "maybe-throw") {
@@ -81,7 +83,8 @@ function pruneMaybeThrowsImpl(fn: HIRFunction): boolean {
83
instructionMayThrow(instr)
84
);
85
if (!canThrow) {
84
- hasChanges = true;
86
+ const source = terminalMapping.get(block.id) ?? block.id;
87
+ terminalMapping.set(terminal.continuation, source);
88
block.terminal = {
89
kind: "goto",
90
block: terminal.continuation,
@@ -91,7 +94,7 @@ function pruneMaybeThrowsImpl(fn: HIRFunction): boolean {
94
};
95
}
96
}
94
- return hasChanges;
97
+ return terminalMapping.size > 0 ? terminalMapping : null;
98
}
99
100
function instructionMayThrow(instr: Instruction): boolean {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.repro-preds-undefined.js
deleted
-13
@@ -1,13 +0,0 @@
1
-// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
2
-function useSupportsTouchEvent() {
3
- return useMemo(() => {
4
- if (checkforTouchEvents) {
5
- try {
6
- document.createEvent("TouchEvent");
7
- return true;
8
- } catch {
9
- return false;
10
- }
11
- }
12
- }, []);
13
-}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-preds-undefined-try-catch-return-primitive.expect.md
new
+64
@@ -0,0 +1,64 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
6
+
7
+import { useMemo } from "react";
8
+
9
+const checkforTouchEvents = true;
10
+function useSupportsTouchEvent() {
11
+ return useMemo(() => {
12
+ if (checkforTouchEvents) {
13
+ try {
14
+ document.createEvent("TouchEvent");
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ }, []);
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: useSupportsTouchEvent,
25
+ params: [],
26
+};
27
+
28
+```
29
+
30
+## Code
31
+
32
+```javascript
33
+// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
34
+
35
+import { useMemo } from "react";
36
+
37
+const checkforTouchEvents = true;
38
+function useSupportsTouchEvent() {
39
+ let t0;
40
+ bb15: {
41
+ if (checkforTouchEvents) {
42
+ try {
43
+ document.createEvent("TouchEvent");
44
+ t0 = true;
45
+ break bb15;
46
+ } catch {
47
+ t0 = false;
48
+ break bb15;
49
+ }
50
+ }
51
+ t0 = undefined;
52
+ }
53
+ return t0;
54
+}
55
+
56
+export const FIXTURE_ENTRYPOINT = {
57
+ fn: useSupportsTouchEvent,
58
+ params: [],
59
+};
60
+
61
+```
62
+
63
+### Eval output
64
+(kind: ok) true
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-preds-undefined-try-catch-return-primitive.js
renamed
+7
-13
@@ -1,8 +1,8 @@
1
+// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
2
2
-## Input
3
+import { useMemo } from "react";
4
4
-```javascript
5
-// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
5
+const checkforTouchEvents = true;
6
function useSupportsTouchEvent() {
7
return useMemo(() => {
8
if (checkforTouchEvents) {
@@ -16,13 +16,7 @@ function useSupportsTouchEvent() {
16
}, []);
17
}
18
19
-```
20
-
21
-
22
-## Error
23
-
24
-```
25
-Cannot read properties of undefined (reading 'preds')
26
-```
27
-
28
-
\ No newline at end of file
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: useSupportsTouchEvent,
21
+ params: [],
22
+};