This is experimental, and has not tests.
It's been used to debug validation of structured control flow.
- Has a legend describing special arcs to merge blocks and continue
targets.
- Labels the function entry block, with the Id of the function.
Revision history for SPIRV-Tools
-v2016.3-dev 2016-08-05
+v2016.3-dev 2016-08-11
- Start v2016.3
- Add target environment enums for OpenCL 2.1, OpenCL 2.2,
OpenGL 4.0, OpenGL 4.1, OpenGL 4.2, OpenGL 4.3, OpenGL 4.5.
+ - Add spirv-cfg, an experimental tool to dump the control flow graph
+ as a GraphiViz "dot" graph
+ - Add optimization pass: Eliminate dead constants.
- Fixes issues:
#288: Check def-use dominance rules for OpPhi (variable,parent) operands
- - Add optimization pass: Eliminate dead constants.
v2016.2 2016-08-05
- Validator is incomplete
* `spirv-val` - the standalone validator
* `<spirv-dir>/tools/val`
+### Control flow dumper tool
+
+The control flow dumper prints the control flow graph for a SPIR-V module as a
+[GraphViz](http://www.graphviz.org/) graph.
+
+This is experimental.
+
+* `spirv-cfg` - the control flow graph dumper
+ * `<spirv-dir>/tools/cfg`
+
### Tests
Tests are only built when googletest is found.
add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
+ add_spvtools_tool(TARGET spirv-cfg
+ SRCS cfg/cfg.cpp
+ cfg/bin_to_dot.h
+ cfg/bin_to_dot.cpp
+ LIBS ${SPIRV_TOOLS})
+ target_include_directories(spirv-cfg PRIVATE ${spirv-tools_SOURCE_DIR}
+ ${SPIRV_HEADER_INCLUDE_DIR})
- set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt)
+ set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-cfg)
install(TARGETS ${SPIRV_INSTALL_TARGETS}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
--- /dev/null
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "bin_to_dot.h"
+
+#include <cassert>
+#include <iostream>
+#include <vector>
+
+#include "assembly_grammar.h"
+
+namespace {
+
+const char* kMergeStyle = "style=dashed";
+const char* kContinueStyle = "style=dotted";
+
+// A DotConverter can be used to dump the GraphViz "dot" graph for
+// a SPIR-V module.
+class DotConverter {
+ public:
+ DotConverter(std::iostream* out) : out_(*out) {}
+
+ // Emits the graph preamble.
+ void Begin() const {
+ out_ << "digraph {\n";
+ // Emit a simple legend
+ out_ << "legend_merge_src [shape=plaintext, label=\"\"];\n"
+ << "legend_merge_dest [shape=plaintext, label=\"\"];\n"
+ << "legend_merge_src -> legend_merge_dest [label=\" merge\","
+ << kMergeStyle << "];\n"
+ << "legend_continue_src [shape=plaintext, label=\"\"];\n"
+ << "legend_continue_dest [shape=plaintext, label=\"\"];\n"
+ << "legend_continue_src -> legend_continue_dest [label=\" continue\","
+ << kContinueStyle << "];\n";
+ }
+ // Emits the graph postamble.
+ void End() const { out_ << "}\n"; }
+
+ // Emits the Dot commands for the given instruction.
+ spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
+
+ private:
+ // Ends processing for the current block, emitting its dot code.
+ void FlushBlock(const std::vector<uint32_t>& successors);
+
+ // The ID of the current functio, or 0 if outside of a function.
+ uint32_t current_function_id_ = 0;
+
+ // The ID of the current basic block, or 0 if outside of a block.
+ uint32_t current_block_id_ = 0;
+
+ // Have we completed processing for the entry block to this fuction?
+ bool seen_function_entry_block_ = false;
+
+ // The Id of the merge block for this block if it exists, or 0 otherwise.
+ uint32_t merge_ = 0;
+ // The Id of the continue target block for this block if it exists, or 0
+ // otherwise.
+ uint32_t continue_target_ = 0;
+
+ // The output stream.
+ std::ostream& out_;
+};
+
+spv_result_t DotConverter::HandleInstruction(
+ const spv_parsed_instruction_t& inst) {
+ switch(inst.opcode) {
+ case SpvOpFunction:
+ current_function_id_ = inst.result_id;
+ seen_function_entry_block_ = false;
+ break;
+ case SpvOpFunctionEnd:
+ current_function_id_ = 0;
+ break;
+
+ case SpvOpLabel:
+ current_block_id_ = inst.result_id;
+ break;
+
+ case SpvOpBranch:
+ FlushBlock({inst.words[1]});
+ break;
+ case SpvOpBranchConditional:
+ FlushBlock({inst.words[2], inst.words[3]});
+ break;
+ case SpvOpSwitch: {
+ std::vector<uint32_t> successors{inst.words[2]};
+ for (size_t i = 3; i < inst.num_operands; i += 2) {
+ successors.push_back(inst.words[inst.operands[i].offset]);
+ }
+ FlushBlock(successors);
+ } break;
+
+ case SpvOpKill:
+ case SpvOpReturn:
+ case SpvOpUnreachable:
+ case SpvOpReturnValue:
+ FlushBlock({});
+ break;
+
+ case SpvOpLoopMerge:
+ merge_ = inst.words[1];
+ continue_target_ = inst.words[2];
+ break;
+ case SpvOpSelectionMerge:
+ merge_ = inst.words[1];
+ break;
+ default:
+ break;
+ }
+ return SPV_SUCCESS;
+}
+
+void DotConverter::FlushBlock(const std::vector<uint32_t>& successors) {
+ out_ << current_block_id_;
+ if (!seen_function_entry_block_) {
+ out_ << " [label=\"" << current_block_id_ << "\nFn " << current_function_id_
+ << " entry\", shape=box]";
+ }
+ out_ << ";\n";
+
+ for (auto successor : successors) {
+ out_ << current_block_id_ << " -> " << successor << ";\n";
+ }
+
+ if (merge_) {
+ out_ << current_block_id_ << " -> " << merge_ << " [" << kMergeStyle
+ << "];\n";
+ }
+ if (continue_target_) {
+ out_ << current_block_id_ << " -> " << continue_target_ << " ["
+ << kContinueStyle << "];\n";
+ }
+
+ // Reset the book-keeping for a block.
+ seen_function_entry_block_ = true;
+ merge_ = 0;
+ continue_target_ = 0;
+}
+
+spv_result_t HandleInstruction(
+ void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
+ assert(user_data);
+ auto converter = static_cast<DotConverter*>(user_data);
+ return converter->HandleInstruction(*parsed_instruction);
+}
+
+} // anonymous namespace
+
+spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words,
+ size_t num_words, std::iostream* out,
+ spv_diagnostic* diagnostic) {
+ // Invalid arguments return error codes, but don't necessarily generate
+ // diagnostics. These are programmer errors, not user errors.
+ if (!diagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
+ const libspirv::AssemblyGrammar grammar(context);
+ if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
+
+ DotConverter converter(out);
+ converter.Begin();
+ if (auto error = spvBinaryParse(context, &converter, words, num_words,
+ nullptr, HandleInstruction, diagnostic)) {
+ return error;
+ }
+ converter.End();
+
+ return SPV_SUCCESS;
+}
--- /dev/null
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef BIN_TO_DOT_H_
+#define BIN_TO_DOT_H_
+
+#include <iostream>
+#include "spirv-tools/libspirv.h"
+
+// Dumps the control flow graph for the given module to the output stream.
+// Returns SPV_SUCCESS on succes.
+spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words,
+ size_t num_words, std::iostream* out,
+ spv_diagnostic* diagnostic);
+
+#endif // BIN_TO_DOT_H_
--- /dev/null
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "spirv-tools/libspirv.h"
+#include "tools/io.h"
+
+#include "bin_to_dot.h"
+
+// Prints a program usage message to stdout.
+static void print_usage(const char* argv0) {
+ printf(
+ R"(%s - Show the control flow graph in GraphiViz "dot" form. EXPERIMENTAL
+
+Usage: %s [options] [<filename>]
+
+The SPIR-V binary is read from <filename>. If no file is specified,
+or if the filename is "-", then the binary is read from standard input.
+
+Options:
+
+ -h, --help Print this help.
+ --version Display version information.
+
+ -o <filename> Set the output filename.
+ Output goes to standard output if this option is
+ not specified, or if the filename is "-".
+)",
+ argv0, argv0);
+}
+
+int main(int argc, char** argv) {
+ const char* inFile = nullptr;
+ const char* outFile = nullptr; // Stays nullptr if printing to stdout.
+
+ for (int argi = 1; argi < argc; ++argi) {
+ if ('-' == argv[argi][0]) {
+ switch (argv[argi][1]) {
+ case 'h':
+ print_usage(argv[0]);
+ return 0;
+ case 'o': {
+ if (!outFile && argi + 1 < argc) {
+ outFile = argv[++argi];
+ } else {
+ print_usage(argv[0]);
+ return 1;
+ }
+ } break;
+ case '-': {
+ // Long options
+ if (0 == strcmp(argv[argi], "--help")) {
+ print_usage(argv[0]);
+ return 0;
+ } else if (0 == strcmp(argv[argi], "--version")) {
+ printf("%s EXPERIMENTAL\n", spvSoftwareVersionDetailsString());
+ printf("Target: %s\n",
+ spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1));
+ return 0;
+ } else {
+ print_usage(argv[0]);
+ return 1;
+ }
+ } break;
+ case 0: {
+ // Setting a filename of "-" to indicate stdin.
+ if (!inFile) {
+ inFile = argv[argi];
+ } else {
+ fprintf(stderr, "error: More than one input file specified\n");
+ return 1;
+ }
+ } break;
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ } else {
+ if (!inFile) {
+ inFile = argv[argi];
+ } else {
+ fprintf(stderr, "error: More than one input file specified\n");
+ return 1;
+ }
+ }
+ }
+
+ // Read the input binary.
+ std::vector<uint32_t> contents;
+ if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
+ spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
+ spv_diagnostic diagnostic = nullptr;
+
+ std::stringstream ss;
+ auto error = BinaryToDot(context, contents.data(), contents.size(), &ss, &diagnostic);
+ if (error) {
+ spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(context);
+ return error;
+ }
+ std::string str = ss.str();
+ WriteFile(outFile, "w", str.data(), str.size());
+
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(context);
+
+ return 0;
+}