Skip to content

Commit a1b3bd0

Browse files
authored
Optimize method calls w props receiver (#31775)
Redo of #31771 without ghstack
1 parent 1520802 commit a1b3bd0

File tree

7 files changed

+164
-9
lines changed

7 files changed

+164
-9
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetSta
104104
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
105105
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
106106
import {outlineJSX} from '../Optimization/OutlineJsx';
107+
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
107108

108109
export type CompilerPipelineValue =
109110
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -209,6 +210,9 @@ function* runWithEnvironment(
209210
lowerContextAccess(hir, env.config.lowerContextAccess);
210211
}
211212

213+
optimizePropsMethodCalls(hir);
214+
yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
215+
212216
analyseFunctions(hir);
213217
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
214218

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,10 @@ export function isArrayType(id: Identifier): boolean {
16441644
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
16451645
}
16461646

1647+
export function isPropsType(id: Identifier): boolean {
1648+
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
1649+
}
1650+
16471651
export function isRefValueType(id: Identifier): boolean {
16481652
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
16491653
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
```javascript
55
// @enableJsxOutlining
6-
function Component(arr) {
6+
function Component({arr}) {
77
const x = useX();
88
return arr.map(i => {
99
<>
@@ -49,12 +49,13 @@ export const FIXTURE_ENTRYPOINT = {
4949

5050
```javascript
5151
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
52-
function Component(arr) {
52+
function Component(t0) {
5353
const $ = _c(3);
54+
const { arr } = t0;
5455
const x = useX();
55-
let t0;
56+
let t1;
5657
if ($[0] !== arr || $[1] !== x) {
57-
t0 = arr.map((i) => {
58+
t1 = arr.map((i) => {
5859
arr.map((i_0, id) => {
5960
const T0 = _temp;
6061
const child = <T0 i={i_0} x={x} />;
@@ -65,11 +66,11 @@ function Component(arr) {
6566
});
6667
$[0] = arr;
6768
$[1] = x;
68-
$[2] = t0;
69+
$[2] = t1;
6970
} else {
70-
t0 = $[2];
71+
t1 = $[2];
7172
}
72-
return t0;
73+
return t1;
7374
}
7475
function _temp(t0) {
7576
const $ = _c(5);
@@ -140,4 +141,4 @@ export const FIXTURE_ENTRYPOINT = {
140141
```
141142
142143
### Eval output
143-
(kind: exception) arr.map is not a function
144+
(kind: ok) [null,null]

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @enableJsxOutlining
2-
function Component(arr) {
2+
function Component({arr}) {
33
const x = useX();
44
return arr.map(i => {
55
<>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};

0 commit comments

Comments
 (0)