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