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

[hir] Lower function expressions into HIR

Sathya Gunasekaran committed Jan 20, 2023 at 14:01 UTC c1c30889cc2fa92b92a477994bc5e91b16d888c3
15 files changed +372 -8
compiler/forget/src/CompilerPipeline.ts
+9 -2
@@ -13,7 +13,11 @@ import {
13 mergeConsecutiveBlocks,
14 ReactiveFunction,
15 } from "./HIR";
16 -import { inferMutableRanges, inferReferenceEffects } from "./Inference";
16 +import {
17 + analyseFunctions,
18 + inferMutableRanges,
19 + inferReferenceEffects,
20 +} from "./Inference";
21 import { constantPropagation } from "./Optimization";
22 import {
23 alignReactiveScopesToBlockScopes,
@@ -65,6 +69,9 @@ export function* run(
69 inferTypes(hir);
70 yield log({ kind: "hir", name: "InferTypes", value: hir });
71
72 + analyseFunctions(hir);
73 + yield log({ kind: "hir", name: "analyseFunctions", value: hir });
74 +
75 inferReferenceEffects(hir);
76 yield log({ kind: "hir", name: "InferReferenceEffects", value: hir });
77
@@ -163,7 +170,7 @@ export function compile(func: NodePath<t.FunctionDeclaration>): t.Function {
170 }
171 }
172
166 -function log(value: CompilerPipelineValue): CompilerPipelineValue {
173 +export function log(value: CompilerPipelineValue): CompilerPipelineValue {
174 switch (value.kind) {
175 case "ast": {
176 break;
compiler/forget/src/HIR/BuildHIR.ts
+14
@@ -1216,6 +1216,18 @@ function lowerExpression(
1216 componentScope
1217 );
1218 const body = expr.get("body").node;
1219 + const lowering = lower(expr);
1220 + let loweredFunc: HIRFunction;
1221 + if (lowering.isErr()) {
1222 + lowering.unwrapErr().forEach((e) => builder.pushError(e));
1223 + return {
1224 + kind: "OtherStatement",
1225 + node: expr.node,
1226 + loc: exprLoc,
1227 + };
1228 + }
1229 + loweredFunc = lowering.unwrap();
1230 +
1231 const params: Array<string> = expr.get("params").map((p) => {
1232 todoInvariant(p.isIdentifier(), "handle non identifier params");
1233 return p.node.name;
@@ -1225,7 +1237,9 @@ function lowerExpression(
1237 name,
1238 body,
1239 params,
1240 + loweredFunc,
1241 dependencies,
1242 + mutatedDeps: [],
1243 loc: exprLoc,
1244 };
1245 }
compiler/forget/src/HIR/HIR.ts
+9 -1
@@ -403,6 +403,10 @@ export type InstructionData =
403 name: string | null;
404 params: Array<string>;
405 dependencies: Array<Place>;
406 + // TODO(gsn): Remove this mutatedDeps array and use dependencies as single
407 + // source of truth.
408 + mutatedDeps: Array<Place>;
409 + loweredFunc: HIRFunction;
410 body: t.BlockStatement;
411 }
412
@@ -413,7 +417,11 @@ export type InstructionData =
417 */
418 | {
419 kind: "OtherStatement";
416 - node: t.Statement | t.JSXSpreadChild | t.JSXFragment;
420 + node:
421 + | t.Statement
422 + | t.JSXSpreadChild
423 + | t.JSXFragment
424 + | t.FunctionExpression;
425 };
426
427 /**
compiler/forget/src/Inference/AnalyseFunctions.ts new
+128
@@ -0,0 +1,128 @@
1 +import {
2 + Effect,
3 + HIRFunction,
4 + Identifier,
5 + mergeConsecutiveBlocks,
6 + Place,
7 +} from "../HIR";
8 +import { eachInstructionOperand } from "../HIR/visitors";
9 +import { constantPropagation } from "../Optimization";
10 +import { eliminateRedundantPhi, enterSSA } from "../SSA";
11 +import { inferTypes } from "../TypeInference";
12 +import { logHIRFunction } from "../Utils/logger";
13 +import { inferMutableRanges } from "./InferMutableRanges";
14 +import inferReferenceEffects from "./InferReferenceEffects";
15 +
16 +type Dependency = {
17 + place: Place;
18 + path: Array<string> | null;
19 +};
20 +
21 +function declareProperty(
22 + properties: Map<Identifier, Dependency>,
23 + lvalue: Place,
24 + object: Place,
25 + property: string
26 +): void {
27 + const objectDependency = properties.get(object.identifier);
28 + let nextDependency: Dependency;
29 + if (objectDependency === undefined) {
30 + nextDependency = { place: object, path: [property] };
31 + } else {
32 + nextDependency = {
33 + place: objectDependency.place,
34 + path: [...(objectDependency.path ?? []), property],
35 + };
36 + }
37 + properties.set(lvalue.identifier, nextDependency);
38 +}
39 +
40 +export default function (func: HIRFunction) {
41 + const properties: Map<Identifier, Dependency> = new Map();
42 +
43 + for (const [_, block] of func.body.blocks) {
44 + for (const instr of block.instructions) {
45 + switch (instr.value.kind) {
46 + case "FunctionExpression": {
47 + instr.value.mutatedDeps = buildMutatedDeps(
48 + analyzeMutatedPlaces(instr.value.loweredFunc),
49 + instr.value.dependencies,
50 + properties
51 + );
52 + break;
53 + }
54 + case "PropertyLoad": {
55 + declareProperty(
56 + properties,
57 + instr.lvalue.place,
58 + instr.value.object,
59 + instr.value.property
60 + );
61 + }
62 + }
63 + }
64 + }
65 +}
66 +
67 +function buildMutatedDeps(
68 + mutations: Place[],
69 + capturedDeps: Place[],
70 + properties: Map<Identifier, Dependency>
71 +): Place[] {
72 + const mutatedIds: Set<string> = new Set(
73 + mutations
74 + .map((m) => m.identifier.name)
75 + .filter((m) => m !== null) as string[]
76 + );
77 + const mutatedDeps: Place[] = [];
78 +
79 + for (const dep of capturedDeps) {
80 + if (properties.has(dep.identifier)) {
81 + let captured = properties.get(dep.identifier)!;
82 + let name = captured.place.identifier.name;
83 +
84 + if (name === null || !mutatedIds.has(name)) {
85 + continue;
86 + }
87 +
88 + mutatedDeps.push(dep);
89 + }
90 + }
91 +
92 + return mutatedDeps;
93 +}
94 +
95 +function analyzeMutatedPlaces(func: HIRFunction): Array<Place> {
96 + mergeConsecutiveBlocks(func);
97 + enterSSA(func);
98 + eliminateRedundantPhi(func);
99 + constantPropagation(func);
100 + inferTypes(func);
101 + inferReferenceEffects(func);
102 + inferMutableRanges(func);
103 + logHIRFunction("AnalyseFunction (inner)", func);
104 +
105 + const mutations: Array<Place> = [];
106 + for (const [_, block] of func.body.blocks) {
107 + for (const instr of block.instructions) {
108 + if (
109 + instr.value.kind === "FunctionExpression" &&
110 + instr.value.loweredFunc !== null
111 + ) {
112 + mutations.push(...analyzeMutatedPlaces(instr.value.loweredFunc));
113 + }
114 +
115 + for (const operand of eachInstructionOperand(instr)) {
116 + if (isMutated(operand)) {
117 + mutations.push(operand);
118 + }
119 + }
120 + }
121 + }
122 +
123 + return mutations;
124 +}
125 +
126 +function isMutated(place: Place): boolean {
127 + return place.effect === Effect.Mutate || place.effect === Effect.Store;
128 +}
compiler/forget/src/Inference/InferAliasForStores.ts
+5
@@ -46,6 +46,11 @@ export function inferAliasForStores(
46 maybeAlias(aliases, value.object, value.value, instr.id);
47 break;
48 }
49 + case "FunctionExpression": {
50 + for (const dep of value.mutatedDeps) {
51 + maybeAlias(aliases, lvalue.place, dep, instr.id);
52 + }
53 + }
54 }
55 }
56 }
compiler/forget/src/Inference/InferReferenceEffects.ts
+4 -5
@@ -211,11 +211,10 @@ class Environment {
211 */
212 alias(place: Place, value: Place) {
213 const values = this.#variables.get(value.identifier.id);
214 - invariant(
215 - values != null,
216 - "Expected value for identifier `%s` to be initialized.",
217 - value.identifier.id
218 - );
214 + // A value can be undefined if it has been captured from outside scope.
215 + if (value === undefined) {
216 + return;
217 + }
218 this.#variables.set(place.identifier.id, new Set(values));
219 }
220
compiler/forget/src/Inference/index.ts
+1
@@ -6,4 +6,5 @@
6 */
7
8 export { inferMutableRanges } from "./InferMutableRanges";
9 +export { default as analyseFunctions } from "./AnalyseFunctions";
10 export { default as inferReferenceEffects } from "./InferReferenceEffects";
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-2.expect.md new
+52
@@ -0,0 +1,52 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component(a, b) {
6 + let y = { b };
7 + let z = { a };
8 + let x = function () {
9 + z.a = 2;
10 + y.b;
11 + };
12 + x();
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function component(a, b) {
22 + const $ = React.useMemoCache();
23 + const c_0 = $[0] !== b;
24 + let y;
25 + if (c_0) {
26 + y = { b: b };
27 + $[0] = b;
28 + $[1] = y;
29 + } else {
30 + y = $[1];
31 + }
32 + const c_2 = $[2] !== a;
33 + const c_3 = $[3] !== y.b;
34 + let x;
35 + if (c_2 || c_3) {
36 + const z = { a: a };
37 + x = function () {
38 + z.a = 2;
39 + y.b;
40 + };
41 + x();
42 + $[2] = a;
43 + $[3] = y.b;
44 + $[4] = x;
45 + } else {
46 + x = $[4];
47 + }
48 + return x;
49 +}
50 +
51 +```
52 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-2.js new
+10
@@ -0,0 +1,10 @@
1 +function component(a, b) {
2 + let y = { b };
3 + let z = { a };
4 + let x = function () {
5 + z.a = 2;
6 + y.b;
7 + };
8 + x();
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-3.expect.md new
+58
@@ -0,0 +1,58 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component(a, b) {
6 + let y = { b };
7 + let z = { a };
8 + let x = function () {
9 + z.a = 2;
10 + y.b;
11 + };
12 + return x;
13 +}
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function component(a, b) {
21 + const $ = React.useMemoCache();
22 + const c_0 = $[0] !== b;
23 + let y;
24 + if (c_0) {
25 + y = { b: b };
26 + $[0] = b;
27 + $[1] = y;
28 + } else {
29 + y = $[1];
30 + }
31 + const c_2 = $[2] !== a;
32 + let z;
33 + if (c_2) {
34 + z = { a: a };
35 + $[2] = a;
36 + $[3] = z;
37 + } else {
38 + z = $[3];
39 + }
40 + const c_4 = $[4] !== z.a;
41 + const c_5 = $[5] !== y.b;
42 + let x;
43 + if (c_4 || c_5) {
44 + x = function () {
45 + z.a = 2;
46 + y.b;
47 + };
48 + $[4] = z.a;
49 + $[5] = y.b;
50 + $[6] = x;
51 + } else {
52 + x = $[6];
53 + }
54 + return x;
55 +}
56 +
57 +```
58 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-3.js new
+9
@@ -0,0 +1,9 @@
1 +function component(a, b) {
2 + let y = { b };
3 + let z = { a };
4 + let x = function () {
5 + z.a = 2;
6 + y.b;
7 + };
8 + return x;
9 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-nested.js new
+10
@@ -0,0 +1,10 @@
1 +// @skip
2 +// TODO(gsn): This doesn't seem to work correctly. Need to debug more.
3 +function component(a) {
4 + let y = { b: { a } };
5 + let x = function () {
6 + y.b.a = 2;
7 + };
8 + x();
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate.expect.md new
+52
@@ -0,0 +1,52 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component(a, b) {
6 + let z = { a };
7 + let y = { b };
8 + let x = function () {
9 + z.a = 2;
10 + y.b;
11 + };
12 + x();
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function component(a, b) {
22 + const $ = React.useMemoCache();
23 + const c_0 = $[0] !== a;
24 + const c_1 = $[1] !== b;
25 + let x;
26 + if (c_0 || c_1) {
27 + const z = { a: a };
28 + const c_3 = $[3] !== b;
29 + let y;
30 + if (c_3) {
31 + y = { b: b };
32 + $[3] = b;
33 + $[4] = y;
34 + } else {
35 + y = $[4];
36 + }
37 + x = function () {
38 + z.a = 2;
39 + y.b;
40 + };
41 + x();
42 + $[0] = a;
43 + $[1] = b;
44 + $[2] = x;
45 + } else {
46 + x = $[2];
47 + }
48 + return x;
49 +}
50 +
51 +```
52 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate.js new
+10
@@ -0,0 +1,10 @@
1 +function component(a, b) {
2 + let z = { a };
3 + let y = { b };
4 + let x = function () {
5 + z.a = 2;
6 + y.b;
7 + };
8 + x();
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-var-in-nested-func-decl.js
+1
@@ -1,3 +1,4 @@
1 +// @skip -- TODO: support lowering Function Declaration in HIR
2 function component(a) {
3 let z = { a };
4 let x = function () {