🌲 Create scopes for primitive operations

In normal React certain operations don't allocate new objects (property loads, 
binary expressions, etc) and therefore don't need a reactive scope in Forget. 
For example, property loads only extract part of an existing value and don't 
allocate something new, while binary expressions are known to produce primitive 
values that don't allocate. We rely on the fact that whenever their inputs 
change we will re-run the component/hook and propagate the result forward. 

For Forest, the only way to propagate data is via reactive scopes: the component 
code is equivalent to a "setup" function. This PR updates some of our passes to 
ensure that we create (and don't prune) scopes for these types of operations. I 
started with a conservative set for now.
This commit is contained in:
Joe Savona
2023-12-11 11:34:23 -08:00
parent 1debb59830
commit 9be2efdc57
3 changed files with 23 additions and 27 deletions
@@ -279,11 +279,7 @@ function* runWithEnvironment(
value: reactiveFunction,
});
let memoizeJsxElements = env.config.memoizeJsxElements;
if (env.config.enableForest) {
memoizeJsxElements = false;
}
pruneNonEscapingScopes(reactiveFunction, { memoizeJsxElements });
pruneNonEscapingScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonEscapingDependencies",
@@ -234,16 +234,13 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
case "StoreLocal":
case "LoadGlobal":
case "TypeCastExpression":
case "BinaryExpression":
case "LoadLocal":
case "LoadContext":
case "StoreContext":
case "PropertyLoad":
case "PropertyDelete":
case "ComputedLoad":
case "ComputedDelete":
case "JSXText":
case "UnaryExpression":
case "TemplateLiteral":
case "Primitive":
case "NextIterableOf":
@@ -251,6 +248,11 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
case "Debugger": {
return false;
}
case "UnaryExpression":
case "BinaryExpression":
case "PropertyLoad": {
return env.config.enableForest;
}
case "CallExpression":
case "MethodCall": {
return instruction.lvalue.identifier.type.kind !== "Primitive";
@@ -107,10 +107,7 @@ import {
* conditional node with an aliased dep promotes to aliased).
* 4. Finally we prune scopes whose outputs weren't marked.
*/
export function pruneNonEscapingScopes(
fn: ReactiveFunction,
options: MemoizationOptions
): void {
export function pruneNonEscapingScopes(fn: ReactiveFunction): void {
/*
* First build up a map of which instructions are involved in creating which values,
* and which values are returned.
@@ -123,11 +120,7 @@ export function pruneNonEscapingScopes(
state.declare(param.place.identifier.id);
}
}
visitReactiveFunction(
fn,
new CollectDependenciesVisitor(fn.env, options),
state
);
visitReactiveFunction(fn, new CollectDependenciesVisitor(fn.env), state);
// log(() => prettyFormat(state));
@@ -147,6 +140,7 @@ export function pruneNonEscapingScopes(
export type MemoizationOptions = {
memoizeJsxElements: boolean;
forceMemoizePrimitives: boolean;
};
// Describes how to determine whether a value should be memoized, relative to dependees and dependencies
@@ -468,12 +462,12 @@ function computeMemoizationInputs(
case "JSXText":
case "BinaryExpression":
case "UnaryExpression": {
const level = options.forceMemoizePrimitives
? MemoizationLevel.Memoized
: MemoizationLevel.Never;
return {
// All of these instructions return a primitive value and never need to be memoized
lvalues:
lvalue !== null
? [{ place: lvalue, level: MemoizationLevel.Never }]
: [],
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
rvalues: [],
};
}
@@ -589,12 +583,12 @@ function computeMemoizationInputs(
}
case "ComputedLoad":
case "PropertyLoad": {
const level = options.forceMemoizePrimitives
? MemoizationLevel.Memoized
: MemoizationLevel.Conditional;
return {
// Indirection for the inner value, memoized if the value is
lvalues:
lvalue !== null
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
: [],
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
/*
* Only the object is aliased to the result, and the result only needs to be
* memoized if the object is
@@ -768,10 +762,14 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
env: Environment;
options: MemoizationOptions;
constructor(env: Environment, options: MemoizationOptions) {
constructor(env: Environment) {
super();
this.env = env;
this.options = options;
this.options = {
memoizeJsxElements:
this.env.config.memoizeJsxElements && !this.env.config.enableForest,
forceMemoizePrimitives: this.env.config.enableForest,
};
}
override visitInstruction(