[rhir] Patch: ordering of overlapping input dependencies does not matter

--- 

Patch and simplify logic around merging overlapping reactive dependencies. 

Added `reduce-reactive-unconditional-deps` test fixtures, which tries to cover 
all cases of merging unconditional dependencies (to a minimal dependencies set). 
Please let me know if I missed any
This commit is contained in:
Mofei Zhang
2023-02-27 13:38:02 -05:00
parent 8bc8d67aee
commit f335e7d4d9
15 changed files with 357 additions and 12 deletions
@@ -189,24 +189,17 @@ class Context {
(currentDeclaration.scope == null ||
!this.#isScopeActive(currentDeclaration.scope))
) {
// Below logic ensures that `operand` is either added to `this.#dependencies`
// directly, or is covered by an existing dependency.
// Check if there is an existing dependency that describes this operand
for (const dep of this.#dependencies) {
// not the same identifier
if (dep.place.identifier.id !== maybeDependency.place.identifier.id) {
continue;
}
const depPath = dep.path;
// existing dep covers all paths
if (depPath === null) {
return;
}
const operandPath = maybeDependency.path;
// existing dep is for a path, this operand covers all paths so swap them
if (operandPath === null) {
this.#dependencies.delete(dep);
this.#dependencies.add(maybeDependency);
return;
}
const depPath = dep.path ?? [];
const operandPath = maybeDependency.path ?? [];
// both the operand and dep have paths, determine if the existing path
// is a subset of the new path
let commonPathIndex = 0;
@@ -218,7 +211,13 @@ class Context {
commonPathIndex++;
}
if (commonPathIndex === depPath.length) {
// existing dep is a subpath of the operand, so we don't need to
// add the operand
return;
} else if (commonPathIndex === operandPath.length) {
// operand is a subpath of the existing path, delete the existing
// path
this.#dependencies.delete(dep);
}
}
this.#dependencies.add(maybeDependency);
@@ -0,0 +1,44 @@
## Input
```javascript
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingDescendantTracked(props) {
let x = {};
x.a = props.a.x.y;
x.b = props.b;
x.c = props.a.c.x.y.z;
return x;
}
```
## Code
```javascript
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingDescendantTracked(props) {
const $ = React.unstable_useMemoCache(4);
const c_0 = $[0] !== props.a.x.y;
const c_1 = $[1] !== props.b;
const c_2 = $[2] !== props.a.c.x.y.z;
let x;
if (c_0 || c_1 || c_2) {
x = {};
x.a = props.a.x.y;
x.b = props.b;
x.c = props.a.c.x.y.z;
$[0] = props.a.x.y;
$[1] = props.b;
$[2] = props.a.c.x.y.z;
$[3] = x;
} else {
x = $[3];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingDescendantTracked(props) {
let x = {};
x.a = props.a.x.y;
x.b = props.b;
x.c = props.a.c.x.y.z;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingTracked(props) {
let x = {};
x.b = props.a.b;
x.c = props.a.c;
return x;
}
```
## Code
```javascript
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingTracked(props) {
const $ = React.unstable_useMemoCache(3);
const c_0 = $[0] !== props.a.b;
const c_1 = $[1] !== props.a.c;
let x;
if (c_0 || c_1) {
x = {};
x.b = props.a.b;
x.c = props.a.c;
$[0] = props.a.b;
$[1] = props.a.c;
$[2] = x;
} else {
x = $[2];
}
return x;
}
```
@@ -0,0 +1,8 @@
// Test that we can track non-overlapping dependencies separately.
// (not needed for correctness but for dependency granularity)
function TestNonOverlappingTracked(props) {
let x = {};
x.b = props.a.b;
x.c = props.a.c;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingDescendantTracked(props) {
let x = {};
x.b = props.a.b.c;
x.c = props.a.b.c.x.y;
x.a = props.a;
return x;
}
```
## Code
```javascript
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingDescendantTracked(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.a;
let x;
if (c_0) {
x = {};
x.b = props.a.b.c;
x.c = props.a.b.c.x.y;
x.a = props.a;
$[0] = props.a;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingDescendantTracked(props) {
let x = {};
x.b = props.a.b.c;
x.c = props.a.b.c.x.y;
x.a = props.a;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingTracked(props) {
let x = {};
x.b = props.a.b;
x.c = props.a.c;
x.a = props.a;
return x;
}
```
## Code
```javascript
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingTracked(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.a;
let x;
if (c_0) {
x = {};
x.b = props.a.b;
x.c = props.a.c;
x.a = props.a;
$[0] = props.a;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Test that we correctly track a subpath if the subpath itself is accessed as
// a dependency
function TestOverlappingTracked(props) {
let x = {};
x.b = props.a.b;
x.c = props.a.c;
x.a = props.a;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder1(props) {
let x = {};
x.b = props.a.b;
x.a = props.a;
x.c = props.a.b.c;
return x;
}
```
## Code
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder1(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.a;
let x;
if (c_0) {
x = {};
x.b = props.a.b;
x.a = props.a;
x.c = props.a.b.c;
$[0] = props.a;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder1(props) {
let x = {};
x.b = props.a.b;
x.a = props.a;
x.c = props.a.b.c;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder2(props) {
let x = {};
x.a = props.a;
x.b = props.a.b;
x.c = props.a.b.c;
return x;
}
```
## Code
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder2(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.a;
let x;
if (c_0) {
x = {};
x.a = props.a;
x.b = props.a.b;
x.c = props.a.b.c;
$[0] = props.a;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder2(props) {
let x = {};
x.a = props.a;
x.b = props.a.b;
x.c = props.a.b.c;
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder3(props) {
let x = {};
x.c = props.a.b.c;
x.a = props.a;
x.b = props.a.b;
return x;
}
```
## Code
```javascript
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder3(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props.a;
let x;
if (c_0) {
x = {};
x.c = props.a.b.c;
x.a = props.a;
x.b = props.a.b;
$[0] = props.a;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,9 @@
// Determine that we only need to track p.a here
// Ordering of access should not matter
function TestDepsSubpathOrder3(props) {
let x = {};
x.c = props.a.b.c;
x.a = props.a;
x.b = props.a.b;
return x;
}