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