From 9261261da87ee8bfc9564c5addec3d59cf4308e0 Mon Sep 17 00:00:00 2001 From: dan sinclair Date: Tue, 1 Nov 2022 18:15:50 +0000 Subject: [PATCH] [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 Reviewed-by: Ben Clayton Kokoro: Kokoro --- .gitignore | 1 + CMakeLists.txt | 3 + src/tint/CMakeLists.txt | 56 ++++++++----- src/tint/cmd/main.cc | 33 +++++++- src/tint/ir/builder_impl.cc | 13 +-- src/tint/ir/debug.cc | 159 ++++++++++++++++++++++++++++++++++++ src/tint/ir/debug.h | 35 ++++++++ 7 files changed, 272 insertions(+), 28 deletions(-) create mode 100644 src/tint/ir/debug.cc create mode 100644 src/tint/ir/debug.h diff --git a/.gitignore b/.gitignore index 6fb333a514..15b0736366 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,4 @@ lcov.info ### Clang-Tidy files all_findings.json +tint.dot diff --git a/CMakeLists.txt b/CMakeLists.txt index 25035836fb..b918c756a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,7 @@ option_if_not_defined(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ${D 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 MSL writer: ${TINT_BUILD_MSL_WRITER}") 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 @@ function(tint_core_compile_options TARGET) target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_MSL_WRITER=$) target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_WRITER=$) target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_WRITER=$) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_IR=$) common_compile_options(${TARGET}) diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index 71f85e8d5f..e2bc7f8f9d 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -238,26 +238,6 @@ set(TINT_LIB_SRCS 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 @@ if(${TINT_BUILD_HLSL_WRITER}) ) 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 @@ if(TINT_BUILD_TESTS) 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 @@ if(TINT_BUILD_TESTS) ) 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 bf5a99303d..485ca5a12d 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 @@ struct Options { std::string xcrun_path; std::unordered_map overrides; std::optional 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] @@ -436,6 +445,10 @@ bool ParseArgs(const std::vector& args, Options* opts) { 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 @@ int main(int argc, const char** argv) { 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 @@ int main(int argc, const char** argv) { 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 224d1ae3d1..5a35b1216d 100644 --- a/src/tint/ir/builder_impl.cc +++ b/src/tint/ir/builder_impl.cc @@ -138,8 +138,10 @@ ResultType BuilderImpl::Build() { 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 @@ bool BuilderImpl::EmitStatement(const ast::Statement* stmt) { 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 0000000000..be83111202 --- /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 +#include +#include + +#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 visited; + std::unordered_set merge_nodes; + std::unordered_map 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 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 0000000000..bd0570b9aa --- /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 + +#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_