Skip to content

Commit b687fd2

Browse files
committed
compiler: Use types to decide which scopes are eligible for merging
In MergeReactiveScopesThatInvalidateTogether when deciding which scopes were eligible for mergin at all, we looked specifically at the instructions whose lvalue produces the declaration. So if a scope declaration was `t0`, we'd love for the instruction where `t0` was the lvalue and look at the instruction type to decide if it is eligible for merging. Here, we use the inferred type instead (now that the inferred types support the same set of types of instructions we looked at before). This allows us to find more cases where scopes can be merged. ghstack-source-id: 0e3e05f24ea0ac6e3c43046bc3e114f906747a04 Pull Request resolved: #29157
1 parent 890896b commit b687fd2

28 files changed

+744
-489
lines changed

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts

+3-61
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
Place,
1414
ReactiveBlock,
1515
ReactiveFunction,
16-
ReactiveInstruction,
1716
ReactiveScope,
1817
ReactiveScopeBlock,
1918
ReactiveScopeDependencies,
@@ -515,64 +514,7 @@ function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean {
515514
*/
516515
return true;
517516
}
518-
const visitor = new DeclarationTypeVisitor(scopeBlock.scope);
519-
visitor.visitScope(scopeBlock, undefined);
520-
return visitor.alwaysInvalidatesOnInputChange;
521-
}
522-
523-
class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
524-
scope: ReactiveScope;
525-
alwaysInvalidatesOnInputChange: boolean = false;
526-
527-
constructor(scope: ReactiveScope) {
528-
super();
529-
this.scope = scope;
530-
}
531-
532-
override visitScope(scopeBlock: ReactiveScopeBlock, state: void): void {
533-
if (scopeBlock.scope.id !== this.scope.id) {
534-
return;
535-
}
536-
this.traverseScope(scopeBlock, state);
537-
}
538-
539-
override visitInstruction(
540-
instruction: ReactiveInstruction,
541-
state: void
542-
): void {
543-
this.traverseInstruction(instruction, state);
544-
if (
545-
instruction.lvalue === null ||
546-
!this.scope.declarations.has(instruction.lvalue.identifier.id)
547-
) {
548-
/*
549-
* no lvalue or this instruction isn't directly constructing a
550-
* scope output value, skip
551-
*/
552-
log(
553-
` skip instruction lvalue=${
554-
instruction.lvalue?.identifier.id
555-
} declaration?=${
556-
instruction.lvalue != null &&
557-
this.scope.declarations.has(instruction.lvalue.identifier.id)
558-
} scope=${printReactiveScopeSummary(this.scope)}`
559-
);
560-
return;
561-
}
562-
switch (instruction.value.kind) {
563-
case "FunctionExpression":
564-
case "ArrayExpression":
565-
case "JsxExpression":
566-
case "JsxFragment":
567-
case "ObjectExpression": {
568-
/*
569-
* These instruction types *always* allocate. If they execute
570-
* they will produce a new value, triggering downstream reactive
571-
* updates
572-
*/
573-
this.alwaysInvalidatesOnInputChange = true;
574-
break;
575-
}
576-
}
577-
}
517+
return [...scopeBlock.scope.declarations].some(([, decl]) =>
518+
isAlwaysInvalidatingType(decl.identifier.type)
519+
);
578520
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md

+59-29
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,33 @@
55
// bar(props.b) is an allocating expression that produces a primitive, which means
66
// that Forget should memoize it.
77
// Correctness:
8+
9+
import { identity, mutate, setProperty } from "shared-runtime";
10+
811
// - y depends on either bar(props.b) or bar(props.b) + 1
912
function AllocatingPrimitiveAsDepNested(props) {
1013
let x = {};
1114
mutate(x);
12-
let y = foo(bar(props.b) + 1);
13-
mutate(x, props.a);
15+
let y = identity(identity(props.b) + 1);
16+
setProperty(x, props.a);
1417
return [x, y];
1518
}
1619

20+
export const FIXTURE_ENTRYPOINT = {
21+
fn: AllocatingPrimitiveAsDepNested,
22+
params: [{ a: 1, b: 2 }],
23+
sequentialRenders: [
24+
// change b
25+
{ a: 1, b: 3 },
26+
// change b
27+
{ a: 1, b: 4 },
28+
// change a
29+
{ a: 2, b: 4 },
30+
// change a
31+
{ a: 3, b: 4 },
32+
],
33+
};
34+
1735
```
1836

1937
## Code
@@ -22,44 +40,56 @@ function AllocatingPrimitiveAsDepNested(props) {
2240
import { c as _c } from "react/compiler-runtime"; // bar(props.b) is an allocating expression that produces a primitive, which means
2341
// that Forget should memoize it.
2442
// Correctness:
43+
44+
import { identity, mutate, setProperty } from "shared-runtime";
45+
2546
// - y depends on either bar(props.b) or bar(props.b) + 1
2647
function AllocatingPrimitiveAsDepNested(props) {
27-
const $ = _c(9);
28-
let x;
29-
let y;
48+
const $ = _c(5);
49+
let t0;
3050
if ($[0] !== props.b || $[1] !== props.a) {
31-
x = {};
51+
const x = {};
3252
mutate(x);
33-
const t0 = bar(props.b) + 1;
34-
let t1;
35-
if ($[4] !== t0) {
36-
t1 = foo(t0);
37-
$[4] = t0;
38-
$[5] = t1;
53+
const t1 = identity(props.b) + 1;
54+
let t2;
55+
if ($[3] !== t1) {
56+
t2 = identity(t1);
57+
$[3] = t1;
58+
$[4] = t2;
3959
} else {
40-
t1 = $[5];
60+
t2 = $[4];
4161
}
42-
y = t1;
43-
mutate(x, props.a);
62+
const y = t2;
63+
setProperty(x, props.a);
64+
t0 = [x, y];
4465
$[0] = props.b;
4566
$[1] = props.a;
46-
$[2] = x;
47-
$[3] = y;
48-
} else {
49-
x = $[2];
50-
y = $[3];
51-
}
52-
let t0;
53-
if ($[6] !== x || $[7] !== y) {
54-
t0 = [x, y];
55-
$[6] = x;
56-
$[7] = y;
57-
$[8] = t0;
67+
$[2] = t0;
5868
} else {
59-
t0 = $[8];
69+
t0 = $[2];
6070
}
6171
return t0;
6272
}
6373

74+
export const FIXTURE_ENTRYPOINT = {
75+
fn: AllocatingPrimitiveAsDepNested,
76+
params: [{ a: 1, b: 2 }],
77+
sequentialRenders: [
78+
// change b
79+
{ a: 1, b: 3 },
80+
// change b
81+
{ a: 1, b: 4 },
82+
// change a
83+
{ a: 2, b: 4 },
84+
// change a
85+
{ a: 3, b: 4 },
86+
],
87+
};
88+
6489
```
65-
90+
91+
### Eval output
92+
(kind: ok) [{"wat0":"joe","wat1":1},4]
93+
[{"wat0":"joe","wat1":1},5]
94+
[{"wat0":"joe","wat1":2},5]
95+
[{"wat0":"joe","wat1":3},5]
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
// bar(props.b) is an allocating expression that produces a primitive, which means
22
// that Forget should memoize it.
33
// Correctness:
4+
5+
import { identity, mutate, setProperty } from "shared-runtime";
6+
47
// - y depends on either bar(props.b) or bar(props.b) + 1
58
function AllocatingPrimitiveAsDepNested(props) {
69
let x = {};
710
mutate(x);
8-
let y = foo(bar(props.b) + 1);
9-
mutate(x, props.a);
11+
let y = identity(identity(props.b) + 1);
12+
setProperty(x, props.a);
1013
return [x, y];
1114
}
15+
16+
export const FIXTURE_ENTRYPOINT = {
17+
fn: AllocatingPrimitiveAsDepNested,
18+
params: [{ a: 1, b: 2 }],
19+
sequentialRenders: [
20+
// change b
21+
{ a: 1, b: 3 },
22+
// change b
23+
{ a: 1, b: 4 },
24+
// change a
25+
{ a: 2, b: 4 },
26+
// change a
27+
{ a: 3, b: 4 },
28+
],
29+
};

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-access-assignment.expect.md

+41-35
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
## Input
33

44
```javascript
5-
function foo(a, b, c) {
5+
function Component({ a, b, c }) {
66
const x = [a];
77
const y = [null, b];
88
const z = [[], [], [c]];
@@ -12,9 +12,15 @@ function foo(a, b, c) {
1212
}
1313

1414
export const FIXTURE_ENTRYPOINT = {
15-
fn: foo,
16-
params: [1, 2, 3],
17-
isComponent: false,
15+
fn: Component,
16+
params: [{ a: 1, b: 20, c: 300 }],
17+
sequentialRenders: [
18+
{ a: 2, b: 20, c: 300 },
19+
{ a: 3, b: 20, c: 300 },
20+
{ a: 3, b: 21, c: 300 },
21+
{ a: 3, b: 22, c: 300 },
22+
{ a: 3, b: 22, c: 301 },
23+
],
1824
};
1925

2026
```
@@ -23,52 +29,52 @@ export const FIXTURE_ENTRYPOINT = {
2329

2430
```javascript
2531
import { c as _c } from "react/compiler-runtime";
26-
function foo(a, b, c) {
27-
const $ = _c(10);
28-
let x;
29-
let z;
32+
function Component(t0) {
33+
const $ = _c(6);
34+
const { a, b, c } = t0;
35+
let t1;
3036
if ($[0] !== a || $[1] !== b || $[2] !== c) {
31-
x = [a];
32-
let t0;
33-
if ($[5] !== b) {
34-
t0 = [null, b];
35-
$[5] = b;
36-
$[6] = t0;
37+
const x = [a];
38+
let t2;
39+
if ($[4] !== b) {
40+
t2 = [null, b];
41+
$[4] = b;
42+
$[5] = t2;
3743
} else {
38-
t0 = $[6];
44+
t2 = $[5];
3945
}
40-
const y = t0;
41-
z = [[], [], [c]];
46+
const y = t2;
47+
const z = [[], [], [c]];
4248
x[0] = y[1];
4349
z[0][0] = x[0];
50+
t1 = [x, z];
4451
$[0] = a;
4552
$[1] = b;
4653
$[2] = c;
47-
$[3] = x;
48-
$[4] = z;
54+
$[3] = t1;
4955
} else {
50-
x = $[3];
51-
z = $[4];
56+
t1 = $[3];
5257
}
53-
let t0;
54-
if ($[7] !== x || $[8] !== z) {
55-
t0 = [x, z];
56-
$[7] = x;
57-
$[8] = z;
58-
$[9] = t0;
59-
} else {
60-
t0 = $[9];
61-
}
62-
return t0;
58+
return t1;
6359
}
6460

6561
export const FIXTURE_ENTRYPOINT = {
66-
fn: foo,
67-
params: [1, 2, 3],
68-
isComponent: false,
62+
fn: Component,
63+
params: [{ a: 1, b: 20, c: 300 }],
64+
sequentialRenders: [
65+
{ a: 2, b: 20, c: 300 },
66+
{ a: 3, b: 20, c: 300 },
67+
{ a: 3, b: 21, c: 300 },
68+
{ a: 3, b: 22, c: 300 },
69+
{ a: 3, b: 22, c: 301 },
70+
],
6971
};
7072

7173
```
7274
7375
### Eval output
74-
(kind: ok) [[2],[[2],[],[3]]]
76+
(kind: ok) [[20],[[20],[],[300]]]
77+
[[20],[[20],[],[300]]]
78+
[[21],[[21],[],[300]]]
79+
[[22],[[22],[],[300]]]
80+
[[22],[[22],[],[301]]]
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function foo(a, b, c) {
1+
function Component({ a, b, c }) {
22
const x = [a];
33
const y = [null, b];
44
const z = [[], [], [c]];
@@ -8,7 +8,13 @@ function foo(a, b, c) {
88
}
99

1010
export const FIXTURE_ENTRYPOINT = {
11-
fn: foo,
12-
params: [1, 2, 3],
13-
isComponent: false,
11+
fn: Component,
12+
params: [{ a: 1, b: 20, c: 300 }],
13+
sequentialRenders: [
14+
{ a: 2, b: 20, c: 300 },
15+
{ a: 3, b: 20, c: 300 },
16+
{ a: 3, b: 21, c: 300 },
17+
{ a: 3, b: 22, c: 300 },
18+
{ a: 3, b: 22, c: 301 },
19+
],
1420
};

0 commit comments

Comments
 (0)