mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Infer closures as frozen if they dont capture mutable values
Fix for the previous issue, suggested by @gsathya: when we run InferReferenceEffects on the outer function we check each closure to see if it actually captured any mutable values. If it didn't, we can mark the closure as readonly and memoize it independently.
This commit is contained in:
@@ -762,6 +762,19 @@ export enum Effect {
|
||||
Store = "store",
|
||||
}
|
||||
|
||||
export function isMutableEffect(effect: Effect): boolean {
|
||||
switch (effect) {
|
||||
case Effect.Capture:
|
||||
case Effect.Mutate:
|
||||
case Effect.Store: {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ReactiveScope = {
|
||||
id: ScopeId;
|
||||
range: MutableRange;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
InstructionValue,
|
||||
isMutableEffect,
|
||||
isObjectType,
|
||||
MethodCall,
|
||||
Phi,
|
||||
@@ -679,13 +680,20 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case "FunctionExpression": {
|
||||
let hasMutableOperand = false;
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
state.reference(
|
||||
operand,
|
||||
operand.effect === Effect.Unknown ? Effect.Read : operand.effect
|
||||
);
|
||||
hasMutableOperand ||= isMutableEffect(operand.effect);
|
||||
}
|
||||
state.initialize(instrValue, ValueKind.Mutable);
|
||||
// If a closure did not capture any mutable values, then we can consider it to be
|
||||
// frozen, which allows it to be independently memoized.
|
||||
state.initialize(
|
||||
instrValue,
|
||||
hasMutableOperand ? ValueKind.Mutable : ValueKind.Frozen
|
||||
);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
continue;
|
||||
|
||||
@@ -9,10 +9,10 @@ import invariant from "invariant";
|
||||
import prettyFormat from "pretty-format";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {
|
||||
Effect,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
isHookType,
|
||||
isMutableEffect,
|
||||
Pattern,
|
||||
Place,
|
||||
ReactiveFunction,
|
||||
@@ -702,16 +702,3 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMutableEffect(effect: Effect): boolean {
|
||||
switch (effect) {
|
||||
case Effect.Capture:
|
||||
case Effect.Mutate:
|
||||
case Effect.Store: {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -29,19 +29,26 @@ function changeF(o) {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const $ = React.unstable_useMemoCache(1);
|
||||
let x;
|
||||
const $ = React.unstable_useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: () => console.log("original") };
|
||||
t0 = () => console.log("original");
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
let x;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: t0 };
|
||||
|
||||
console.log("A");
|
||||
console.log("B");
|
||||
changeF(x);
|
||||
console.log("arg");
|
||||
x.f(1);
|
||||
$[0] = x;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[0];
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+12
-5
@@ -29,18 +29,25 @@ function changeF(o) {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const $ = React.unstable_useMemoCache(1);
|
||||
let x;
|
||||
const $ = React.unstable_useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: () => console.log("original") };
|
||||
t0 = () => console.log("original");
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
let x;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: t0 };
|
||||
|
||||
console.log("A");
|
||||
changeF(x);
|
||||
console.log("arg");
|
||||
x.f(1);
|
||||
$[0] = x;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[0];
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ function Component(props) {
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(5);
|
||||
const $ = React.unstable_useMemoCache(7);
|
||||
const c_0 = $[0] !== props.x;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
@@ -33,18 +33,27 @@ function Component(props) {
|
||||
const c_3 = $[3] !== x;
|
||||
let t1;
|
||||
if (c_2 || c_3) {
|
||||
const fn = function () {
|
||||
t1 = function () {
|
||||
const arr = [...bar(props)];
|
||||
return arr.at(x);
|
||||
};
|
||||
t1 = fn();
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
const fnResult = t1;
|
||||
const fn = t1;
|
||||
const c_5 = $[5] !== fn;
|
||||
let t2;
|
||||
if (c_5) {
|
||||
t2 = fn();
|
||||
$[5] = fn;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
const fnResult = t2;
|
||||
return fnResult;
|
||||
}
|
||||
|
||||
|
||||
+13
-4
@@ -14,7 +14,7 @@ function component(a, b) {
|
||||
|
||||
```javascript
|
||||
function component(a, b) {
|
||||
const $ = React.unstable_useMemoCache(4);
|
||||
const $ = React.unstable_useMemoCache(6);
|
||||
const c_0 = $[0] !== a;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
@@ -28,14 +28,23 @@ function component(a, b) {
|
||||
const c_2 = $[2] !== z;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
const p = () => <Foo>{z}</Foo>;
|
||||
t1 = p();
|
||||
t1 = () => <Foo>{z}</Foo>;
|
||||
$[2] = z;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
const p = t1;
|
||||
const c_4 = $[4] !== p;
|
||||
let t2;
|
||||
if (c_4) {
|
||||
t2 = p();
|
||||
$[4] = p;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+11
-4
@@ -17,14 +17,21 @@ function component(a) {
|
||||
|
||||
```javascript
|
||||
function component(a) {
|
||||
const $ = React.unstable_useMemoCache(2);
|
||||
const $ = React.unstable_useMemoCache(3);
|
||||
const c_0 = $[0] !== a;
|
||||
let t;
|
||||
if (c_0) {
|
||||
t = { a };
|
||||
const x = function x(p) {
|
||||
p.foo();
|
||||
};
|
||||
let t0;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = function x(p) {
|
||||
p.foo();
|
||||
};
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
const x = t0;
|
||||
x(t);
|
||||
$[0] = a;
|
||||
$[1] = t;
|
||||
|
||||
+21
-5
@@ -22,14 +22,30 @@ function Component(props) {
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(4);
|
||||
const [value, setValue] = useState(null);
|
||||
|
||||
const onChange = (e) => setValue((value) => value + e.target.value);
|
||||
const c_0 = $[0] !== setValue;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = (e) => setValue((value) => value + e.target.value);
|
||||
$[0] = setValue;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const onChange = t0;
|
||||
|
||||
useOtherHook();
|
||||
|
||||
const x = {};
|
||||
foo(x, onChange);
|
||||
const c_2 = $[2] !== onChange;
|
||||
let x;
|
||||
if (c_2) {
|
||||
x = {};
|
||||
foo(x, onChange);
|
||||
$[2] = onChange;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
+21
-11
@@ -5,6 +5,7 @@
|
||||
function Component(props) {
|
||||
const item = useMutable(props.itemId);
|
||||
const dispatch = useDispatch();
|
||||
useFreeze(dispatch);
|
||||
|
||||
const exit = useCallback(() => {
|
||||
dispatch(createExitAction());
|
||||
@@ -30,13 +31,22 @@ function Component(props) {
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(1);
|
||||
const $ = React.unstable_useMemoCache(3);
|
||||
const item = useMutable(props.itemId);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const exit = () => {
|
||||
dispatch(createExitAction());
|
||||
};
|
||||
useFreeze(dispatch);
|
||||
const c_0 = $[0] !== dispatch;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = () => {
|
||||
dispatch(createExitAction());
|
||||
};
|
||||
$[0] = dispatch;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const exit = t0;
|
||||
|
||||
useEffect(() => {
|
||||
const cleanup = GlobalEventEmitter.addListener("onInput", () => {
|
||||
@@ -48,14 +58,14 @@ function Component(props) {
|
||||
}, [exit, item]);
|
||||
|
||||
maybeMutate(item);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div />;
|
||||
$[0] = t0;
|
||||
let t1;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <div />;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
function Component(props) {
|
||||
const item = useMutable(props.itemId);
|
||||
const dispatch = useDispatch();
|
||||
useFreeze(dispatch);
|
||||
|
||||
const exit = useCallback(() => {
|
||||
dispatch(createExitAction());
|
||||
|
||||
@@ -13,27 +13,36 @@ function component(a) {
|
||||
|
||||
```javascript
|
||||
function component(a) {
|
||||
const $ = React.unstable_useMemoCache(4);
|
||||
const $ = React.unstable_useMemoCache(6);
|
||||
const c_0 = $[0] !== a;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = (() => [a])();
|
||||
t0 = () => [a];
|
||||
$[0] = a;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0;
|
||||
const c_2 = $[2] !== x;
|
||||
const c_2 = $[2] !== t0;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
t1 = <Foo x={x} />;
|
||||
$[2] = x;
|
||||
t1 = t0();
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
const x = t1;
|
||||
const c_4 = $[4] !== x;
|
||||
let t2;
|
||||
if (c_4) {
|
||||
t2 = <Foo x={x} />;
|
||||
$[4] = x;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user