Ensure member path assignments memoize independently

Assignment expressions to a member path are a special case because they're the 
only place where a value isn't assigned to a (possibly temporary) variable, 
which is our unit of memoization. #901 demonstrated how this can lead to values 
that can't be independently memoized: 

```javascript 

const x = {a: a} 

x.y = [b, c]; // array recomputed w `x`, even if only `a` changed 

``` 

This PR ensures that assignment expressions where the LHS is a member path lower 
the RHS to a Place. That means the above example is handled as if you wrote: 

```javascript 

const x = {a: a}; 

const tmp1 = [b, c]; 

x.y = tmp1; 

``` 

And we independently memoize the temporary.
This commit is contained in:
Joe Savona
2022-12-20 11:06:08 -08:00
parent cedaee5b03
commit af91a7ab86
6 changed files with 109 additions and 157 deletions
+4 -1
View File
@@ -946,7 +946,10 @@ function lowerExpression(
const operator = expr.node.operator;
if (operator === "=") {
const right = lowerExpression(builder, expr.get("right"));
const right =
left.memberPath === null
? lowerExpression(builder, expr.get("right"))
: lowerExpressionToPlace(builder, expr.get("right"));
builder.push({
id: makeInstructionId(0),
lvalue: { place: left, kind: InstructionKind.Reassign },
@@ -1,131 +0,0 @@
## Input
```javascript
function foo(a, b, c) {
const x = { a: a };
// TODO @josephsavona: this array *should* be memoized independently from `x`,
// similar to the behavior if we extract into a variable as with `z` below:
x.y = [b, c];
const y = { a: a };
// this array correctly memoizes independently
const z = [b, c];
y.y = z;
return [x, y];
}
```
## HIR
```
bb0:
[1] Const mutate x$11_@0:TObject[1:3] = Object { a: read a$8 }
[2] Reassign mutate x$11_@0.y[1:3] = Array [read b$9, read c$10]
[3] Const mutate y$12_@1:TObject[3:6] = Object { a: read a$8 }
[4] Const mutate z$13_@2 = Array [read b$9, read c$10]
[5] Reassign mutate y$12_@1.y[3:6] = read z$13_@2
[6] Const mutate t13$14_@3 = Array [read x$11_@0:TObject, read y$12_@1:TObject]
[7] Return freeze t13$14_@3
```
## Reactive Scopes
```
function foo(
a,
b,
c,
) {
scope @0 [1:3] deps=[read a$8, read b$9, read c$10] out=[x$11_@0] {
[1] Const mutate x$11_@0:TObject[1:3] = Object { a: read a$8 }
[2] Reassign mutate x$11_@0.y[1:3] = Array [read b$9, read c$10]
}
scope @1 [3:6] deps=[read a$8, read b$9, read c$10] out=[y$12_@1] {
[3] Const mutate y$12_@1:TObject[3:6] = Object { a: read a$8 }
scope @2 [4:5] deps=[read b$9, read c$10] out=[z$13_@2] {
[4] Const mutate z$13_@2 = Array [read b$9, read c$10]
}
[5] Reassign mutate y$12_@1.y[3:6] = read z$13_@2
}
scope @3 [6:7] deps=[read x$11_@0:TObject, read y$12_@1:TObject] out=[$14_@3] {
[6] Const mutate $14_@3 = Array [read x$11_@0:TObject, read y$12_@1:TObject]
}
return freeze $14_@3
}
```
## Code
```javascript
function foo$0(a$8, b$9, c$10) {
const $ = React.useMemoCache();
const c_0 = $[0] !== a$8;
const c_1 = $[1] !== b$9;
const c_2 = $[2] !== c$10;
let x$11;
if (c_0 || c_1 || c_2) {
x$11 = {
a: a$8,
};
x$11.y = [b$9, c$10];
$[0] = a$8;
$[1] = b$9;
$[2] = c$10;
$[3] = x$11;
} else {
x$11 = $[3];
}
const c_4 = $[4] !== a$8;
const c_5 = $[5] !== b$9;
const c_6 = $[6] !== c$10;
let y$12;
if (c_4 || c_5 || c_6) {
y$12 = {
a: a$8,
};
const c_8 = $[8] !== b$9;
const c_9 = $[9] !== c$10;
let z$13;
if (c_8 || c_9) {
z$13 = [b$9, c$10];
$[8] = b$9;
$[9] = c$10;
$[10] = z$13;
} else {
z$13 = $[10];
}
y$12.y = z$13;
$[4] = a$8;
$[5] = b$9;
$[6] = c$10;
$[7] = y$12;
} else {
y$12 = $[7];
}
const c_11 = $[11] !== x$11;
const c_12 = $[12] !== y$12;
let t13$14;
if (c_11 || c_12) {
t13$14 = [x$11, y$12];
$[11] = x$11;
$[12] = y$12;
$[13] = t13$14;
} else {
t13$14 = $[13];
}
return t13$14;
}
```
@@ -1,13 +0,0 @@
function foo(a, b, c) {
const x = { a: a };
// TODO @josephsavona: this array *should* be memoized independently from `x`,
// similar to the behavior if we extract into a variable as with `z` below:
x.y = [b, c];
const y = { a: a };
// this array correctly memoizes independently
const z = [b, c];
y.y = z;
return [x, y];
}
@@ -62,11 +62,12 @@ function f$0() {
```
bb0:
[1] Const mutate $5:TPrimitive = 1
[2] Reassign mutate a$4_@0.b.c[0:5] = Binary read a$4_@0.b.c + read $5:TPrimitive
[3] Const mutate $6:TPrimitive = 2
[4] Reassign mutate a$4_@0.b.c[0:5] = Binary read a$4_@0.b.c * read $6:TPrimitive
[5] Return
[1] Const mutate $6:TPrimitive = 1
[2] Const mutate $7:TPrimitive = Binary read a$5_@0.b.c + read $6:TPrimitive
[3] Reassign read a$5_@0.b.c[0:6] = read $7:TPrimitive
[4] Const mutate $8:TPrimitive = 2
[5] Reassign mutate a$5_@0.b.c[0:6] = Binary read a$5_@0.b.c * read $8:TPrimitive
[6] Return
```
## Reactive Scopes
@@ -75,10 +76,11 @@ bb0:
function g(
a,
) {
[1] Const mutate $5:TPrimitive = 1
[2] Reassign mutate a$4_@0.b.c[0:5] = Binary read a$4_@0.b.c + read $5:TPrimitive
[3] Const mutate $6:TPrimitive = 2
[4] Reassign mutate a$4_@0.b.c[0:5] = Binary read a$4_@0.b.c * read $6:TPrimitive
[1] Const mutate $6:TPrimitive = 1
[2] Const mutate $7:TPrimitive = Binary read a$5_@0.b.c + read $6:TPrimitive
[3] Reassign read a$5_@0.b.c[0:6] = read $7:TPrimitive
[4] Const mutate $8:TPrimitive = 2
[5] Reassign mutate a$5_@0.b.c[0:6] = Binary read a$5_@0.b.c * read $8:TPrimitive
return
}
@@ -87,9 +89,9 @@ function g(
## Code
```javascript
function g$0(a$4) {
a$4.c.b = a$4.b.c + 1;
a$4.c.b = a$4.b.c * 2;
function g$0(a$5) {
a$5.c.b = a$5.b.c + 1;
a$5.c.b = a$5.b.c * 2;
}
```
@@ -0,0 +1,84 @@
## Input
```javascript
function foo(a, b, c) {
const x = { a: a };
// NOTE: this array should memoize independently from x, w only b,c as deps
x.y = [b, c];
return x;
}
```
## HIR
```
bb0:
[1] Const mutate x$9_@0:TObject[1:4] = Object { a: read a$6 }
[2] Const mutate t6$10_@1 = Array [read b$7, read c$8]
[3] Reassign mutate x$9_@0.y[1:4] = read t6$10_@1
[4] Return freeze x$9_@0:TObject
```
## Reactive Scopes
```
function foo(
a,
b,
c,
) {
scope @0 [1:4] deps=[read a$6, read b$7, read c$8] out=[x$9_@0] {
[1] Const mutate x$9_@0:TObject[1:4] = Object { a: read a$6 }
scope @1 [2:3] deps=[read b$7, read c$8] out=[$10_@1] {
[2] Const mutate $10_@1 = Array [read b$7, read c$8]
}
[3] Reassign mutate x$9_@0.y[1:4] = read $10_@1
}
return freeze x$9_@0:TObject
}
```
## Code
```javascript
function foo$0(a$6, b$7, c$8) {
const $ = React.useMemoCache();
const c_0 = $[0] !== a$6;
const c_1 = $[1] !== b$7;
const c_2 = $[2] !== c$8;
let x$9;
if (c_0 || c_1 || c_2) {
x$9 = {
a: a$6,
};
const c_4 = $[4] !== b$7;
const c_5 = $[5] !== c$8;
let t6$10;
if (c_4 || c_5) {
t6$10 = [b$7, c$8];
$[4] = b$7;
$[5] = c$8;
$[6] = t6$10;
} else {
t6$10 = $[6];
}
x$9.y = t6$10;
$[0] = a$6;
$[1] = b$7;
$[2] = c$8;
$[3] = x$9;
} else {
x$9 = $[3];
}
return x$9;
}
```
@@ -0,0 +1,7 @@
function foo(a, b, c) {
const x = { a: a };
// NOTE: this array should memoize independently from x, w only b,c as deps
x.y = [b, c];
return x;
}