MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
dot.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <ostream>
3#include <print>
4
5#include "mim/def.h"
6#include "mim/lam.h"
7#include "mim/nest.h"
8#include "mim/world.h"
9
10using namespace std::string_literals;
11
12namespace mim {
13
14namespace {
15
16/// Escapes @p s for a Graphviz HTML-like label (`label=<...>`).
17std::string html(std::string_view s) {
18 std::string res;
19 for (auto c : s) {
20 switch (c) {
21 case '&': res += "&amp;"; break;
22 case '<': res += "&lt;"; break;
23 case '>': res += "&gt;"; break;
24 default: res += c;
25 }
26 }
27 return res;
28}
29
30/// Escapes @p s for a double-quoted Graphviz string such as `tooltip`.
31/// Graphviz turns `\n` back into a newline, so a tooltip may span several lines.
32std::string quote(std::string_view s) {
33 std::string res;
34 for (auto c : s) {
35 switch (c) {
36 case '\\': res += "\\\\"; break;
37 case '"': res += "\\\""; break;
38 case '\n': res += "\\n"; break;
39 default: res += c;
40 }
41 }
42 return res;
43}
44
45// Edges and arrowheads are black by default, which vanishes on a dark backdrop.
46constexpr auto DARK_FG = "#c9d1d9";
47
48class Dot {
49public:
50 Dot(std::ostream& ostream, DotConfig cfg, const Def* root = nullptr)
51 : os_(ostream)
52 , cfg_(cfg)
53 , root_(root) {}
54
55 void prologue() {
56 std::println(os_, "{}digraph {{", tab_);
57 ++tab_;
58 // The embedder paints the backdrop; only what is drawn on it has to change.
59 if (cfg_.dark) {
60 std::println(os_, "{}bgcolor=\"transparent\";", tab_);
61 std::println(os_, "{}edge [color=\"{}\"];", tab_, DARK_FG);
62 }
63 std::println(os_, "{}ordering=out;", tab_);
64 std::println(os_, "{}splines=ortho;", tab_);
65 std::println(os_, "{}newrank=true;", tab_);
66 std::println(os_, "{}margin=0;", tab_);
67 // inline mode is meant for small graphs, so we tighten the spacing.
68 std::println(os_, "{}nodesep={};", tab_, cfg_.inline_consts ? "0.25" : "0.6");
69 std::println(os_, "{}ranksep={};", tab_, cfg_.inline_consts ? "0.4" : "1.2");
70 std::println(os_, "{}node [shape=box,style=filled,fontname=\"monospace\"];", tab_);
71 }
72
73 void epilogue() {
74 --tab_;
75 std::println(os_, "{}}}", tab_);
76 }
77
78 void run(const Def* root, int max) {
79 prologue();
80 recurse(root, max);
81 epilogue();
82 }
83
84 /// Emits a single node with id @p nid for @p def.
85 /// The same @p def may be emitted under several ids when inline_ duplicates shared leaves.
86 void emit_node(std::string_view nid, const Def* def) {
87 std::print(os_, "{}{}[", tab_, nid);
88
89 if (def->isa_mut())
90 if (def == root_)
91 std::print(os_, "style=\"filled,diagonals,bold\",");
92 else
93 std::print(os_, "style=\"filled,diagonals\",penwidth=2,");
94 else if (def == root_)
95 std::print(os_, "style=\"filled,bold\",");
96
97 label(def);
98 color(def);
99 // Pin closed defs to the top row.
100 // In inline mode only mutables are pinned, so shared leaves flow next to their users instead of piling up in a
101 // detached row.
102 if (def->is_closed() && (!cfg_.inline_consts || def->isa_mut())) std::print(os_, "rank=min,");
103 tooltip(def);
104 std::println(os_, "];");
105 }
106
107 void recurse(const Def* def, int max) {
108 if (max == 0 || !done_.emplace(def).second) return;
109
110 emit_node(std::format("_{}", def->gid()), def);
111
112 if (def->is_set()) {
113 for (size_t i = 0, e = def->num_ops(); i != e; ++i) {
114 auto op = def->op(i);
115 // By default hide a Lam::filter() that still carries its kind's default: continuations default to ff,
116 // direct-style functions to tt.
117 if (!cfg_.default_filter && i == 0)
118 if (auto lam = def->isa<Lam>();
119 lam && lam->filter() == (Lam::isa_cn(lam) ? lam->world().lit_ff() : lam->world().lit_tt()))
120 continue;
121
122 // Literals and axioms are heavily shared, so by default we detach their edges (invisible and
123 // non-constraining) to keep the layout readable. With inline_ we instead duplicate such a leaf per use:
124 // each reference gets its own local node, which avoids a star of long shared edges - handy for small
125 // graphs.
126 if (cfg_.inline_consts && (op->isa<Lit>() || op->isa<Axm>())) {
127 auto dup = std::format("_{}_{}", def->gid(), i);
128 emit_node(dup, op);
129 std::println(os_, "{}{} -> {};", tab_, tail(def, i), dup);
130 type_edge(dup, op, max - 1);
131 } else {
132 recurse(op, max - 1);
133 bool detach
134 = !cfg_.inline_consts && (op->isa<Lit>() || op->isa<Axm>() || op->isa<Nat>() || op->isa<Idx>());
135 if (detach) {
136 // Detached edges are transparent by default to keep the layout readable (xdot still
137 // highlights them on hover). With show_hidden we render them statically in a subtle gray.
138 auto edge_color = cfg_.show_hidden ? "gray" : "#00000000";
139 std::println(os_, "{}{} -> _{}[color=\"{}\",constraint=false];", tab_, tail(def, i), op->gid(),
140 edge_color);
141 } else
142 std::println(os_, "{}{} -> _{};", tab_, tail(def, i), op->gid());
143 }
144 }
145 }
146
147 if (auto var = def->isa<Var>()) {
148 recurse(var->binder(), max - 1);
149 std::println(os_, "{}_{} -> _{}[color=\"{}\",constraint=false];", tab_, def->gid(), var->binder()->gid(),
150 cfg_.show_hidden ? "gray" : "#00000000");
151 }
152
153 type_edge(std::format("_{}", def->gid()), def, max - 1);
154 }
155
156 /// Recurses into @p def's Def::type() and wires a type edge from node @p nid to it if DotConfig::follow_types.
157 /// Shared by the normal and the inline_consts duplication path so both honor follow_types uniformly.
158 void type_edge(std::string_view nid, const Def* def, int max) {
159 if (auto t = def->type(); t && cfg_.follow_types) {
160 recurse(t, max);
161 auto edge_color = cfg_.show_hidden ? "gray" : "#00000000";
162 std::println(os_, "{}{} -> _{}[color=\"{}\",constraint=false,style=dashed];", tab_, nid, t->gid(),
163 edge_color);
164 }
165 }
166
167 /// One port per op, so an edge docks under the very op it stands for.
168 void label(const Def* def) {
169 if (cfg_.lean_labels) {
170 std::print(os_, "label=\"{}\",", quote(std::format("{} {}", caption(def), def->unique_name())));
171 return;
172 }
173
174 auto n = def->is_set() ? def->num_ops() : size_t(0);
175 if (n > 0) {
176 std::print(os_, "label=<<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td colspan=\"{}\">",
177 n);
178 emit_name(def);
179 std::print(os_, "</td></tr><tr>");
180 for (size_t i = 0; i < n; ++i)
181 std::print(os_, "<td port=\"{}\" height=\"1\" width=\"8\"></td>", i);
182 std::print(os_, "</tr></table>>,");
183 } else {
184 std::print(os_, "label=<");
185 emit_name(def);
186 std::print(os_, ">,");
187 }
188 }
189
190 /// A Lit shows its value where every other Def shows its node name.
191 std::string caption(const Def* def) {
192 auto lit = def->isa<Lit>();
193 return lit ? std::format("{}", lit) : std::string(def->node_name());
194 }
195
196 void emit_name(const Def* def) {
197 std::print(os_, "{}<br/><font point-size=\"9\">{}</font>", html(caption(def)), html(def->unique_name()));
198 }
199
200 /// Without the port cells of a full label there is nothing to dock at but the node itself.
201 std::string tail(const Def* def, size_t i) {
202 return cfg_.lean_labels ? std::format("_{}", def->gid()) : std::format("_{}:{}", def->gid(), i);
203 }
204
205 void color(const Def* def) {
206 float hue;
207 // clang-format off
208 if (def->is_form()) hue = 0.60f; // blue - type formation
209 else if (def->is_intro()) hue = 0.35f; // green - introduction
210 else if (def->is_elim()) hue = 0.00f; // red - elimination
211 else if (def->is_meta()) hue = 0.15f; // yellow - universe/meta
212 else hue = 0.80f; // purple - Hole
213 // clang-format on
214 std::print(os_, "fillcolor=\"{} 0.5 0.75\",", hue);
215 }
216
217 /// A tooltip is plain text - markup would show up verbatim in xdot and in the browser alike.
218 /// Streaming a Def is Def::Dump::Expr - one line, no analysis.
219 /// Keep it that way: this output is the fallback for when a Nest-based dump dies on a broken World.
220 void tooltip(const Def* def) {
221 if (cfg_.no_tooltip) return;
222
223 std::string s;
224 auto add = [&, sep = ""](std::string_view key, const auto& val) mutable {
225 s += std::format("{}{}: {}", sep, key, val);
226 sep = "\n";
227 };
228
229 add("expr", def);
230 add("type", def->type());
231 add("name", def->sym());
232 add("gid", def->gid());
233 add("flags", std::format("0x{:x}", def->flags()));
234 add("mark", std::format("0x{:x}", def->mark()));
235 add("local_muts", fe::Join(def->local_muts()));
236 add("local_vars", fe::Join(def->local_vars()));
237 add("free_vars", fe::Join(def->free_vars()));
238 if (auto mut = def->isa_mut()) add("users", std::format("{{{}}}", fe::Join(mut->users())));
239 add("loc", def->loc());
240
241 std::print(os_, "tooltip=\"{}\",", quote(s));
242 }
243
244private:
245 std::ostream& os_;
246 DotConfig cfg_;
247 const Def* root_;
248 fe::Tab tab_ = fe::Tab::spaces();
249 DefSet done_;
250};
251
252template<class T>
253void dot_to(const T& t, const char* file, DotConfig cfg) {
254 if (!file) return t.dot(std::cout, cfg);
255 auto ofs = std::ofstream(file);
256 t.dot(ofs, cfg);
257}
258
259} // namespace
260
261void Def::dot(std::ostream& ostream, DotConfig cfg) const { Dot(ostream, cfg, this).run(this, cfg.max); }
262
263void Def::dot(const char* file, DotConfig cfg) const { dot_to(*this, file, cfg); }
264
265void World::dot(const char* file, DotConfig cfg) const { dot_to(*this, file, cfg); }
266
267void World::dot(std::ostream& os, DotConfig cfg) const {
268 Dot dot(os, cfg);
269 dot.prologue();
270 for (auto external : externals().muts())
271 dot.recurse(external, cfg.max);
272 dot.epilogue();
273}
274
275/*
276 * Nest
277 */
278
279void Nest::dot(const char* file, DotConfig cfg) const { dot_to(*this, file, cfg); }
280
281void Nest::dot(std::ostream& os, DotConfig cfg) const {
282 auto tab = fe::Tab::spaces();
283 std::println(os, "{}digraph {{", tab);
284 ++tab;
285 if (cfg.dark) {
286 std::println(os, "{}bgcolor=\"transparent\";", tab);
287 std::println(os, "{}edge [color=\"{}\"];", tab, DARK_FG);
288 }
289 std::println(os, "{}ordering=out;", tab);
290 std::println(os, "{}node [shape=box,style=filled];", tab);
291 root()->dot(tab, os);
292 --tab;
293 std::println(os, "{}}}", tab);
294}
295
296void Nest::Node::dot(fe::Tab tab, std::ostream& os) const {
297 std::string s;
298 for (const auto& scc : topo_) {
299 s += '[';
300 for (auto sep = ""s; auto n : *scc) {
301 s += sep + n->name();
302 sep = ", ";
303 }
304 s += "] ";
305 }
306
307 for (auto sibl : sibl_deps())
308 std::println(os, "{}\"{}\":s -> \"{}\":s [style=dashed,constraint=false,splines=true]", tab, name(),
309 sibl->name());
310
311 auto rec = is_mutually_recursive() ? "rec*" : (is_directly_recursive() ? "rec" : "");
312 auto label = std::format("<b>{}</b>", html(name()));
313 if (*rec) label += std::format("<br/><i>{}</i>", rec);
314 label += std::format("<br/><font point-size=\"8\">depth {}</font>", loop_depth());
315 std::println(os, "{}\"{}\" [label=<{}>,tooltip=\"{}\"]", tab, name(), label, quote(s));
316 for (auto child : children().nodes()) {
317 std::println(os, "{}\"{}\" -> \"{}\" [splines=false]", tab, name(), child->name());
318 child->dot(tab, os);
319 }
320
321 // Overlay domination between siblings and their parent
322 if (idom())
323 std::println(os, "{}\"{}\" -> \"{}\" [color=red,style=bold,constraint=false]", tab, idom()->name(), name());
324}
325
326} // namespace mim
World & world() const noexcept
Definition def.h:1105
void dot(std::ostream &os, DotConfig cfg={}) const
Definition dot.cpp:261
static const Lam * isa_cn(const Def *d)
Definition lam.h:125
std::string name() const
Definition nest.h:31
const Node * idom() const
Immediate Dominator for children in connected components; used for late code placement in [Nest::lca]...
Definition nest.h:36
const SiblDeps & sibl_deps() const
Definition nest.h:116
const Children & children() const
Definition nest.h:82
const SCC & scc() const
The SCC among its siblings this Node belongs to; the Nest::root() has none.
Definition nest.h:135
bool is_directly_recursive() const
Definition nest.h:133
bool is_mutually_recursive() const
Definition nest.h:132
uint32_t loop_depth() const
Definition nest.h:44
void dot(std::ostream &os, DotConfig cfg={}) const
Definition dot.cpp:281
auto nodes() const
Definition nest.h:205
const Node * root() const
Definition nest.h:195
auto & muts()
Definition world.h:715
const Lit * lit_tt()
Definition world.h:597
void dot(std::ostream &os, DotConfig cfg={}) const
Dumps DOT to os, configured via cfg (see DotConfig).
Definition dot.cpp:267
const Lit * lit_ff()
Definition world.h:596
const Externals & externals() const
Definition world.h:286
Definition ast.h:16
int max
Maximum recursion depth.
Definition def.h:242
bool dark
Drop the white background and lighten the edges for a dark backdrop.
Definition def.h:248
GIDSet< const Def * > DefSet
Definition def.h:94
@ Nat
Definition def.h:125
@ Idx
Definition def.h:125
@ Axm
Definition def.h:125
@ Lit
Definition def.h:125
Options for Def::dot, World::dot, and Nest::dot.
Definition def.h:241