• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
DotConverter(spvtools::NameMapper name_mapper,std::iostream * out)34   DotConverter(spvtools::NameMapper name_mapper, std::iostream* out)
35       : name_mapper_(std::move(name_mapper)), out_(*out) {}
36 
37   // Emits the graph preamble.
Begin() const38   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.
End() const51   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 
HandleInstruction(const spv_parsed_instruction_t & inst)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 
FlushBlock(const std::vector<uint32_t> & successors)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 
HandleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)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 
BinaryToDot(const spv_const_context context,const uint32_t * words,size_t num_words,std::iostream * out,spv_diagnostic * diagnostic)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