mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Conditional assignment of unmemoized value doesnt invalidate later scopes
The previous implementation used IdentifierId, but since this pass operates after LeaveSSA the identifier ids are no longer distinct for different SSA instances. Instead we use the Identifier instance, which preserves SSA information (even ever LeaveSSA) and allows distinguishing between variables whose value always changes vs variables that may be reassigned such that they don't always invalidate. In the future when we use HIR everywhere, this pass should use the HIR CFG to understand that phi nodes whose operands all will always invalidate can also be treated as always invalidating. ## Test Plan Synced to www, 91 files have output changes (https://fburl.com/everpaste/3e3hjpjs). I spot checked these and confirmed that they are all from cases where there was already missing memoization of earlier values, where we now can prune later reactive scopes that depend on the un-memoized values.
This commit is contained in:
+19
-19
@@ -11,7 +11,7 @@ import {
|
||||
visitReactiveFunction,
|
||||
} from ".";
|
||||
import {
|
||||
IdentifierId,
|
||||
Identifier,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
@@ -33,8 +33,8 @@ export function pruneAlwaysInvalidatingScopes(fn: ReactiveFunction): void {
|
||||
}
|
||||
|
||||
class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
alwaysInvalidatingValues: Set<IdentifierId> = new Set();
|
||||
unmemoizedValues: Set<IdentifierId> = new Set();
|
||||
alwaysInvalidatingValues: Set<Identifier> = new Set();
|
||||
unmemoizedValues: Set<Identifier> = new Set();
|
||||
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
@@ -50,34 +50,34 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
case "JsxFragment":
|
||||
case "NewExpression": {
|
||||
if (lvalue !== null) {
|
||||
this.alwaysInvalidatingValues.add(lvalue.identifier.id);
|
||||
this.alwaysInvalidatingValues.add(lvalue.identifier);
|
||||
if (!withinScope) {
|
||||
this.unmemoizedValues.add(lvalue.identifier.id);
|
||||
this.unmemoizedValues.add(lvalue.identifier);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
if (this.alwaysInvalidatingValues.has(value.value.identifier.id)) {
|
||||
this.alwaysInvalidatingValues.add(value.lvalue.place.identifier.id);
|
||||
if (this.alwaysInvalidatingValues.has(value.value.identifier)) {
|
||||
this.alwaysInvalidatingValues.add(value.lvalue.place.identifier);
|
||||
}
|
||||
if (this.unmemoizedValues.has(value.value.identifier.id)) {
|
||||
this.unmemoizedValues.add(value.lvalue.place.identifier.id);
|
||||
if (this.unmemoizedValues.has(value.value.identifier)) {
|
||||
this.unmemoizedValues.add(value.lvalue.place.identifier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "LoadLocal": {
|
||||
if (
|
||||
lvalue !== null &&
|
||||
this.alwaysInvalidatingValues.has(value.place.identifier.id)
|
||||
this.alwaysInvalidatingValues.has(value.place.identifier)
|
||||
) {
|
||||
this.alwaysInvalidatingValues.add(lvalue.identifier.id);
|
||||
this.alwaysInvalidatingValues.add(lvalue.identifier);
|
||||
}
|
||||
if (
|
||||
lvalue !== null &&
|
||||
this.unmemoizedValues.has(value.place.identifier.id)
|
||||
this.unmemoizedValues.has(value.place.identifier)
|
||||
) {
|
||||
this.unmemoizedValues.add(lvalue.identifier.id);
|
||||
this.unmemoizedValues.add(lvalue.identifier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -92,19 +92,19 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
this.visitScope(scopeBlock, true);
|
||||
|
||||
for (const dep of scopeBlock.scope.dependencies) {
|
||||
if (this.unmemoizedValues.has(dep.identifier.id)) {
|
||||
if (this.unmemoizedValues.has(dep.identifier)) {
|
||||
/*
|
||||
* This scope depends on an always-invalidating value so the scope will always invalidate:
|
||||
* prune it to avoid wasted comparisons
|
||||
*/
|
||||
for (const [id, _decl] of scopeBlock.scope.declarations) {
|
||||
if (this.alwaysInvalidatingValues.has(id)) {
|
||||
this.unmemoizedValues.add(id);
|
||||
for (const [_, decl] of scopeBlock.scope.declarations) {
|
||||
if (this.alwaysInvalidatingValues.has(decl.identifier)) {
|
||||
this.unmemoizedValues.add(decl.identifier);
|
||||
}
|
||||
}
|
||||
for (const identifier of scopeBlock.scope.reassignments) {
|
||||
if (this.alwaysInvalidatingValues.has(identifier.id)) {
|
||||
this.unmemoizedValues.add(identifier.id);
|
||||
if (this.alwaysInvalidatingValues.has(identifier)) {
|
||||
this.unmemoizedValues.add(identifier);
|
||||
}
|
||||
}
|
||||
return { kind: "replace-many", value: scopeBlock.instructions };
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useHook, identity } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
let x = 42;
|
||||
if (props.cond) {
|
||||
x = [];
|
||||
}
|
||||
useHook(); // intersperse a hook call to prevent memoization of x
|
||||
identity(x);
|
||||
|
||||
const y = [x];
|
||||
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: "sathya" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { useHook, identity } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(4);
|
||||
let x = 42;
|
||||
if (props.cond) {
|
||||
x = [];
|
||||
}
|
||||
|
||||
useHook();
|
||||
identity(x);
|
||||
let t0;
|
||||
if ($[0] !== x) {
|
||||
t0 = [x];
|
||||
$[0] = x;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const y = t0;
|
||||
let t1;
|
||||
if ($[2] !== y) {
|
||||
t1 = [y];
|
||||
$[2] = y;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: "sathya" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[42]]
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { useHook, identity } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
let x = 42;
|
||||
if (props.cond) {
|
||||
x = [];
|
||||
}
|
||||
useHook(); // intersperse a hook call to prevent memoization of x
|
||||
identity(x);
|
||||
|
||||
const y = [x];
|
||||
|
||||
return [y];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: "sathya" }],
|
||||
};
|
||||
Reference in New Issue
Block a user