Add spirv-cfg to dump a GraphViz graph of the CFG
authorDavid Neto <dneto@google.com>
Wed, 3 Aug 2016 15:55:14 +0000 (11:55 -0400)
committerDavid Neto <dneto@google.com>
Thu, 11 Aug 2016 15:24:51 +0000 (11:24 -0400)
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.

CHANGES
README.md
tools/CMakeLists.txt
tools/cfg/bin_to_dot.cpp [new file with mode: 0644]
tools/cfg/bin_to_dot.h [new file with mode: 0644]
tools/cfg/cfg.cpp [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 07ac2bc..9184a89 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,12 +1,14 @@
 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
index d074591..3362099 100644 (file)
--- a/README.md
+++ b/README.md
@@ -224,6 +224,16 @@ The validator operates on the binary form.
 * `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.
index 5916023..dbc218b 100644 (file)
@@ -47,8 +47,15 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
   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
diff --git a/tools/cfg/bin_to_dot.cpp b/tools/cfg/bin_to_dot.cpp
new file mode 100644 (file)
index 0000000..41e7ea5
--- /dev/null
@@ -0,0 +1,191 @@
+// 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;
+}
diff --git a/tools/cfg/bin_to_dot.h b/tools/cfg/bin_to_dot.h
new file mode 100644 (file)
index 0000000..d20ac2e
--- /dev/null
@@ -0,0 +1,39 @@
+// 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_
diff --git a/tools/cfg/cfg.cpp b/tools/cfg/cfg.cpp
new file mode 100644 (file)
index 0000000..4b8cda1
--- /dev/null
@@ -0,0 +1,137 @@
+// 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;
+}