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

[valueblocks] Lower logical expressions into logical terminals

Changes the lowering for LogicalExpression to use the new 'logical' terminal. Whereas before we tried to more directly model the semantics of `??` by generating an `if (<lhs> != null)`, we now generate a branch terminal that looks at the lhs. The HIR -> ReactiveFunction construction for logical and branch terminals are placeholders while I refactor the ReactiveFunction representation to support value blocks.

Joe Savona committed Jan 31, 2023 at 13:39 UTC c6a066af83ee999b11b6496cdc78705931e05b3a
6 files changed +141 -193
compiler/forget/src/HIR/BuildHIR.ts
+63 -72
@@ -891,78 +891,69 @@ function lowerExpression(
891 }
892 case "LogicalExpression": {
893 const expr = exprPath as NodePath<t.LogicalExpression>;
894 - const leftPath = expr.get("left");
895 - const operator = expr.node.operator;
896 - switch (operator) {
897 - case "||": {
898 - const left = lowerExpressionToPlace(builder, leftPath);
899 - return lowerConditional(
900 - builder,
901 - left,
902 - exprLoc,
903 - () => left,
904 - () => lowerExpression(builder, expr.get("right"))
905 - );
906 - }
907 - case "&&": {
908 - const left = lowerExpressionToPlace(builder, leftPath);
909 - return lowerConditional(
910 - builder,
911 - left,
912 - exprLoc,
913 - () => lowerExpression(builder, expr.get("right")),
914 - () => left
915 - );
916 - }
917 - case "??": {
918 - // generate the equivalent of
919 - // const tmp = <left>;
920 - // tmp != null ? tmp : <right>
921 - const left = lowerExpressionToPlace(builder, leftPath);
922 -
923 - const nullPlace: Place = buildTemporaryPlace(builder, left.loc);
924 - builder.push({
925 - id: makeInstructionId(0),
926 - value: {
927 - kind: "Primitive",
928 - value: null,
929 - loc: GeneratedSource,
930 - },
931 - loc: left.loc,
932 - lvalue: { place: { ...nullPlace }, kind: InstructionKind.Const },
933 - });
934 -
935 - const condPlace: Place = buildTemporaryPlace(builder, left.loc);
936 - builder.push({
937 - id: makeInstructionId(0),
938 - lvalue: {
939 - place: { ...condPlace },
940 - kind: InstructionKind.Const,
941 - },
942 - value: {
943 - kind: "BinaryExpression",
944 - operator: "!=",
945 - left,
946 - right: nullPlace,
947 - loc: left.loc,
948 - },
949 - loc: left.loc,
950 - });
951 - return lowerConditional(
952 - builder,
953 - condPlace,
954 - exprLoc,
955 - () => left,
956 - () => lowerExpression(builder, expr.get("right"))
957 - );
958 - }
959 - default: {
960 - assertExhaustive(
961 - operator,
962 - `Unexpected logical operator '${operator as any}'`
963 - );
964 - }
965 - }
894 + const exprLoc = expr.node.loc ?? GeneratedSource;
895 + const continuationBlock = builder.reserve(builder.currentBlockKind());
896 + const testBlock = builder.reserve("value");
897 + const place = buildTemporaryPlace(builder, exprLoc);
898 + const leftPlace = buildTemporaryPlace(
899 + builder,
900 + expr.get("left").node.loc ?? GeneratedSource
901 + );
902 + const consequent = builder.enter("value", () => {
903 + builder.push({
904 + id: makeInstructionId(0),
905 + lvalue: { kind: InstructionKind.Reassign, place: { ...place } },
906 + value: { ...leftPlace },
907 + loc: exprLoc,
908 + });
909 + return {
910 + kind: "goto",
911 + block: continuationBlock.id,
912 + variant: GotoVariant.Break,
913 + id: makeInstructionId(0),
914 + };
915 + });
916 + const alternate = builder.enter("value", () => {
917 + builder.push({
918 + id: makeInstructionId(0),
919 + lvalue: { kind: InstructionKind.Reassign, place: { ...place } },
920 + value: lowerExpressionToPlace(builder, expr.get("right")),
921 + loc: exprLoc,
922 + });
923 + return {
924 + kind: "goto",
925 + block: continuationBlock.id,
926 + variant: GotoVariant.Break,
927 + id: makeInstructionId(0),
928 + };
929 + });
930 + builder.terminateWithContinuation(
931 + {
932 + kind: "logical",
933 + fallthrough: continuationBlock.id,
934 + id: makeInstructionId(0),
935 + test: testBlock.id,
936 + operator: expr.node.operator,
937 + },
938 + testBlock
939 + );
940 + builder.push({
941 + id: makeInstructionId(0),
942 + lvalue: { kind: InstructionKind.Reassign, place: { ...leftPlace } },
943 + value: lowerExpressionToPlace(builder, expr.get("left")),
944 + loc: exprLoc,
945 + });
946 + builder.terminateWithContinuation(
947 + {
948 + kind: "branch",
949 + test: { ...leftPlace },
950 + consequent,
951 + alternate,
952 + id: makeInstructionId(0),
953 + },
954 + continuationBlock
955 + );
956 + return place;
957 }
958 case "AssignmentExpression": {
959 const expr = exprPath as NodePath<t.AssignmentExpression>;
compiler/forget/src/HIR/HIRBuilder.ts
+4
@@ -113,6 +113,10 @@ export default class HIRBuilder {
113 );
114 }
115
116 + currentBlockKind(): BlockKind {
117 + return this.#current.kind;
118 + }
119 +
120 /**
121 * Push a statement or expression onto the current block
122 */
compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts
+50 -3
@@ -24,7 +24,6 @@ import {
24 ReactiveFunction,
25 ReactiveTerminalStatement,
26 } from "../HIR/HIR";
27 -import todo from "../Utils/todo";
27 import { assertExhaustive } from "../Utils/utils";
28
29 /**
@@ -361,10 +360,58 @@ class Driver {
360 break;
361 }
362 case "branch": {
364 - todo("Implement tree visitor for branch terminal");
363 + let consequent: ReactiveBlock | null = null;
364 + if (this.cx.isScheduled(terminal.consequent)) {
365 + const break_ = this.visitBreak(terminal.consequent, null);
366 + if (break_ !== null) {
367 + consequent = [break_];
368 + }
369 + } else {
370 + consequent = this.traverseBlock(
371 + this.cx.ir.blocks.get(terminal.consequent)!
372 + );
373 + }
374 +
375 + let alternate: ReactiveBlock | null = null;
376 + if (this.cx.isScheduled(terminal.alternate)) {
377 + const break_ = this.visitBreak(terminal.alternate, null);
378 + if (break_ !== null) {
379 + alternate = [break_];
380 + }
381 + } else {
382 + alternate = this.traverseBlock(
383 + this.cx.ir.blocks.get(terminal.alternate)!
384 + );
385 + }
386 +
387 + blockValue.push({
388 + kind: "terminal",
389 + terminal: {
390 + kind: "if",
391 + test: terminal.test,
392 + consequent: consequent ?? this.emptyBlock(),
393 + alternate: alternate,
394 + id: terminal.id,
395 + },
396 + label: null,
397 + });
398 +
399 + break;
400 }
401 case "logical": {
367 - todo("Implement tree visitor for logical terminal");
402 + const fallthroughId = terminal.fallthrough;
403 + invariant(
404 + !this.cx.isScheduled(fallthroughId),
405 + "Logical terminal fallthrough cannot have been scheduled"
406 + );
407 + const scheduleId = this.cx.schedule(fallthroughId, "if");
408 + scheduleIds.push(scheduleId);
409 +
410 + this.visitBlock(this.cx.ir.blocks.get(terminal.test)!, blockValue);
411 +
412 + this.cx.unschedule(scheduleId);
413 + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
414 + break;
415 }
416 case "goto": {
417 switch (terminal.variant) {
compiler/forget/src/SSA/LeaveSSA.ts
+1 -1
@@ -8,7 +8,6 @@
8 import invariant from "invariant";
9 import {
10 BasicBlock,
11 - BlockId,
11 Effect,
12 GeneratedSource,
13 HIRFunction,
@@ -124,6 +123,7 @@ export function leaveSSA(fn: HIRFunction) {
123 (terminal.kind === "if" ||
124 terminal.kind === "switch" ||
125 terminal.kind === "while" ||
126 + terminal.kind === "logical" ||
127 terminal.kind === "for") &&
128 terminal.fallthrough !== null
129 ) {
compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md
+17 -104
@@ -2,127 +2,40 @@
2 ## Input
3
4 ```javascript
5 -function And() {
6 - return f() && g();
5 +function component(props) {
6 + let a = props.a && props.b;
7 + return a;
8 + // let b = props.c || props.d;
9 + // let c = props.e ?? props.f;
10 + // return ((a && b) || c) ?? null;
11 }
12
9 -function Or() {
10 - return f() || g();
11 -}
12 -
13 -function QuestionQuestion(props) {
14 - return f() ?? g();
15 -}
16 -
17 -function f() {}
18 -function g() {}
19 -
13 ```
14
15 ## Code
16
17 ```javascript
25 -function And() {
26 - const $ = React.useMemoCache();
27 - let t0;
28 - if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 - t0 = f();
30 - $[0] = t0;
31 - } else {
32 - t0 = $[0];
33 - }
34 - const c_1 = $[1] !== t0;
35 - let t2;
36 - if (c_1) {
37 - t2 = undefined;
38 - if (t0) {
39 - let t3;
40 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
41 - t3 = g();
42 - $[3] = t3;
43 - } else {
44 - t3 = $[3];
45 - }
46 - t2 = t3;
47 - } else {
48 - t2 = t0;
49 - }
50 - $[1] = t0;
51 - $[2] = t2;
52 - } else {
53 - t2 = $[2];
54 - }
55 - return t2;
56 -}
57 -
58 -function Or() {
18 +function component(props) {
19 const $ = React.useMemoCache();
60 - let t0;
61 - if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
62 - t0 = f();
63 - $[0] = t0;
64 - } else {
65 - t0 = $[0];
66 - }
67 - const c_1 = $[1] !== t0;
20 + const c_0 = $[0] !== props.a;
21 + const c_1 = $[1] !== props.b;
22 let t2;
69 - if (c_1) {
23 + if (c_0 || c_1) {
24 t2 = undefined;
71 - if (t0) {
72 - t2 = t0;
25 + if (props.a) {
26 + t2 = props.a;
27 } else {
74 - let t3;
75 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
76 - t3 = g();
77 - $[3] = t3;
78 - } else {
79 - t3 = $[3];
80 - }
81 - t2 = t3;
28 + t2 = props.b;
29 }
83 - $[1] = t0;
30 + $[0] = props.a;
31 + $[1] = props.b;
32 $[2] = t2;
33 } else {
34 t2 = $[2];
35 }
88 - return t2;
36 + const a = t2;
37 + return a;
38 }
39
91 -function QuestionQuestion(props) {
92 - const $ = React.useMemoCache();
93 - let t0;
94 - if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
95 - t0 = f();
96 - $[0] = t0;
97 - } else {
98 - t0 = $[0];
99 - }
100 - const c_1 = $[1] !== t0;
101 - let t2;
102 - if (c_1) {
103 - t2 = undefined;
104 - if (t0 != null) {
105 - t2 = t0;
106 - } else {
107 - let t3;
108 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
109 - t3 = g();
110 - $[3] = t3;
111 - } else {
112 - t3 = $[3];
113 - }
114 - t2 = t3;
115 - }
116 - $[1] = t0;
117 - $[2] = t2;
118 - } else {
119 - t2 = $[2];
120 - }
121 - return t2;
122 -}
123 -
124 -function f() {}
125 -function g() {}
126 -
40 ```
41
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/logical-expression.js
+6 -13
@@ -1,14 +1,7 @@
1 -function And() {
2 - return f() && g();
1 +function component(props) {
2 + let a = props.a && props.b;
3 + return a;
4 + // let b = props.c || props.d;
5 + // let c = props.e ?? props.f;
6 + // return ((a && b) || c) ?? null;
7 }
4 -
5 -function Or() {
6 - return f() || g();
7 -}
8 -
9 -function QuestionQuestion(props) {
10 - return f() ?? g();
11 -}
12 -
13 -function f() {}
14 -function g() {}