TryStatement: handle mutation of try values via catch param
Joe Savona committed
Sep 8, 2023 at 11:03 UTC
c8cebdd946c5514d6ff35a5b6fc5660f826cc998
10 files changed
+298
-17
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableRanges.ts
+5
@@ -11,6 +11,7 @@ import { inferAliasForPhis } from "./InferAliasForPhis";
11
import { inferAliasForStores } from "./InferAliasForStores";
12
import { inferMutableLifetimes } from "./InferMutableLifetimes";
13
import { inferMutableRangesForAlias } from "./InferMutableRangesForAlias";
14
+import { inferTryCatchAliases } from "./InferTryCatchAliases";
15
16
export function inferMutableRanges(ir: HIRFunction): void {
17
// Infer mutable ranges for non fields
@@ -18,6 +19,10 @@ export function inferMutableRanges(ir: HIRFunction): void {
19
20
// Calculate aliases
21
const aliases = inferAliases(ir);
22
+ // Calculate aliases for try/catch, where any value created
23
+ // in the try block could be aliased to the catch param
24
+ inferTryCatchAliases(ir, aliases);
25
+
26
// Eagerly canonicalize so that if nothing changes we can bail out
27
// after a single iteration
28
let prevAliases: Map<Identifier, Identifier> = aliases.canonicalize();
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+8
-1
@@ -14,6 +14,7 @@ import {
14
Effect,
15
HIRFunction,
16
IdentifierId,
17
+ InstructionKind,
18
InstructionValue,
19
isMutableEffect,
20
isObjectType,
@@ -924,7 +925,13 @@ function inferBlock(
925
loc: instrValue.loc,
926
value: undefined,
927
};
927
- state.initialize(value, ValueKind.Immutable);
928
+ state.initialize(
929
+ value,
930
+ // Catch params may be aliased to mutable values
931
+ instrValue.lvalue.kind === InstructionKind.Catch
932
+ ? ValueKind.Mutable
933
+ : ValueKind.Immutable
934
+ );
935
state.define(instrValue.lvalue.place, value);
936
continue;
937
}
compiler/packages/babel-plugin-react-forget/src/Inference/InferTryCatchAliases.ts
new
+45
@@ -0,0 +1,45 @@
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 { BlockId, HIRFunction, Identifier } from "../HIR";
9
+import DisjointSet from "../Utils/DisjointSet";
10
+
11
+/**
12
+ * Any values created within a try/catch block could be aliased to the try handler.
13
+ * Our lowering ensures that every instruction within a try block will be lowered into a
14
+ * basic block ending in a maybe-throw terminal that points to its catch block, so we can
15
+ * iterate such blocks and alias their instruction lvalues to the handler's param (if present).
16
+ */
17
+export function inferTryCatchAliases(
18
+ fn: HIRFunction,
19
+ aliases: DisjointSet<Identifier>
20
+): void {
21
+ const handlerParams: Map<BlockId, Identifier> = new Map();
22
+ for (const [_, block] of fn.body.blocks) {
23
+ if (
24
+ block.terminal.kind === "try" &&
25
+ block.terminal.handlerBinding !== null
26
+ ) {
27
+ handlerParams.set(
28
+ block.terminal.handler,
29
+ block.terminal.handlerBinding.identifier
30
+ );
31
+ } else if (block.terminal.kind === "maybe-throw") {
32
+ const handlerParam = handlerParams.get(block.terminal.handler);
33
+ if (handlerParam === undefined) {
34
+ // There's no catch clause param, nothing to alias to so
35
+ // skip this block
36
+ continue;
37
+ }
38
+ // Otherwise alias all values created in this block to the
39
+ // catch clause param
40
+ for (const instr of block.instructions) {
41
+ aliases.union([handlerParam, instr.lvalue.identifier]);
42
+ }
43
+ }
44
+ }
45
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-alias-try-values.expect.md
new
+62
@@ -0,0 +1,62 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+const { throwInput } = require("shared-runtime");
6
+
7
+function Component(props) {
8
+ let y;
9
+ let x = [];
10
+ try {
11
+ // throws x
12
+ throwInput(x);
13
+ } catch (e) {
14
+ // e = x
15
+ y = e; // y = x
16
+ }
17
+ y.push(null);
18
+ return x;
19
+}
20
+
21
+export const FIXTURE_ENTRYPOINT = {
22
+ fn: Component,
23
+ params: [{}],
24
+};
25
+
26
+```
27
+
28
+## Code
29
+
30
+```javascript
31
+import { unstable_useMemoCache as useMemoCache } from "react";
32
+const { throwInput } = require("shared-runtime");
33
+
34
+function Component(props) {
35
+ const $ = useMemoCache(1);
36
+ let x;
37
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38
+ let y;
39
+ x = [];
40
+ try {
41
+ throwInput(x);
42
+ } catch (t27) {
43
+ const e = t27;
44
+
45
+ y = e;
46
+ }
47
+
48
+ y.push(null);
49
+ $[0] = x;
50
+ } else {
51
+ x = $[0];
52
+ }
53
+ return x;
54
+}
55
+
56
+export const FIXTURE_ENTRYPOINT = {
57
+ fn: Component,
58
+ params: [{}],
59
+};
60
+
61
+```
62
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-alias-try-values.js
new
+20
@@ -0,0 +1,20 @@
1
+const { throwInput } = require("shared-runtime");
2
+
3
+function Component(props) {
4
+ let y;
5
+ let x = [];
6
+ try {
7
+ // throws x
8
+ throwInput(x);
9
+ } catch (e) {
10
+ // e = x
11
+ y = e; // y = x
12
+ }
13
+ y.push(null);
14
+ return x;
15
+}
16
+
17
+export const FIXTURE_ENTRYPOINT = {
18
+ fn: Component,
19
+ params: [{}],
20
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.expect.md
new
+63
@@ -0,0 +1,63 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+const { throwInput } = require("shared-runtime");
6
+
7
+function Component(props) {
8
+ let x;
9
+ try {
10
+ const y = [];
11
+ y.push(props.y);
12
+ throwInput(y);
13
+ } catch (e) {
14
+ e.push(props.e);
15
+ x = e;
16
+ }
17
+ return x;
18
+}
19
+
20
+export const FIXTURE_ENTRYPOINT = {
21
+ fn: Component,
22
+ params: [{ y: "foo", e: "bar" }],
23
+};
24
+
25
+```
26
+
27
+## Code
28
+
29
+```javascript
30
+import { unstable_useMemoCache as useMemoCache } from "react";
31
+const { throwInput } = require("shared-runtime");
32
+
33
+function Component(props) {
34
+ const $ = useMemoCache(3);
35
+ let x;
36
+ const c_0 = $[0] !== props.y;
37
+ const c_1 = $[1] !== props.e;
38
+ if (c_0 || c_1) {
39
+ try {
40
+ const y = [];
41
+ y.push(props.y);
42
+ throwInput(y);
43
+ } catch (t30) {
44
+ const e = t30;
45
+ e.push(props.e);
46
+ x = e;
47
+ }
48
+ $[0] = props.y;
49
+ $[1] = props.e;
50
+ $[2] = x;
51
+ } else {
52
+ x = $[2];
53
+ }
54
+ return x;
55
+}
56
+
57
+export const FIXTURE_ENTRYPOINT = {
58
+ fn: Component,
59
+ params: [{ y: "foo", e: "bar" }],
60
+};
61
+
62
+```
63
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch-escaping.js
new
+19
@@ -0,0 +1,19 @@
1
+const { throwInput } = require("shared-runtime");
2
+
3
+function Component(props) {
4
+ let x;
5
+ try {
6
+ const y = [];
7
+ y.push(props.y);
8
+ throwInput(y);
9
+ } catch (e) {
10
+ e.push(props.e);
11
+ x = e;
12
+ }
13
+ return x;
14
+}
15
+
16
+export const FIXTURE_ENTRYPOINT = {
17
+ fn: Component,
18
+ params: [{ y: "foo", e: "bar" }],
19
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md
new
+50
@@ -0,0 +1,50 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+const { throwInput } = require("shared-runtime");
6
+
7
+function Component(props) {
8
+ try {
9
+ const y = [];
10
+ y.push(props.y);
11
+ throwInput(y);
12
+ } catch (e) {
13
+ e.push(props.e);
14
+ return e;
15
+ }
16
+ return null;
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{ y: "foo", e: "bar" }],
22
+};
23
+
24
+```
25
+
26
+## Code
27
+
28
+```javascript
29
+const { throwInput } = require("shared-runtime");
30
+
31
+function Component(props) {
32
+ try {
33
+ const y = [];
34
+ y.push(props.y);
35
+ throwInput(y);
36
+ } catch (t25) {
37
+ const e = t25;
38
+ e.push(props.e);
39
+ return e;
40
+ }
41
+ return null;
42
+}
43
+
44
+export const FIXTURE_ENTRYPOINT = {
45
+ fn: Component,
46
+ params: [{ y: "foo", e: "bar" }],
47
+};
48
+
49
+```
50
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.js
new
+18
@@ -0,0 +1,18 @@
1
+const { throwInput } = require("shared-runtime");
2
+
3
+function Component(props) {
4
+ try {
5
+ const y = [];
6
+ y.push(props.y);
7
+ throwInput(y);
8
+ } catch (e) {
9
+ e.push(props.e);
10
+ return e;
11
+ }
12
+ return null;
13
+}
14
+
15
+export const FIXTURE_ENTRYPOINT = {
16
+ fn: Component,
17
+ params: [{ y: "foo", e: "bar" }],
18
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md
+8
-16
@@ -27,25 +27,17 @@ export const FIXTURE_ENTRYPOINT = {
27
## Code
28
29
```javascript
30
-import { unstable_useMemoCache as useMemoCache } from "react";
30
const { throwInput } = require("shared-runtime");
31
32
function Component(props) {
34
- const $ = useMemoCache(1);
35
- let x;
36
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37
- x = [];
38
- try {
39
- throwInput(x);
40
- } catch (t22) {
41
- const e = t22;
42
-
43
- e.push(null);
44
- return e;
45
- }
46
- $[0] = x;
47
- } else {
48
- x = $[0];
33
+ const x = [];
34
+ try {
35
+ throwInput(x);
36
+ } catch (t22) {
37
+ const e = t22;
38
+
39
+ e.push(null);
40
+ return e;
41
}
42
return x;
43
}