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