1// Copyright (c) 2015-2016 The Khronos Group Inc.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and/or associated documentation files (the
5// "Materials"), to deal in the Materials without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Materials, and to
8// permit persons to whom the Materials are furnished to do so, subject to
9// the following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Materials.
13//
14// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
15// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
16// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
17//    https://www.khronos.org/registry/
18//
19// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
26
27// This file contains a disassembler:  It converts a SPIR-V binary
28// to text.
29
30#include <algorithm>
31#include <cassert>
32#include <cstring>
33#include <iomanip>
34#include <memory>
35#include <unordered_map>
36
37#include "assembly_grammar.h"
38#include "binary.h"
39#include "diagnostic.h"
40#include "ext_inst.h"
41#include "name_mapper.h"
42#include "opcode.h"
43#include "print.h"
44#include "spirv-tools/libspirv.h"
45#include "spirv_constant.h"
46#include "spirv_endian.h"
47#include "util/hex_float.h"
48
49namespace {
50
51// A Disassembler instance converts a SPIR-V binary to its assembly
52// representation.
53class Disassembler {
54 public:
55  Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options,
56               libspirv::NameMapper name_mapper)
57      : grammar_(grammar),
58        print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
59        color_(print_ &&
60               spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
61        indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
62                    ? kStandardIndent
63                    : 0),
64        text_(),
65        out_(print_ ? out_stream() : out_stream(text_)),
66        stream_(out_.get()),
67        header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
68        show_byte_offset_(spvIsInBitfield(
69            SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
70        byte_offset_(0),
71        name_mapper_(std::move(name_mapper)) {}
72
73  // Emits the assembly header for the module, and sets up internal state
74  // so subsequent callbacks can handle the cases where the entire module
75  // is either big-endian or little-endian.
76  spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version,
77                            uint32_t generator, uint32_t id_bound,
78                            uint32_t schema);
79  // Emits the assembly text for the given instruction.
80  spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
81
82  // If not printing, populates text_result with the accumulated text.
83  // Returns SPV_SUCCESS on success.
84  spv_result_t SaveTextResult(spv_text* text_result) const;
85
86 private:
87  enum { kStandardIndent = 15 };
88
89  using out_stream = libspirv::out_stream;
90
91  // Emits an operand for the given instruction, where the instruction
92  // is at offset words from the start of the binary.
93  void EmitOperand(const spv_parsed_instruction_t& inst,
94                   const uint16_t operand_index);
95
96  // Emits a mask expression for the given mask word of the specified type.
97  void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
98
99  // Resets the output color, if color is turned on.
100  void ResetColor() {
101    if (color_) out_.get() << libspirv::clr::reset();
102  }
103  // Sets the output to grey, if color is turned on.
104  void SetGrey() {
105    if (color_) out_.get() << libspirv::clr::grey();
106  }
107  // Sets the output to blue, if color is turned on.
108  void SetBlue() {
109    if (color_) out_.get() << libspirv::clr::blue();
110  }
111  // Sets the output to yellow, if color is turned on.
112  void SetYellow() {
113    if (color_) out_.get() << libspirv::clr::yellow();
114  }
115  // Sets the output to red, if color is turned on.
116  void SetRed() {
117    if (color_) out_.get() << libspirv::clr::red();
118  }
119  // Sets the output to green, if color is turned on.
120  void SetGreen() {
121    if (color_) out_.get() << libspirv::clr::green();
122  }
123
124  const libspirv::AssemblyGrammar& grammar_;
125  const bool print_;  // Should we also print to the standard output stream?
126  const bool color_;  // Should we print in colour?
127  const int indent_;  // How much to indent. 0 means don't indent
128  spv_endianness_t endian_;  // The detected endianness of the binary.
129  std::stringstream text_;   // Captures the text, if not printing.
130  out_stream out_;  // The Output stream.  Either to text_ or standard output.
131  std::ostream& stream_;  // The output std::stream.
132  const bool header_;     // Should we output header as the leading comment?
133  const bool show_byte_offset_;  // Should we print byte offset, in hex?
134  size_t byte_offset_;           // The number of bytes processed so far.
135  libspirv::NameMapper name_mapper_;
136};
137
138spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
139                                        uint32_t version, uint32_t generator,
140                                        uint32_t id_bound, uint32_t schema) {
141  endian_ = endian;
142
143  if (header_) {
144    SetGrey();
145    const char* generator_tool =
146        spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
147    stream_ << "; SPIR-V\n"
148            << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
149            << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n"
150            << "; Generator: " << generator_tool;
151    // For unknown tools, print the numeric tool value.
152    if (0 == strcmp("Unknown", generator_tool)) {
153      stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
154    }
155    // Print the miscellaneous part of the generator word on the same
156    // line as the tool name.
157    stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
158            << "; Bound: " << id_bound << "\n"
159            << "; Schema: " << schema << "\n";
160    ResetColor();
161  }
162
163  byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
164
165  return SPV_SUCCESS;
166}
167
168spv_result_t Disassembler::HandleInstruction(
169    const spv_parsed_instruction_t& inst) {
170  if (inst.result_id) {
171    SetBlue();
172    const std::string id_name = name_mapper_(inst.result_id);
173    if (indent_)
174      stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
175    stream_ << "%" << id_name;
176    ResetColor();
177    stream_ << " = ";
178  } else {
179    stream_ << std::string(indent_, ' ');
180  }
181
182  stream_ << "Op" << spvOpcodeString(static_cast<SpvOp>(inst.opcode));
183
184  for (uint16_t i = 0; i < inst.num_operands; i++) {
185    const spv_operand_type_t type = inst.operands[i].type;
186    assert(type != SPV_OPERAND_TYPE_NONE);
187    if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
188    stream_ << " ";
189    EmitOperand(inst, i);
190  }
191
192  if (show_byte_offset_) {
193    SetGrey();
194    auto saved_flags = stream_.flags();
195    auto saved_fill = stream_.fill();
196    stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
197            << byte_offset_;
198    stream_.flags(saved_flags);
199    stream_.fill(saved_fill);
200    ResetColor();
201  }
202
203  byte_offset_ += inst.num_words * sizeof(uint32_t);
204
205  stream_ << "\n";
206  return SPV_SUCCESS;
207}
208
209void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
210                               const uint16_t operand_index) {
211  assert(operand_index < inst.num_operands);
212  const spv_parsed_operand_t& operand = inst.operands[operand_index];
213  const uint32_t word = inst.words[operand.offset];
214  switch (operand.type) {
215    case SPV_OPERAND_TYPE_RESULT_ID:
216      assert(false && "<result-id> is not supposed to be handled here");
217      SetBlue();
218      stream_ << "%" << name_mapper_(word);
219      break;
220    case SPV_OPERAND_TYPE_ID:
221    case SPV_OPERAND_TYPE_TYPE_ID:
222    case SPV_OPERAND_TYPE_SCOPE_ID:
223    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
224      SetYellow();
225      stream_ << "%" << name_mapper_(word);
226      break;
227    case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
228      spv_ext_inst_desc ext_inst;
229      if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst))
230        assert(false && "should have caught this earlier");
231      SetRed();
232      stream_ << ext_inst->name;
233    } break;
234    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
235      spv_opcode_desc opcode_desc;
236      if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
237        assert(false && "should have caught this earlier");
238      SetRed();
239      stream_ << opcode_desc->name;
240    } break;
241    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
242    case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
243      SetRed();
244      if (operand.num_words == 1) {
245        switch (operand.number_kind) {
246          case SPV_NUMBER_SIGNED_INT:
247            stream_ << int32_t(word);
248            break;
249          case SPV_NUMBER_UNSIGNED_INT:
250            stream_ << word;
251            break;
252          case SPV_NUMBER_FLOATING:
253            if (operand.number_bit_width == 16) {
254              stream_ << spvutils::FloatProxy<spvutils::Float16>(
255                  uint16_t(word & 0xFFFF));
256            } else {
257              // Assume 32-bit floats.
258              stream_ << spvutils::FloatProxy<float>(word);
259            }
260            break;
261          default:
262            assert(false && "Unreachable");
263        }
264      } else if (operand.num_words == 2) {
265        // Multi-word numbers are presented with lower order words first.
266        uint64_t bits =
267            uint64_t(word) | (uint64_t(inst.words[operand.offset + 1]) << 32);
268        switch (operand.number_kind) {
269          case SPV_NUMBER_SIGNED_INT:
270            stream_ << int64_t(bits);
271            break;
272          case SPV_NUMBER_UNSIGNED_INT:
273            stream_ << bits;
274            break;
275          case SPV_NUMBER_FLOATING:
276            // Assume only 64-bit floats.
277            stream_ << spvutils::FloatProxy<double>(bits);
278            break;
279          default:
280            assert(false && "Unreachable");
281        }
282      } else {
283        // TODO(dneto): Support more than 64-bits at a time.
284        assert(false && "Unhandled");
285      }
286    } break;
287    case SPV_OPERAND_TYPE_LITERAL_STRING: {
288      stream_ << "\"";
289      SetGreen();
290      // Strings are always little-endian, and null-terminated.
291      // Write out the characters, escaping as needed, and without copying
292      // the entire string.
293      auto c_str = reinterpret_cast<const char*>(inst.words + operand.offset);
294      for (auto p = c_str; *p; ++p) {
295        if (*p == '"' || *p == '\\') stream_ << '\\';
296        stream_ << *p;
297      }
298      ResetColor();
299      stream_ << '"';
300    } break;
301    case SPV_OPERAND_TYPE_CAPABILITY:
302    case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
303    case SPV_OPERAND_TYPE_EXECUTION_MODEL:
304    case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
305    case SPV_OPERAND_TYPE_MEMORY_MODEL:
306    case SPV_OPERAND_TYPE_EXECUTION_MODE:
307    case SPV_OPERAND_TYPE_STORAGE_CLASS:
308    case SPV_OPERAND_TYPE_DIMENSIONALITY:
309    case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
310    case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
311    case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
312    case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
313    case SPV_OPERAND_TYPE_LINKAGE_TYPE:
314    case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
315    case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
316    case SPV_OPERAND_TYPE_DECORATION:
317    case SPV_OPERAND_TYPE_BUILT_IN:
318    case SPV_OPERAND_TYPE_GROUP_OPERATION:
319    case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
320    case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: {
321      spv_operand_desc entry;
322      if (grammar_.lookupOperand(operand.type, word, &entry))
323        assert(false && "should have caught this earlier");
324      stream_ << entry->name;
325    } break;
326    case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
327    case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
328    case SPV_OPERAND_TYPE_LOOP_CONTROL:
329    case SPV_OPERAND_TYPE_IMAGE:
330    case SPV_OPERAND_TYPE_MEMORY_ACCESS:
331    case SPV_OPERAND_TYPE_SELECTION_CONTROL:
332      EmitMaskOperand(operand.type, word);
333      break;
334    default:
335      assert(false && "unhandled or invalid case");
336  }
337  ResetColor();
338}
339
340void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
341                                   const uint32_t word) {
342  // Scan the mask from least significant bit to most significant bit.  For each
343  // set bit, emit the name of that bit. Separate multiple names with '|'.
344  uint32_t remaining_word = word;
345  uint32_t mask;
346  int num_emitted = 0;
347  for (mask = 1; remaining_word; mask <<= 1) {
348    if (remaining_word & mask) {
349      remaining_word ^= mask;
350      spv_operand_desc entry;
351      if (grammar_.lookupOperand(type, mask, &entry))
352        assert(false && "should have caught this earlier");
353      if (num_emitted) stream_ << "|";
354      stream_ << entry->name;
355      num_emitted++;
356    }
357  }
358  if (!num_emitted) {
359    // An operand value of 0 was provided, so represent it by the name
360    // of the 0 value. In many cases, that's "None".
361    spv_operand_desc entry;
362    if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
363      stream_ << entry->name;
364  }
365}
366
367spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
368  if (!print_) {
369    size_t length = text_.str().size();
370    char* str = new char[length + 1];
371    if (!str) return SPV_ERROR_OUT_OF_MEMORY;
372    strncpy(str, text_.str().c_str(), length + 1);
373    spv_text text = new spv_text_t();
374    if (!text) {
375      delete[] str;
376      return SPV_ERROR_OUT_OF_MEMORY;
377    }
378    text->str = str;
379    text->length = length;
380    *text_result = text;
381  }
382  return SPV_SUCCESS;
383}
384
385spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
386                               uint32_t /* magic */, uint32_t version,
387                               uint32_t generator, uint32_t id_bound,
388                               uint32_t schema) {
389  assert(user_data);
390  auto disassembler = static_cast<Disassembler*>(user_data);
391  return disassembler->HandleHeader(endian, version, generator, id_bound,
392                                    schema);
393}
394
395spv_result_t DisassembleInstruction(
396    void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
397  assert(user_data);
398  auto disassembler = static_cast<Disassembler*>(user_data);
399  return disassembler->HandleInstruction(*parsed_instruction);
400}
401
402}  // anonymous namespace
403
404spv_result_t spvBinaryToText(const spv_const_context context,
405                             const uint32_t* code, const size_t wordCount,
406                             const uint32_t options, spv_text* pText,
407                             spv_diagnostic* pDiagnostic) {
408  // Invalid arguments return error codes, but don't necessarily generate
409  // diagnostics.  These are programmer errors, not user errors.
410  if (!pDiagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
411  const libspirv::AssemblyGrammar grammar(context);
412  if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
413
414  // Generate friendly names for Ids if requested.
415  std::unique_ptr<libspirv::FriendlyNameMapper> friendly_mapper;
416  libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper();
417  if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
418    friendly_mapper.reset(
419        new libspirv::FriendlyNameMapper(context, code, wordCount));
420    name_mapper = friendly_mapper->GetNameMapper();
421  }
422
423  // Now disassemble!
424  Disassembler disassembler(grammar, options, name_mapper);
425  if (auto error = spvBinaryParse(context, &disassembler, code, wordCount,
426                                  DisassembleHeader, DisassembleInstruction,
427                                  pDiagnostic)) {
428    return error;
429  }
430
431  return disassembler.SaveTextResult(pText);
432}
433