1 // Copyright (c) 2016 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "tools/cfg/bin_to_dot.h" 16 17 #include <cassert> 18 #include <iostream> 19 #include <utility> 20 #include <vector> 21 22 #include "source/assembly_grammar.h" 23 #include "source/name_mapper.h" 24 25 namespace { 26 27 const char* kMergeStyle = "style=dashed"; 28 const char* kContinueStyle = "style=dotted"; 29 30 // A DotConverter can be used to dump the GraphViz "dot" graph for 31 // a SPIR-V module. 32 class DotConverter { 33 public: 34 DotConverter(spvtools::NameMapper name_mapper, std::iostream* out) 35 : name_mapper_(std::move(name_mapper)), out_(*out) {} 36 37 // Emits the graph preamble. 38 void Begin() const { 39 out_ << "digraph {\n"; 40 // Emit a simple legend 41 out_ << "legend_merge_src [shape=plaintext, label=\"\"];\n" 42 << "legend_merge_dest [shape=plaintext, label=\"\"];\n" 43 << "legend_merge_src -> legend_merge_dest [label=\" merge\"," 44 << kMergeStyle << "];\n" 45 << "legend_continue_src [shape=plaintext, label=\"\"];\n" 46 << "legend_continue_dest [shape=plaintext, label=\"\"];\n" 47 << "legend_continue_src -> legend_continue_dest [label=\" continue\"," 48 << kContinueStyle << "];\n"; 49 } 50 // Emits the graph postamble. 51 void End() const { out_ << "}\n"; } 52 53 // Emits the Dot commands for the given instruction. 54 spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst); 55 56 private: 57 // Ends processing for the current block, emitting its dot code. 58 void FlushBlock(const std::vector<uint32_t>& successors); 59 60 // The ID of the current functio, or 0 if outside of a function. 61 uint32_t current_function_id_ = 0; 62 63 // The ID of the current basic block, or 0 if outside of a block. 64 uint32_t current_block_id_ = 0; 65 66 // Have we completed processing for the entry block to this fuction? 67 bool seen_function_entry_block_ = false; 68 69 // The Id of the merge block for this block if it exists, or 0 otherwise. 70 uint32_t merge_ = 0; 71 // The Id of the continue target block for this block if it exists, or 0 72 // otherwise. 73 uint32_t continue_target_ = 0; 74 75 // An object for mapping Ids to names. 76 spvtools::NameMapper name_mapper_; 77 78 // The output stream. 79 std::ostream& out_; 80 }; 81 82 spv_result_t DotConverter::HandleInstruction( 83 const spv_parsed_instruction_t& inst) { 84 switch (inst.opcode) { 85 case SpvOpFunction: 86 current_function_id_ = inst.result_id; 87 seen_function_entry_block_ = false; 88 break; 89 case SpvOpFunctionEnd: 90 current_function_id_ = 0; 91 break; 92 93 case SpvOpLabel: 94 current_block_id_ = inst.result_id; 95 break; 96 97 case SpvOpBranch: 98 FlushBlock({inst.words[1]}); 99 break; 100 case SpvOpBranchConditional: 101 FlushBlock({inst.words[2], inst.words[3]}); 102 break; 103 case SpvOpSwitch: { 104 std::vector<uint32_t> successors{inst.words[2]}; 105 for (size_t i = 3; i < inst.num_operands; i += 2) { 106 successors.push_back(inst.words[inst.operands[i].offset]); 107 } 108 FlushBlock(successors); 109 } break; 110 111 case SpvOpKill: 112 case SpvOpReturn: 113 case SpvOpUnreachable: 114 case SpvOpReturnValue: 115 FlushBlock({}); 116 break; 117 118 case SpvOpLoopMerge: 119 merge_ = inst.words[1]; 120 continue_target_ = inst.words[2]; 121 break; 122 case SpvOpSelectionMerge: 123 merge_ = inst.words[1]; 124 break; 125 default: 126 break; 127 } 128 return SPV_SUCCESS; 129 } 130 131 void DotConverter::FlushBlock(const std::vector<uint32_t>& successors) { 132 out_ << current_block_id_; 133 if (!seen_function_entry_block_) { 134 out_ << " [label=\"" << name_mapper_(current_block_id_) << "\nFn " 135 << name_mapper_(current_function_id_) << " entry\", shape=box];\n"; 136 } else { 137 out_ << " [label=\"" << name_mapper_(current_block_id_) << "\"];\n"; 138 } 139 140 for (auto successor : successors) { 141 out_ << current_block_id_ << " -> " << successor << ";\n"; 142 } 143 144 if (merge_) { 145 out_ << current_block_id_ << " -> " << merge_ << " [" << kMergeStyle 146 << "];\n"; 147 } 148 if (continue_target_) { 149 out_ << current_block_id_ << " -> " << continue_target_ << " [" 150 << kContinueStyle << "];\n"; 151 } 152 153 // Reset the book-keeping for a block. 154 seen_function_entry_block_ = true; 155 merge_ = 0; 156 continue_target_ = 0; 157 } 158 159 spv_result_t HandleInstruction( 160 void* user_data, const spv_parsed_instruction_t* parsed_instruction) { 161 assert(user_data); 162 auto converter = static_cast<DotConverter*>(user_data); 163 return converter->HandleInstruction(*parsed_instruction); 164 } 165 166 } // anonymous namespace 167 168 spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words, 169 size_t num_words, std::iostream* out, 170 spv_diagnostic* diagnostic) { 171 // Invalid arguments return error codes, but don't necessarily generate 172 // diagnostics. These are programmer errors, not user errors. 173 if (!diagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC; 174 const spvtools::AssemblyGrammar grammar(context); 175 if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE; 176 177 spvtools::FriendlyNameMapper friendly_mapper(context, words, num_words); 178 DotConverter converter(friendly_mapper.GetNameMapper(), out); 179 converter.Begin(); 180 if (auto error = spvBinaryParse(context, &converter, words, num_words, 181 nullptr, HandleInstruction, diagnostic)) { 182 return error; 183 } 184 converter.End(); 185 186 return SPV_SUCCESS; 187 } 188