• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2015-2016 The Khronos Group 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 // This file contains a disassembler:  It converts a SPIR-V binary
16 // to text.
17 
18 #include <algorithm>
19 #include <cassert>
20 #include <cstring>
21 #include <iomanip>
22 #include <memory>
23 #include <unordered_map>
24 
25 #include "assembly_grammar.h"
26 #include "binary.h"
27 #include "diagnostic.h"
28 #include "ext_inst.h"
29 #include "name_mapper.h"
30 #include "opcode.h"
31 #include "parsed_operand.h"
32 #include "print.h"
33 #include "spirv-tools/libspirv.h"
34 #include "spirv_constant.h"
35 #include "spirv_endian.h"
36 #include "util/hex_float.h"
37 
38 namespace {
39 
40 // A Disassembler instance converts a SPIR-V binary to its assembly
41 // representation.
42 class Disassembler {
43  public:
Disassembler(const libspirv::AssemblyGrammar & grammar,uint32_t options,libspirv::NameMapper name_mapper)44   Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options,
45                libspirv::NameMapper name_mapper)
46       : grammar_(grammar),
47         print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
48         color_(print_ &&
49                spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
50         indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
51                     ? kStandardIndent
52                     : 0),
53         text_(),
54         out_(print_ ? out_stream() : out_stream(text_)),
55         stream_(out_.get()),
56         header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
57         show_byte_offset_(spvIsInBitfield(
58             SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
59         byte_offset_(0),
60         name_mapper_(std::move(name_mapper)) {}
61 
62   // Emits the assembly header for the module, and sets up internal state
63   // so subsequent callbacks can handle the cases where the entire module
64   // is either big-endian or little-endian.
65   spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version,
66                             uint32_t generator, uint32_t id_bound,
67                             uint32_t schema);
68   // Emits the assembly text for the given instruction.
69   spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
70 
71   // If not printing, populates text_result with the accumulated text.
72   // Returns SPV_SUCCESS on success.
73   spv_result_t SaveTextResult(spv_text* text_result) const;
74 
75  private:
76   enum { kStandardIndent = 15 };
77 
78   using out_stream = libspirv::out_stream;
79 
80   // Emits an operand for the given instruction, where the instruction
81   // is at offset words from the start of the binary.
82   void EmitOperand(const spv_parsed_instruction_t& inst,
83                    const uint16_t operand_index);
84 
85   // Emits a mask expression for the given mask word of the specified type.
86   void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
87 
88   // Resets the output color, if color is turned on.
ResetColor()89   void ResetColor() {
90     if (color_) out_.get() << libspirv::clr::reset();
91   }
92   // Sets the output to grey, if color is turned on.
SetGrey()93   void SetGrey() {
94     if (color_) out_.get() << libspirv::clr::grey();
95   }
96   // Sets the output to blue, if color is turned on.
SetBlue()97   void SetBlue() {
98     if (color_) out_.get() << libspirv::clr::blue();
99   }
100   // Sets the output to yellow, if color is turned on.
SetYellow()101   void SetYellow() {
102     if (color_) out_.get() << libspirv::clr::yellow();
103   }
104   // Sets the output to red, if color is turned on.
SetRed()105   void SetRed() {
106     if (color_) out_.get() << libspirv::clr::red();
107   }
108   // Sets the output to green, if color is turned on.
SetGreen()109   void SetGreen() {
110     if (color_) out_.get() << libspirv::clr::green();
111   }
112 
113   const libspirv::AssemblyGrammar& grammar_;
114   const bool print_;  // Should we also print to the standard output stream?
115   const bool color_;  // Should we print in colour?
116   const int indent_;  // How much to indent. 0 means don't indent
117   spv_endianness_t endian_;  // The detected endianness of the binary.
118   std::stringstream text_;   // Captures the text, if not printing.
119   out_stream out_;  // The Output stream.  Either to text_ or standard output.
120   std::ostream& stream_;  // The output std::stream.
121   const bool header_;     // Should we output header as the leading comment?
122   const bool show_byte_offset_;  // Should we print byte offset, in hex?
123   size_t byte_offset_;           // The number of bytes processed so far.
124   libspirv::NameMapper name_mapper_;
125 };
126 
HandleHeader(spv_endianness_t endian,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)127 spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
128                                         uint32_t version, uint32_t generator,
129                                         uint32_t id_bound, uint32_t schema) {
130   endian_ = endian;
131 
132   if (header_) {
133     SetGrey();
134     const char* generator_tool =
135         spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
136     stream_ << "; SPIR-V\n"
137             << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
138             << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n"
139             << "; Generator: " << generator_tool;
140     // For unknown tools, print the numeric tool value.
141     if (0 == strcmp("Unknown", generator_tool)) {
142       stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
143     }
144     // Print the miscellaneous part of the generator word on the same
145     // line as the tool name.
146     stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
147             << "; Bound: " << id_bound << "\n"
148             << "; Schema: " << schema << "\n";
149     ResetColor();
150   }
151 
152   byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
153 
154   return SPV_SUCCESS;
155 }
156 
HandleInstruction(const spv_parsed_instruction_t & inst)157 spv_result_t Disassembler::HandleInstruction(
158     const spv_parsed_instruction_t& inst) {
159   if (inst.result_id) {
160     SetBlue();
161     const std::string id_name = name_mapper_(inst.result_id);
162     if (indent_)
163       stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
164     stream_ << "%" << id_name;
165     ResetColor();
166     stream_ << " = ";
167   } else {
168     stream_ << std::string(indent_, ' ');
169   }
170 
171   stream_ << "Op" << spvOpcodeString(static_cast<SpvOp>(inst.opcode));
172 
173   for (uint16_t i = 0; i < inst.num_operands; i++) {
174     const spv_operand_type_t type = inst.operands[i].type;
175     assert(type != SPV_OPERAND_TYPE_NONE);
176     if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
177     stream_ << " ";
178     EmitOperand(inst, i);
179   }
180 
181   if (show_byte_offset_) {
182     SetGrey();
183     auto saved_flags = stream_.flags();
184     auto saved_fill = stream_.fill();
185     stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
186             << byte_offset_;
187     stream_.flags(saved_flags);
188     stream_.fill(saved_fill);
189     ResetColor();
190   }
191 
192   byte_offset_ += inst.num_words * sizeof(uint32_t);
193 
194   stream_ << "\n";
195   return SPV_SUCCESS;
196 }
197 
EmitOperand(const spv_parsed_instruction_t & inst,const uint16_t operand_index)198 void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
199                                const uint16_t operand_index) {
200   assert(operand_index < inst.num_operands);
201   const spv_parsed_operand_t& operand = inst.operands[operand_index];
202   const uint32_t word = inst.words[operand.offset];
203   switch (operand.type) {
204     case SPV_OPERAND_TYPE_RESULT_ID:
205       assert(false && "<result-id> is not supposed to be handled here");
206       SetBlue();
207       stream_ << "%" << name_mapper_(word);
208       break;
209     case SPV_OPERAND_TYPE_ID:
210     case SPV_OPERAND_TYPE_TYPE_ID:
211     case SPV_OPERAND_TYPE_SCOPE_ID:
212     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
213       SetYellow();
214       stream_ << "%" << name_mapper_(word);
215       break;
216     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
217       spv_ext_inst_desc ext_inst;
218       if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst))
219         assert(false && "should have caught this earlier");
220       SetRed();
221       stream_ << ext_inst->name;
222     } break;
223     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
224       spv_opcode_desc opcode_desc;
225       if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
226         assert(false && "should have caught this earlier");
227       SetRed();
228       stream_ << opcode_desc->name;
229     } break;
230     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
231     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
232       SetRed();
233       libspirv::EmitNumericLiteral(&stream_, inst, operand);
234       ResetColor();
235     } break;
236     case SPV_OPERAND_TYPE_LITERAL_STRING: {
237       stream_ << "\"";
238       SetGreen();
239       // Strings are always little-endian, and null-terminated.
240       // Write out the characters, escaping as needed, and without copying
241       // the entire string.
242       auto c_str = reinterpret_cast<const char*>(inst.words + operand.offset);
243       for (auto p = c_str; *p; ++p) {
244         if (*p == '"' || *p == '\\') stream_ << '\\';
245         stream_ << *p;
246       }
247       ResetColor();
248       stream_ << '"';
249     } break;
250     case SPV_OPERAND_TYPE_CAPABILITY:
251     case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
252     case SPV_OPERAND_TYPE_EXECUTION_MODEL:
253     case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
254     case SPV_OPERAND_TYPE_MEMORY_MODEL:
255     case SPV_OPERAND_TYPE_EXECUTION_MODE:
256     case SPV_OPERAND_TYPE_STORAGE_CLASS:
257     case SPV_OPERAND_TYPE_DIMENSIONALITY:
258     case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
259     case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
260     case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
261     case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
262     case SPV_OPERAND_TYPE_LINKAGE_TYPE:
263     case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
264     case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
265     case SPV_OPERAND_TYPE_DECORATION:
266     case SPV_OPERAND_TYPE_BUILT_IN:
267     case SPV_OPERAND_TYPE_GROUP_OPERATION:
268     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
269     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: {
270       spv_operand_desc entry;
271       if (grammar_.lookupOperand(operand.type, word, &entry))
272         assert(false && "should have caught this earlier");
273       stream_ << entry->name;
274     } break;
275     case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
276     case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
277     case SPV_OPERAND_TYPE_LOOP_CONTROL:
278     case SPV_OPERAND_TYPE_IMAGE:
279     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
280     case SPV_OPERAND_TYPE_SELECTION_CONTROL:
281       EmitMaskOperand(operand.type, word);
282       break;
283     default:
284       assert(false && "unhandled or invalid case");
285   }
286   ResetColor();
287 }
288 
EmitMaskOperand(const spv_operand_type_t type,const uint32_t word)289 void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
290                                    const uint32_t word) {
291   // Scan the mask from least significant bit to most significant bit.  For each
292   // set bit, emit the name of that bit. Separate multiple names with '|'.
293   uint32_t remaining_word = word;
294   uint32_t mask;
295   int num_emitted = 0;
296   for (mask = 1; remaining_word; mask <<= 1) {
297     if (remaining_word & mask) {
298       remaining_word ^= mask;
299       spv_operand_desc entry;
300       if (grammar_.lookupOperand(type, mask, &entry))
301         assert(false && "should have caught this earlier");
302       if (num_emitted) stream_ << "|";
303       stream_ << entry->name;
304       num_emitted++;
305     }
306   }
307   if (!num_emitted) {
308     // An operand value of 0 was provided, so represent it by the name
309     // of the 0 value. In many cases, that's "None".
310     spv_operand_desc entry;
311     if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
312       stream_ << entry->name;
313   }
314 }
315 
SaveTextResult(spv_text * text_result) const316 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
317   if (!print_) {
318     size_t length = text_.str().size();
319     char* str = new char[length + 1];
320     if (!str) return SPV_ERROR_OUT_OF_MEMORY;
321     strncpy(str, text_.str().c_str(), length + 1);
322     spv_text text = new spv_text_t();
323     if (!text) {
324       delete[] str;
325       return SPV_ERROR_OUT_OF_MEMORY;
326     }
327     text->str = str;
328     text->length = length;
329     *text_result = text;
330   }
331   return SPV_SUCCESS;
332 }
333 
DisassembleHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)334 spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
335                                uint32_t /* magic */, uint32_t version,
336                                uint32_t generator, uint32_t id_bound,
337                                uint32_t schema) {
338   assert(user_data);
339   auto disassembler = static_cast<Disassembler*>(user_data);
340   return disassembler->HandleHeader(endian, version, generator, id_bound,
341                                     schema);
342 }
343 
DisassembleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)344 spv_result_t DisassembleInstruction(
345     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
346   assert(user_data);
347   auto disassembler = static_cast<Disassembler*>(user_data);
348   return disassembler->HandleInstruction(*parsed_instruction);
349 }
350 
351 }  // anonymous namespace
352 
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)353 spv_result_t spvBinaryToText(const spv_const_context context,
354                              const uint32_t* code, const size_t wordCount,
355                              const uint32_t options, spv_text* pText,
356                              spv_diagnostic* pDiagnostic) {
357   spv_context_t hijack_context = *context;
358   if (pDiagnostic) {
359     *pDiagnostic = nullptr;
360     libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
361   }
362 
363   const libspirv::AssemblyGrammar grammar(&hijack_context);
364   if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
365 
366   // Generate friendly names for Ids if requested.
367   std::unique_ptr<libspirv::FriendlyNameMapper> friendly_mapper;
368   libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper();
369   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
370     friendly_mapper.reset(
371         new libspirv::FriendlyNameMapper(&hijack_context, code, wordCount));
372     name_mapper = friendly_mapper->GetNameMapper();
373   }
374 
375   // Now disassemble!
376   Disassembler disassembler(grammar, options, name_mapper);
377   if (auto error = spvBinaryParse(&hijack_context, &disassembler, code,
378                                   wordCount, DisassembleHeader,
379                                   DisassembleInstruction, pDiagnostic)) {
380     return error;
381   }
382 
383   return disassembler.SaveTextResult(pText);
384 }
385