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

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.

Joe Savona committed Feb 14, 2023 at 14:09 UTC 2b47cac5fdf3e96f2e69d9081f5a03db4a2c97a2
3 files changed +51 -14
compiler/forget/src/CompilerPipeline.ts
+8
@@ -35,6 +35,7 @@ import {
35 renameVariables,
36 } from "./ReactiveScopes";
37 import { flattenScopesWithHooks } from "./ReactiveScopes/FlattenScopesWithHooks";
38 +import { pruneNonReactiveDependencies } from "./ReactiveScopes/PruneNonReactiveDependencies";
39 import { eliminateRedundantPhi, enterSSA, leaveSSA } from "./SSA";
40 import { inferTypes } from "./TypeInference";
41 import { logHIRFunction, logReactiveFunction } from "./Utils/logger";
@@ -137,6 +138,13 @@ export function* run(
138 value: reactiveFunction,
139 });
140
141 + pruneNonReactiveDependencies(reactiveFunction);
142 + yield log({
143 + kind: "reactive",
144 + name: "PruneNonReactiveDependencies",
145 + value: reactiveFunction,
146 + });
147 +
148 pruneUnusedScopes(reactiveFunction);
149 yield log({
150 kind: "reactive",
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+2 -14
@@ -22,7 +22,6 @@ import {
22 } from "../HIR/HIR";
23 import { eachInstructionValueOperand } from "../HIR/visitors";
24 import { assertExhaustive } from "../Utils/utils";
25 -import { inferReactiveIdentifiers } from "./InferReactiveIdentifiers";
25 import { eachReactiveValueOperand } from "./visitors";
26
27 /**
@@ -32,7 +31,7 @@ import { eachReactiveValueOperand } from "./visitors";
31 * their direct dependencies and those of their child scopes.
32 */
33 export function propagateScopeDependencies(fn: ReactiveFunction): void {
35 - const context = new Context(inferReactiveIdentifiers(fn));
34 + const context = new Context();
35 if (fn.id !== null) {
36 context.declare(fn.id, {
37 kind: DeclKind.Const,
@@ -67,16 +66,12 @@ type Scopes = Array<ReactiveScope>;
66 class Context {
67 #declarations: DeclMap = new Map();
68 #dependencies: Set<ReactiveScopeDependency> = new Set();
70 - #reactiveIdentifiers: Set<Identifier>;
69 // Produces a de-duplicated mapping of Id -> ReactiveScopeDependency
70 // This helps with.. temporaries that are created only for property loads
71 // but can be generalized to all non-allocating temporaries
72 #properties: Map<Identifier, ReactiveScopeDependency> = new Map();
73 #scopes: Scopes = [];
74
77 - constructor(reactiveIdentifiers: Set<Identifier>) {
78 - this.#reactiveIdentifiers = reactiveIdentifiers;
79 - }
75 enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
76 const previousDependencies = this.#dependencies;
77 const scopedDependencies = new Set<ReactiveScopeDependency>();
@@ -120,10 +115,6 @@ class Context {
115 return this.#scopes.at(-1) ?? null;
116 }
117
123 - isReactive(id: Identifier): boolean {
124 - return this.#reactiveIdentifiers.has(id);
125 - }
126 -
118 visitOperand(place: Place): void {
119 this.visitDependency({ place, path: null });
120 }
@@ -383,11 +374,8 @@ function visitInstruction(context: Context, instr: ReactiveInstruction): void {
374 if (lvalue.kind === InstructionKind.Reassign) {
375 context.visitReassignment(lvalue);
376 } else {
386 - const kind = context.isReactive(lvalue.place.identifier)
387 - ? DeclKind.Dynamic
388 - : DeclKind.Const;
377 context.declare(lvalue.place.identifier, {
390 - kind,
378 + kind: DeclKind.Dynamic,
379 id: lvalue.place.identifier.mutableRange.start,
380 scope: context.currentScope,
381 });
compiler/forget/src/ReactiveScopes/PruneNonReactiveDependencies.ts new
+41
@@ -0,0 +1,41 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import { Identifier, ReactiveFunction, ReactiveScopeBlock } from "../HIR";
9 +import { inferReactiveIdentifiers } from "./InferReactiveIdentifiers";
10 +import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
11 +
12 +/**
13 + * PropagateScopeDependencies infers dependencies without considering whether dependencies
14 + * are actually reactive or not (ie, whether their value can change over time).
15 + *
16 + * This pass prunes dependencies that are guaranteed to be non-reactive.
17 + */
18 +export function pruneNonReactiveDependencies(fn: ReactiveFunction): void {
19 + const state = inferReactiveIdentifiers(fn);
20 + visitReactiveFunction(fn, new Visitor(), state);
21 +}
22 +
23 +type State = Set<Identifier>;
24 +
25 +class Visitor extends ReactiveFunctionVisitor<State> {
26 + override visitScope(scope: ReactiveScopeBlock, state: State): void {
27 + this.traverseScope(scope, state);
28 + for (const dep of scope.scope.dependencies) {
29 + const isReactive = state.has(dep.place.identifier);
30 + if (!isReactive) {
31 + scope.scope.dependencies.delete(dep);
32 + }
33 + }
34 + // If a scope now has no dependencies, then its declarations are all non-reactive
35 + if (scope.scope.dependencies.size === 0) {
36 + for (const [, declaration] of scope.scope.declarations) {
37 + state.delete(declaration);
38 + }
39 + }
40 + }
41 +}