@samitouri / QOS-React-2 / commits / 890896b2db

compiler: Improve merging of memo scopes that invalidate together

Improves merging of consecutive scopes so that we now merge two scopes if the dependencies of the second scope are a subset of the previous scope's output *and* that dependency has a type that will always produce a new value (array, object, jsx, function) if it is re-evaluated. To make this easier, we extend the set of builtin types to include ones for function expressions and JSX and to infer these types in InferTypes. This allows using the already inferred types in MergeReactiveScopesThatInvalidateTogether. ghstack-source-id: e9119fc4e02b3665848113d71fdff0c5bac3348a Pull Request resolved: https://github.com/facebook/react/pull/29156

Joe Savona committed May 21, 2024 at 13:10 UTC 890896b2db40b9994b3f1b817928e931b675eba9
6 files changed +48 -34
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+2
@@ -188,6 +188,8 @@ export type ObjectShape = {
188 */
189 export type ShapeRegistry = Map<string, ObjectShape>;
190 export const BuiltInArrayId = "BuiltInArray";
191 +export const BuiltInFunctionId = "BuiltInFunction";
192 +export const BuiltInJsxId = "BuiltInJsx";
193 export const BuiltInObjectId = "BuiltInObject";
194 export const BuiltInUseStateId = "BuiltInUseState";
195 export const BuiltInSetStateId = "BuiltInSetState";
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts
+28 -1
@@ -19,8 +19,15 @@ import {
19 ReactiveScopeDependencies,
20 ReactiveScopeDependency,
21 ReactiveStatement,
22 + Type,
23 makeInstructionId,
24 } from "../HIR";
25 +import {
26 + BuiltInArrayId,
27 + BuiltInFunctionId,
28 + BuiltInJsxId,
29 + BuiltInObjectId,
30 +} from "../HIR/ObjectShape";
31 import { eachInstructionLValue } from "../HIR/visitors";
32 import { assertExhaustive } from "../Utils/utils";
33 import { printReactiveScopeSummary } from "./PrintReactiveFunction";
@@ -430,7 +437,13 @@ function canMergeScopes(
437 }))
438 ),
439 next.scope.dependencies
433 - )
440 + ) ||
441 + (next.scope.dependencies.size !== 0 &&
442 + [...next.scope.dependencies].every(
443 + (dep) =>
444 + current.scope.declarations.has(dep.identifier.id) &&
445 + isAlwaysInvalidatingType(dep.identifier.type)
446 + ))
447 ) {
448 log(` outputs of prev are input to current`);
449 return true;
@@ -441,6 +454,20 @@ function canMergeScopes(
454 return false;
455 }
456
457 +function isAlwaysInvalidatingType(type: Type): boolean {
458 + if (type.kind === "Object") {
459 + switch (type.shapeId) {
460 + case BuiltInArrayId:
461 + case BuiltInObjectId:
462 + case BuiltInFunctionId:
463 + case BuiltInJsxId: {
464 + return true;
465 + }
466 + }
467 + }
468 + return false;
469 +}
470 +
471 function areEqualDependencies(
472 a: Set<ReactiveScopeDependency>,
473 b: Set<ReactiveScopeDependency>
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+8 -2
@@ -20,6 +20,8 @@ import {
20 } from "../HIR/HIR";
21 import {
22 BuiltInArrayId,
23 + BuiltInFunctionId,
24 + BuiltInJsxId,
25 BuiltInObjectId,
26 BuiltInUseRefId,
27 } from "../HIR/ObjectShape";
@@ -313,6 +315,7 @@ function* generateInstructionTypes(
315
316 case "FunctionExpression": {
317 yield* generate(value.loweredFunc.func);
318 + yield equation(left, { kind: "Object", shapeId: BuiltInFunctionId });
319 break;
320 }
321
@@ -327,10 +330,13 @@ function* generateInstructionTypes(
330 break;
331 }
332
333 + case "JsxExpression":
334 + case "JsxFragment": {
335 + yield equation(left, { kind: "Object", shapeId: BuiltInJsxId });
336 + break;
337 + }
338 case "DeclareLocal":
339 case "NewExpression":
332 - case "JsxExpression":
333 - case "JsxFragment":
340 case "RegExpLiteral":
341 case "PropertyStore":
342 case "ComputedStore":
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md
+1 -1
@@ -22,7 +22,7 @@ function Component(props) {
22 7 | return <Foo item={item} current={current} />;
23 8 | };
24 > 9 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
25 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $64[13:15] (9:9)
25 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $64[13:15]:TObject<BuiltInFunction> (9:9)
26 10 | }
27 11 |
28 ```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-in-callback-invoked-during-render.expect.md
+1 -1
@@ -21,7 +21,7 @@ function Component(props) {
21 6 | return <Foo item={item} current={current} />;
22 7 | };
23 > 8 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
24 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $60[14:16] (8:8)
24 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $60[14:16]:TObject<BuiltInFunction> (8:8)
25 9 | }
26 10 |
27 ```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-deps-subset-of-decls.expect.md
+8 -29
@@ -33,44 +33,23 @@ import { c as _c } from "react/compiler-runtime";
33 import { useState } from "react";
34
35 function Component() {
36 - const $ = _c(8);
36 + const $ = _c(2);
37 const [count, setCount] = useState(0);
38 let t0;
39 - let t1;
39 if ($[0] !== count) {
41 - t0 = <button onClick={() => setCount(count - 1)}>Decrement</button>;
40 + t0 = (
41 + <div>
42 + <button onClick={() => setCount(count - 1)}>Decrement</button>
43
43 - t1 = () => setCount(count + 1);
44 + <button onClick={() => setCount(count + 1)}>Increment</button>
45 + </div>
46 + );
47 $[0] = count;
48 $[1] = t0;
46 - $[2] = t1;
49 } else {
50 t0 = $[1];
49 - t1 = $[2];
50 - }
51 - let t2;
52 - if ($[3] !== t1) {
53 - t2 = <button onClick={t1}>Increment</button>;
54 - $[3] = t1;
55 - $[4] = t2;
56 - } else {
57 - t2 = $[4];
58 - }
59 - let t3;
60 - if ($[5] !== t0 || $[6] !== t2) {
61 - t3 = (
62 - <div>
63 - {t0}
64 - {t2}
65 - </div>
66 - );
67 - $[5] = t0;
68 - $[6] = t2;
69 - $[7] = t3;
70 - } else {
71 - t3 = $[7];
51 }
73 - return t3;
52 + return t0;
53 }
54
55 export const FIXTURE_ENTRYPOINT = {