// Copyright (c) 2016 Google Inc. // // 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 "tools/cfg/bin_to_dot.h" #include <cassert> #include <iostream> #include <utility> #include <vector> #include "source/assembly_grammar.h" #include "source/name_mapper.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(spvtools::NameMapper name_mapper, std::iostream* out) : name_mapper_(std::move(name_mapper)), 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; // An object for mapping Ids to names. spvtools::NameMapper name_mapper_; // 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=\"" << name_mapper_(current_block_id_) << "\nFn " << name_mapper_(current_function_id_) << " entry\", shape=box];\n"; } else { out_ << " [label=\"" << name_mapper_(current_block_id_) << "\"];\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 spvtools::AssemblyGrammar grammar(context); if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE; spvtools::FriendlyNameMapper friendly_mapper(context, words, num_words); DotConverter converter(friendly_mapper.GetNameMapper(), out); converter.Begin(); if (auto error = spvBinaryParse(context, &converter, words, num_words, nullptr, HandleInstruction, diagnostic)) { return error; } converter.End(); return SPV_SUCCESS; }