@samitouri / QOS-React-2 / commits / 346ecb27fe

[hir] Gather dependencies for lambdas

Sathya Gunasekaran committed Jan 9, 2023 at 18:22 UTC 346ecb27fe1b5dc31912d0120929250affbdd24e
11 files changed +250 -2
compiler/forget/src/HIR/BuildHIR.ts
+54 -1
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import { NodePath } from "@babel/traverse";
8 +import { NodePath, Scope } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import { invariant } from "../Utils/CompilerError";
11 import todo, { todoInvariant } from "../Utils/todo";
@@ -1151,6 +1151,8 @@ function lowerExpression(
1151 case "FunctionExpression": {
1152 const expr = exprPath as NodePath<t.FunctionExpression>;
1153 const name: string | null = expr.get("id")?.node?.name ?? null;
1154 + const componentScope: Scope = expr.scope.parent.getFunctionParent()!;
1155 + const dependencies = gatherCapturedDeps(expr, componentScope);
1156 const body = expr.get("body").node;
1157 const params: Array<string> = expr.get("params").map((p) => {
1158 todoInvariant(p.isIdentifier(), "handle non identifier params");
@@ -1161,6 +1163,7 @@ function lowerExpression(
1163 name,
1164 body,
1165 params,
1166 + dependencies,
1167 loc: exprLoc,
1168 };
1169 }
@@ -1540,3 +1543,53 @@ function lowerAssignment(
1543 }
1544 }
1545 }
1546 +
1547 +function capturePureScopes(
1548 + currentScope: Scope,
1549 + componentScope: Scope
1550 +): Set<Scope> {
1551 + let pureScopes: Set<Scope> = new Set();
1552 + while (currentScope) {
1553 + pureScopes.add(currentScope);
1554 +
1555 + if (currentScope === componentScope) {
1556 + break;
1557 + }
1558 +
1559 + currentScope = currentScope.parent;
1560 + }
1561 + return pureScopes;
1562 +}
1563 +
1564 +function gatherCapturedDeps(
1565 + fn: NodePath<t.FunctionExpression>,
1566 + componentScope: Scope
1567 +): Set<t.Identifier> {
1568 + const captured: Set<t.Identifier> = new Set();
1569 +
1570 + // Capture all the scopes from the parent of this function up to and including
1571 + // the component scope.
1572 + const pureScopes: Set<Scope> = capturePureScopes(
1573 + fn.scope.parent,
1574 + componentScope
1575 + );
1576 +
1577 + fn.get("body").traverse({
1578 + Expression(path) {
1579 + // TODO(gsn): Handle member expressions
1580 + if (!path.isIdentifier) {
1581 + return;
1582 + }
1583 +
1584 + const id = path as NodePath<t.Identifier>;
1585 + const binding = id.scope.getBinding(id.node.name);
1586 + if (binding === undefined || !pureScopes.has(binding.scope)) {
1587 + return;
1588 + }
1589 +
1590 + captured.add(binding.identifier);
1591 + },
1592 + });
1593 +
1594 + return captured;
1595 +}
compiler/forget/src/HIR/HIR.ts
+1
@@ -313,6 +313,7 @@ export type InstructionData =
313 kind: "FunctionExpression";
314 name: string | null;
315 params: Array<string>;
316 + dependencies: Set<t.Identifier>;
317 body: t.BlockStatement;
318 }
319
compiler/forget/src/HIR/PrintHIR.ts
+4 -1
@@ -293,7 +293,10 @@ export function printInstructionValue(instrValue: InstructionValue): string {
293 case "FunctionExpression": {
294 const params = instrValue.params.join(",");
295 const body = generate(instrValue.body).code;
296 - value = `Function ${instrValue.name}(${params}){${body}}`;
296 + const deps = [...instrValue.dependencies].map((i) => i.name).join(",");
297 + value = `Function ${instrValue.name ?? ""} @deps[${deps}] (${
298 + params ?? ""
299 + }){${body}}`;
300 break;
301 }
302 default: {
compiler/forget/src/__tests__/fixtures/hir/capturing-function-1.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component() {
6 + let z = 100;
7 + let x = function () {
8 + z;
9 + };
10 + return x;
11 +}
12 +
13 +```
14 +
15 +## Code
16 +
17 +```javascript
18 +function component() {
19 + const $ = React.useMemoCache();
20 + const z = 100;
21 + let x;
22 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 + x = function () {
24 + z;
25 + };
26 +
27 + $[0] = x;
28 + } else {
29 + x = $[0];
30 + }
31 +
32 + return x;
33 +}
34 +
35 +```
36 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-function-1.js new
+7
@@ -0,0 +1,7 @@
1 +function component() {
2 + let z = 100;
3 + let x = function () {
4 + z;
5 + };
6 + return x;
7 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md new
+40
@@ -0,0 +1,40 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component() {
6 + let z = 100;
7 + let x;
8 + {
9 + x = function () {
10 + z;
11 + };
12 + }
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function component() {
22 + const $ = React.useMemoCache();
23 + const z = 100;
24 + const x = undefined;
25 + let x$0;
26 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27 + x$0 = function () {
28 + z;
29 + };
30 +
31 + $[0] = x$0;
32 + } else {
33 + x$0 = $[0];
34 + }
35 +
36 + return x$0;
37 +}
38 +
39 +```
40 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.js new
+10
@@ -0,0 +1,10 @@
1 +function component() {
2 + let z = 100;
3 + let x;
4 + {
5 + x = function () {
6 + z;
7 + };
8 + }
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-block.expect.md new
+40
@@ -0,0 +1,40 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component() {
6 + let z = 100;
7 + let x = function () {
8 + {
9 + z;
10 + }
11 + };
12 + return x;
13 +}
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function component() {
21 + const $ = React.useMemoCache();
22 + const z = 100;
23 + let x;
24 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 + x = function () {
26 + {
27 + z;
28 + }
29 + };
30 +
31 + $[0] = x;
32 + } else {
33 + x = $[0];
34 + }
35 +
36 + return x;
37 +}
38 +
39 +```
40 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-block.js new
+9
@@ -0,0 +1,9 @@
1 +function component() {
2 + let z = 100;
3 + let x = function () {
4 + {
5 + z;
6 + }
7 + };
8 + return x;
9 +}
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-function.expect.md new
+40
@@ -0,0 +1,40 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component() {
6 + let z = 100;
7 + let x = function () {
8 + (function () {
9 + z;
10 + })();
11 + };
12 + return x;
13 +}
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function component() {
21 + const $ = React.useMemoCache();
22 + const z = 100;
23 + let x;
24 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 + x = function () {
26 + (function () {
27 + z;
28 + })();
29 + };
30 +
31 + $[0] = x;
32 + } else {
33 + x = $[0];
34 + }
35 +
36 + return x;
37 +}
38 +
39 +```
40 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-variable-in-nested-function.js new
+9
@@ -0,0 +1,9 @@
1 +function component() {
2 + let z = 100;
3 + let x = function () {
4 + (function () {
5 + z;
6 + })();
7 + };
8 + return x;
9 +}