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 = {