| // 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; |
| } |