@samitouri / QOS-React-2 / commits / 90ab346505

[new-arch][easy] Codegen for switch statements

Flushes out basic codegen for switch statements. This is more indication that we can recover nearly the original source even for complex control-flow, given the right IR design.

Joseph Savona committed Oct 5, 2022 at 19:45 UTC 90ab346505d211745ac286f2ec8329e380687df1
4 files changed +169 -19
compiler/forget/src/HIR/Codegen.ts
+39 -18
@@ -73,42 +73,41 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
73 for (const instr of block.instructions) {
74 writeInstr(cx, instr, body);
75 }
76 - switch (block.terminal.kind) {
76 + const terminal = block.terminal;
77 + switch (terminal.kind) {
78 case "return": {
79 const value =
79 - block.terminal.value != null
80 - ? codegenPlace(cx, block.terminal.value)
81 - : null;
80 + terminal.value != null ? codegenPlace(cx, terminal.value) : null;
81 body.push(t.returnStatement(value));
82 break;
83 }
84 case "throw": {
86 - const value = codegenPlace(cx, block.terminal.value);
85 + const value = codegenPlace(cx, terminal.value);
86 body.push(t.throwStatement(value));
87 break;
88 }
89 case "if": {
91 - const test = codegenPlace(cx, block.terminal.test);
90 + const test = codegenPlace(cx, terminal.test);
91 const consequent = codegenBlock(
92 cx,
94 - cx.ir.blocks.get(block.terminal.consequent)!
93 + cx.ir.blocks.get(terminal.consequent)!
94 );
95 const fallthrough =
97 - block.terminal.fallthrough !== null &&
98 - block.terminal.fallthrough !== block.terminal.alternate
99 - ? block.terminal.fallthrough
96 + terminal.fallthrough !== null &&
97 + terminal.fallthrough !== terminal.alternate
98 + ? terminal.fallthrough
99 : null;
100 if (fallthrough !== null) {
101 const alternate = codegenBlock(
102 cx,
104 - cx.ir.blocks.get(block.terminal.alternate)!
103 + cx.ir.blocks.get(terminal.alternate)!
104 );
105 body.push(t.ifStatement(test, consequent, alternate));
106 const fallthroughBlock = cx.ir.blocks.get(fallthrough)!;
107 writeBlock(cx, fallthroughBlock, body);
108 } else {
109 body.push(t.ifStatement(test, consequent));
111 - writeBlock(cx, cx.ir.blocks.get(block.terminal.alternate)!, body);
110 + writeBlock(cx, cx.ir.blocks.get(terminal.alternate)!, body);
111 }
112 break;
113 }
@@ -121,15 +120,37 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
120 break;
121 }
122 case "switch": {
124 - body.push(
125 - t.expressionStatement(
126 - t.stringLiteral("<<TODO: handle switch in codegen>>")
127 - )
128 - );
123 + const cases: Array<t.SwitchCase> = [];
124 + terminal.cases.forEach((case_, index) => {
125 + const test = case_.test !== null ? codegenPlace(cx, case_.test) : null;
126 + // If the final case is a `default` *and* points directly to the
127 + // fallthrough branch, then we can skip emitting `default: break`
128 + // since this implied. For a default in any other position, or
129 + // for a default pointing to a different block, emit a case
130 + // normally.
131 + if (
132 + index === terminal.cases.length - 1 &&
133 + test === null &&
134 + case_.block === terminal.fallthrough
135 + ) {
136 + return;
137 + } else if (case_.block === terminal.fallthrough) {
138 + // Otherwise for any block that points directly to the fallthrough,
139 + // emit a break instead
140 + cases.push(t.switchCase(test, [t.breakStatement()]));
141 + } else {
142 + const consequent = codegenBlock(cx, cx.ir.blocks.get(case_.block)!);
143 + cases.push(t.switchCase(test, [consequent]));
144 + }
145 + });
146 + body.push(t.switchStatement(codegenPlace(cx, terminal.test), cases));
147 + if (terminal.fallthrough !== null) {
148 + writeBlock(cx, cx.ir.blocks.get(terminal.fallthrough)!, body);
149 + }
150 break;
151 }
152 default: {
132 - assertExhaustive(block.terminal, "Unexpected terminal");
153 + assertExhaustive(terminal, "Unexpected terminal");
154 }
155 }
156 }
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md new
+90
@@ -0,0 +1,90 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x = [];
7 + let y;
8 + switch (props.p0) {
9 + case 1: {
10 + break;
11 + }
12 + case true: {
13 + x.push(props.p2);
14 + y = [];
15 + }
16 + default: {
17 + break;
18 + }
19 + case false: {
20 + y = x;
21 + break;
22 + }
23 + }
24 + const child = <Component data={x} />;
25 + y.push(props.p4);
26 + return <Component data={y}>{child}</Component>;
27 +}
28 +
29 +```
30 +
31 +## HIR
32 +
33 +```
34 +bb0:
35 + readonly x$2 = Array []
36 + readonly y$3 = undefined
37 + readonly $4 = false
38 + readonly $5 = true
39 + readonly $6 = 1
40 + Switch (frozen props$1.p0)
41 + Case readonly $6: bb1
42 + Case readonly $5: bb6
43 + Default: bb1
44 + Case readonly $4: bb2
45 +bb1:
46 + readonly child$7 = JSX <frozen Component$0 data={frozen x$2} ></frozen Component$0>
47 + Call mutable y$3.push(frozen props$1.p4)
48 + readonly $8 = JSX <frozen Component$0 data={frozen y$3} >{frozen child$7}</frozen Component$0>
49 + Return frozen $8
50 +bb6:
51 + Call mutable x$2.push(frozen props$1.p2)
52 + readonly y$3 = Array []
53 + Goto bb1
54 +bb2:
55 + readonly y$3 = readonly x$2
56 + Goto bb1
57 +```
58 +
59 +## Code
60 +
61 +```javascript
62 +function Component$0(props$1) {
63 + x$2 = [];
64 + y$3 = undefined;
65 + switch (props$1.p0) {
66 + case 1:
67 + break;
68 +
69 + case true: {
70 + x$2.push(props$1.p2);
71 + y$3 = [];
72 + ("<<TODO: handle complex control flow in codegen>>");
73 + }
74 +
75 + default:
76 + break;
77 +
78 + case false: {
79 + y$3 = x$2;
80 + ("<<TODO: handle complex control flow in codegen>>");
81 + }
82 + }
83 +
84 + child$7 = <Component$0 data={x$2}></Component$0>;
85 + y$3.push(props$1.p4);
86 + return <Component$0 data={y$3}>{child$7}</Component$0>;
87 +}
88 +
89 +```
90 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.js new
+23
@@ -0,0 +1,23 @@
1 +function Component(props) {
2 + let x = [];
3 + let y;
4 + switch (props.p0) {
5 + case 1: {
6 + break;
7 + }
8 + case true: {
9 + x.push(props.p2);
10 + y = [];
11 + }
12 + default: {
13 + break;
14 + }
15 + case false: {
16 + y = x;
17 + break;
18 + }
19 + }
20 + const child = <Component data={x} />;
21 + y.push(props.p4);
22 + return <Component data={y}>{child}</Component>;
23 +}
compiler/forget/src/__tests__/fixtures/hir/switch.expect.md
+17 -1
@@ -57,7 +57,23 @@ bb1:
57 function Component$0(props$1) {
58 x$2 = [];
59 y$3 = undefined;
60 - ("<<TODO: handle switch in codegen>>");
60 + switch (props$1.p0) {
61 + case true: {
62 + x$2.push(props$1.p2);
63 + x$2.push(props$1.p3);
64 + y$3 = [];
65 + ("<<TODO: handle complex control flow in codegen>>");
66 + }
67 +
68 + case false: {
69 + y$3 = x$2;
70 + ("<<TODO: handle complex control flow in codegen>>");
71 + }
72 + }
73 +
74 + child$6 = <Component$0 data={x$2}></Component$0>;
75 + y$3.push(props$1.p4);
76 + return <Component$0 data={y$3}>{child$6}</Component$0>;
77 }
78
79 ```