Skip to content

Commit 58eec90

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: f36a92a Pull Request resolved: #29157
1 parent 3b2fcd8 commit 58eec90

25 files changed

+523
-468
lines changed

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

Lines changed: 3 additions & 61 deletions
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,
@@ -520,64 +519,7 @@ function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean {
520519
*/
521520
return true;
522521
}
523-
const visitor = new DeclarationTypeVisitor(scopeBlock.scope);
524-
visitor.visitScope(scopeBlock, undefined);
525-
return visitor.alwaysInvalidatesOnInputChange;
526-
}
527-
528-
class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
529-
scope: ReactiveScope;
530-
alwaysInvalidatesOnInputChange: boolean = false;
531-
532-
constructor(scope: ReactiveScope) {
533-
super();
534-
this.scope = scope;
535-
}
536-
537-
override visitScope(scopeBlock: ReactiveScopeBlock, state: void): void {
538-
if (scopeBlock.scope.id !== this.scope.id) {
539-
return;
540-
}
541-
this.traverseScope(scopeBlock, state);
542-
}
543-
544-
override visitInstruction(
545-
instruction: ReactiveInstruction,
546-
state: void
547-
): void {
548-
this.traverseInstruction(instruction, state);
549-
if (
550-
instruction.lvalue === null ||
551-
!this.scope.declarations.has(instruction.lvalue.identifier.id)
552-
) {
553-
/*
554-
* no lvalue or this instruction isn't directly constructing a
555-
* scope output value, skip
556-
*/
557-
log(
558-
` skip instruction lvalue=${
559-
instruction.lvalue?.identifier.id
560-
} declaration?=${
561-
instruction.lvalue != null &&
562-
this.scope.declarations.has(instruction.lvalue.identifier.id)
563-
} scope=${printReactiveScopeSummary(this.scope)}`
564-
);
565-
return;
566-
}
567-
switch (instruction.value.kind) {
568-
case "FunctionExpression":
569-
case "ArrayExpression":
570-
case "JsxExpression":
571-
case "JsxFragment":
572-
case "ObjectExpression": {
573-
/*
574-
* These instruction types *always* allocate. If they execute
575-
* they will produce a new value, triggering downstream reactive
576-
* updates
577-
*/
578-
this.alwaysInvalidatesOnInputChange = true;
579-
break;
580-
}
581-
}
582-
}
522+
return [...scopeBlock.scope.declarations].some(([, decl]) =>
523+
isAlwaysInvalidatingType(decl.identifier.type)
524+
);
583525
}

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

Lines changed: 59 additions & 29 deletions
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]
Lines changed: 20 additions & 2 deletions
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

Lines changed: 41 additions & 35 deletions
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]]]
Lines changed: 10 additions & 4 deletions
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)