From c22c4e727fb09f03443719ceb7cfe33a02b45d3f Mon Sep 17 00:00:00 2001 From: Mike Vitousek Date: Sun, 30 Jun 2024 23:51:32 -0700 Subject: [PATCH] [compiler] More fine-grained memoization in change detection mode [ghstack-poisoned] --- .../ReactiveScopes/CodegenReactiveFunction.ts | 7 ++ .../InferReactiveScopeVariables.ts | 14 ++-- ...d-other-hook-unpruned-dependency.expect.md | 77 +++++++++++-------- ...-pruned-dependency-change-detect.expect.md | 42 ++++++---- .../useState-unpruned-dependency.expect.md | 77 +++++++++++-------- 5 files changed, 138 insertions(+), 79 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index f047077540..34f6513fb8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -653,6 +653,7 @@ function codegenReactiveScope( const cacheLoadOldValueStatements: Array = []; const changeDetectionStatements: Array = []; const idempotenceDetectionStatements: Array = []; + const restoreOldValueStatements: Array = []; for (const { name, index, value } of cacheLoads) { const loadName = cx.synthesizeName(`old$${name.name}`); @@ -669,6 +670,11 @@ function codegenReactiveScope( t.variableDeclarator(t.identifier(loadName), slot), ]) ); + if (scope.source) { + restoreOldValueStatements.push( + t.expressionStatement(t.assignmentExpression("=", t.cloneNode(name, true), t.identifier(loadName))) + ); + } changeDetectionStatements.push( t.expressionStatement( t.callExpression(t.identifier(detectionFunction), [ @@ -709,6 +715,7 @@ function codegenReactiveScope( t.blockStatement([ ...cacheLoadOldValueStatements, ...changeDetectionStatements, + ...restoreOldValueStatements, ]) ), ...cacheStoreStatements, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index d0c6d41e92..8ae5205c54 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -191,11 +191,10 @@ export function mayAllocate(env: Environment, instruction: Instruction, conserva const { value } = instruction; switch (value.kind) { case "Destructure": { - return doesPatternContainSpreadElement(value.lvalue.pattern); + return doesPatternContainSpreadElement(value.lvalue.pattern) || env.config.enableChangeDetectionForDebugging != null; } case "PostfixUpdate": case "PrefixUpdate": - case "Await": case "DeclareLocal": case "DeclareContext": case "StoreLocal": @@ -206,26 +205,29 @@ export function mayAllocate(env: Environment, instruction: Instruction, conserva case "LoadContext": case "StoreContext": case "PropertyDelete": - case "ComputedLoad": case "ComputedDelete": case "JSXText": case "TemplateLiteral": case "Primitive": case "GetIterator": case "IteratorNext": - case "NextPropertyOf": case "Debugger": case "StartMemoize": case "FinishMemoize": case "UnaryExpression": case "BinaryExpression": - case "PropertyLoad": case "StoreGlobal": { return false; } + case "PropertyLoad": + case "NextPropertyOf": + case "ComputedLoad": + case "Await": { + return env.config.enableChangeDetectionForDebugging != null; + } case "CallExpression": case "MethodCall": { - return conservative || instruction.lvalue.identifier.type.kind !== "Primitive"; + return conservative || instruction.lvalue.identifier.type.kind !== "Primitive" || env.config.enableChangeDetectionForDebugging != null; } case "RegExpLiteral": case "PropertyStore": diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md index 63203246d6..8ab8c40872 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md @@ -39,43 +39,60 @@ function useOther(x) { } function Component(props) { - const $ = _c(4); - let t0; - { - t0 = f(props.x); - let condition = $[0] !== props.x; - if (!condition) { - let old$t0 = $[1]; - $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(8:8)"); - } - $[0] = props.x; - $[1] = t0; - if (condition) { - t0 = f(props.x); - $structuralCheck($[1], t0, "t0", "Component", "recomputed", "(8:8)"); - t0 = $[1]; - } - } - const w = t0; - const z = useOther(w); - const [x] = useState(z); + const $ = _c(6); + const t0 = props.x; let t1; { - t1 =
{x}
; - let condition = $[2] !== x; + t1 = f(t0); + let condition = $[0] !== t0; if (!condition) { - let old$t1 = $[3]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(11:11)"); + let old$t1 = $[1]; + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(8:8)"); } - $[2] = x; - $[3] = t1; + $[0] = t0; + $[1] = t1; if (condition) { - t1 =
{x}
; - $structuralCheck($[3], t1, "t1", "Component", "recomputed", "(11:11)"); - t1 = $[3]; + t1 = f(t0); + $structuralCheck($[1], t1, "t1", "Component", "recomputed", "(8:8)"); + t1 = $[1]; } } - return t1; + const w = t1; + const z = useOther(w); + const t2 = useState(z); + let x; + { + [x] = t2; + let condition = $[2] !== t2; + if (!condition) { + let old$x = $[3]; + $structuralCheck(old$x, x, "x", "Component", "cached", "(10:10)"); + } + $[2] = t2; + $[3] = x; + if (condition) { + [x] = t2; + $structuralCheck($[3], x, "x", "Component", "recomputed", "(10:10)"); + x = $[3]; + } + } + let t3; + { + t3 =
{x}
; + let condition = $[4] !== x; + if (!condition) { + let old$t3 = $[5]; + $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(11:11)"); + } + $[4] = x; + $[5] = t3; + if (condition) { + t3 =
{x}
; + $structuralCheck($[5], t3, "t3", "Component", "recomputed", "(11:11)"); + t3 = $[5]; + } + } + return t3; } function f(x) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md index 4ae84cfdf2..f93ff8f534 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md @@ -20,7 +20,7 @@ import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDe import { useState } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(5); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = f(props.x); @@ -28,24 +28,40 @@ function Component(props) { } else { t0 = $[0]; } - const [x] = useState(t0); - let t1; + const t1 = useState(t0); + let x; { - t1 =
{x}
; - let condition = $[1] !== x; + [x] = t1; + let condition = $[1] !== t1; if (!condition) { - let old$t1 = $[2]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(6:6)"); + let old$x = $[2]; + $structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)"); } - $[1] = x; - $[2] = t1; + $[1] = t1; + $[2] = x; if (condition) { - t1 =
{x}
; - $structuralCheck($[2], t1, "t1", "Component", "recomputed", "(6:6)"); - t1 = $[2]; + [x] = t1; + $structuralCheck($[2], x, "x", "Component", "recomputed", "(5:5)"); + x = $[2]; } } - return t1; + let t2; + { + t2 =
{x}
; + let condition = $[3] !== x; + if (!condition) { + let old$t2 = $[4]; + $structuralCheck(old$t2, t2, "t2", "Component", "cached", "(6:6)"); + } + $[3] = x; + $[4] = t2; + if (condition) { + t2 =
{x}
; + $structuralCheck($[4], t2, "t2", "Component", "recomputed", "(6:6)"); + t2 = $[4]; + } + } + return t2; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index 8ca0d23ba8..3341feb7b5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -35,53 +35,70 @@ import { c as _c } from "react/compiler-runtime"; import { useState } from "react"; // @enableChangeDetectionForDebugging function Component(props) { - const $ = _c(5); - let t0; - { - t0 = f(props.x); - let condition = $[0] !== props.x; - if (!condition) { - let old$t0 = $[1]; - $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(4:4)"); - } - $[0] = props.x; - $[1] = t0; - if (condition) { - t0 = f(props.x); - $structuralCheck($[1], t0, "t0", "Component", "recomputed", "(4:4)"); - t0 = $[1]; - } - } - const w = t0; - const [x] = useState(w); + const $ = _c(7); + const t0 = props.x; let t1; { - t1 = ( + t1 = f(t0); + let condition = $[0] !== t0; + if (!condition) { + let old$t1 = $[1]; + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(4:4)"); + } + $[0] = t0; + $[1] = t1; + if (condition) { + t1 = f(t0); + $structuralCheck($[1], t1, "t1", "Component", "recomputed", "(4:4)"); + t1 = $[1]; + } + } + const w = t1; + const t2 = useState(w); + let x; + { + [x] = t2; + let condition = $[2] !== t2; + if (!condition) { + let old$x = $[3]; + $structuralCheck(old$x, x, "x", "Component", "cached", "(5:5)"); + } + $[2] = t2; + $[3] = x; + if (condition) { + [x] = t2; + $structuralCheck($[3], x, "x", "Component", "recomputed", "(5:5)"); + x = $[3]; + } + } + let t3; + { + t3 = (
{x} {w}
); - let condition = $[2] !== x || $[3] !== w; + let condition = $[4] !== x || $[5] !== w; if (!condition) { - let old$t1 = $[4]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); + let old$t3 = $[6]; + $structuralCheck(old$t3, t3, "t3", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; - $[4] = t1; + $[4] = x; + $[5] = w; + $[6] = t3; if (condition) { - t1 = ( + t3 = (
{x} {w}
); - $structuralCheck($[4], t1, "t1", "Component", "recomputed", "(7:10)"); - t1 = $[4]; + $structuralCheck($[6], t3, "t3", "Component", "recomputed", "(7:10)"); + t3 = $[6]; } } - return t1; + return t3; } function f(x) {