[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 () {