Optimize method calls w props receiver (#31775)
Redo of #31771 without ghstack
Joseph Savona committed
Dec 13, 2024 at 17:10 UTC
a1b3bd0da0357bdd4b5ecfc26ea6ca3fad0e2f20
7 files changed
+164
-9
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+4
@@ -104,6 +104,7 @@ import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetSta
104
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
105
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
106
import {outlineJSX} from '../Optimization/OutlineJsx';
107
+import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
108
109
export type CompilerPipelineValue =
110
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -209,6 +210,9 @@ function* runWithEnvironment(
210
lowerContextAccess(hir, env.config.lowerContextAccess);
211
}
212
213
+ optimizePropsMethodCalls(hir);
214
+ yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
215
+
216
analyseFunctions(hir);
217
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
218
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+4
@@ -1644,6 +1644,10 @@ export function isArrayType(id: Identifier): boolean {
1644
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
1645
}
1646
1647
+export function isPropsType(id: Identifier): boolean {
1648
+ return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
1649
+}
1650
+
1651
export function isRefValueType(id: Identifier): boolean {
1652
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
1653
}
compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts
new
+52
@@ -0,0 +1,52 @@
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 {HIRFunction, isPropsType} from '../HIR';
9
+
10
+/**
11
+ * Converts method calls into regular calls where the receiver is the props object:
12
+ *
13
+ * Example:
14
+ *
15
+ * ```
16
+ * // INPUT
17
+ * props.foo();
18
+ *
19
+ * // OUTPUT
20
+ * const t0 = props.foo;
21
+ * t0();
22
+ * ```
23
+ *
24
+ * Counter example:
25
+ *
26
+ * Here the receiver is `props.foo`, not the props object, so we don't rewrite it:
27
+ *
28
+ * // INPUT
29
+ * props.foo.bar();
30
+ *
31
+ * // OUTPUT
32
+ * props.foo.bar();
33
+ * ```
34
+ */
35
+export function optimizePropsMethodCalls(fn: HIRFunction): void {
36
+ for (const [, block] of fn.body.blocks) {
37
+ for (let i = 0; i < block.instructions.length; i++) {
38
+ const instr = block.instructions[i]!;
39
+ if (
40
+ instr.value.kind === 'MethodCall' &&
41
+ isPropsType(instr.value.receiver.identifier)
42
+ ) {
43
+ instr.value = {
44
+ kind: 'CallExpression',
45
+ callee: instr.value.property,
46
+ args: instr.value.args,
47
+ loc: instr.value.loc,
48
+ };
49
+ }
50
+ }
51
+ }
52
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md
+9
-8
@@ -3,7 +3,7 @@
3
4
```javascript
5
// @enableJsxOutlining
6
-function Component(arr) {
6
+function Component({arr}) {
7
const x = useX();
8
return arr.map(i => {
9
<>
@@ -49,12 +49,13 @@ export const FIXTURE_ENTRYPOINT = {
49
50
```javascript
51
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
52
-function Component(arr) {
52
+function Component(t0) {
53
const $ = _c(3);
54
+ const { arr } = t0;
55
const x = useX();
55
- let t0;
56
+ let t1;
57
if ($[0] !== arr || $[1] !== x) {
57
- t0 = arr.map((i) => {
58
+ t1 = arr.map((i) => {
59
arr.map((i_0, id) => {
60
const T0 = _temp;
61
const child = <T0 i={i_0} x={x} />;
@@ -65,11 +66,11 @@ function Component(arr) {
66
});
67
$[0] = arr;
68
$[1] = x;
68
- $[2] = t0;
69
+ $[2] = t1;
70
} else {
70
- t0 = $[2];
71
+ t1 = $[2];
72
}
72
- return t0;
73
+ return t1;
74
}
75
function _temp(t0) {
76
const $ = _c(5);
@@ -140,4 +141,4 @@ export const FIXTURE_ENTRYPOINT = {
141
```
142
143
### Eval output
143
-(kind: exception) arr.map is not a function
\ No newline at end of file
144
+(kind: ok) [null,null]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js
+1
-1
@@ -1,5 +1,5 @@
1
// @enableJsxOutlining
2
-function Component(arr) {
2
+function Component({arr}) {
3
const x = useX();
4
return arr.map(i => {
5
<>
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md
new
+78
@@ -0,0 +1,78 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer)
6
+import {useMemo} from 'react';
7
+import {ValidateMemoization} from 'shared-runtime';
8
+
9
+function Component(props) {
10
+ const x = useMemo(() => props.x(), [props.x]);
11
+ return <ValidateMemoization inputs={[props.x]} output={x} />;
12
+}
13
+
14
+const f = () => ['React'];
15
+const g = () => ['Compiler'];
16
+export const FIXTURE_ENTRYPOINT = {
17
+ fn: Component,
18
+ params: [{x: () => ['React']}],
19
+ sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
20
+};
21
+
22
+```
23
+
24
+## Code
25
+
26
+```javascript
27
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
28
+import { useMemo } from "react";
29
+import { ValidateMemoization } from "shared-runtime";
30
+
31
+function Component(props) {
32
+ const $ = _c(7);
33
+ let t0;
34
+ let t1;
35
+ if ($[0] !== props.x) {
36
+ t1 = props.x();
37
+ $[0] = props.x;
38
+ $[1] = t1;
39
+ } else {
40
+ t1 = $[1];
41
+ }
42
+ t0 = t1;
43
+ const x = t0;
44
+ let t2;
45
+ if ($[2] !== props.x) {
46
+ t2 = [props.x];
47
+ $[2] = props.x;
48
+ $[3] = t2;
49
+ } else {
50
+ t2 = $[3];
51
+ }
52
+ let t3;
53
+ if ($[4] !== t2 || $[5] !== x) {
54
+ t3 = <ValidateMemoization inputs={t2} output={x} />;
55
+ $[4] = t2;
56
+ $[5] = x;
57
+ $[6] = t3;
58
+ } else {
59
+ t3 = $[6];
60
+ }
61
+ return t3;
62
+}
63
+
64
+const f = () => ["React"];
65
+const g = () => ["Compiler"];
66
+export const FIXTURE_ENTRYPOINT = {
67
+ fn: Component,
68
+ params: [{ x: () => ["React"] }],
69
+ sequentialRenders: [{ x: f }, { x: g }, { x: g }, { x: f }],
70
+};
71
+
72
+```
73
+
74
+### Eval output
75
+(kind: ok) <div>{"inputs":["[[ function params=0 ]]"],"output":["React"]}</div>
76
+<div>{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}</div>
77
+<div>{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}</div>
78
+<div>{"inputs":["[[ function params=0 ]]"],"output":["React"]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js
new
+16
@@ -0,0 +1,16 @@
1
+// @compilationMode(infer)
2
+import {useMemo} from 'react';
3
+import {ValidateMemoization} from 'shared-runtime';
4
+
5
+function Component(props) {
6
+ const x = useMemo(() => props.x(), [props.x]);
7
+ return <ValidateMemoization inputs={[props.x]} output={x} />;
8
+}
9
+
10
+const f = () => ['React'];
11
+const g = () => ['Compiler'];
12
+export const FIXTURE_ENTRYPOINT = {
13
+ fn: Component,
14
+ params: [{x: () => ['React']}],
15
+ sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
16
+};