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
+}