Add spirv-cfg to dump a GraphViz graph of the CFG
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.
This commit is contained in:
Родитель
23266c9b56
Коммит
996a814ae3
6
CHANGES
6
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
|
||||
|
|
10
README.md
10
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
Загрузка…
Ссылка в новой задаче