mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[patch] Fix for constant propagation bug in VR Store
---
Changes in `@enableOptimizeFunctionExpressions` caused a bug in the last Forget
sync to VR Store. The repro can be summarized to something like this:
```js
function foo() {
const x = true; // some constant or global
// Add some branching for type inference
// This can be a Logical expression as well (e.g. `4 || 5`)
if (...) { }
// In this HIR block, SSA inserts a `x$2 = phi(x$1, x$1)`.
// EliminateRedundantPhiNodes needs to rewrite all references of `x$2` to `x$1`
const accessXInLambda = () => x;
return accessXInLambda;
}
This commit is contained in:
+14
-3
@@ -27,9 +27,13 @@ import {
|
||||
* and phis rewrite all their identifiers based on this table. The algorithm loops over the CFG repeatedly
|
||||
* until there are no new rewrites: for a CFG without back-edges it completes in a single pass.
|
||||
*/
|
||||
export function eliminateRedundantPhi(fn: HIRFunction): void {
|
||||
export function eliminateRedundantPhi(
|
||||
fn: HIRFunction,
|
||||
sharedRewrites?: Map<Identifier, Identifier>
|
||||
): void {
|
||||
const ir = fn.body;
|
||||
const rewrites: Map<Identifier, Identifier> = new Map();
|
||||
const rewrites: Map<Identifier, Identifier> =
|
||||
sharedRewrites != null ? sharedRewrites : new Map();
|
||||
|
||||
// Whether or the CFG has a back-edge (a loop). We determine this dynamically
|
||||
// during the first iteration over the CFG by recording which blocks were already
|
||||
@@ -102,6 +106,13 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
|
||||
for (const place of eachInstructionOperand(instr)) {
|
||||
rewritePlace(place, rewrites);
|
||||
}
|
||||
if (instr.value.kind === "FunctionExpression") {
|
||||
const { context } = instr.value.loweredFunc;
|
||||
for (const place of context) {
|
||||
rewritePlace(place, rewrites);
|
||||
}
|
||||
}
|
||||
|
||||
rewritePlace(instr.lvalue, rewrites);
|
||||
|
||||
// visit function expressions on first iteration of each block
|
||||
@@ -110,7 +121,7 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
|
||||
instr.value.kind === "FunctionExpression" &&
|
||||
fn.env.enableOptimizeFunctionExpressions
|
||||
) {
|
||||
eliminateRedundantPhi(instr.value.loweredFunc);
|
||||
eliminateRedundantPhi(instr.value.loweredFunc, rewrites);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
|
||||
globalThis.constantValue = "global test value";
|
||||
|
||||
test("literal-constant-propagation", () => {
|
||||
function Component() {
|
||||
const x = "test value 1";
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
const { asFragment, rerender } = render(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
|
||||
rerender(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
});
|
||||
|
||||
test("global-constant-propagation", () => {
|
||||
function Component() {
|
||||
const x = constantValue;
|
||||
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
const { asFragment, rerender } = render(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
global test value
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
|
||||
rerender(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
global test value
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
});
|
||||
|
||||
test("lambda-constant-propagation", () => {
|
||||
function Component() {
|
||||
const x = "test value 1";
|
||||
const getDiv = () => <div>{x}</div>;
|
||||
return getDiv();
|
||||
}
|
||||
const { asFragment, rerender } = render(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
|
||||
rerender(<Component />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
});
|
||||
|
||||
test("lambda-constant-propagation-of-phi-node", () => {
|
||||
function Component({ noopCallback }) {
|
||||
const x = "test value 1";
|
||||
if (constantValue) {
|
||||
noopCallback();
|
||||
}
|
||||
const getDiv = () => <div>{x}</div>;
|
||||
return getDiv();
|
||||
}
|
||||
|
||||
const { asFragment, rerender } = render(
|
||||
<Component noopCallback={() => {}} />
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
|
||||
rerender(<Component noopCallback={() => {}} />);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
test value 1
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function ConstantPropagationBug() {
|
||||
const x = CONSTANT1;
|
||||
const createPhiNode = CONSTANT2 || 5;
|
||||
|
||||
const getFoo = () => <Foo x={x} y={createPhiNode} />;
|
||||
|
||||
return getFoo();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function ConstantPropagationBug() {
|
||||
const $ = useMemoCache(2);
|
||||
|
||||
const createPhiNode = CONSTANT2 || 5;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = () => <Foo x={CONSTANT1} y={createPhiNode} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const getFoo = t0;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = getFoo();
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
function ConstantPropagationBug() {
|
||||
const x = CONSTANT1;
|
||||
const createPhiNode = CONSTANT2 || 5;
|
||||
|
||||
const getFoo = () => <Foo x={x} y={createPhiNode} />;
|
||||
|
||||
return getFoo();
|
||||
}
|
||||
Reference in New Issue
Block a user