Skip to content

Commit 691cad6

Browse files
committed
Optimization for scope merging when no deps
Addresses T168684688 (#2242). MergeReactiveScopesThatInvalidateTogether does not merge scopes if their output is not guaranteed to change when their inputs do. So for example a case such as `{session_id: bar(props.bar)}` will not merge the scopes for `t0 = bar(props.bar)` and `t1 = {session_id: t0}`, because t0 isn't guaranteed to change when `props.bar` does, and we want to avoid recreating the t1 object unless it semantically changes. But there's a special case: if a scope has no dependencies, then we'll never execute it again anyway. So it doesn't matter what kind of value it produces and it's safe to merge with subsequent scopes: ```javascript return {session_id: bar()} ``` Without the reactive input, `bar()` will always return the same value since we'll only ever call it once anyway. So it's safe to then merge with the scope for the outer object literal.
1 parent e35df45 commit 691cad6

20 files changed

+173
-248
lines changed

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
232232
*/
233233
current.lvalues.clear();
234234

235-
if (!scopeAlwaysInvalidatesOnDependencyChanges(instr)) {
235+
if (!scopeIsEligibleForMerging(instr)) {
236236
/*
237237
* The subsequent scope that we just merged isn't guaranteed to invalidate if its
238238
* inputs change, so it is not a candidate for future merging
@@ -252,7 +252,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
252252
reset();
253253
}
254254
// Only set a new merge candidate if the scope is guaranteed to invalidate on changes
255-
if (scopeAlwaysInvalidatesOnDependencyChanges(instr)) {
255+
if (scopeIsEligibleForMerging(instr)) {
256256
current = {
257257
scope: instr,
258258
from: i,
@@ -432,9 +432,24 @@ function areEqualPaths(a: Array<string>, b: Array<string>): boolean {
432432
return a.length === b.length && a.every((item, ix) => item === b[ix]);
433433
}
434434

435-
function scopeAlwaysInvalidatesOnDependencyChanges(
436-
scope: ReactiveScopeBlock
437-
): boolean {
435+
/**
436+
* Is this scope eligible for merging with subsequent scopes? In general this
437+
* is only true if the scope's output values are guaranteed to change when its
438+
* input changes. When the output may not change, it's better to avoid merging
439+
* with subsequent scopes so that they can compare the input and avoid updating
440+
* when there are no changes.
441+
*
442+
* A special-case is if the scope has no dependencies, then its output will
443+
* *never* change and it's also eligible for merging.
444+
*/
445+
function scopeIsEligibleForMerging(scope: ReactiveScopeBlock): boolean {
446+
if (scope.scope.dependencies.size === 0) {
447+
/*
448+
* Regardless of the type of value produced, if the scope has no dependencies
449+
* then its value will never change.
450+
*/
451+
return true;
452+
}
438453
const visitor = new DeclarationTypeVisitor(scope.scope);
439454
visitor.visitScope(scope, undefined);
440455
return visitor.alwaysInvalidatesOnInputChange;

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,19 @@ import { unstable_useMemoCache as useMemoCache } from "react";
3131
import { makeObject_Primitives, mutate } from "shared-runtime";
3232

3333
function Component() {
34-
const $ = useMemoCache(3);
35-
let x;
36-
let a;
34+
const $ = useMemoCache(1);
35+
let t0;
3736
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38-
a = makeObject_Primitives();
37+
const a = makeObject_Primitives();
3938

40-
x = [];
39+
const x = [];
4140
x.push(a);
4241

4342
mutate(x);
44-
$[0] = x;
45-
$[1] = a;
46-
} else {
47-
x = $[0];
48-
a = $[1];
49-
}
50-
let t0;
51-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
5243
t0 = [x, a];
53-
$[2] = t0;
44+
$[0] = t0;
5445
} else {
55-
t0 = $[2];
46+
t0 = $[0];
5647
}
5748
return t0;
5849
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function Component() {
2020
```javascript
2121
import { unstable_useMemoCache as useMemoCache } from "react";
2222
function Component() {
23-
const $ = useMemoCache(3);
23+
const $ = useMemoCache(2);
2424
let t0;
2525
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
2626
t0 = someObj();
@@ -29,20 +29,15 @@ function Component() {
2929
t0 = $[0];
3030
}
3131
const a = t0;
32-
let x;
32+
let t1;
3333
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
34-
x = [];
34+
const x = [];
3535
x.push(a);
36-
$[1] = x;
37-
} else {
38-
x = $[1];
39-
}
40-
let t1;
41-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
36+
4237
t1 = [x, a];
43-
$[2] = t1;
38+
$[1] = t1;
4439
} else {
45-
t1 = $[2];
40+
t1 = $[1];
4641
}
4742
return t1;
4843
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/alias-nested-member-path.expect.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
2424
```javascript
2525
import { unstable_useMemoCache as useMemoCache } from "react";
2626
function component() {
27-
const $ = useMemoCache(3);
27+
const $ = useMemoCache(2);
2828
let t0;
2929
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
3030
t0 = [];
@@ -33,21 +33,15 @@ function component() {
3333
t0 = $[0];
3434
}
3535
const z = t0;
36-
let y;
36+
let x;
3737
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
38-
y = {};
38+
const y = {};
3939
y.z = z;
40-
$[1] = y;
41-
} else {
42-
y = $[1];
43-
}
44-
let x;
45-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
4640
x = {};
4741
x.y = y;
48-
$[2] = x;
42+
$[1] = x;
4943
} else {
50-
x = $[2];
44+
x = $[1];
5145
}
5246
return x;
5347
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/builtin-jsx-tag-lowered-between-mutations.expect.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,16 @@ function Component(props) {
1414
```javascript
1515
import { unstable_useMemoCache as useMemoCache } from "react";
1616
function Component(props) {
17-
const $ = useMemoCache(2);
17+
const $ = useMemoCache(1);
1818
let t0;
1919
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
2020
const maybeMutable = new MaybeMutable();
21-
t0 = maybeMutate(maybeMutable);
21+
t0 = <div>{maybeMutate(maybeMutable)}</div>;
2222
$[0] = t0;
2323
} else {
2424
t0 = $[0];
2525
}
26-
let t1;
27-
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
28-
t1 = <div>{t0}</div>;
29-
$[1] = t1;
30-
} else {
31-
t1 = $[1];
32-
}
33-
return t1;
26+
return t0;
3427
}
3528

3629
```

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/call.expect.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,18 @@ import { unstable_useMemoCache as useMemoCache } from "react";
2222
function foo() {}
2323

2424
function Component(props) {
25-
const $ = useMemoCache(3);
26-
let a;
27-
let b;
25+
const $ = useMemoCache(1);
26+
let t0;
2827
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29-
a = [];
30-
b = {};
28+
const a = [];
29+
const b = {};
3130
foo(a, b);
3231

3332
foo(b);
34-
$[0] = a;
35-
$[1] = b;
36-
} else {
37-
a = $[0];
38-
b = $[1];
39-
}
40-
let t0;
41-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
4233
t0 = <div a={a} b={b} />;
43-
$[2] = t0;
34+
$[0] = t0;
4435
} else {
45-
t0 = $[2];
36+
t0 = $[0];
4637
}
4738
return t0;
4839
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constructor.expect.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,17 @@ import { unstable_useMemoCache as useMemoCache } from "react";
2222
function Foo() {}
2323

2424
function Component(props) {
25-
const $ = useMemoCache(3);
26-
let a;
27-
let b;
25+
const $ = useMemoCache(1);
26+
let t0;
2827
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29-
a = [];
30-
b = {};
28+
const a = [];
29+
const b = {};
3130
new Foo(a, b);
3231
new Foo(b);
33-
$[0] = a;
34-
$[1] = b;
35-
} else {
36-
a = $[0];
37-
b = $[1];
38-
}
39-
let t0;
40-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
4132
t0 = <div a={a} b={b} />;
42-
$[2] = t0;
33+
$[0] = t0;
4334
} else {
44-
t0 = $[2];
35+
t0 = $[0];
4536
}
4637
return t0;
4738
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-of-mutate.expect.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,19 @@ import { unstable_useMemoCache as useMemoCache } from "react";
2828
import { makeObject_Primitives, mutate, Stringify } from "shared-runtime";
2929

3030
function Component(_props) {
31-
const $ = useMemoCache(2);
32-
let results;
31+
const $ = useMemoCache(1);
32+
let t0;
3333
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
3434
const collection = [makeObject_Primitives()];
35-
results = [];
35+
const results = [];
3636
for (const item of collection) {
3737
results.push(<div key={Stringify(item)}>{Stringify(mutate(item))}</div>);
3838
}
39-
$[0] = results;
40-
} else {
41-
results = $[0];
42-
}
43-
let t0;
44-
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
39+
4540
t0 = <div>{results}</div>;
46-
$[1] = t0;
41+
$[0] = t0;
4742
} else {
48-
t0 = $[1];
43+
t0 = $[0];
4944
}
5045
return t0;
5146
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/global-jsx-tag-lowered-between-mutations.expect.md

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,17 @@ function Component(props) {
2525
```javascript
2626
import { unstable_useMemoCache as useMemoCache } from "react";
2727
function Component(props) {
28-
const $ = useMemoCache(3);
29-
let T0;
30-
let t1;
28+
const $ = useMemoCache(1);
29+
let t0;
3130
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
3231
const maybeMutable = new MaybeMutable();
3332

34-
T0 = View;
35-
t1 = maybeMutate(maybeMutable);
36-
$[0] = T0;
37-
$[1] = t1;
33+
t0 = <View>{maybeMutate(maybeMutable)}</View>;
34+
$[0] = t0;
3835
} else {
39-
T0 = $[0];
40-
t1 = $[1];
36+
t0 = $[0];
4137
}
42-
let t2;
43-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
44-
t2 = <T0>{t1}</T0>;
45-
$[2] = t2;
46-
} else {
47-
t2 = $[2];
48-
}
49-
return t2;
38+
return t0;
5039
}
5140

5241
```

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-const-declaration.expect.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,17 @@ export const FIXTURE_ENTRYPOINT = {
2424
```javascript
2525
import { unstable_useMemoCache as useMemoCache } from "react";
2626
function hoisting() {
27-
const $ = useMemoCache(2);
28-
let foo;
27+
const $ = useMemoCache(1);
28+
let t0;
2929
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30-
foo = () => bar + baz;
30+
const foo = () => bar + baz;
3131

3232
const bar = 3;
3333
const baz = 2;
34-
$[0] = foo;
35-
} else {
36-
foo = $[0];
37-
}
38-
let t0;
39-
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
4034
t0 = foo();
41-
$[1] = t0;
35+
$[0] = t0;
4236
} else {
43-
t0 = $[1];
37+
t0 = $[0];
4438
}
4539
return t0;
4640
}

compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-member-expression-tag-grouping.expect.md

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,16 @@ function Component(props) {
1414
```javascript
1515
import { unstable_useMemoCache as useMemoCache } from "react";
1616
function Component(props) {
17-
const $ = useMemoCache(3);
18-
let T0;
19-
let t1;
17+
const $ = useMemoCache(1);
18+
let t0;
2019
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
2120
const maybeMutable = new MaybeMutable();
22-
T0 = Foo.Bar;
23-
t1 = maybeMutate(maybeMutable);
24-
$[0] = T0;
25-
$[1] = t1;
21+
t0 = <Foo.Bar>{maybeMutate(maybeMutable)}</Foo.Bar>;
22+
$[0] = t0;
2623
} else {
27-
T0 = $[0];
28-
t1 = $[1];
24+
t0 = $[0];
2925
}
30-
let t2;
31-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
32-
t2 = <T0>{t1}</T0>;
33-
$[2] = t2;
34-
} else {
35-
t2 = $[2];
36-
}
37-
return t2;
26+
return t0;
3827
}
3928

4029
```

0 commit comments

Comments
 (0)