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 <sstream>
4
5#include "mim/def.h"
6#include "mim/driver.h"
7#include "mim/lam.h"
8#include "mim/nest.h"
9#include "mim/world.h"
10
11using namespace std::string_literals;
12
13namespace mim {
14
15namespace {
16
17template<class T>
18std::string escape(const T& val) {
19 std::ostringstream oss;
20 oss << val;
21 auto str = oss.str();
22 fe::find_and_replace(str, "<", "&lt;");
23 fe::find_and_replace(str, ">", "&gt;");
24 return str;
25}
26
27class Dot {
28public:
29 Dot(std::ostream& ostream, DotConfig cfg, const Def* root = nullptr)
30 : os_(ostream)
31 , cfg_(cfg)
32 , root_(root) {}
33
34 void prologue() {
35 std::println(os_, "{}digraph {{", tab_);
36 ++tab_;
37 std::println(os_, "{}ordering=out;", tab_);
38 std::println(os_, "{}splines=ortho;", tab_);
39 std::println(os_, "{}newrank=true;", tab_);
40 std::println(os_, "{}margin=0;", tab_);
41 // inline mode is meant for small graphs, so we tighten the spacing.
42 std::println(os_, "{}nodesep={};", tab_, cfg_.inline_consts ? "0.25" : "0.6");
43 std::println(os_, "{}ranksep={};", tab_, cfg_.inline_consts ? "0.4" : "1.2");
44 std::println(os_, "{}node [shape=box,style=filled,fontname=\"monospace\"];", tab_);
45 }
46
47 void epilogue() {
48 --tab_;
49 std::println(os_, "{}}}", tab_);
50 }
51
52 void run(const Def* root, int max) {
53 prologue();
54 recurse(root, max);
55 epilogue();
56 }
57
58 /// Emits a single node with id @p nid for @p def.
59 /// The same @p def may be emitted under several ids when inline_ duplicates shared leaves.
60 void emit_node(std::string_view nid, const Def* def) {
61 std::print(os_, "{}{}[", tab_, nid);
62
63 if (def->isa_mut())
64 if (def == root_)
65 os_ << "style=\"filled,diagonals,bold\",";
66 else
67 os_ << "style=\"filled,diagonals\",penwidth=2,";
68 else if (def == root_)
69 os_ << "style=\"filled,bold\",";
70
71 label(def) << ',';
72 color(def) << ',';
73 // Pin closed defs to the top row.
74 // In inline mode only mutables are pinned, so shared leaves flow next to their users instead of piling up in a
75 // detached row.
76 if (def->is_closed() && (!cfg_.inline_consts || def->isa_mut())) os_ << "rank=min,";
77 tooltip(def) << "];\n";
78 }
79
80 void recurse(const Def* def, int max) {
81 if (max == 0 || !done_.emplace(def).second) return;
82
83 emit_node(std::format("_{}", def->gid()), def);
84
85 if (def->is_set()) {
86 for (size_t i = 0, e = def->num_ops(); i != e; ++i) {
87 auto op = def->op(i);
88 // By default hide a Lam::filter() that still carries its kind's default: continuations default to ff,
89 // direct-style functions to tt.
90 if (!cfg_.default_filter && i == 0)
91 if (auto lam = def->isa<Lam>();
92 lam && lam->filter() == (Lam::isa_cn(lam) ? lam->world().lit_ff() : lam->world().lit_tt()))
93 continue;
94
95 // Literals and axioms are heavily shared, so by default we detach their edges (invisible and
96 // non-constraining) to keep the layout readable. With inline_ we instead duplicate such a leaf per use:
97 // each reference gets its own local node, which avoids a star of long shared edges - handy for small
98 // graphs. A Var points back at its binder, so its edge is always detached to avoid long back-edges.
99 if (cfg_.inline_consts && (op->isa<Lit>() || op->isa<Axm>())) {
100 auto dup = std::format("_{}_{}", def->gid(), i);
101 emit_node(dup, op);
102 std::println(os_, "{}_{}:{} -> {};", tab_, def->gid(), i, dup);
103 type_edge(dup, op, max - 1);
104 } else {
105 recurse(op, max - 1);
106 bool detach = def->isa<Var>()
107 || (!cfg_.inline_consts
108 && (op->isa<Lit>() || op->isa<Axm>() || def->isa<Nat>() || def->isa<Idx>()));
109 if (detach) {
110 // Detached edges are transparent by default to keep the layout readable (xdot still
111 // highlights them on hover). With show_hidden we render them statically in a subtle gray.
112 auto edge_color = cfg_.show_hidden ? "gray" : "#00000000";
113 std::println(os_, "{}_{}:{} -> _{}[color=\"{}\",constraint=false];", tab_, def->gid(), i,
114 op->gid(), edge_color);
115 } else
116 std::println(os_, "{}_{}:{} -> _{};", tab_, def->gid(), i, op->gid());
117 }
118 }
119 }
120
121 type_edge(std::format("_{}", def->gid()), def, max - 1);
122 }
123
124 /// Recurses into @p def's Def::type() and wires a type edge from node @p nid to it if DotConfig::follow_types.
125 /// Shared by the normal and the inline_consts duplication path so both honor follow_types uniformly.
126 void type_edge(std::string_view nid, const Def* def, int max) {
127 if (auto t = def->type(); t && cfg_.follow_types) {
128 recurse(t, max);
129 auto edge_color = cfg_.show_hidden ? "gray" : "#00000000";
130 std::println(os_, "{}{} -> _{}[color=\"{}\",constraint=false,style=dashed];", tab_, nid, t->gid(),
131 edge_color);
132 }
133 }
134
135 std::ostream& label(const Def* def) {
136 auto n = def->is_set() ? def->num_ops() : size_t(0);
137 if (n > 0) {
138 std::print(os_, "label=<<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td colspan=\"{}\">",
139 n);
140 emit_name(def);
141 os_ << "</td></tr><tr>";
142 for (size_t i = 0; i < n; ++i)
143 std::print(os_, "<td port=\"{}\" cellpadding=\"0\" height=\"1\" width=\"8\"></td>", i);
144 os_ << "</tr></table>>";
145 } else {
146 os_ << "label=<";
147 emit_name(def);
148 os_ << ">";
149 }
150 return os_;
151 }
152
153 void emit_name(const Def* def) {
154 if (auto lit = def->isa<Lit>())
155 os_ << lit;
156 else
157 os_ << def->node_name();
158 std::print(os_, "<br/><font point-size=\"9\">{}</font>", escape(def->unique_name()));
159 }
160
161 std::ostream& color(const Def* def) {
162 float hue;
163 // clang-format off
164 if (def->is_form()) hue = 0.60f; // blue - type formation
165 else if (def->is_intro()) hue = 0.35f; // green - introduction
166 else if (def->is_elim()) hue = 0.00f; // red - elimination
167 else if (def->is_meta()) hue = 0.15f; // yellow - universe/meta
168 else hue = 0.80f; // purple - Hole
169 // clang-format on
170 return os_ << std::format("fillcolor=\"{} 0.5 0.75\"", hue);
171 }
172
173 std::ostream& tooltip(const Def* def) {
174 static constexpr auto NL = "&#13;&#10;"; // newline
175
176 auto loc = escape(def->loc());
177 auto type = escape(def->type());
178 std::print(os_, "tooltip=\"");
179 std::print(os_, "<b>expr:</b> {}{}", def, NL);
180 std::print(os_, "<b>type:</b> {}{}", type, NL);
181 std::print(os_, "<b>name:</b> {}{}", def->sym(), NL);
182 std::print(os_, "<b>gid:</b> {}{}", def->gid(), NL);
183 std::print(os_, "<b>flags:</b> 0x{:x}{}", def->flags(), NL);
184 std::print(os_, "<b>mark:</b> 0x{:x}{}", def->mark(), NL);
185 std::print(os_, "<b>local_muts:</b> {}{}", fe::Join(def->local_muts()), NL);
186 std::print(os_, "<b>local_vars:</b> {}{}", fe::Join(def->local_vars()), NL);
187 std::print(os_, "<b>free_vars:</b> {}{}", fe::Join(def->free_vars()), NL);
188 if (auto mut = def->isa_mut()) std::print(os_, "<b>users:</b> {{{}}}{}", fe::Join(mut->users()), NL);
189 std::print(os_, "<b>loc:</b> {}", loc);
190 return os_ << std::format("\"");
191 }
192
193private:
194 std::ostream& os_;
195 DotConfig cfg_;
196 const Def* root_;
197 fe::Tab tab_ = fe::Tab::spaces();
198 DefSet done_;
199};
200
201} // namespace
202
203void Def::dot(std::ostream& ostream, DotConfig cfg) const { Dot(ostream, cfg, this).run(this, cfg.max); }
204
205void Def::dot(const char* file, DotConfig cfg) const {
206 if (!file) {
207 dot(std::cout, cfg);
208 } else {
209 auto of = std::ofstream(file);
210 dot(of, cfg);
211 }
212}
213
214void World::dot(const char* file, DotConfig cfg) const {
215 if (!file) {
216 dot(std::cout, cfg);
217 } else {
218 auto of = std::ofstream(file);
219 dot(of, cfg);
220 }
221}
222
223void World::dot(std::ostream& os, DotConfig cfg) const {
224 Dot dot(os, cfg);
225 dot.prologue();
226 for (auto external : externals().muts())
227 dot.recurse(external, cfg.max);
228 if (cfg.all_annexes)
229 for (auto annex : annexes().defs())
230 dot.recurse(annex, cfg.max);
231 dot.epilogue();
232}
233
234/*
235 * Nest
236 */
237
238void Nest::dot(const char* file) const {
239 if (!file) {
240 dot(std::cout);
241 } else {
242 auto of = std::ofstream(file);
243 dot(of);
244 }
245}
246
247void Nest::dot(std::ostream& os) const {
248 auto tab = fe::Tab::spaces();
249 std::println(os, "{}digraph {{", tab);
250 ++tab;
251 std::println(os, "{}ordering=out;", tab);
252 std::println(os, "{}node [shape=box,style=filled];", tab);
253 root()->dot(tab, os);
254 --tab;
255 std::println(os, "{}}}", tab);
256}
257
258void Nest::Node::dot(fe::Tab tab, std::ostream& os) const {
259 std::string s;
260 for (const auto& scc : topo_) {
261 s += '[';
262 for (auto sep = ""s; auto n : *scc) {
263 s += sep + n->name();
264 sep = ", ";
265 }
266 s += "] ";
267 }
268
269 for (auto sibl : sibl_deps())
270 std::println(os, "{}\"{}\":s -> \"{}\":s [style=dashed,constraint=false,splines=true]", tab, name(),
271 sibl->name());
272
273 auto rec = is_mutually_recursive() ? "rec*" : (is_directly_recursive() ? "rec" : "");
274 auto html = "<b>" + name() + "</b>";
275 if (*rec) html += "<br/><i>"s + rec + "</i>";
276 html += "<br/><font point-size=\"8\">depth " + std::to_string(loop_depth()) + "</font>";
277 std::println(os, "{}\"{}\" [label=<{}>,tooltip=\"{}\"]", tab, name(), html, s);
278 for (auto child : children().nodes()) {
279 std::println(os, "{}\"{}\" -> \"{}\" [splines=false]", tab, name(), child->name());
280 child->dot(tab, os);
281 }
282
283 // Overlay domination between siblings and their parent
284 if (idom())
285 std::println(os, "{}\"{}\" -> \"{}\" [color=red,style=bold,constraint=false]", tab, idom()->name(), name());
286}
287
288} // namespace mim
World & world() const noexcept
Definition def.h:1097
void dot(std::ostream &os, DotConfig cfg={}) const
Definition dot.cpp:203
static const Lam * isa_cn(const Def *d)
Definition lam.h:144
std::string name() const
Definition nest.h:37
auto & sibl_deps()
Definition nest.h:135
const Children & children() const
Definition nest.h:95
auto idom() const
Immediate Dominator for children in connected components.
Definition nest.h:43
bool is_directly_recursive() const
Definition nest.h:160
bool is_mutually_recursive() const
Definition nest.h:159
uint32_t loop_depth() const
Definition nest.h:51
void dot(std::ostream &os) const
Definition dot.cpp:247
auto nodes() const
Definition nest.h:235
const Node * root() const
Definition nest.h:224
auto & muts()
Definition world.h:695
const Lit * lit_tt()
Definition world.h:584
void dot(std::ostream &os, DotConfig cfg={}) const
Dumps DOT to os, configured via cfg (see DotConfig).
Definition dot.cpp:223
const Def * annex(Sym sym)
Lookup annex by Sym.
Definition world.h:294
Annexes & annexes()
Definition world.h:281
const Lit * lit_ff()
Definition world.h:583
const Externals & externals() const
Definition world.h:278
Definition ast.h:16
int max
Maximum recursion depth.
Definition def.h:230
bool all_annexes
Include all annexes - even if unused (World::dot only).
Definition def.h:231
GIDSet< const Def * > DefSet
Definition def.h:89
@ Var
Definition def.h:122
Options for Def::dot and World::dot.
Definition def.h:229