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: 0e3e05f24e
Pull Request resolved: https://github.com/facebook/react/pull/29157
This commit is contained in:
Joe Savona
2024-05-23 17:44:56 +01:00
parent 890896b2db
commit b687fd27b5
28 changed files with 744 additions and 489 deletions
@@ -13,7 +13,6 @@ import {
Place,
ReactiveBlock,
ReactiveFunction,
ReactiveInstruction,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDependencies,
@@ -515,64 +514,7 @@ function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean {
*/
return true;
}
const visitor = new DeclarationTypeVisitor(scopeBlock.scope);
visitor.visitScope(scopeBlock, undefined);
return visitor.alwaysInvalidatesOnInputChange;
}
class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
scope: ReactiveScope;
alwaysInvalidatesOnInputChange: boolean = false;
constructor(scope: ReactiveScope) {
super();
this.scope = scope;
}
override visitScope(scopeBlock: ReactiveScopeBlock, state: void): void {
if (scopeBlock.scope.id !== this.scope.id) {
return;
}
this.traverseScope(scopeBlock, state);
}
override visitInstruction(
instruction: ReactiveInstruction,
state: void
): void {
this.traverseInstruction(instruction, state);
if (
instruction.lvalue === null ||
!this.scope.declarations.has(instruction.lvalue.identifier.id)
) {
/*
* no lvalue or this instruction isn't directly constructing a
* scope output value, skip
*/
log(
` skip instruction lvalue=${
instruction.lvalue?.identifier.id
} declaration?=${
instruction.lvalue != null &&
this.scope.declarations.has(instruction.lvalue.identifier.id)
} scope=${printReactiveScopeSummary(this.scope)}`
);
return;
}
switch (instruction.value.kind) {
case "FunctionExpression":
case "ArrayExpression":
case "JsxExpression":
case "JsxFragment":
case "ObjectExpression": {
/*
* These instruction types *always* allocate. If they execute
* they will produce a new value, triggering downstream reactive
* updates
*/
this.alwaysInvalidatesOnInputChange = true;
break;
}
}
}
return [...scopeBlock.scope.declarations].some(([, decl]) =>
isAlwaysInvalidatingType(decl.identifier.type)
);
}
@@ -5,15 +5,33 @@
// bar(props.b) is an allocating expression that produces a primitive, which means
// that Forget should memoize it.
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// - y depends on either bar(props.b) or bar(props.b) + 1
function AllocatingPrimitiveAsDepNested(props) {
let x = {};
mutate(x);
let y = foo(bar(props.b) + 1);
mutate(x, props.a);
let y = identity(identity(props.b) + 1);
setProperty(x, props.a);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: AllocatingPrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
```
## Code
@@ -22,44 +40,56 @@ function AllocatingPrimitiveAsDepNested(props) {
import { c as _c } from "react/compiler-runtime"; // bar(props.b) is an allocating expression that produces a primitive, which means
// that Forget should memoize it.
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// - y depends on either bar(props.b) or bar(props.b) + 1
function AllocatingPrimitiveAsDepNested(props) {
const $ = _c(9);
let x;
let y;
const $ = _c(5);
let t0;
if ($[0] !== props.b || $[1] !== props.a) {
x = {};
const x = {};
mutate(x);
const t0 = bar(props.b) + 1;
let t1;
if ($[4] !== t0) {
t1 = foo(t0);
$[4] = t0;
$[5] = t1;
const t1 = identity(props.b) + 1;
let t2;
if ($[3] !== t1) {
t2 = identity(t1);
$[3] = t1;
$[4] = t2;
} else {
t1 = $[5];
t2 = $[4];
}
y = t1;
mutate(x, props.a);
const y = t2;
setProperty(x, props.a);
t0 = [x, y];
$[0] = props.b;
$[1] = props.a;
$[2] = x;
$[3] = y;
$[2] = t0;
} else {
x = $[2];
y = $[3];
}
let t0;
if ($[6] !== x || $[7] !== y) {
t0 = [x, y];
$[6] = x;
$[7] = y;
$[8] = t0;
} else {
t0 = $[8];
t0 = $[2];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: AllocatingPrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
```
### Eval output
(kind: ok) [{"wat0":"joe","wat1":1},4]
[{"wat0":"joe","wat1":1},5]
[{"wat0":"joe","wat1":2},5]
[{"wat0":"joe","wat1":3},5]
@@ -1,11 +1,29 @@
// bar(props.b) is an allocating expression that produces a primitive, which means
// that Forget should memoize it.
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// - y depends on either bar(props.b) or bar(props.b) + 1
function AllocatingPrimitiveAsDepNested(props) {
let x = {};
mutate(x);
let y = foo(bar(props.b) + 1);
mutate(x, props.a);
let y = identity(identity(props.b) + 1);
setProperty(x, props.a);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: AllocatingPrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
@@ -2,7 +2,7 @@
## Input
```javascript
function foo(a, b, c) {
function Component({ a, b, c }) {
const x = [a];
const y = [null, b];
const z = [[], [], [c]];
@@ -12,9 +12,15 @@ function foo(a, b, c) {
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [1, 2, 3],
isComponent: false,
fn: Component,
params: [{ a: 1, b: 20, c: 300 }],
sequentialRenders: [
{ a: 2, b: 20, c: 300 },
{ a: 3, b: 20, c: 300 },
{ a: 3, b: 21, c: 300 },
{ a: 3, b: 22, c: 300 },
{ a: 3, b: 22, c: 301 },
],
};
```
@@ -23,52 +29,52 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function foo(a, b, c) {
const $ = _c(10);
let x;
let z;
function Component(t0) {
const $ = _c(6);
const { a, b, c } = t0;
let t1;
if ($[0] !== a || $[1] !== b || $[2] !== c) {
x = [a];
let t0;
if ($[5] !== b) {
t0 = [null, b];
$[5] = b;
$[6] = t0;
const x = [a];
let t2;
if ($[4] !== b) {
t2 = [null, b];
$[4] = b;
$[5] = t2;
} else {
t0 = $[6];
t2 = $[5];
}
const y = t0;
z = [[], [], [c]];
const y = t2;
const z = [[], [], [c]];
x[0] = y[1];
z[0][0] = x[0];
t1 = [x, z];
$[0] = a;
$[1] = b;
$[2] = c;
$[3] = x;
$[4] = z;
$[3] = t1;
} else {
x = $[3];
z = $[4];
t1 = $[3];
}
let t0;
if ($[7] !== x || $[8] !== z) {
t0 = [x, z];
$[7] = x;
$[8] = z;
$[9] = t0;
} else {
t0 = $[9];
}
return t0;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [1, 2, 3],
isComponent: false,
fn: Component,
params: [{ a: 1, b: 20, c: 300 }],
sequentialRenders: [
{ a: 2, b: 20, c: 300 },
{ a: 3, b: 20, c: 300 },
{ a: 3, b: 21, c: 300 },
{ a: 3, b: 22, c: 300 },
{ a: 3, b: 22, c: 301 },
],
};
```
### Eval output
(kind: ok) [[2],[[2],[],[3]]]
(kind: ok) [[20],[[20],[],[300]]]
[[20],[[20],[],[300]]]
[[21],[[21],[],[300]]]
[[22],[[22],[],[300]]]
[[22],[[22],[],[301]]]
@@ -1,4 +1,4 @@
function foo(a, b, c) {
function Component({ a, b, c }) {
const x = [a];
const y = [null, b];
const z = [[], [], [c]];
@@ -8,7 +8,13 @@ function foo(a, b, c) {
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [1, 2, 3],
isComponent: false,
fn: Component,
params: [{ a: 1, b: 20, c: 300 }],
sequentialRenders: [
{ a: 2, b: 20, c: 300 },
{ a: 3, b: 20, c: 300 },
{ a: 3, b: 21, c: 300 },
{ a: 3, b: 22, c: 300 },
{ a: 3, b: 22, c: 301 },
],
};
@@ -23,26 +23,20 @@ import { makeReadOnly } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze true
function MyComponentName(props) {
const $ = _c(5);
let x;
const $ = _c(3);
let y;
if ($[0] !== props.a || $[1] !== props.b) {
x = {};
const x = {};
foo(x, props.a);
foo(x, props.b);
$[0] = props.a;
$[1] = props.b;
$[2] = __DEV__ ? makeReadOnly(x, "MyComponentName") : x;
} else {
x = $[2];
}
let y;
if ($[3] !== x) {
y = [];
y.push(x);
$[3] = x;
$[4] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
$[0] = props.a;
$[1] = props.b;
$[2] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
} else {
y = $[4];
y = $[2];
}
return y;
}
@@ -0,0 +1,162 @@
## Input
```javascript
import { ValidateMemoization } from "shared-runtime";
// Achieving Forget's level of memoization precision in this example isn't possible with useMemo
// without significantly altering the code, so disable the non-Forget evaluation of this fixture.
// @disableNonForgetInSprout
function Component({ a, b, c }) {
const x = [];
let y;
if (a) {
y = [b];
}
x.push(c);
// this scope should not merge with the above scope because y does not invalidate
// on changes to `c`
const z = [y];
// return [x, z];
return (
<>
<ValidateMemoization inputs={[a, b, c]} output={x} />
<ValidateMemoization inputs={[a, b]} output={z} />
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: false, b: null, c: 0 }],
sequentialRenders: [
{ a: false, b: null, c: 0 },
{ a: false, b: null, c: 1 },
{ a: true, b: 0, c: 1 },
{ a: true, b: 1, c: 1 },
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { ValidateMemoization } from "shared-runtime";
// Achieving Forget's level of memoization precision in this example isn't possible with useMemo
// without significantly altering the code, so disable the non-Forget evaluation of this fixture.
// @disableNonForgetInSprout
function Component(t0) {
const $ = _c(25);
const { a, b, c } = t0;
let y;
let x;
if ($[0] !== a || $[1] !== b || $[2] !== c) {
x = [];
if (a) {
let t1;
if ($[5] !== b) {
t1 = [b];
$[5] = b;
$[6] = t1;
} else {
t1 = $[6];
}
y = t1;
}
x.push(c);
$[0] = a;
$[1] = b;
$[2] = c;
$[3] = y;
$[4] = x;
} else {
y = $[3];
x = $[4];
}
let t1;
if ($[7] !== y) {
t1 = [y];
$[7] = y;
$[8] = t1;
} else {
t1 = $[8];
}
const z = t1;
let t2;
if ($[9] !== a || $[10] !== b || $[11] !== c) {
t2 = [a, b, c];
$[9] = a;
$[10] = b;
$[11] = c;
$[12] = t2;
} else {
t2 = $[12];
}
let t3;
if ($[13] !== t2 || $[14] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} />;
$[13] = t2;
$[14] = x;
$[15] = t3;
} else {
t3 = $[15];
}
let t4;
if ($[16] !== a || $[17] !== b) {
t4 = [a, b];
$[16] = a;
$[17] = b;
$[18] = t4;
} else {
t4 = $[18];
}
let t5;
if ($[19] !== t4 || $[20] !== z) {
t5 = <ValidateMemoization inputs={t4} output={z} />;
$[19] = t4;
$[20] = z;
$[21] = t5;
} else {
t5 = $[21];
}
let t6;
if ($[22] !== t3 || $[23] !== t5) {
t6 = (
<>
{t3}
{t5}
</>
);
$[22] = t3;
$[23] = t5;
$[24] = t6;
} else {
t6 = $[24];
}
return t6;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: false, b: null, c: 0 }],
sequentialRenders: [
{ a: false, b: null, c: 0 },
{ a: false, b: null, c: 1 },
{ a: true, b: 0, c: 1 },
{ a: true, b: 1, c: 1 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[false,null,0],"output":[0]}</div><div>{"inputs":[false,null],"output":[null]}</div>
<div>{"inputs":[false,null,1],"output":[1]}</div><div>{"inputs":[false,null],"output":[null]}</div>
<div>{"inputs":[true,0,1],"output":[1]}</div><div>{"inputs":[true,0],"output":[[0]]}</div>
<div>{"inputs":[true,1,1],"output":[1]}</div><div>{"inputs":[true,1],"output":[[1]]}</div>
@@ -0,0 +1,36 @@
import { ValidateMemoization } from "shared-runtime";
// Achieving Forget's level of memoization precision in this example isn't possible with useMemo
// without significantly altering the code, so disable the non-Forget evaluation of this fixture.
// @disableNonForgetInSprout
function Component({ a, b, c }) {
const x = [];
let y;
if (a) {
y = [b];
}
x.push(c);
// this scope should not merge with the above scope because y does not invalidate
// on changes to `c`
const z = [y];
// return [x, z];
return (
<>
<ValidateMemoization inputs={[a, b, c]} output={x} />
<ValidateMemoization inputs={[a, b]} output={z} />
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: false, b: null, c: 0 }],
sequentialRenders: [
{ a: false, b: null, c: 0 },
{ a: false, b: null, c: 1 },
{ a: true, b: 0, c: 1 },
{ a: true, b: 1, c: 1 },
],
};
@@ -13,6 +13,10 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ hello: null, world: undefined, "!": true }],
sequentialRenders: [
{ a: null, b: null, c: null },
{ lauren: true, mofei: true, sathya: true, jason: true },
],
};
```
@@ -22,25 +26,19 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
let items;
const $ = _c(2);
let t0;
if ($[0] !== props) {
items = [];
const items = [];
for (const key in props) {
items.push(<div key={key}>{key}</div>);
}
$[0] = props;
$[1] = items;
} else {
items = $[1];
}
let t0;
if ($[2] !== items) {
t0 = <div>{items}</div>;
$[2] = items;
$[3] = t0;
$[0] = props;
$[1] = t0;
} else {
t0 = $[3];
t0 = $[1];
}
return t0;
}
@@ -48,9 +46,14 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ hello: null, world: undefined, "!": true }],
sequentialRenders: [
{ a: null, b: null, c: null },
{ lauren: true, mofei: true, sathya: true, jason: true },
],
};
```
### Eval output
(kind: ok) <div><div>hello</div><div>world</div><div>!</div></div>
(kind: ok) <div><div>a</div><div>b</div><div>c</div></div>
<div><div>lauren</div><div>mofei</div><div>sathya</div><div>jason</div></div>
@@ -9,4 +9,8 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ hello: null, world: undefined, "!": true }],
sequentialRenders: [
{ a: null, b: null, c: null },
{ lauren: true, mofei: true, sathya: true, jason: true },
],
};
@@ -23,6 +23,38 @@ export const FIXTURE_ENTRYPOINT = {
],
},
],
sequentialRenders: [
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 2,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 0,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
],
};
```
@@ -33,27 +65,21 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
const TOTAL = 10;
function Component(props) {
const $ = _c(5);
let items;
const $ = _c(3);
let t0;
if ($[0] !== props.start || $[1] !== props.items) {
items = [];
const items = [];
for (let i = props.start ?? 0; i < props.items.length; i++) {
const item = props.items[i];
items.push(<div key={item.id}>{item.value}</div>);
}
t0 = <div>{items}</div>;
$[0] = props.start;
$[1] = props.items;
$[2] = items;
$[2] = t0;
} else {
items = $[2];
}
let t0;
if ($[3] !== items) {
t0 = <div>{items}</div>;
$[3] = items;
$[4] = t0;
} else {
t0 = $[4];
t0 = $[2];
}
return t0;
}
@@ -69,9 +95,45 @@ export const FIXTURE_ENTRYPOINT = {
],
},
],
sequentialRenders: [
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 2,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 0,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
],
};
```
### Eval output
(kind: ok) <div><div>zero</div><div>one</div></div>
(kind: ok) <div><div>one</div></div>
<div></div>
<div><div>zero</div><div>one</div><div>two</div></div>
<div><div>one</div><div>two</div></div>
@@ -19,4 +19,36 @@ export const FIXTURE_ENTRYPOINT = {
],
},
],
sequentialRenders: [
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 2,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
],
},
{
start: 0,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
{
start: 1,
items: [
{ id: 0, value: "zero" },
{ id: 1, value: "one" },
{ id: 2, value: "two" },
],
},
],
};
@@ -22,6 +22,11 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true, value: 42 }],
sequentialRenders: [
{ cond: true, value: 3.14 },
{ cond: false, value: 3.14 },
{ cond: true, value: 42 },
],
};
```
@@ -31,11 +36,11 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(6);
let x;
let y;
const $ = _c(2);
let t0;
if ($[0] !== props) {
x = {};
const x = {};
let y;
if (props.cond) {
y = [props.value];
} else {
@@ -43,21 +48,12 @@ function Component(props) {
}
y.push(x);
$[0] = props;
$[1] = x;
$[2] = y;
} else {
x = $[1];
y = $[2];
}
let t0;
if ($[3] !== x || $[4] !== y) {
t0 = [x, y];
$[3] = x;
$[4] = y;
$[5] = t0;
$[0] = props;
$[1] = t0;
} else {
t0 = $[5];
t0 = $[1];
}
return t0;
}
@@ -65,9 +61,16 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true, value: 42 }],
sequentialRenders: [
{ cond: true, value: 3.14 },
{ cond: false, value: 3.14 },
{ cond: true, value: 42 },
],
};
```
### Eval output
(kind: ok) [{},[42,"[[ cyclic ref *1 ]]"]]
(kind: ok) [{},[3.14,"[[ cyclic ref *1 ]]"]]
[{},["[[ cyclic ref *1 ]]"]]
[{},[42,"[[ cyclic ref *1 ]]"]]
@@ -18,4 +18,9 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true, value: 42 }],
sequentialRenders: [
{ cond: true, value: 3.14 },
{ cond: false, value: 3.14 },
{ cond: true, value: 42 },
],
};
@@ -37,41 +37,30 @@ import { arrayPush } from "shared-runtime";
// useCallback-produced values can exist in nested reactive blocks, as long
// as their reactive dependencies are a subset of depslist from source
function useFoo(minWidth, otherProp) {
const $ = _c(11);
const $ = _c(7);
const [width] = useState(1);
let style;
let x;
let t0;
if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) {
x = [];
let t0;
if ($[5] !== minWidth || $[6] !== width) {
t0 = () => ({ width: Math.max(minWidth, width) });
$[5] = minWidth;
$[6] = width;
$[7] = t0;
const x = [];
let t1;
if ($[4] !== minWidth || $[5] !== width) {
t1 = () => ({ width: Math.max(minWidth, width) });
$[4] = minWidth;
$[5] = width;
$[6] = t1;
} else {
t0 = $[7];
t1 = $[6];
}
style = t0;
const style = t1;
arrayPush(x, otherProp);
t0 = [style, x];
$[0] = width;
$[1] = minWidth;
$[2] = otherProp;
$[3] = style;
$[4] = x;
$[3] = t0;
} else {
style = $[3];
x = $[4];
}
let t0;
if ($[8] !== style || $[9] !== x) {
t0 = [style, x];
$[8] = style;
$[9] = x;
$[10] = t0;
} else {
t0 = $[10];
t0 = $[3];
}
return t0;
}
@@ -37,44 +37,33 @@ import { arrayPush } from "shared-runtime";
// useMemo-produced values can exist in nested reactive blocks, as long
// as their reactive dependencies are a subset of depslist from source
function useFoo(minWidth, otherProp) {
const $ = _c(10);
const $ = _c(6);
const [width] = useState(1);
let style;
let x;
let t0;
if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) {
x = [];
let t0;
const x = [];
let t1;
const t1 = Math.max(minWidth, width);
let t2;
if ($[5] !== t1) {
t2 = { width: t1 };
$[5] = t1;
$[6] = t2;
const t2 = Math.max(minWidth, width);
let t3;
if ($[4] !== t2) {
t3 = { width: t2 };
$[4] = t2;
$[5] = t3;
} else {
t2 = $[6];
t3 = $[5];
}
t0 = t2;
style = t0;
t1 = t3;
const style = t1;
arrayPush(x, otherProp);
t0 = [style, x];
$[0] = width;
$[1] = minWidth;
$[2] = otherProp;
$[3] = style;
$[4] = x;
$[3] = t0;
} else {
style = $[3];
x = $[4];
}
let t0;
if ($[7] !== style || $[8] !== x) {
t0 = [style, x];
$[7] = style;
$[8] = x;
$[9] = t0;
} else {
t0 = $[9];
t0 = $[3];
}
return t0;
}
@@ -6,15 +6,33 @@
// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
// separately from props.b)
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// y depends on either props.b or props.b + 1
function PrimitiveAsDepNested(props) {
let x = {};
mutate(x);
let y = foo(props.b + 1);
mutate(x, props.a);
let y = identity(props.b + 1);
setProperty(x, props.a);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: PrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
```
## Code
@@ -24,44 +42,56 @@ import { c as _c } from "react/compiler-runtime"; // props.b + 1 is an non-alloc
// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
// separately from props.b)
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// y depends on either props.b or props.b + 1
function PrimitiveAsDepNested(props) {
const $ = _c(9);
let x;
let y;
const $ = _c(5);
let t0;
if ($[0] !== props.b || $[1] !== props.a) {
x = {};
const x = {};
mutate(x);
const t0 = props.b + 1;
let t1;
if ($[4] !== t0) {
t1 = foo(t0);
$[4] = t0;
$[5] = t1;
const t1 = props.b + 1;
let t2;
if ($[3] !== t1) {
t2 = identity(t1);
$[3] = t1;
$[4] = t2;
} else {
t1 = $[5];
t2 = $[4];
}
y = t1;
mutate(x, props.a);
const y = t2;
setProperty(x, props.a);
t0 = [x, y];
$[0] = props.b;
$[1] = props.a;
$[2] = x;
$[3] = y;
$[2] = t0;
} else {
x = $[2];
y = $[3];
}
let t0;
if ($[6] !== x || $[7] !== y) {
t0 = [x, y];
$[6] = x;
$[7] = y;
$[8] = t0;
} else {
t0 = $[8];
t0 = $[2];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: PrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
```
### Eval output
(kind: ok) [{"wat0":"joe","wat1":1},4]
[{"wat0":"joe","wat1":1},5]
[{"wat0":"joe","wat1":2},5]
[{"wat0":"joe","wat1":3},5]
@@ -2,11 +2,29 @@
// emit it trivially and repeatedly (e.g. no need to memoize props.b + 1
// separately from props.b)
// Correctness:
import { identity, mutate, setProperty } from "shared-runtime";
// y depends on either props.b or props.b + 1
function PrimitiveAsDepNested(props) {
let x = {};
mutate(x);
let y = foo(props.b + 1);
mutate(x, props.a);
let y = identity(props.b + 1);
setProperty(x, props.a);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: PrimitiveAsDepNested,
params: [{ a: 1, b: 2 }],
sequentialRenders: [
// change b
{ a: 1, b: 3 },
// change b
{ a: 1, b: 4 },
// change a
{ a: 2, b: 4 },
// change a
{ a: 3, b: 4 },
],
};
@@ -27,25 +27,19 @@ import { c as _c } from "react/compiler-runtime";
const { mutate } = require("shared-runtime");
function Component(props) {
const $ = _c(4);
let x;
const $ = _c(2);
let t0;
if ($[0] !== props.y) {
x = {};
const x = {};
const y = props.y;
const z = [x, y];
mutate(z);
$[0] = props.y;
$[1] = x;
} else {
x = $[1];
}
let t0;
if ($[2] !== x) {
t0 = [x];
$[2] = x;
$[3] = t0;
$[0] = props.y;
$[1] = t0;
} else {
t0 = $[3];
t0 = $[1];
}
return t0;
}
@@ -26,28 +26,22 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function f(a, b) {
const $ = _c(5);
let x;
const $ = _c(3);
let t0;
if ($[0] !== a.length || $[1] !== b) {
x = [];
const x = [];
if (a.length === 1) {
if (b) {
x.push(b);
}
}
t0 = <div>{x}</div>;
$[0] = a.length;
$[1] = b;
$[2] = x;
$[2] = t0;
} else {
x = $[2];
}
let t0;
if ($[3] !== x) {
t0 = <div>{x}</div>;
$[3] = x;
$[4] = t0;
} else {
t0 = $[4];
t0 = $[2];
}
return t0;
}
@@ -35,35 +35,32 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(7);
const $ = _c(6);
let a;
let t0;
if ($[0] !== props.b) {
a = {};
const b = [];
b.push(props.b);
a.a = null;
t0 = [a];
$[0] = props.b;
$[1] = a;
$[2] = t0;
} else {
a = $[1];
}
let t0;
if ($[2] !== a) {
t0 = [a];
$[2] = a;
$[3] = t0;
} else {
t0 = $[3];
t0 = $[2];
}
const c = t0;
let t1;
if ($[4] !== c || $[5] !== a) {
if ($[3] !== c || $[4] !== a) {
t1 = [c, a];
$[4] = c;
$[5] = a;
$[6] = t1;
$[3] = c;
$[4] = a;
$[5] = t1;
} else {
t1 = $[6];
t1 = $[5];
}
return t1;
}
@@ -41,63 +41,60 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function foo(a, b, c) {
const $ = _c(11);
const $ = _c(10);
let x;
let t0;
if ($[0] !== a) {
x = [];
if (a) {
x.push(a);
}
t0 = <div>{x}</div>;
$[0] = a;
$[1] = x;
$[2] = t0;
} else {
x = $[1];
}
let t0;
if ($[2] !== x) {
t0 = <div>{x}</div>;
$[2] = x;
$[3] = t0;
} else {
t0 = $[3];
t0 = $[2];
}
const y = t0;
bb0: switch (b) {
case 0: {
if ($[4] !== b) {
if ($[3] !== b) {
x = [];
x.push(b);
$[4] = b;
$[5] = x;
$[3] = b;
$[4] = x;
} else {
x = $[5];
x = $[4];
}
break bb0;
}
default: {
if ($[6] !== c) {
if ($[5] !== c) {
x = [];
x.push(c);
$[6] = c;
$[7] = x;
$[5] = c;
$[6] = x;
} else {
x = $[7];
x = $[6];
}
}
}
let t1;
if ($[8] !== y || $[9] !== x) {
if ($[7] !== y || $[8] !== x) {
t1 = (
<div>
{y}
{x}
</div>
);
$[8] = y;
$[9] = x;
$[10] = t1;
$[7] = y;
$[8] = x;
$[9] = t1;
} else {
t1 = $[10];
t1 = $[9];
}
return t1;
}
@@ -61,38 +61,28 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization,
import { CONST_TRUE, setProperty } from "shared-runtime";
function useJoinCondDepsInUncondScopes(props) {
const $ = _c(8);
let x;
let y;
const $ = _c(4);
let t0;
if ($[0] !== props.a.b) {
y = {};
if ($[3] !== props) {
const y = {};
let x;
if ($[2] !== props) {
x = {};
if (CONST_TRUE) {
setProperty(x, props.a.b);
}
$[3] = props;
$[4] = x;
$[2] = props;
$[3] = x;
} else {
x = $[4];
x = $[3];
}
setProperty(y, props.a.b);
$[0] = props.a.b;
$[1] = x;
$[2] = y;
} else {
x = $[1];
y = $[2];
}
let t0;
if ($[5] !== x || $[6] !== y) {
t0 = [x, y];
$[5] = x;
$[6] = y;
$[7] = t0;
$[0] = props.a.b;
$[1] = t0;
} else {
t0 = $[7];
t0 = $[1];
}
return t0;
}
@@ -39,11 +39,11 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
const someGlobal = true;
export default function Component(props) {
const $ = _c(4);
const $ = _c(2);
const { b } = props;
let items;
let t0;
if ($[0] !== b) {
items = [];
const items = [];
let i = 0;
while (i < 10) {
if (someGlobal) {
@@ -51,18 +51,12 @@ export default function Component(props) {
i++;
}
}
$[0] = b;
$[1] = items;
} else {
items = $[1];
}
let t0;
if ($[2] !== items) {
t0 = <>{items}</>;
$[2] = items;
$[3] = t0;
$[0] = b;
$[1] = t0;
} else {
t0 = $[3];
t0 = $[1];
}
return t0;
}
@@ -46,7 +46,7 @@ import { Stringify, identity, makeArray, toJSON } from "shared-runtime";
import { useMemo } from "react";
function Component(props) {
const $ = _c(29);
const $ = _c(13);
let t0;
let t1;
let t2;
@@ -83,87 +83,50 @@ function Component(props) {
t3 = $[5];
}
const linkProps = t3;
let T0;
let t4;
let t5;
let t6;
let t7;
let t8;
let t9;
let t10;
if ($[6] !== linkProps) {
const x = {};
T0 = Stringify;
t4 = linkProps;
if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
let t5;
let t6;
let t7;
let t8;
let t9;
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
t5 = [1];
t6 = [2];
t7 = [3];
t8 = [4];
t9 = [5];
$[15] = t5;
$[16] = t6;
$[17] = t7;
$[18] = t8;
$[19] = t9;
$[8] = t5;
$[9] = t6;
$[10] = t7;
$[11] = t8;
$[12] = t9;
} else {
t5 = $[15];
t6 = $[16];
t7 = $[17];
t8 = $[18];
t9 = $[19];
t5 = $[8];
t6 = $[9];
t7 = $[10];
t8 = $[11];
t9 = $[12];
}
t10 = makeArray(x, 2);
$[6] = linkProps;
$[7] = T0;
$[8] = t4;
$[9] = t5;
$[10] = t6;
$[11] = t7;
$[12] = t8;
$[13] = t9;
$[14] = t10;
} else {
T0 = $[7];
t4 = $[8];
t5 = $[9];
t6 = $[10];
t7 = $[11];
t8 = $[12];
t9 = $[13];
t10 = $[14];
}
let t11;
if (
$[20] !== T0 ||
$[21] !== t4 ||
$[22] !== t5 ||
$[23] !== t6 ||
$[24] !== t7 ||
$[25] !== t8 ||
$[26] !== t9 ||
$[27] !== t10
) {
t11 = (
<T0 link={t4} val1={t5} val2={t6} val3={t7} val4={t8} val5={t9}>
{t10}
</T0>
t4 = (
<Stringify
link={linkProps}
val1={t5}
val2={t6}
val3={t7}
val4={t8}
val5={t9}
>
{makeArray(x, 2)}
</Stringify>
);
$[20] = T0;
$[21] = t4;
$[22] = t5;
$[23] = t6;
$[24] = t7;
$[25] = t8;
$[26] = t9;
$[27] = t10;
$[28] = t11;
$[6] = linkProps;
$[7] = t4;
} else {
t11 = $[28];
t4 = $[7];
}
return t11;
return t4;
}
export const FIXTURE_ENTRYPOINT = {
@@ -52,68 +52,65 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime"; // note: comments are for the ideal scopes, not what is currently
// emitted
function foo(props) {
const $ = _c(15);
const $ = _c(14);
let x;
let t0;
if ($[0] !== props.a) {
x = [];
x.push(props.a);
t0 = <div>{x}</div>;
$[0] = props.a;
$[1] = x;
$[2] = t0;
} else {
x = $[1];
}
let t0;
if ($[2] !== x) {
t0 = <div>{x}</div>;
$[2] = x;
$[3] = t0;
} else {
t0 = $[3];
t0 = $[2];
}
const header = t0;
let y;
if ($[4] !== x || $[5] !== props.b || $[6] !== props.c) {
if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) {
y = [x];
x = [];
y.push(props.b);
x.push(props.c);
$[4] = x;
$[5] = props.b;
$[6] = props.c;
$[7] = y;
$[8] = x;
$[3] = x;
$[4] = props.b;
$[5] = props.c;
$[6] = y;
$[7] = x;
} else {
y = $[7];
x = $[8];
y = $[6];
x = $[7];
}
let t1;
if ($[9] !== x || $[10] !== y) {
if ($[8] !== x || $[9] !== y) {
t1 = (
<div>
{x}
{y}
</div>
);
$[9] = x;
$[10] = y;
$[11] = t1;
$[8] = x;
$[9] = y;
$[10] = t1;
} else {
t1 = $[11];
t1 = $[10];
}
const content = t1;
let t2;
if ($[12] !== header || $[13] !== content) {
if ($[11] !== header || $[12] !== content) {
t2 = (
<>
{header}
{content}
</>
);
$[12] = header;
$[13] = content;
$[14] = t2;
$[11] = header;
$[12] = content;
$[13] = t2;
} else {
t2 = $[14];
t2 = $[13];
}
return t2;
}
@@ -220,7 +220,6 @@ const skipFilter = new Set([
* Tests with one or more params, with external references.
*/
"alias-computed-load",
"allocating-primitive-as-dep-nested-scope",
"allocating-primitive-as-dep",
"allow-passing-refs-as-props",
"array-at-closure",
@@ -309,7 +308,6 @@ const skipFilter = new Set([
"optional-receiver-method-call",
"optional-receiver-optional-method",
"primitive-alias-mutate",
"primitive-as-dep-nested-scope",
"primitive-as-dep",
"property-assignment",
"property-call-spread",
+23 -21
View File
@@ -32,33 +32,35 @@ export function runSprout(
originalCode: string,
forgetCode: string
): SproutResult {
const nonForgetResult = doEval(originalCode);
const forgetResult = doEval(forgetCode);
if (forgetResult.kind === "UnexpectedError") {
return makeError("Unexpected error in Forget runner", forgetResult.value);
} else if (nonForgetResult.kind === "UnexpectedError") {
return makeError(
"Unexpected error in non-forget runner",
nonForgetResult.value
);
} else if (
forgetResult.kind !== nonForgetResult.kind ||
forgetResult.value !== nonForgetResult.value ||
!logsEqual(forgetResult.logs, nonForgetResult.logs)
) {
return makeError(
"Found differences in evaluator results",
`Non-forget (expected):
}
if (originalCode.indexOf("@disableNonForgetInSprout") === -1) {
const nonForgetResult = doEval(originalCode);
if (nonForgetResult.kind === "UnexpectedError") {
return makeError(
"Unexpected error in non-forget runner",
nonForgetResult.value
);
} else if (
forgetResult.kind !== nonForgetResult.kind ||
forgetResult.value !== nonForgetResult.value ||
!logsEqual(forgetResult.logs, nonForgetResult.logs)
) {
return makeError(
"Found differences in evaluator results",
`Non-forget (expected):
${stringify(nonForgetResult)}
Forget:
${stringify(forgetResult)}
`
);
} else {
return {
kind: "success",
value: stringify(forgetResult),
};
);
}
}
return {
kind: "success",
value: stringify(forgetResult),
};
}