@samitouri / QOS-React-2 / commits / 0522d5233b

[λ] Store and define captured bindings as context

This lets us treat global references differently from captured references.

Sathya Gunasekaran committed Feb 2, 2023 at 14:05 UTC 0522d5233bc1bd5a98ba5c266bfa4c08dc7eaa72
11 files changed +179 -82
compiler/forget/src/HIR/BuildHIR.ts
+22 -12
@@ -49,10 +49,21 @@ import HIRBuilder, { Environment } from "./HIRBuilder";
49 * grained reactivity.
50 */
51 export function lower(
52 - func: NodePath<t.Function>
52 + func: NodePath<t.Function>,
53 + capturedRefs?: t.Identifier[]
54 ): Result<HIRFunction, CompilerError> {
55 const env = new Environment();
56 const builder = new HIRBuilder(env);
57 + const context: Place[] = [];
58 +
59 + for (const ref of capturedRefs ?? []) {
60 + context.push({
61 + kind: "Identifier",
62 + identifier: builder.resolveBinding(ref),
63 + effect: Effect.Unknown,
64 + loc: GeneratedSource,
65 + });
66 + }
67
68 // Internal babel is on an older version that does not have hasNode (v7.17)
69 // See https://github.com/babel/babel/pull/13940/files for impl
@@ -125,6 +136,7 @@ export function lower(
136 id,
137 params,
138 body: builder.build(),
139 + context,
140 generator: func.node.generator === true,
141 async: func.node.async === true,
142 loc: func.node.loc ?? GeneratedSource,
@@ -1300,12 +1312,8 @@ function lowerExpression(
1312 name = expr.get("id")?.node?.name ?? null;
1313 }
1314 const componentScope: Scope = expr.scope.parent.getFunctionParent()!;
1303 - const dependencies: Array<Place> = gatherCapturedDeps(
1304 - builder,
1305 - expr,
1306 - componentScope
1307 - );
1308 - const lowering = lower(expr);
1315 + const captured = gatherCapturedDeps(builder, expr, componentScope);
1316 + const lowering = lower(expr, captured.identifiers);
1317 let loweredFunc: HIRFunction;
1318 if (lowering.isErr()) {
1319 lowering
@@ -1340,7 +1348,7 @@ function lowerExpression(
1348 name,
1349 params,
1350 loweredFunc,
1343 - dependencies,
1351 + dependencies: captured.refs,
1352 mutatedDeps: [],
1353 expr: expr.node,
1354 loc: exprLoc,
@@ -1919,8 +1927,9 @@ function gatherCapturedDeps(
1927 builder: HIRBuilder,
1928 fn: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>,
1929 componentScope: Scope
1922 -): Array<Place> {
1923 - const captured: Set<Place> = new Set();
1930 +): { identifiers: t.Identifier[]; refs: Place[] } {
1931 + const capturedIds: Set<t.Identifier> = new Set();
1932 + const capturedRefs: Set<Place> = new Set();
1933
1934 // Capture all the scopes from the parent of this function up to and including
1935 // the component scope.
@@ -1946,9 +1955,10 @@ function gatherCapturedDeps(
1955 }
1956
1957 path.skip();
1949 - captured.add(lowerExpressionToPlace(builder, path));
1958 + capturedIds.add(binding.identifier);
1959 + capturedRefs.add(lowerExpressionToPlace(builder, path));
1960 },
1961 });
1962
1953 - return [...captured];
1963 + return { identifiers: [...capturedIds], refs: [...capturedRefs] };
1964 }
compiler/forget/src/HIR/HIR.ts
+1
@@ -184,6 +184,7 @@ export type HIRFunction = {
184 id: Identifier | null;
185 env: Environment;
186 params: Array<Place>;
187 + context: Array<Place>;
188 body: HIR;
189 generator: boolean;
190 async: boolean;
compiler/forget/src/HIR/HIRBuilder.ts
+5
@@ -172,6 +172,11 @@ export default class HIRBuilder {
172 const node =
173 path.scope.getBindingIdentifier(originalName) ??
174 getOrAddGlobal(originalName);
175 + return this.resolveBinding(node);
176 + }
177 +
178 + resolveBinding(node: t.Identifier) {
179 + const originalName = node.name;
180 let name = originalName;
181 let index = 0;
182 while (true) {
compiler/forget/src/Inference/AnalyseFunctions.ts
+4 -16
@@ -1,11 +1,4 @@
1 -import {
2 - Effect,
3 - HIRFunction,
4 - Identifier,
5 - mergeConsecutiveBlocks,
6 - Place,
7 -} from "../HIR";
8 -import { eachInstructionOperand } from "../HIR/visitors";
1 +import { HIRFunction, Identifier, mergeConsecutiveBlocks, Place } from "../HIR";
2 import { constantPropagation } from "../Optimization";
3 import { eliminateRedundantPhi, enterSSA } from "../SSA";
4 import { inferTypes } from "../TypeInference";
@@ -116,18 +109,13 @@ function analyzeMutatedPlaces(func: HIRFunction): Array<Place> {
109 ) {
110 mutations.push(...analyzeMutatedPlaces(instr.value.loweredFunc));
111 }
119 -
120 - for (const operand of eachInstructionOperand(instr)) {
121 - if (isMutated(operand)) {
122 - mutations.push(operand);
123 - }
124 - }
112 }
113 }
114
115 + mutations.push(...func.context.filter((dep) => isMutated(dep.identifier)));
116 return mutations;
117 }
118
131 -function isMutated(place: Place): boolean {
132 - return place.effect === Effect.Mutate || place.effect === Effect.Store;
119 +function isMutated(id: Identifier) {
120 + return id.mutableRange.end - id.mutableRange.start > 1;
121 }
compiler/forget/src/Inference/InferReferenceEffects.ts
+11
@@ -93,6 +93,17 @@ export default function inferReferenceEffects(fn: HIRFunction) {
93 initialEnvironment.define(id, value);
94 }
95
96 + for (const ref of fn.context) {
97 + // TODO(gsn): This is a hack.
98 + const value: InstructionValue = {
99 + kind: "ObjectExpression",
100 + properties: null,
101 + loc: ref.loc,
102 + };
103 + initialEnvironment.initialize(value, ValueKind.Mutable);
104 + initialEnvironment.define(ref, value);
105 + }
106 +
107 for (const param of fn.params) {
108 const value: InstructionValue = {
109 kind: "Primitive",
compiler/forget/src/SSA/EnterSSA.ts
+1
@@ -195,6 +195,7 @@ export default function enterSSA(func: HIRFunction) {
195 builder.startBlock(block);
196
197 if (func.body.entry === blockId) {
198 + func.context = func.context.map((p) => builder.definePlace(p));
199 func.params = func.params.map((p) => builder.definePlace(p));
200 }
201
compiler/forget/src/__tests__/fixtures/hir/capture-param-mutate.expect.md new
+97
@@ -0,0 +1,97 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function getNativeLogFunction(level) {
6 + return function () {
7 + let str;
8 + if (arguments.length === 1 && typeof arguments[0] === "string") {
9 + str = arguments[0];
10 + } else {
11 + str = Array.prototype.map
12 + .call(arguments, function (arg) {
13 + return inspect(arg, {
14 + depth: 10,
15 + });
16 + })
17 + .join(", ");
18 + }
19 + const firstArg = arguments[0];
20 + let logLevel = level;
21 + if (
22 + typeof firstArg === "string" &&
23 + firstArg.slice(0, 9) === "Warning: " &&
24 + logLevel >= LOG_LEVELS.error
25 + ) {
26 + logLevel = LOG_LEVELS.warn;
27 + }
28 + if (global.__inspectorLog) {
29 + global.__inspectorLog(
30 + INSPECTOR_LEVELS[logLevel],
31 + str,
32 + [].slice.call(arguments),
33 + INSPECTOR_FRAMES_TO_SKIP
34 + );
35 + }
36 + if (groupStack.length) {
37 + str = groupFormat("", str);
38 + }
39 + global.nativeLoggingHook(str, logLevel);
40 + };
41 +}
42 +
43 +```
44 +
45 +## Code
46 +
47 +```javascript
48 +function getNativeLogFunction(level) {
49 + const $ = React.useMemoCache();
50 + const c_0 = $[0] !== level;
51 + let t1;
52 + if (c_0) {
53 + t1 = function () {
54 + let str;
55 + if (arguments.length === 1 && typeof arguments[0] === "string") {
56 + str = arguments[0];
57 + } else {
58 + str = Array.prototype.map
59 + .call(arguments, function (arg) {
60 + return inspect(arg, {
61 + depth: 10,
62 + });
63 + })
64 + .join(", ");
65 + }
66 + const firstArg = arguments[0];
67 + let logLevel = level;
68 + if (
69 + typeof firstArg === "string" &&
70 + firstArg.slice(0, 9) === "Warning: " &&
71 + logLevel >= LOG_LEVELS.error
72 + ) {
73 + logLevel = LOG_LEVELS.warn;
74 + }
75 + if (global.__inspectorLog) {
76 + global.__inspectorLog(
77 + INSPECTOR_LEVELS[logLevel],
78 + str,
79 + [].slice.call(arguments),
80 + INSPECTOR_FRAMES_TO_SKIP
81 + );
82 + }
83 + if (groupStack.length) {
84 + str = groupFormat("", str);
85 + }
86 + global.nativeLoggingHook(str, logLevel);
87 + };
88 + $[0] = level;
89 + $[1] = t1;
90 + } else {
91 + t1 = $[1];
92 + }
93 + return t1;
94 +}
95 +
96 +```
97 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capture-param-mutate.js renamed
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-nested.expect.md new
+38
@@ -0,0 +1,38 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function component(a) {
6 + let y = { b: { a } };
7 + let x = function () {
8 + y.b.a = 2;
9 + };
10 + x();
11 + return x;
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +function component(a) {
20 + const $ = React.useMemoCache();
21 + const c_0 = $[0] !== a;
22 + let x;
23 + if (c_0) {
24 + const y = { b: { a: a } };
25 + x = function () {
26 + y.b.a = 2;
27 + };
28 + x();
29 + $[0] = a;
30 + $[1] = x;
31 + } else {
32 + x = $[1];
33 + }
34 + return x;
35 +}
36 +
37 +```
38 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-mutate-nested.js
-2
@@ -1,5 +1,3 @@
1 -// @skip
2 -// TODO(gsn): This doesn't seem to work correctly. Need to debug more.
1 function component(a) {
2 let y = { b: { a } };
3 let x = function () {
compiler/forget/src/__tests__/fixtures/hir/error._bug_fbsource_pokes_infer_reference_effects.expect.md deleted
-52
@@ -1,52 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function getNativeLogFunction(level) {
6 - return function () {
7 - let str;
8 - if (arguments.length === 1 && typeof arguments[0] === "string") {
9 - str = arguments[0];
10 - } else {
11 - str = Array.prototype.map
12 - .call(arguments, function (arg) {
13 - return inspect(arg, {
14 - depth: 10,
15 - });
16 - })
17 - .join(", ");
18 - }
19 - const firstArg = arguments[0];
20 - let logLevel = level;
21 - if (
22 - typeof firstArg === "string" &&
23 - firstArg.slice(0, 9) === "Warning: " &&
24 - logLevel >= LOG_LEVELS.error
25 - ) {
26 - logLevel = LOG_LEVELS.warn;
27 - }
28 - if (global.__inspectorLog) {
29 - global.__inspectorLog(
30 - INSPECTOR_LEVELS[logLevel],
31 - str,
32 - [].slice.call(arguments),
33 - INSPECTOR_FRAMES_TO_SKIP
34 - );
35 - }
36 - if (groupStack.length) {
37 - str = groupFormat("", str);
38 - }
39 - global.nativeLoggingHook(str, logLevel);
40 - };
41 -}
42 -
43 -```
44 -
45 -
46 -## Error
47 -
48 -```
49 -[ReactForget] Invariant: InferReferenceEffects::kind: Expected at least one value at '<unknown> logLevel$85:TPrimitive' (20:20)
50 -```
51 -
52 -
\ No newline at end of file