[IR] Add ability to dump IR graph to dot
This CL adds the ability to dump an IR graph to a dot file. The
`--dump-ir-graph` option is added to the main tint command. The IR code
is moved behind a TINT_BUILD_IR flag in order to allow the GN build to
continue to build the tint program.
Bug: tint:1718
Change-Id: I0953fe2a59a34c21bb6cd288cb90e9d0298af793
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107860
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/.gitignore b/.gitignore
index 6fb333a..15b0736 100644
--- a/.gitignore
+++ b/.gitignore
@@ -128,3 +128,4 @@
### Clang-Tidy files
all_findings.json
+tint.dot
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2503583..b918c75 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,6 +155,7 @@
option_if_not_defined(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
option_if_not_defined(TINT_BUILD_RUN_GENERATOR "Run the intrinsic generator" OFF)
+option_if_not_defined(TINT_BUILD_IR "Build the IR" ON)
option_if_not_defined(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
option_if_not_defined(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF)
@@ -290,6 +291,7 @@
message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
message(STATUS "Tint build run generator: ${TINT_BUILD_RUN_GENERATOR}")
+message(STATUS "Tint build IR: ${TINT_BUILD_IR}")
message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
message(STATUS "Tint build AST fuzzer: ${TINT_BUILD_AST_FUZZER}")
@@ -495,6 +497,7 @@
target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_MSL_WRITER=$<BOOL:${TINT_BUILD_MSL_WRITER}>)
target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_WRITER=$<BOOL:${TINT_BUILD_SPV_WRITER}>)
target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_WRITER=$<BOOL:${TINT_BUILD_WGSL_WRITER}>)
+ target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_IR=$<BOOL:${TINT_BUILD_IR}>)
common_compile_options(${TARGET})
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 71f85e8..e2bc7f8 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -238,26 +238,6 @@
inspector/resource_binding.h
inspector/scalar.cc
inspector/scalar.h
- ir/block.cc
- ir/block.h
- ir/builder.cc
- ir/builder.h
- ir/builder_impl.cc
- ir/builder_impl.h
- ir/flow_node.cc
- ir/flow_node.h
- ir/function.cc
- ir/function.h
- ir/if.cc
- ir/if.h
- ir/loop.cc
- ir/loop.h
- ir/module.cc
- ir/module.h
- ir/switch.cc
- ir/switch.h
- ir/terminator.cc
- ir/terminator.h
number.cc
number.h
program_builder.cc
@@ -701,6 +681,33 @@
)
endif()
+if(${TINT_BUILD_IR})
+ list(APPEND TINT_LIB_SRCS
+ ir/block.cc
+ ir/block.h
+ ir/builder.cc
+ ir/builder.h
+ ir/builder_impl.cc
+ ir/builder_impl.h
+ ir/debug.cc
+ ir/debug.h
+ ir/flow_node.cc
+ ir/flow_node.h
+ ir/function.cc
+ ir/function.h
+ ir/if.cc
+ ir/if.h
+ ir/loop.cc
+ ir/loop.h
+ ir/module.cc
+ ir/module.h
+ ir/switch.cc
+ ir/switch.h
+ ir/terminator.cc
+ ir/terminator.h
+ )
+endif()
+
if(MSVC)
list(APPEND TINT_LIB_SRCS
tint.natvis
@@ -864,8 +871,6 @@
diagnostic/diagnostic_test.cc
diagnostic/formatter_test.cc
diagnostic/printer_test.cc
- ir/builder_impl_test.cc
- ir/test_helper.h
number_test.cc
program_builder_test.cc
program_test.cc
@@ -1368,6 +1373,13 @@
)
endif()
+ if (${TINT_BUILD_IR})
+ list(APPEND TINT_TEST_SRCS
+ ir/builder_impl_test.cc
+ ir/test_helper.h
+ )
+ endif()
+
if (${TINT_BUILD_FUZZERS})
list(APPEND TINT_TEST_SRCS
fuzzers/mersenne_twister_engine.cc
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index bf5a993..485ca5a 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -26,7 +26,7 @@
#if TINT_BUILD_GLSL_WRITER
#include "StandAlone/ResourceLimits.h"
#include "glslang/Public/ShaderLang.h"
-#endif
+#endif // TINT_BUILD_GLSL_WRITER
#if TINT_BUILD_SPV_READER
#include "spirv-tools/libspirv.hpp"
@@ -39,6 +39,11 @@
#include "src/tint/val/val.h"
#include "tint/tint.h"
+#if TINT_BUILD_IR
+#include "src/tint/ir/debug.h"
+#include "src/tint/ir/module.h"
+#endif // TINT_BUILD_IR
+
namespace {
[[noreturn]] void TintInternalCompilerErrorReporter(const tint::diag::List& diagnostics) {
@@ -94,6 +99,10 @@
std::string xcrun_path;
std::unordered_map<std::string, double> overrides;
std::optional<tint::sem::BindingPoint> hlsl_root_constant_binding_point;
+
+#if TINT_BUILD_IR
+ bool dump_ir_graph = false;
+#endif // TINT_BUILD_IR
};
const char kUsage[] = R"(Usage: tint [options] <input-file>
@@ -436,6 +445,10 @@
return false;
}
opts->dxc_path = args[i];
+#if TINT_BUILD_IR
+ } else if (arg == "--dump-ir-graph") {
+ opts->dump_ir_graph = true;
+#endif // TINT_BUILD_IR
} else if (arg == "--xcrun") {
++i;
if (i >= args.size()) {
@@ -1135,6 +1148,11 @@
if (options.show_help) {
std::string usage = tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names());
+#if TINT_BUILD_IR
+ usage +=
+ " --dump-ir-graph -- Writes the IR graph to 'tint.dot' as a dot graph\n";
+#endif // TINT_BUILD_IR
+
std::cout << usage << std::endl;
return 0;
}
@@ -1253,6 +1271,19 @@
return 1;
}
+#if TINT_BUILD_IR
+ if (options.dump_ir_graph) {
+ auto result = tint::ir::Module::FromProgram(program.get());
+ if (!result) {
+ std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
+ } else {
+ auto mod = result.Move();
+ auto graph = tint::ir::Debug::AsDotGraph(&mod);
+ WriteFile("tint.dot", "w", graph);
+ }
+ }
+#endif // TINT_BUILD_IR
+
tint::inspector::Inspector inspector(program.get());
if (options.dump_inspector_bindings) {
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 224d1ae..5a35b12 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -138,8 +138,10 @@
return true;
},
[&](Default) {
- TINT_ICE(IR, diagnostics_) << "unhandled type: " << decl->TypeInfo().name;
- return false;
+ diagnostics_.add_warning(tint::diag::System::IR,
+ "unknown type: " + std::string(decl->TypeInfo().name),
+ decl->source);
+ return true;
});
if (!ok) {
return utils::Failure;
@@ -220,9 +222,10 @@
return true; // Not emitted
},
[&](Default) {
- TINT_ICE(IR, diagnostics_)
- << "unknown statement type: " << std::string(stmt->TypeInfo().name);
- return false;
+ diagnostics_.add_warning(
+ tint::diag::System::IR,
+ "unknown statement type: " + std::string(stmt->TypeInfo().name), stmt->source);
+ return true;
});
}
diff --git a/src/tint/ir/debug.cc b/src/tint/ir/debug.cc
new file mode 100644
index 0000000..be83111
--- /dev/null
+++ b/src/tint/ir/debug.cc
@@ -0,0 +1,159 @@
+// 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
diff --git a/src/tint/ir/debug.h b/src/tint/ir/debug.h
new file mode 100644
index 0000000..bd0570b
--- /dev/null
+++ b/src/tint/ir/debug.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef SRC_TINT_IR_DEBUG_H_
+#define SRC_TINT_IR_DEBUG_H_
+
+#include <string>
+
+#include "src/tint/ir/module.h"
+
+namespace tint::ir {
+
+/// Helper class to debug IR.
+class Debug {
+ public:
+ /// Returns the module as a dot graph
+ /// @param mod the module to emit
+ /// @returns the dot graph for the given module
+ static std::string AsDotGraph(const Module* mod);
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_DEBUG_H_