@samitouri / QOS-React-1 / commits / f92a058ca5

Codegen comments to explain output

Joe Savona committed Nov 10, 2023 at 11:45 UTC f92a058ca5ebde1566d70f85cfd40d7fb5cfc38b
4 files changed +192 -1
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+8
@@ -257,6 +257,14 @@ const EnvironmentConfigSchema = z.object({
257 * ```
258 */
259 enableChangeVariableCodegen: z.boolean().default(false),
260 +
261 + /**
262 + * Enable emitting comments that explain Forget's output, and which
263 + * values are being checked and which values produced by each memo block.
264 + *
265 + * Intended for use in demo purposes (incl playground)
266 + */
267 + enableMemoizationComments: z.boolean().default(false),
268 });
269
270 export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+94 -1
@@ -240,9 +240,12 @@ function codegenMemoBlockForReactiveScope(
240 const cacheStoreStatements: Array<t.Statement> = [];
241 const cacheLoadStatements: Array<t.Statement> = [];
242 const changeExpressions: Array<t.Expression> = [];
243 + const changeExpressionComments: Array<string> = [];
244 + const outputComments: Array<string> = [];
245 for (const dep of scope.dependencies) {
246 const index = cx.nextCacheIndex;
247 const depValue = codegenDependency(cx, dep);
248 + changeExpressionComments.push(printDependencyComment(dep));
249 const comparison = t.binaryExpression(
250 "!==",
251 t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
@@ -285,6 +288,7 @@ function codegenMemoBlockForReactiveScope(
288 });
289
290 const name = convertIdentifier(identifier);
291 + outputComments.push(name.name);
292 if (!cx.hasDeclared(identifier)) {
293 statements.push(
294 t.variableDeclaration("let", [t.variableDeclarator(name)])
@@ -316,6 +320,7 @@ function codegenMemoBlockForReactiveScope(
320 firstOutputIndex = index;
321 }
322 const name = convertIdentifier(reassignment);
323 + outputComments.push(name.name);
324
325 cacheStoreStatements.push(
326 t.expressionStatement(
@@ -369,7 +374,60 @@ function codegenMemoBlockForReactiveScope(
374 const computationBlock = codegenBlock(cx, block);
375 computationBlock.body.push(...cacheStoreStatements);
376 const memoBlock = t.blockStatement(cacheLoadStatements);
372 - statements.push(t.ifStatement(testCondition, computationBlock, memoBlock));
377 + const memoStatement = t.ifStatement(
378 + testCondition,
379 + computationBlock,
380 + memoBlock
381 + );
382 + if (cx.env.config.enableMemoizationComments) {
383 + if (changeExpressionComments.length) {
384 + t.addComment(
385 + memoStatement,
386 + "leading",
387 + ` check if ${printDelimitedCommentList(
388 + changeExpressionComments,
389 + "or"
390 + )} changed`,
391 + true
392 + );
393 + t.addComment(
394 + memoStatement,
395 + "leading",
396 + ` "useMemo" for ${printDelimitedCommentList(outputComments, "and")}:`,
397 + true
398 + );
399 + } else {
400 + t.addComment(
401 + memoStatement,
402 + "leading",
403 + " cache value with no dependencies",
404 + true
405 + );
406 + t.addComment(
407 + memoStatement,
408 + "leading",
409 + ` "useMemo" for ${printDelimitedCommentList(outputComments, "and")}:`,
410 + true
411 + );
412 + }
413 + if (computationBlock.body.length > 0) {
414 + t.addComment(
415 + computationBlock.body[0]!,
416 + "leading",
417 + ` Inputs changed, recompute`,
418 + true
419 + );
420 + }
421 + if (memoBlock.body.length > 0) {
422 + t.addComment(
423 + memoBlock.body[0]!,
424 + "leading",
425 + ` Inputs did not change, use cached value`,
426 + true
427 + );
428 + }
429 + }
430 + statements.push(memoStatement);
431 }
432
433 function codegenSignalBlockForReactiveScope(
@@ -789,6 +847,41 @@ function codegenForInit(
847 }
848 }
849
850 +function printDependencyComment(dependency: ReactiveScopeDependency): string {
851 + const identifier = convertIdentifier(dependency.identifier);
852 + let name = identifier.name;
853 + if (dependency.path !== null) {
854 + for (const path of dependency.path) {
855 + name += `.${path}`;
856 + }
857 + }
858 + return name;
859 +}
860 +
861 +function printDelimitedCommentList(
862 + items: Array<string>,
863 + finalCompletion: string
864 +): string {
865 + if (items.length === 2) {
866 + return items.join(` ${finalCompletion} `);
867 + } else if (items.length <= 1) {
868 + return items.join("");
869 + }
870 +
871 + let output = [];
872 + for (let i = 0; i < items.length; i++) {
873 + const item = items[i]!;
874 + if (i < items.length - 2) {
875 + output.push(`${item}, `);
876 + } else if (i === items.length - 2) {
877 + output.push(`${item}, ${finalCompletion} `);
878 + } else {
879 + output.push(item);
880 + }
881 + }
882 + return output.join("");
883 +}
884 +
885 function codegenDependency(
886 cx: Context,
887 dependency: ReactiveScopeDependency
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/memoization-comments.expect.md new
+76
@@ -0,0 +1,76 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableMemoizationComments
6 +import { addOne, getNumber, identity } from "shared-runtime";
7 +
8 +function Component(props) {
9 + const x = identity(props.a);
10 + const y = addOne(x);
11 + const z = identity(props.b);
12 + return [x, y, z];
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{ a: 1, b: 10 }],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { unstable_useMemoCache as useMemoCache } from "react"; // @enableMemoizationComments
26 +import { addOne, getNumber, identity } from "shared-runtime";
27 +
28 +function Component(props) {
29 + const $ = useMemoCache(9);
30 + let t0;
31 + let x; // "useMemo" for t0 and x:
32 + // check if props.a changed
33 + if ($[0] !== props.a) {
34 + // Inputs changed, recompute
35 + x = identity(props.a);
36 + t0 = addOne(x);
37 + $[0] = props.a;
38 + $[1] = t0;
39 + $[2] = x;
40 + } else {
41 + // Inputs did not change, use cached value
42 + t0 = $[1];
43 + x = $[2];
44 + }
45 + const y = t0;
46 + let t1; // "useMemo" for t1:
47 + // check if props.b changed
48 + if ($[3] !== props.b) {
49 + // Inputs changed, recompute
50 + t1 = identity(props.b);
51 + $[3] = props.b;
52 + $[4] = t1;
53 + } else {
54 + // Inputs did not change, use cached value
55 + t1 = $[4];
56 + }
57 + const z = t1;
58 + let t2; // "useMemo" for t2:
59 + // check if x, y, or z changed
60 + if ($[5] !== x || $[6] !== y || $[7] !== z) {
61 + // Inputs changed, recompute
62 + t2 = [x, y, z];
63 + $[5] = x;
64 + $[6] = y;
65 + $[7] = z;
66 + $[8] = t2;
67 + } else {
68 + // Inputs did not change, use cached value
69 + t2 = $[8];
70 + }
71 + return t2;
72 +}
73 +export const FIXTURE_ENTRYPOINT = { fn: Component, params: [{ a: 1, b: 10 }] };
74 +
75 +```
76 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/memoization-comments.js new
+14
@@ -0,0 +1,14 @@
1 +// @enableMemoizationComments
2 +import { addOne, getNumber, identity } from "shared-runtime";
3 +
4 +function Component(props) {
5 + const x = identity(props.a);
6 + const y = addOne(x);
7 + const z = identity(props.b);
8 + return [x, y, z];
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{ a: 1, b: 10 }],
14 +};