@samitouri / QOS-React-2 / commits / a1b3bd0da0

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 +};