@samitouri / QOS-React / commits / c8cebdd946

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 }