[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
```