1
+import { assertExhaustive } from "../Common/utils";
2
+import { BasicBlock, BlockId, HIRFunction, Terminal } from "./HIR";
3
+import { printInstruction, printPlace } from "./PrintHIR";
4
+
5
+enum MermaidFlowchartDirection {
6
+ TopBottom = "TB", // top to bottom
7
+ TopDown = "TD", // top-down/ same as top to bottom
8
+ BottomTop = "BT", // bottom to top
9
+ RightLeft = "RL", // right to left
10
+ LeftRight = "LR", // left to right
11
+}
12
+
13
+function indent(str: string, level: number = 1) {
14
+ const leadingSpaces = " ".repeat(level * 2);
15
+ return str
16
+ .split("\n")
17
+ .map((line) => `${leadingSpaces}${line}`)
18
+ .join("\n");
19
+}
20
+
21
+function printBlockId(id: BlockId): string {
22
+ return `bb${id}`;
23
+}
24
+
25
+function printJump(from: BlockId, to: BlockId, label: string | null): string {
26
+ const fromId = printBlockId(from);
27
+ const toId = printBlockId(to);
28
+ if (label != null) {
29
+ return `${fromId}_terminal -- ${label} --> ${toId}\n`;
30
+ }
31
+ return `${fromId}_terminal --> ${toId}\n`;
32
+}
33
+
34
+function visualizeInstructions({ id, instructions }: BasicBlock): string {
35
+ return (
36
+ `${printBlockId(id)}_instrs["\n` +
37
+ indent(
38
+ instructions
39
+ .map((instr) => printInstruction(instr).replaceAll('"', "'"))
40
+ .join("\n"),
41
+ 2
42
+ ) +
43
+ indent('\n"]')
44
+ );
45
+}
46
+
47
+function visualizeTerminal(terminal: Terminal) {
48
+ let buffer = "";
49
+ switch (terminal.kind) {
50
+ case "if": {
51
+ buffer = buffer.concat(`If (${printPlace(terminal.test)})`);
52
+ break;
53
+ }
54
+ case "goto": {
55
+ buffer = buffer.concat("Goto");
56
+ break;
57
+ }
58
+ case "return": {
59
+ if (terminal.value != null) {
60
+ buffer = buffer.concat(`Return ${printPlace(terminal.value)}`);
61
+ } else {
62
+ buffer = buffer.concat("Return");
63
+ }
64
+ break;
65
+ }
66
+ case "switch":
67
+ buffer = buffer.concat(`Switch (${printPlace(terminal.test)})`);
68
+ break;
69
+ case "throw":
70
+ buffer = buffer.concat(`Throw ${printPlace(terminal.value)}`);
71
+ break;
72
+ case "while":
73
+ buffer = buffer.concat("While");
74
+ break;
75
+ default:
76
+ assertExhaustive(terminal, `unhandled terminal ${terminal}`);
77
+ }
78
+ return buffer;
79
+}
80
+
81
+function visualizeJump(blockId: BlockId, terminal: Terminal): string {
82
+ let buffer = "";
83
+ switch (terminal.kind) {
84
+ case "if": {
85
+ buffer = buffer.concat(printJump(blockId, terminal.consequent, "then"));
86
+ buffer = buffer.concat(printJump(blockId, terminal.alternate, "else"));
87
+ if (
88
+ terminal.fallthrough != null &&
89
+ terminal.alternate !== terminal.fallthrough
90
+ ) {
91
+ buffer = buffer.concat(
92
+ printJump(blockId, terminal.fallthrough, "fallthrough")
93
+ );
94
+ }
95
+ break;
96
+ }
97
+ case "goto": {
98
+ buffer = buffer.concat(printJump(blockId, terminal.block, null));
99
+ break;
100
+ }
101
+ case "switch": {
102
+ terminal.cases.forEach((case_) => {
103
+ if (case_.test != null) {
104
+ buffer = buffer.concat(
105
+ printJump(blockId, case_.block, printPlace(case_.test))
106
+ );
107
+ } else {
108
+ buffer = buffer.concat(printJump(blockId, case_.block, "default"));
109
+ }
110
+ });
111
+ if (terminal.fallthrough != null) {
112
+ buffer = buffer.concat(
113
+ printJump(blockId, terminal.fallthrough, "fallthrough")
114
+ );
115
+ }
116
+ break;
117
+ }
118
+ case "while": {
119
+ buffer = buffer.concat(printJump(blockId, terminal.test, "test"));
120
+ buffer = buffer.concat(printJump(blockId, terminal.loop, "loop"));
121
+ buffer = buffer.concat(
122
+ printJump(blockId, terminal.fallthrough, "fallthrough")
123
+ );
124
+ break;
125
+ }
126
+ case "throw":
127
+ case "return": {
128
+ break;
129
+ }
130
+ default:
131
+ assertExhaustive(terminal, `unhandled terminal ${terminal}`);
132
+ }
133
+ return buffer;
134
+}
135
+
136
+/**
137
+ * Visualizes the HIR as a mermaid.js diagram.
138
+ */
139
+export default function visualizeHIRMermaid(fn: HIRFunction): string {
140
+ const ir = fn.body;
141
+ const subgraphs = [];
142
+ const jumps = [];
143
+
144
+ for (const [blockId, block] of ir.blocks) {
145
+ let buffer;
146
+ const bbId = printBlockId(blockId);
147
+ buffer = indent(`subgraph ${bbId}\n`);
148
+ if (block.instructions.length > 0) {
149
+ buffer = buffer.concat(indent(visualizeInstructions(block)));
150
+ buffer = buffer.concat(
151
+ indent(
152
+ `\n${bbId}_instrs --> ${bbId}_terminal(["${visualizeTerminal(
153
+ block.terminal
154
+ )}"])`,
155
+ 2
156
+ )
157
+ );
158
+ } else {
159
+ buffer = buffer.concat(
160
+ indent(`${bbId}_terminal(["${visualizeTerminal(block.terminal)}"])`)
161
+ );
162
+ }
163
+ buffer = buffer.concat(indent("\nend\n"));
164
+ subgraphs.push(buffer);
165
+ }
166
+
167
+ for (const [blockId, block] of ir.blocks) {
168
+ const jump = visualizeJump(blockId, block.terminal);
169
+ if (jump.length > 0) {
170
+ jumps.push(indent(jump));
171
+ }
172
+ }
173
+
174
+ return `flowchart ${MermaidFlowchartDirection.TopBottom}
175
+ %% Basic Blocks
176
+${subgraphs.length ? subgraphs.join("\n") : " %% empty"}
177
+
178
+ %% Jumps
179
+${jumps.length ? jumps.join("\n") : " %% empty"}`;
180
+}