Home | History | Annotate | Download | only in source
      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:
     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.
     89   void ResetColor() {
     90     if (color_) out_.get() << libspirv::clr::reset();
     91   }
     92   // Sets the output to grey, if color is turned on.
     93   void SetGrey() {
     94     if (color_) out_.get() << libspirv::clr::grey();
     95   }
     96   // Sets the output to blue, if color is turned on.
     97   void SetBlue() {
     98     if (color_) out_.get() << libspirv::clr::blue();
     99   }
    100   // Sets the output to yellow, if color is turned on.
    101   void SetYellow() {
    102     if (color_) out_.get() << libspirv::clr::yellow();
    103   }
    104   // Sets the output to red, if color is turned on.
    105   void SetRed() {
    106     if (color_) out_.get() << libspirv::clr::red();
    107   }
    108   // Sets the output to green, if color is turned on.
    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 
    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 
    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 
    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 
    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 
    316 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 
    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 
    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 
    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