| // Copyright 2022 The Tint Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/tint/ir/debug.h" | |
| #include <sstream> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include "src/tint/ir/block.h" | |
| #include "src/tint/ir/if.h" | |
| #include "src/tint/ir/loop.h" | |
| #include "src/tint/ir/switch.h" | |
| #include "src/tint/ir/terminator.h" | |
| #include "src/tint/program.h" | |
| namespace tint::ir { | |
| // static | |
| std::string Debug::AsDotGraph(const Module* mod) { | |
| size_t node_count = 0; | |
| std::unordered_set<const FlowNode*> visited; | |
| std::unordered_set<const FlowNode*> merge_nodes; | |
| std::unordered_map<const FlowNode*, std::string> node_to_name; | |
| std::stringstream out; | |
| auto name_for = [&](const FlowNode* node) -> std::string { | |
| if (node_to_name.count(node) > 0) { | |
| return node_to_name[node]; | |
| } | |
| std::string name = "node_" + std::to_string(node_count); | |
| node_count += 1; | |
| node_to_name[node] = name; | |
| return name; | |
| }; | |
| std::function<void(const FlowNode*)> Graph = [&](const FlowNode* node) { | |
| if (visited.count(node) > 0) { | |
| return; | |
| } | |
| visited.insert(node); | |
| tint::Switch( | |
| node, | |
| [&](const ir::Block* b) { | |
| if (node_to_name.count(b) == 0) { | |
| out << name_for(b) << R"( [label="block"])" << std::endl; | |
| } | |
| out << name_for(b) << " -> " << name_for(b->branch_target); | |
| // Dashed lines to merge blocks | |
| if (merge_nodes.count(b->branch_target) != 0) { | |
| out << " [style=dashed]"; | |
| } | |
| out << std::endl; | |
| Graph(b->branch_target); | |
| }, | |
| [&](const ir::Switch* s) { | |
| out << name_for(s) << R"( [label="switch"])" << std::endl; | |
| out << name_for(s->merge_target) << R"( [label="switch merge"])" << std::endl; | |
| merge_nodes.insert(s->merge_target); | |
| size_t i = 0; | |
| for (const auto& c : s->cases) { | |
| out << name_for(c.start_target) | |
| << R"( [label="case )" + std::to_string(i++) + R"("])" << std::endl; | |
| } | |
| out << name_for(s) << " -> {"; | |
| for (const auto& c : s->cases) { | |
| if (&c != &(s->cases[0])) { | |
| out << ", "; | |
| } | |
| out << name_for(c.start_target); | |
| } | |
| out << "}" << std::endl; | |
| for (const auto& c : s->cases) { | |
| Graph(c.start_target); | |
| } | |
| Graph(s->merge_target); | |
| }, | |
| [&](const ir::If* i) { | |
| out << name_for(i) << R"( [label="if"])" << std::endl; | |
| out << name_for(i->true_target) << R"( [label="true"])" << std::endl; | |
| out << name_for(i->false_target) << R"( [label="false"])" << std::endl; | |
| out << name_for(i->merge_target) << R"( [label="if merge"])" << std::endl; | |
| merge_nodes.insert(i->merge_target); | |
| out << name_for(i) << " -> {"; | |
| out << name_for(i->true_target) << ", " << name_for(i->false_target); | |
| out << "}" << std::endl; | |
| // Subgraph if true/false branches so they draw on the same line | |
| out << "subgraph sub_" << name_for(i) << " {" << std::endl; | |
| out << R"(rank="same")" << std::endl; | |
| out << name_for(i->true_target) << std::endl; | |
| out << name_for(i->false_target) << std::endl; | |
| out << "}" << std::endl; | |
| Graph(i->true_target); | |
| Graph(i->false_target); | |
| Graph(i->merge_target); | |
| }, | |
| [&](const ir::Loop* l) { | |
| out << name_for(l) << R"( [label="loop"])" << std::endl; | |
| out << name_for(l->start_target) << R"( [label="start"])" << std::endl; | |
| out << name_for(l->continuing_target) << R"( [label="continuing"])" << std::endl; | |
| out << name_for(l->merge_target) << R"( [label="loop merge"])" << std::endl; | |
| merge_nodes.insert(l->merge_target); | |
| // Subgraph the continuing and merge so they get drawn on the same line | |
| out << "subgraph sub_" << name_for(l) << " {" << std::endl; | |
| out << R"(rank="same")" << std::endl; | |
| out << name_for(l->continuing_target) << std::endl; | |
| out << name_for(l->merge_target) << std::endl; | |
| out << "}" << std::endl; | |
| out << name_for(l) << " -> " << name_for(l->start_target) << std::endl; | |
| Graph(l->start_target); | |
| Graph(l->continuing_target); | |
| Graph(l->merge_target); | |
| }, | |
| [&](const ir::Terminator*) { | |
| // Already done | |
| }); | |
| }; | |
| out << "digraph G {" << std::endl; | |
| for (const auto* func : mod->functions) { | |
| // Cluster each function to label and draw a box around it. | |
| out << "subgraph cluster_" << name_for(func) << " {" << std::endl; | |
| out << R"(label=")" << mod->program->Symbols().NameFor(func->source->symbol) << R"(")" | |
| << std::endl; | |
| out << name_for(func->start_target) << R"( [label="start"])" << std::endl; | |
| out << name_for(func->end_target) << R"( [label="end"])" << std::endl; | |
| Graph(func->start_target); | |
| out << "}" << std::endl; | |
| } | |
| out << "}"; | |
| return out.str(); | |
| } | |
| } // namespace tint::ir |