Propagate reactivity to other operands accounting for mutable ranges

Previously if any operand was reactive, we transferred that reactivity to other 
operands that had a mutable effect (capture, conditionally mutate, mutate, or 
store). But a value can be captured without ever being modified again. This PR 
updates the logic to only transfer reactivity among operands that are actually 
mutable at the given instruction, based on the mutable range. This is strictly 
more precise.
This commit is contained in:
Joe Savona
2023-11-01 17:13:05 -07:00
parent b34172c11e
commit 3e157bbc27
7 changed files with 150 additions and 15 deletions
@@ -23,6 +23,7 @@ import {
eachTerminalOperand,
} from "../HIR/visitors";
import { hasBackEdge } from "../Optimization/DeadCodeElimination";
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
import { assertExhaustive } from "../Utils/utils";
/**
@@ -195,11 +196,13 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.Mutate: {
const resolvedId = identifierMapping.get(operand.identifier);
if (resolvedId !== undefined) {
reactiveIdentifiers.markReactiveIdentifier(resolvedId);
if (isMutable(instruction, operand)) {
const resolvedId = identifierMapping.get(operand.identifier);
if (resolvedId !== undefined) {
reactiveIdentifiers.markReactiveIdentifier(resolvedId);
}
reactiveIdentifiers.markReactive(operand);
}
reactiveIdentifiers.markReactive(operand);
break;
}
case Effect.Freeze:
@@ -212,7 +212,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
}
// Is the operand mutable at this given instruction
function isMutable({ id }: Instruction, place: Place): boolean {
export function isMutable({ id }: Instruction, place: Place): boolean {
const range = place.identifier.mutableRange;
return id >= range.start && id < range.end;
}
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
function Component(props) {
const $ = useMemoCache(7);
const $ = useMemoCache(5);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
@@ -42,7 +42,7 @@ function Component(props) {
}
const x = t0;
let y;
if ($[1] !== props || $[2] !== x) {
if ($[1] !== props) {
if (props.cond) {
y = {};
} else {
@@ -51,19 +51,17 @@ function Component(props) {
y.x = x;
$[1] = props;
$[2] = x;
$[3] = y;
$[2] = y;
} else {
y = $[3];
y = $[2];
}
let t1;
if ($[4] !== x || $[5] !== y) {
if ($[3] !== y) {
t1 = [x, y];
$[4] = x;
$[5] = y;
$[6] = t1;
$[3] = y;
$[4] = t1;
} else {
t1 = $[6];
t1 = $[4];
}
return t1;
}
@@ -0,0 +1,50 @@
## Input
```javascript
function Component(props) {
const x = {};
const y = props.y;
return [x, y]; // x is captured here along with a reactive value. this shouldn't make `x` reactive!
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
const y = props.y;
let t1;
if ($[1] !== y) {
t1 = [x, y];
$[1] = y;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};
```
@@ -0,0 +1,10 @@
function Component(props) {
const x = {};
const y = props.y;
return [x, y]; // x is captured here along with a reactive value. this shouldn't make `x` reactive!
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};
@@ -0,0 +1,59 @@
## Input
```javascript
const { mutate } = require("shared-runtime");
function Component(props) {
const x = {};
const y = props.y;
const z = [x, y];
mutate(z);
// x's object identity can change bc it co-mutates with z, which is reactive via props.y
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
const { mutate } = require("shared-runtime");
function Component(props) {
const $ = useMemoCache(4);
let x;
if ($[0] !== props.y) {
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;
} else {
t0 = $[3];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};
```
@@ -0,0 +1,15 @@
const { mutate } = require("shared-runtime");
function Component(props) {
const x = {};
const y = props.y;
const z = [x, y];
mutate(z);
// x's object identity can change bc it co-mutates with z, which is reactive via props.y
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ y: 42 }],
};