@samitouri / QOS-React / commits / 2710795a1e

[compiler] Cleanup for @enablePreserveExistingMemoizationGuarantees (#34346)

I tried turning on `@enablePreserveExistingMemoizationGuarantees` by default and cleaned up a couple small things: * We emit freeze calls for StartMemoize deps but these had ValueReason.Other so the message wasn't great. We now treat these like other hook arguments. * PruneNonEscapingScopes was being too aggressive in this mode and memoizing even loads of globals. Switching to MemoizationLevel.Conditional ensures we build a graph that connects through to primitive-returning function calls, but doesn't unnecessarily force memoization otherwise. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34346). * #34347 * __->__ #34346

Joseph Savona committed Sep 3, 2025 at 21:30 UTC 2710795a1ed339764d2fa76b6d7bc94dded6ee60
8 files changed +138 -23
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
+1 -1
@@ -2089,7 +2089,7 @@ function computeSignatureForInstruction(
2089 effects.push({
2090 kind: 'Freeze',
2091 value: operand,
2092 - reason: ValueReason.Other,
2092 + reason: ValueReason.HookCaptured,
2093 });
2094 }
2095 }
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts
+2 -4
@@ -546,7 +546,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
546 * memoization. Note: we may still prune primitive-producing scopes if
547 * they don't ultimately escape at all.
548 */
549 - const level = MemoizationLevel.Memoized;
549 + const level = MemoizationLevel.Conditional;
550 return {
551 lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
552 rvalues: [...eachReactiveValueOperand(value)],
@@ -701,9 +701,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
701 }
702 case 'ComputedLoad':
703 case 'PropertyLoad': {
704 - const level = options.forceMemoizePrimitives
705 - ? MemoizationLevel.Memoized
706 - : MemoizationLevel.Conditional;
704 + const level = MemoizationLevel.Conditional;
705 return {
706 // Indirection for the inner value, memoized if the value is
707 lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md
+2 -1
@@ -2,6 +2,7 @@
2 ## Input
3
4 ```javascript
5 +// @compilationMode:"infer"
6 import {makeArray} from 'shared-runtime';
7
8 function Component() {
@@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
31 ## Code
32
33 ```javascript
33 -import { c as _c } from "react/compiler-runtime";
34 +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
35 import { makeArray } from "shared-runtime";
36
37 function Component() {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js
+1
@@ -1,3 +1,4 @@
1 +// @compilationMode:"infer"
2 import {makeArray} from 'shared-runtime';
3
4 function Component() {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.expect.md new
+86
@@ -0,0 +1,86 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingMemoizationGuarantees
6 +import {fbt} from 'fbt';
7 +
8 +function Component() {
9 + const buttonLabel = () => {
10 + if (!someCondition) {
11 + return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
12 + } else if (
13 + !iconOnly &&
14 + showPrice &&
15 + item?.current_gift_offer?.price?.formatted != null
16 + ) {
17 + return (
18 + <fbt desc="Gift button's label">
19 + {'Gift | '}
20 + <fbt:param name="price">
21 + {item?.current_gift_offer?.price?.formatted}
22 + </fbt:param>
23 + </fbt>
24 + );
25 + } else if (!iconOnly && !showPrice) {
26 + return <fbt desc="Gift button's label">{'Gift'}</fbt>;
27 + }
28 + };
29 +
30 + return (
31 + <View>
32 + <Button text={buttonLabel()} />
33 + </View>
34 + );
35 +}
36 +
37 +```
38 +
39 +## Code
40 +
41 +```javascript
42 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
43 +import { fbt } from "fbt";
44 +
45 +function Component() {
46 + const $ = _c(1);
47 + const buttonLabel = _temp;
48 + let t0;
49 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
50 + t0 = (
51 + <View>
52 + <Button text={buttonLabel()} />
53 + </View>
54 + );
55 + $[0] = t0;
56 + } else {
57 + t0 = $[0];
58 + }
59 + return t0;
60 +}
61 +function _temp() {
62 + if (!someCondition) {
63 + return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
64 + } else {
65 + if (
66 + !iconOnly &&
67 + showPrice &&
68 + item?.current_gift_offer?.price?.formatted != null
69 + ) {
70 + return fbt._(
71 + "Gift | {price}",
72 + [fbt._param("price", item?.current_gift_offer?.price?.formatted)],
73 + { hk: "3GTnGE" },
74 + );
75 + } else {
76 + if (!iconOnly && !showPrice) {
77 + return fbt._("Gift", null, { hk: "3fqfrk" });
78 + }
79 + }
80 + }
81 +}
82 +
83 +```
84 +
85 +### Eval output
86 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.js new
+31
@@ -0,0 +1,31 @@
1 +// @enablePreserveExistingMemoizationGuarantees
2 +import {fbt} from 'fbt';
3 +
4 +function Component() {
5 + const buttonLabel = () => {
6 + if (!someCondition) {
7 + return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
8 + } else if (
9 + !iconOnly &&
10 + showPrice &&
11 + item?.current_gift_offer?.price?.formatted != null
12 + ) {
13 + return (
14 + <fbt desc="Gift button's label">
15 + {'Gift | '}
16 + <fbt:param name="price">
17 + {item?.current_gift_offer?.price?.formatted}
18 + </fbt:param>
19 + </fbt>
20 + );
21 + } else if (!iconOnly && !showPrice) {
22 + return <fbt desc="Gift button's label">{'Gift'}</fbt>;
23 + }
24 + };
25 +
26 + return (
27 + <View>
28 + <Button text={buttonLabel()} />
29 + </View>
30 + );
31 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md
+14 -16
@@ -2,7 +2,7 @@
2 ## Input
3
4 ```javascript
5 -// @enableForest
5 +// @enablePreserveExistingMemoizationGuarantees
6 function Component({base, start, increment, test}) {
7 let value = base;
8 for (let i = start; i < test; i += increment) {
@@ -27,25 +27,23 @@ export const FIXTURE_ENTRYPOINT = {
27 ## Code
28
29 ```javascript
30 -import { c as _c } from "react/compiler-runtime"; // @enableForest
30 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
31 function Component(t0) {
32 - const $ = _c(5);
32 + const $ = _c(2);
33 const { base, start, increment, test } = t0;
34 - let value;
35 - if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) {
36 - value = base;
37 - for (let i = start; i < test; i = i + increment, i) {
38 - value = value + i;
39 - }
40 - $[0] = base;
41 - $[1] = increment;
42 - $[2] = start;
43 - $[3] = test;
44 - $[4] = value;
34 + let value = base;
35 + for (let i = start; i < test; i = i + increment, i) {
36 + value = value + i;
37 + }
38 + let t1;
39 + if ($[0] !== value) {
40 + t1 = <div>{value}</div>;
41 + $[0] = value;
42 + $[1] = t1;
43 } else {
46 - value = $[4];
44 + t1 = $[1];
45 }
48 - return <div>{value}</div>;
46 + return t1;
47 }
48
49 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.js
+1 -1
@@ -1,4 +1,4 @@
1 -// @enableForest
1 +// @enablePreserveExistingMemoizationGuarantees
2 function Component({base, start, increment, test}) {
3 let value = base;
4 for (let i = start; i < test; i += increment) {