Create a separate pass to prune non-reactive dependencies

The fact that InferReactiveIdentifiers is integrated directly into 
PropagateScopeDependencies has made the latter pretty tricky to debug at times. 
If a dependency is missing, we have to introspect and figure out if that's 
because it was somehow inferred as non-reactive. This PR creates a new 
PruneNonReactiveDependencies pass to separate out these phases.
This commit is contained in:
Joe Savona
2023-02-14 14:09:53 -08:00
parent fb8f293c32
commit 2b47cac5fd
3 changed files with 51 additions and 14 deletions
+8
View File
@@ -35,6 +35,7 @@ import {
renameVariables,
} from "./ReactiveScopes";
import { flattenScopesWithHooks } from "./ReactiveScopes/FlattenScopesWithHooks";
import { pruneNonReactiveDependencies } from "./ReactiveScopes/PruneNonReactiveDependencies";
import { eliminateRedundantPhi, enterSSA, leaveSSA } from "./SSA";
import { inferTypes } from "./TypeInference";
import { logHIRFunction, logReactiveFunction } from "./Utils/logger";
@@ -137,6 +138,13 @@ export function* run(
value: reactiveFunction,
});
pruneNonReactiveDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonReactiveDependencies",
value: reactiveFunction,
});
pruneUnusedScopes(reactiveFunction);
yield log({
kind: "reactive",
@@ -22,7 +22,6 @@ import {
} from "../HIR/HIR";
import { eachInstructionValueOperand } from "../HIR/visitors";
import { assertExhaustive } from "../Utils/utils";
import { inferReactiveIdentifiers } from "./InferReactiveIdentifiers";
import { eachReactiveValueOperand } from "./visitors";
/**
@@ -32,7 +31,7 @@ import { eachReactiveValueOperand } from "./visitors";
* their direct dependencies and those of their child scopes.
*/
export function propagateScopeDependencies(fn: ReactiveFunction): void {
const context = new Context(inferReactiveIdentifiers(fn));
const context = new Context();
if (fn.id !== null) {
context.declare(fn.id, {
kind: DeclKind.Const,
@@ -67,16 +66,12 @@ type Scopes = Array<ReactiveScope>;
class Context {
#declarations: DeclMap = new Map();
#dependencies: Set<ReactiveScopeDependency> = new Set();
#reactiveIdentifiers: Set<Identifier>;
// Produces a de-duplicated mapping of Id -> ReactiveScopeDependency
// This helps with.. temporaries that are created only for property loads
// but can be generalized to all non-allocating temporaries
#properties: Map<Identifier, ReactiveScopeDependency> = new Map();
#scopes: Scopes = [];
constructor(reactiveIdentifiers: Set<Identifier>) {
this.#reactiveIdentifiers = reactiveIdentifiers;
}
enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
const previousDependencies = this.#dependencies;
const scopedDependencies = new Set<ReactiveScopeDependency>();
@@ -120,10 +115,6 @@ class Context {
return this.#scopes.at(-1) ?? null;
}
isReactive(id: Identifier): boolean {
return this.#reactiveIdentifiers.has(id);
}
visitOperand(place: Place): void {
this.visitDependency({ place, path: null });
}
@@ -383,11 +374,8 @@ function visitInstruction(context: Context, instr: ReactiveInstruction): void {
if (lvalue.kind === InstructionKind.Reassign) {
context.visitReassignment(lvalue);
} else {
const kind = context.isReactive(lvalue.place.identifier)
? DeclKind.Dynamic
: DeclKind.Const;
context.declare(lvalue.place.identifier, {
kind,
kind: DeclKind.Dynamic,
id: lvalue.place.identifier.mutableRange.start,
scope: context.currentScope,
});
@@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Identifier, ReactiveFunction, ReactiveScopeBlock } from "../HIR";
import { inferReactiveIdentifiers } from "./InferReactiveIdentifiers";
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
/**
* PropagateScopeDependencies infers dependencies without considering whether dependencies
* are actually reactive or not (ie, whether their value can change over time).
*
* This pass prunes dependencies that are guaranteed to be non-reactive.
*/
export function pruneNonReactiveDependencies(fn: ReactiveFunction): void {
const state = inferReactiveIdentifiers(fn);
visitReactiveFunction(fn, new Visitor(), state);
}
type State = Set<Identifier>;
class Visitor extends ReactiveFunctionVisitor<State> {
override visitScope(scope: ReactiveScopeBlock, state: State): void {
this.traverseScope(scope, state);
for (const dep of scope.scope.dependencies) {
const isReactive = state.has(dep.place.identifier);
if (!isReactive) {
scope.scope.dependencies.delete(dep);
}
}
// If a scope now has no dependencies, then its declarations are all non-reactive
if (scope.scope.dependencies.size === 0) {
for (const [, declaration] of scope.scope.declarations) {
state.delete(declaration);
}
}
}
}