@samitouri / QOS-React-2 / commits / c77acb3ac6

Separate mode to validate preserving manual memoization

Adds a new mode which validates that existing manual memoization is preserved _without_ using information from the manual memoization to affect compilation. This gives us a way to try out the more aggressive version of Forget — ignoring manual memoization — first and see how much code bails out and what patterns cause this. We can then proceed to enable the mode to actually _preserve_ existing memo guarantees only where necessary.

Joe Savona committed Dec 15, 2023 at 17:12 UTC c77acb3ac664a85d303f898220eb35dc8d2c0bca
4 files changed +30 -6
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+4 -1
@@ -349,7 +349,10 @@ function* runWithEnvironment(
349 validateMemoizedEffectDependencies(reactiveFunction);
350 }
351
352 - if (env.config.enablePreserveExistingMemoizationGuarantees) {
352 + if (
353 + env.config.enablePreserveExistingMemoizationGuarantees ||
354 + env.config.validatePreserveExistingMemoizationGuarantees
355 + ) {
356 validatePreservedManualMemoization(reactiveFunction);
357 }
358
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+15
@@ -126,6 +126,21 @@ const EnvironmentConfigSchema = z.object({
126 */
127 enablePreserveExistingMemoizationGuarantees: z.boolean().default(false),
128
129 + /**
130 + * Validates that all useMemo/useCallback values are also memoized by Forget. This mode can be
131 + * used with or without @enablePreserveExistingMemoizationGuarantees.
132 + *
133 + * With enablePreserveExistingMemoizationGuarantees, this validation enables automatically and
134 + * verifies that Forget was able to preserve manual memoization semantics under that mode's
135 + * additional assumptions about the input.
136 + *
137 + * With enablePreserveExistingMemoizationGuarantees off, this validation ignores manual memoization
138 + * when determining program behavior, and only uses information from useMemo/useCallback to check
139 + * that the memoization was preserved. This can be useful for determining where referential equalities
140 + * may change under Forget.
141 + */
142 + validatePreserveExistingMemoizationGuarantees: z.boolean().default(false),
143 +
144 // 🌲
145 enableForest: z.boolean().default(false),
146 // <🌲>
compiler/packages/babel-plugin-react-forget/src/Inference/DropManualMemoization.ts
+6 -4
@@ -16,7 +16,7 @@ import {
16 SpreadPattern,
17 makeInstructionId,
18 } from "../HIR";
19 -import { createTemporaryPlace } from "../HIR/HIRBuilder";
19 +import { createTemporaryPlace, markInstructionIds } from "../HIR/HIRBuilder";
20 import { HookKind } from "../HIR/ObjectShape";
21 import { eachInstructionValueOperand } from "../HIR/visitors";
22
@@ -114,7 +114,8 @@ export function dropManualMemoization(func: HIRFunction): void {
114 };
115
116 if (
117 - func.env.config.enablePreserveExistingMemoizationGuarantees
117 + func.env.config.enablePreserveExistingMemoizationGuarantees ||
118 + func.env.config.validatePreserveExistingMemoizationGuarantees
119 ) {
120 /**
121 * When this flag is enabled we also compile in a 'Memoize' instruction
@@ -215,7 +216,8 @@ export function dropManualMemoization(func: HIRFunction): void {
216 loc: instr.value.loc,
217 };
218 if (
218 - func.env.config.enablePreserveExistingMemoizationGuarantees
219 + func.env.config.enablePreserveExistingMemoizationGuarantees ||
220 + func.env.config.validatePreserveExistingMemoizationGuarantees
221 ) {
222 nextInstructions =
223 nextInstructions ?? block.instructions.slice(0, i);
@@ -295,6 +297,6 @@ export function dropManualMemoization(func: HIRFunction): void {
297 }
298 }
299 if (hasChanges) {
298 - // markInstructionIds(func.body);
300 + markInstructionIds(func.body);
301 }
302 }
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+5 -1
@@ -1163,7 +1163,11 @@ function inferBlock(
1163 continue;
1164 }
1165 case "Memoize": {
1166 - state.reference(instrValue.value, Effect.Freeze, ValueReason.Other);
1166 + if (env.config.enablePreserveExistingMemoizationGuarantees) {
1167 + state.reference(instrValue.value, Effect.Freeze, ValueReason.Other);
1168 + } else {
1169 + state.reference(instrValue.value, Effect.Read, ValueReason.Other);
1170 + }
1171 const lvalue = instr.lvalue;
1172 lvalue.effect = Effect.ConditionallyMutate;
1173 state.initialize(instrValue, {