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.
This commit is contained in:
Joe Savona
2023-12-15 17:12:06 -08:00
parent 3e79c38604
commit c77acb3ac6
4 changed files with 30 additions and 6 deletions
@@ -349,7 +349,10 @@ function* runWithEnvironment(
validateMemoizedEffectDependencies(reactiveFunction);
}
if (env.config.enablePreserveExistingMemoizationGuarantees) {
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
validatePreservedManualMemoization(reactiveFunction);
}
@@ -126,6 +126,21 @@ const EnvironmentConfigSchema = z.object({
*/
enablePreserveExistingMemoizationGuarantees: z.boolean().default(false),
/**
* Validates that all useMemo/useCallback values are also memoized by Forget. This mode can be
* used with or without @enablePreserveExistingMemoizationGuarantees.
*
* With enablePreserveExistingMemoizationGuarantees, this validation enables automatically and
* verifies that Forget was able to preserve manual memoization semantics under that mode's
* additional assumptions about the input.
*
* With enablePreserveExistingMemoizationGuarantees off, this validation ignores manual memoization
* when determining program behavior, and only uses information from useMemo/useCallback to check
* that the memoization was preserved. This can be useful for determining where referential equalities
* may change under Forget.
*/
validatePreserveExistingMemoizationGuarantees: z.boolean().default(false),
// 🌲
enableForest: z.boolean().default(false),
// <🌲>
@@ -16,7 +16,7 @@ import {
SpreadPattern,
makeInstructionId,
} from "../HIR";
import { createTemporaryPlace } from "../HIR/HIRBuilder";
import { createTemporaryPlace, markInstructionIds } from "../HIR/HIRBuilder";
import { HookKind } from "../HIR/ObjectShape";
import { eachInstructionValueOperand } from "../HIR/visitors";
@@ -114,7 +114,8 @@ export function dropManualMemoization(func: HIRFunction): void {
};
if (
func.env.config.enablePreserveExistingMemoizationGuarantees
func.env.config.enablePreserveExistingMemoizationGuarantees ||
func.env.config.validatePreserveExistingMemoizationGuarantees
) {
/**
* When this flag is enabled we also compile in a 'Memoize' instruction
@@ -215,7 +216,8 @@ export function dropManualMemoization(func: HIRFunction): void {
loc: instr.value.loc,
};
if (
func.env.config.enablePreserveExistingMemoizationGuarantees
func.env.config.enablePreserveExistingMemoizationGuarantees ||
func.env.config.validatePreserveExistingMemoizationGuarantees
) {
nextInstructions =
nextInstructions ?? block.instructions.slice(0, i);
@@ -295,6 +297,6 @@ export function dropManualMemoization(func: HIRFunction): void {
}
}
if (hasChanges) {
// markInstructionIds(func.body);
markInstructionIds(func.body);
}
}
@@ -1163,7 +1163,11 @@ function inferBlock(
continue;
}
case "Memoize": {
state.reference(instrValue.value, Effect.Freeze, ValueReason.Other);
if (env.config.enablePreserveExistingMemoizationGuarantees) {
state.reference(instrValue.value, Effect.Freeze, ValueReason.Other);
} else {
state.reference(instrValue.value, Effect.Read, ValueReason.Other);
}
const lvalue = instr.lvalue;
lvalue.effect = Effect.ConditionallyMutate;
state.initialize(instrValue, {