• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / sdfglib / 16475549974

23 Jul 2025 03:52PM UTC coverage: 65.677% (+0.7%) from 64.967%
16475549974

Pull #157

github

web-flow
Merge a4e68743e into eb8a57231
Pull Request #157: Gemm/lib node

208 of 260 new or added lines in 3 files covered. (80.0%)

39 existing lines in 2 files now uncovered.

8607 of 13105 relevant lines covered (65.68%)

130.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.57
/src/visualizer/dot_visualizer.cpp
1
#include "sdfg/visualizer/dot_visualizer.h"
2

3
#include <cstddef>
4
#include <string>
5
#include <utility>
6

7
#include <regex>
8
#include "sdfg/data_flow/access_node.h"
9
#include "sdfg/data_flow/memlet.h"
10
#include "sdfg/structured_control_flow/control_flow_node.h"
11
#include "sdfg/structured_control_flow/sequence.h"
12
#include "sdfg/structured_sdfg.h"
13

14
namespace sdfg {
15
namespace visualizer {
16

17
static std::regex dotIdBadChars("[^a-zA-Z0-9_]+");
2✔
18

19
static std::string escapeDotId(size_t id, const std::string& prefix = "") { return prefix + std::to_string(id); }
823✔
20

21
static std::string escapeDotId(const std::string& id, const std::string& prefix = "") {
350✔
22
    return prefix + std::regex_replace(id, dotIdBadChars, "_");
350✔
23
}
×
24

25
void DotVisualizer::visualizeBlock(const StructuredSDFG& sdfg, const structured_control_flow::Block& block) {
185✔
26
    auto id = escapeDotId(block.element_id(), "block_");
185✔
27
    this->stream_ << "subgraph cluster_" << id << " {" << std::endl;
185✔
28
    this->stream_.setIndent(this->stream_.indent() + 4);
185✔
29
    this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\"\";" << std::endl;
185✔
30
    this->last_comp_name_cluster_ = "cluster_" + id;
185✔
31
    if (block.dataflow().nodes().empty()) {
185✔
32
        this->stream_ << id << " [shape=point,style=invis,label=\"\"];" << std::endl;
2✔
33
        this->stream_.setIndent(this->stream_.indent() - 4);
2✔
34
        this->stream_ << "}" << std::endl;
2✔
35
        this->last_comp_name_ = id;
2✔
36
        return;
2✔
37
    }
38
    this->last_comp_name_.clear();
183✔
39
    std::list<const data_flow::DataFlowNode*> nodes = block.dataflow().topological_sort();
183✔
40
    for (const data_flow::DataFlowNode* node : nodes) {
581✔
41
        std::vector<std::string> in_connectors;
398✔
42
        bool is_access_node = false;
398✔
43
        bool node_will_show_literal_connectors = false;
398✔
44
        auto nodeId = escapeDotId(node->element_id(), "n_");
398✔
45
        if (this->last_comp_name_.empty()) this->last_comp_name_ = nodeId;
398✔
46
        if (const data_flow::Tasklet* tasklet = dynamic_cast<const data_flow::Tasklet*>(node)) {
398✔
47
            this->stream_ << nodeId << " [shape=octagon,label=\"" << tasklet->output().first << " = ";
186✔
48
            this->visualizeTasklet(*tasklet);
186✔
49
            this->stream_ << "\"];" << std::endl;
186✔
50

51
            std::ranges::
186✔
52
                transform(tasklet->inputs(), std::inserter(in_connectors, in_connectors.end()), [](const auto& pair) {
421✔
53
                    return pair.first;
235✔
54
                });
55
            node_will_show_literal_connectors = true;
186✔
56
        } else if (const data_flow::AccessNode* access_node = dynamic_cast<const data_flow::AccessNode*>(node)) {
398✔
57
            this->stream_ << nodeId << " [";
211✔
58
            if (!sdfg.is_internal(access_node->data())) this->stream_ << "penwidth=3.0,";
211✔
59
            if (sdfg.is_transient(access_node->data())) this->stream_ << "style=\"dashed,filled\",";
211✔
60
            this->stream_ << "label=\"" << access_node->data() << "\"];" << std::endl;
211✔
61
            is_access_node = true;
211✔
62
        } else if (const data_flow::LibraryNode* libnode = dynamic_cast<const data_flow::LibraryNode*>(node)) {
212✔
63
            this->stream_ << nodeId << " [shape=doubleoctagon,label=\"" << libnode->toStr() << "\"];" << std::endl;
1✔
64
            std::ranges::
1✔
65
                transform(libnode->inputs(), std::inserter(in_connectors, in_connectors.end()), [](const auto& elem) {
6✔
66
                    return elem;
5✔
67
                });
68
        }
1✔
69

70
        std::unordered_set<std::string> unused_connectors(in_connectors.begin(), in_connectors.end());
398✔
71
        for (const data_flow::Memlet& iedge : block.dataflow().in_edges(*node)) {
613✔
72
            auto& src = iedge.src();
215✔
73
            auto& dst_conn = iedge.dst_conn();
215✔
74
            bool nonexistent_conn = false;
215✔
75

76
            if (!is_access_node) {
215✔
77
                auto it = unused_connectors.find(dst_conn);
28✔
78
                if (it != unused_connectors.end()) {
28✔
79
                    unused_connectors.erase(it); // remove connector from in_connectors, so it is not used again
28✔
80
                } else {
28✔
NEW
81
                    nonexistent_conn = true;
×
82
                }
83
            }
28✔
84

85
            this->stream_ << escapeDotId(src.element_id(), "n_") << " -> " << nodeId << " [label=\"   ";
215✔
86
            bool dstIsVoid = dst_conn == "void";
215✔
87
            bool dstIsRef = dst_conn == "ref";
215✔
88
            bool dstIsDeref = dst_conn == "deref";
215✔
89
            auto& src_conn = iedge.src_conn();
215✔
90
            bool srcIsVoid = src_conn == "void";
215✔
91
            bool srcIsDeref = src_conn == "deref";
215✔
92

93
            if (nonexistent_conn) {
215✔
NEW
94
                this->stream_ << "!!"; // this should not happen, but if it does, we can still visualize the memlet
×
NEW
95
            }
×
96

97
            if (dstIsVoid || dstIsRef || dstIsDeref) { // subset applies to dst
215✔
98
                auto& dstVar = dynamic_cast<data_flow::AccessNode const&>(iedge.dst()).data();
187✔
99
                bool subsetOnDst = false;
187✔
100
                if (srcIsDeref && dstIsVoid) { // Pure Store by Memlet definition (Dereference Memlet Store)
187✔
NEW
101
                    auto& subset_begin = iedge.begin_subset();
×
NEW
102
                    auto& subset_end = iedge.end_subset();
×
NEW
103
                    if (subset_begin.size() == subset_end.size() == 1 && subset_begin[0] == subset_end[0] == 0) {
×
NEW
104
                        this->stream_ << "*" << dstVar; // store to pointer without further address calc
×
NEW
105
                    } else { // fallback, this should not be allowed to happen
×
NEW
106
                        this->stream_ << dstVar; // use access node name instead of connector-name
×
NEW
107
                        subsetOnDst = true;
×
108
                    }
109
                } else if (dstIsVoid) { // computational memlet / output from tasklet / memory store
187✔
110
                    this->stream_ << dstVar; // use access node name instead of connector-name
187✔
111
                    subsetOnDst = true;
187✔
112
                } else {
187✔
NEW
113
                    this->stream_ << dstVar; // use access node name instead of connector-name
×
114
                }
115
                if (subsetOnDst) {
187✔
116
                    types::IType const* dstTypePtr = sdfg.exists(dstVar) ? &sdfg.type(dstVar) : nullptr;
187✔
117
                    this->visualizeSubset(sdfg, iedge.begin_subset(), iedge.end_subset(), dstTypePtr);
187✔
118
                }
187✔
119
            } else { // dst is a tasklet/library node
187✔
120
                this->stream_ << dst_conn;
28✔
121
            }
122

123
            this->stream_ << " = ";
215✔
124

125
            if (srcIsVoid || srcIsDeref) { // subset applies to src, could be computational, reference or dereference
215✔
126
                                           // memlet
127
                auto& srcVar = dynamic_cast<data_flow::AccessNode const&>(src).data();
28✔
128
                bool subsetOnSrc = false;
28✔
129
                if (srcIsVoid && dstIsRef) { // reference memlet / address-of / get-element-ptr equivalent
28✔
130
                    this->stream_ << "&";
×
NEW
131
                    subsetOnSrc = true;
×
132
                } else if (srcIsVoid && dstIsDeref) { // Dereference memlet / load from address
28✔
NEW
133
                    this->stream_ << "*";
×
NEW
134
                    auto& subset_begin = iedge.begin_subset();
×
NEW
135
                    auto& subset_end = iedge.end_subset();
×
NEW
136
                    if (subset_begin.size() == subset_end.size() != 1 ||
×
NEW
137
                        subset_begin[0] == subset_end[0] != 0) { // does not match memlet definition -> fallback
×
NEW
138
                        subsetOnSrc = true;
×
NEW
139
                    }
×
140
                } else if (srcIsVoid) {
28✔
141
                    subsetOnSrc = true;
28✔
142
                }
28✔
143
                this->stream_ << srcVar;
28✔
144
                if (subsetOnSrc) {
28✔
145
                    types::IType const* srcTypePtr = sdfg.exists(srcVar) ? &sdfg.type(srcVar) : nullptr;
28✔
146
                    this->visualizeSubset(sdfg, iedge.begin_subset(), iedge.end_subset(), srcTypePtr);
28✔
147
                }
28✔
148
            } else {
28✔
149
                this->stream_ << src_conn;
187✔
150
            }
151
            this->stream_ << "   \"];" << std::endl;
215✔
152
        }
153

154
        if (!node_will_show_literal_connectors) {
398✔
155
            for (uint64_t i = 0; i < in_connectors.size(); ++i) {
217✔
156
                auto& in_conn = in_connectors[i];
5✔
157
                auto it = unused_connectors.find(in_conn);
5✔
158
                if (it != unused_connectors.end()) {
5✔
159
                    auto literal_id = escapeDotId(node->element_id(), "n_") + "_" + escapeDotId(i, "in");
2✔
160
                    this->stream_ << literal_id << " [style=\"dotted\", label=\"" << in_conn << "\"];" << std::endl;
2✔
161
                    this->stream_ << literal_id << " -> " << nodeId << " [label=\"" << i << "\"]" << ";" << std::endl;
2✔
162
                }
2✔
163
            }
5✔
164
        }
212✔
165
    }
398✔
166
    this->stream_.setIndent(this->stream_.indent() - 4);
183✔
167
    this->stream_ << "}" << std::endl;
183✔
168
}
185✔
169

170
void DotVisualizer::visualizeSequence(const StructuredSDFG& sdfg, const structured_control_flow::Sequence& sequence) {
197✔
171
    std::string last_comp_name_tmp, last_comp_name_cluster_tmp;
197✔
172
    for (size_t i = 0; i < sequence.size(); ++i) {
404✔
173
        std::pair<const structured_control_flow::ControlFlowNode&, const structured_control_flow::Transition&> child =
174
            sequence.at(i);
207✔
175
        this->visualizeNode(sdfg, child.first);
207✔
176
        if ((i > 0) && !last_comp_name_tmp.empty() && !this->last_comp_name_.empty()) {
207✔
177
            this->stream_ << last_comp_name_tmp << " -> " << this->last_comp_name_ << " [";
10✔
178
            if (!last_comp_name_cluster_tmp.empty()) this->stream_ << "ltail=\"" << last_comp_name_cluster_tmp << "\",";
10✔
179
            if (!this->last_comp_name_cluster_.empty())
10✔
180
                this->stream_ << "lhead=\"" << this->last_comp_name_cluster_ << "\",";
8✔
181
            this->stream_ << "minlen=3]"
10✔
182
                          << ";" << std::endl;
10✔
183
        }
10✔
184
        last_comp_name_tmp = this->last_comp_name_;
207✔
185
        this->last_comp_name_.clear();
207✔
186
        last_comp_name_cluster_tmp = this->last_comp_name_cluster_;
207✔
187
        this->last_comp_name_cluster_.clear();
207✔
188
    }
207✔
189
}
197✔
190

191
void DotVisualizer::visualizeIfElse(const StructuredSDFG& sdfg, const structured_control_flow::IfElse& if_else) {
3✔
192
    auto id = escapeDotId(if_else.element_id(), "if_");
3✔
193
    this->stream_ << "subgraph cluster_" << id << " {" << std::endl;
3✔
194
    this->stream_.setIndent(this->stream_.indent() + 4);
3✔
195
    this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\"if:\";" << std::endl
3✔
196
                  << id << " [shape=point,style=invis,label=\"\"];" << std::endl;
3✔
197
    for (size_t i = 0; i < if_else.size(); ++i) {
9✔
198
        this->stream_ << "subgraph cluster_" << id << "_" << std::to_string(i) << " {" << std::endl;
6✔
199
        this->stream_.setIndent(this->stream_.indent() + 4);
6✔
200
        this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\""
12✔
201
                      << this->expression(if_else.at(i).second->__str__()) << "\";" << std::endl;
6✔
202
        this->visualizeSequence(sdfg, if_else.at(i).first);
6✔
203
        this->stream_.setIndent(this->stream_.indent() - 4);
6✔
204
        this->stream_ << "}" << std::endl;
6✔
205
    }
6✔
206
    this->stream_.setIndent(this->stream_.indent() - 4);
3✔
207
    this->stream_ << "}" << std::endl;
3✔
208
    this->last_comp_name_ = id;
3✔
209
    this->last_comp_name_cluster_ = "cluster_" + id;
3✔
210
}
3✔
211

212
void DotVisualizer::visualizeWhile(const StructuredSDFG& sdfg, const structured_control_flow::While& while_loop) {
2✔
213
    auto id = escapeDotId(while_loop.element_id(), "while_");
2✔
214
    this->stream_ << "subgraph cluster_" << id << " {" << std::endl;
2✔
215
    this->stream_.setIndent(this->stream_.indent() + 4);
2✔
216
    this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\"while:\";" << std::endl
2✔
217
                  << id << " [shape=point,style=invis,label=\"\"];" << std::endl;
2✔
218
    this->visualizeSequence(sdfg, while_loop.root());
2✔
219
    this->stream_.setIndent(this->stream_.indent() - 4);
2✔
220
    this->stream_ << "}" << std::endl;
2✔
221
    this->last_comp_name_ = id;
2✔
222
    this->last_comp_name_cluster_ = "cluster_" + id;
2✔
223
}
2✔
224

225
void DotVisualizer::visualizeFor(const StructuredSDFG& sdfg, const structured_control_flow::For& loop) {
10✔
226
    auto id = escapeDotId(loop.element_id(), "for_");
10✔
227
    this->stream_ << "subgraph cluster_" << id << " {" << std::endl;
10✔
228
    this->stream_.setIndent(this->stream_.indent() + 4);
10✔
229
    this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\"for: ";
10✔
230
    this->visualizeForBounds(loop.indvar(), loop.init(), loop.condition(), loop.update());
10✔
231
    this->stream_ << "\";" << std::endl << id << " [shape=point,style=invis,label=\"\"];" << std::endl;
10✔
232
    this->visualizeSequence(sdfg, loop.root());
10✔
233
    this->stream_.setIndent(this->stream_.indent() - 4);
10✔
234
    this->stream_ << "}" << std::endl;
10✔
235
    this->last_comp_name_ = id;
10✔
236
    this->last_comp_name_cluster_ = "cluster_" + id;
10✔
237
}
10✔
238

239
void DotVisualizer::visualizeReturn(const StructuredSDFG& sdfg, const structured_control_flow::Return& return_node) {
1✔
240
    auto id = escapeDotId(return_node.element_id(), "return_");
1✔
241
    this->stream_ << id << " [shape=cds,label=\" return  \"];" << std::endl;
1✔
242
    this->last_comp_name_ = id;
1✔
243
    this->last_comp_name_cluster_.clear();
1✔
244
}
1✔
245
void DotVisualizer::visualizeBreak(const StructuredSDFG& sdfg, const structured_control_flow::Break& break_node) {
1✔
246
    auto id = escapeDotId(break_node.element_id(), "break_");
1✔
247
    this->stream_ << id << " [shape=cds,label=\" break  \"];" << std::endl;
1✔
248
    this->last_comp_name_ = id;
1✔
249
    this->last_comp_name_cluster_.clear();
1✔
250
}
1✔
251

252
void DotVisualizer::visualizeContinue(const StructuredSDFG& sdfg, const structured_control_flow::Continue& continue_node) {
1✔
253
    auto id = escapeDotId(continue_node.element_id(), "cont_");
1✔
254
    this->stream_ << id << " [shape=cds,label=\" continue  \"];" << std::endl;
1✔
255
    this->last_comp_name_ = id;
1✔
256
    this->last_comp_name_cluster_.clear();
1✔
257
}
1✔
258

259
void DotVisualizer::visualizeMap(const StructuredSDFG& sdfg, const structured_control_flow::Map& map_node) {
3✔
260
    auto id = escapeDotId(map_node.element_id(), "map_");
3✔
261
    this->stream_ << "subgraph cluster_" << id << " {" << std::endl;
3✔
262
    this->stream_.setIndent(this->stream_.indent() + 4);
3✔
263
    this->stream_ << "style=filled;shape=box;fillcolor=white;color=black;label=\"map: ";
3✔
264
    this->visualizeForBounds(map_node.indvar(), map_node.init(), map_node.condition(), map_node.update());
3✔
265
    this->stream_ << "\";" << std::endl << id << " [shape=point,style=invis,label=\"\"];" << std::endl;
3✔
266
    this->visualizeSequence(sdfg, map_node.root());
3✔
267
    this->stream_.setIndent(this->stream_.indent() - 4);
3✔
268
    this->stream_ << "}" << std::endl;
3✔
269
    this->last_comp_name_ = id;
3✔
270
    this->last_comp_name_cluster_ = "cluster_" + id;
3✔
271
}
3✔
272

273
void DotVisualizer::visualize() {
175✔
274
    this->stream_.clear();
175✔
275
    this->stream_ << "digraph " << escapeDotId(this->sdfg_.name()) << " {" << std::endl;
175✔
276
    this->stream_.setIndent(4);
175✔
277
    this->stream_ << "graph [compound=true];" << std::endl;
175✔
278
    this->stream_ << "subgraph cluster_" << escapeDotId(this->sdfg_.name()) << " {" << std::endl;
175✔
279
    this->stream_.setIndent(8);
175✔
280
    this->stream_ << "node [style=filled,fillcolor=white];" << std::endl
175✔
281
                  << "style=filled;color=lightblue;label=\"\";" << std::endl;
175✔
282
    this->visualizeSequence(this->sdfg_, this->sdfg_.root());
175✔
283
    this->stream_.setIndent(4);
175✔
284
    this->stream_ << "}" << std::endl;
175✔
285
    this->stream_.setIndent(0);
175✔
286
    this->stream_ << "}" << std::endl;
175✔
287
}
175✔
288

289
void DotVisualizer::writeToFile(const StructuredSDFG& sdfg, const std::filesystem::path* file) {
2✔
290
    DotVisualizer viz(sdfg);
2✔
291
    viz.visualize();
2✔
292

293
    std::filesystem::path fileName = file ? *file : std::filesystem::path(sdfg.name() + ".dot");
2✔
294

295
    std::ofstream dotOutput(fileName, std::ofstream::out);
2✔
296

297
    dotOutput << viz.getStream().str();
2✔
298
    dotOutput.close();
2✔
299
    std::cout << "Wrote graph to : " << fileName << std::endl;
2✔
300
}
2✔
301
} // namespace visualizer
302
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc