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