diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts index 427e7f8f4a..07559fd8e6 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts @@ -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 +): void { const ir = fn.body; - const rewrites: Map = new Map(); + const rewrites: Map = + 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); } } diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/constant-prop.e2e.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/constant-prop.e2e.js new file mode 100644 index 0000000000..3446030107 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/e2e/constant-prop.e2e.js @@ -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
{x}
; + } + const { asFragment, rerender } = render(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); + + rerender(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); +}); + +test("global-constant-propagation", () => { + function Component() { + const x = constantValue; + + return
{x}
; + } + const { asFragment, rerender } = render(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ global test value +
+
+ `); + + rerender(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ global test value +
+
+ `); +}); + +test("lambda-constant-propagation", () => { + function Component() { + const x = "test value 1"; + const getDiv = () =>
{x}
; + return getDiv(); + } + const { asFragment, rerender } = render(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); + + rerender(); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); +}); + +test("lambda-constant-propagation-of-phi-node", () => { + function Component({ noopCallback }) { + const x = "test value 1"; + if (constantValue) { + noopCallback(); + } + const getDiv = () =>
{x}
; + return getDiv(); + } + + const { asFragment, rerender } = render( + {}} /> + ); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); + + rerender( {}} />); + + expect(asFragment()).toMatchInlineSnapshot(` + +
+ test value 1 +
+
+ `); +}); diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md new file mode 100644 index 0000000000..fbf001317a --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +function ConstantPropagationBug() { + const x = CONSTANT1; + const createPhiNode = CONSTANT2 || 5; + + const getFoo = () => ; + + 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 = () => ; + $[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; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.js new file mode 100644 index 0000000000..8ed131ece9 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.js @@ -0,0 +1,8 @@ +function ConstantPropagationBug() { + const x = CONSTANT1; + const createPhiNode = CONSTANT2 || 5; + + const getFoo = () => ; + + return getFoo(); +}