• 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 "source/disassemble.h"
21 
22 #include <algorithm>
23 #include <cassert>
24 #include <cstring>
25 #include <iomanip>
26 #include <memory>
27 #include <unordered_map>
28 #include <utility>
29 
30 #include "source/assembly_grammar.h"
31 #include "source/binary.h"
32 #include "source/diagnostic.h"
33 #include "source/ext_inst.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 spvtools {
44 namespace {
45 
46 // A Disassembler instance converts a SPIR-V binary to its assembly
47 // representation.
48 class Disassembler {
49  public:
Disassembler(const AssemblyGrammar & grammar,uint32_t options,NameMapper name_mapper)50   Disassembler(const AssemblyGrammar& grammar, uint32_t options,
51                NameMapper name_mapper)
52       : print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
53         text_(),
54         out_(print_ ? out_stream() : out_stream(text_)),
55         instruction_disassembler_(grammar, out_.get(), options, name_mapper),
56         header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
57         byte_offset_(0) {}
58 
59   // Emits the assembly header for the module, and sets up internal state
60   // so subsequent callbacks can handle the cases where the entire module
61   // is either big-endian or little-endian.
62   spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version,
63                             uint32_t generator, uint32_t id_bound,
64                             uint32_t schema);
65   // Emits the assembly text for the given instruction.
66   spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
67 
68   // If not printing, populates text_result with the accumulated text.
69   // Returns SPV_SUCCESS on success.
70   spv_result_t SaveTextResult(spv_text* text_result) const;
71 
72  private:
73   const bool print_;  // Should we also print to the standard output stream?
74   spv_endianness_t endian_;  // The detected endianness of the binary.
75   std::stringstream text_;   // Captures the text, if not printing.
76   out_stream out_;  // The Output stream.  Either to text_ or standard output.
77   disassemble::InstructionDisassembler instruction_disassembler_;
78   const bool header_;   // Should we output header as the leading comment?
79   size_t byte_offset_;  // The number of bytes processed so far.
80   bool inserted_decoration_space_ = false;
81   bool inserted_debug_space_ = false;
82   bool inserted_type_space_ = false;
83 };
84 
HandleHeader(spv_endianness_t endian,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)85 spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
86                                         uint32_t version, uint32_t generator,
87                                         uint32_t id_bound, uint32_t schema) {
88   endian_ = endian;
89 
90   if (header_) {
91     instruction_disassembler_.EmitHeaderSpirv();
92     instruction_disassembler_.EmitHeaderVersion(version);
93     instruction_disassembler_.EmitHeaderGenerator(generator);
94     instruction_disassembler_.EmitHeaderIdBound(id_bound);
95     instruction_disassembler_.EmitHeaderSchema(schema);
96   }
97 
98   byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
99 
100   return SPV_SUCCESS;
101 }
102 
HandleInstruction(const spv_parsed_instruction_t & inst)103 spv_result_t Disassembler::HandleInstruction(
104     const spv_parsed_instruction_t& inst) {
105   instruction_disassembler_.EmitSectionComment(inst, inserted_decoration_space_,
106                                                inserted_debug_space_,
107                                                inserted_type_space_);
108 
109   instruction_disassembler_.EmitInstruction(inst, byte_offset_);
110 
111   byte_offset_ += inst.num_words * sizeof(uint32_t);
112 
113   return SPV_SUCCESS;
114 }
115 
SaveTextResult(spv_text * text_result) const116 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
117   if (!print_) {
118     size_t length = text_.str().size();
119     char* str = new char[length + 1];
120     if (!str) return SPV_ERROR_OUT_OF_MEMORY;
121     strncpy(str, text_.str().c_str(), length + 1);
122     spv_text text = new spv_text_t();
123     if (!text) {
124       delete[] str;
125       return SPV_ERROR_OUT_OF_MEMORY;
126     }
127     text->str = str;
128     text->length = length;
129     *text_result = text;
130   }
131   return SPV_SUCCESS;
132 }
133 
DisassembleHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)134 spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
135                                uint32_t /* magic */, uint32_t version,
136                                uint32_t generator, uint32_t id_bound,
137                                uint32_t schema) {
138   assert(user_data);
139   auto disassembler = static_cast<Disassembler*>(user_data);
140   return disassembler->HandleHeader(endian, version, generator, id_bound,
141                                     schema);
142 }
143 
DisassembleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)144 spv_result_t DisassembleInstruction(
145     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
146   assert(user_data);
147   auto disassembler = static_cast<Disassembler*>(user_data);
148   return disassembler->HandleInstruction(*parsed_instruction);
149 }
150 
151 // Simple wrapper class to provide extra data necessary for targeted
152 // instruction disassembly.
153 class WrappedDisassembler {
154  public:
WrappedDisassembler(Disassembler * dis,const uint32_t * binary,size_t wc)155   WrappedDisassembler(Disassembler* dis, const uint32_t* binary, size_t wc)
156       : disassembler_(dis), inst_binary_(binary), word_count_(wc) {}
157 
disassembler()158   Disassembler* disassembler() { return disassembler_; }
inst_binary() const159   const uint32_t* inst_binary() const { return inst_binary_; }
word_count() const160   size_t word_count() const { return word_count_; }
161 
162  private:
163   Disassembler* disassembler_;
164   const uint32_t* inst_binary_;
165   const size_t word_count_;
166 };
167 
DisassembleTargetHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)168 spv_result_t DisassembleTargetHeader(void* user_data, spv_endianness_t endian,
169                                      uint32_t /* magic */, uint32_t version,
170                                      uint32_t generator, uint32_t id_bound,
171                                      uint32_t schema) {
172   assert(user_data);
173   auto wrapped = static_cast<WrappedDisassembler*>(user_data);
174   return wrapped->disassembler()->HandleHeader(endian, version, generator,
175                                                id_bound, schema);
176 }
177 
DisassembleTargetInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)178 spv_result_t DisassembleTargetInstruction(
179     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
180   assert(user_data);
181   auto wrapped = static_cast<WrappedDisassembler*>(user_data);
182   // Check if this is the instruction we want to disassemble.
183   if (wrapped->word_count() == parsed_instruction->num_words &&
184       std::equal(wrapped->inst_binary(),
185                  wrapped->inst_binary() + wrapped->word_count(),
186                  parsed_instruction->words)) {
187     // Found the target instruction. Disassemble it and signal that we should
188     // stop searching so we don't output the same instruction again.
189     if (auto error =
190             wrapped->disassembler()->HandleInstruction(*parsed_instruction))
191       return error;
192     return SPV_REQUESTED_TERMINATION;
193   }
194   return SPV_SUCCESS;
195 }
196 
GetLineLengthWithoutColor(const std::string line)197 uint32_t GetLineLengthWithoutColor(const std::string line) {
198   // Currently, every added color is in the form \x1b...m, so instead of doing a
199   // lot of string comparisons with spvtools::clr::* strings, we just ignore
200   // those ranges.
201   uint32_t length = 0;
202   for (size_t i = 0; i < line.size(); ++i) {
203     if (line[i] == '\x1b') {
204       do {
205         ++i;
206       } while (line[i] != 'm');
207       continue;
208     }
209 
210     ++length;
211   }
212 
213   return length;
214 }
215 
216 constexpr int kStandardIndent = 15;
217 constexpr uint32_t kCommentColumn = 50;
218 }  // namespace
219 
220 namespace disassemble {
InstructionDisassembler(const AssemblyGrammar & grammar,std::ostream & stream,uint32_t options,NameMapper name_mapper)221 InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
222                                                  std::ostream& stream,
223                                                  uint32_t options,
224                                                  NameMapper name_mapper)
225     : grammar_(grammar),
226       stream_(stream),
227       print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
228       color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
229       indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
230                   ? kStandardIndent
231                   : 0),
232       comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
233       show_byte_offset_(
234           spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
235       name_mapper_(std::move(name_mapper)),
236       last_instruction_comment_alignment_(0) {}
237 
EmitHeaderSpirv()238 void InstructionDisassembler::EmitHeaderSpirv() { stream_ << "; SPIR-V\n"; }
239 
EmitHeaderVersion(uint32_t version)240 void InstructionDisassembler::EmitHeaderVersion(uint32_t version) {
241   stream_ << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
242           << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n";
243 }
244 
EmitHeaderGenerator(uint32_t generator)245 void InstructionDisassembler::EmitHeaderGenerator(uint32_t generator) {
246   const char* generator_tool =
247       spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
248   stream_ << "; Generator: " << generator_tool;
249   // For unknown tools, print the numeric tool value.
250   if (0 == strcmp("Unknown", generator_tool)) {
251     stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
252   }
253   // Print the miscellaneous part of the generator word on the same
254   // line as the tool name.
255   stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n";
256 }
257 
EmitHeaderIdBound(uint32_t id_bound)258 void InstructionDisassembler::EmitHeaderIdBound(uint32_t id_bound) {
259   stream_ << "; Bound: " << id_bound << "\n";
260 }
261 
EmitHeaderSchema(uint32_t schema)262 void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
263   stream_ << "; Schema: " << schema << "\n";
264 }
265 
EmitInstruction(const spv_parsed_instruction_t & inst,size_t inst_byte_offset)266 void InstructionDisassembler::EmitInstruction(
267     const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
268   auto opcode = static_cast<spv::Op>(inst.opcode);
269 
270   // To better align the comments (if any), write the instruction to a line
271   // first so its length can be readily available.
272   std::ostringstream line;
273 
274   if (inst.result_id) {
275     SetBlue();
276     const std::string id_name = name_mapper_(inst.result_id);
277     if (indent_)
278       line << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
279     line << "%" << id_name;
280     ResetColor();
281     line << " = ";
282   } else {
283     line << std::string(indent_, ' ');
284   }
285 
286   line << "Op" << spvOpcodeString(opcode);
287 
288   for (uint16_t i = 0; i < inst.num_operands; i++) {
289     const spv_operand_type_t type = inst.operands[i].type;
290     assert(type != SPV_OPERAND_TYPE_NONE);
291     if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
292     line << " ";
293     EmitOperand(line, inst, i);
294   }
295 
296   // For the sake of comment generation, store information from some
297   // instructions for the future.
298   if (comment_) {
299     GenerateCommentForDecoratedId(inst);
300   }
301 
302   std::ostringstream comments;
303   const char* comment_separator = "";
304 
305   if (show_byte_offset_) {
306     SetGrey(comments);
307     auto saved_flags = comments.flags();
308     auto saved_fill = comments.fill();
309     comments << comment_separator << "0x" << std::setw(8) << std::hex
310              << std::setfill('0') << inst_byte_offset;
311     comments.flags(saved_flags);
312     comments.fill(saved_fill);
313     ResetColor(comments);
314     comment_separator = ", ";
315   }
316 
317   if (comment_ && opcode == spv::Op::OpName) {
318     const spv_parsed_operand_t& operand = inst.operands[0];
319     const uint32_t word = inst.words[operand.offset];
320     comments << comment_separator << "id %" << word;
321     comment_separator = ", ";
322   }
323 
324   if (comment_ && inst.result_id && id_comments_.count(inst.result_id) > 0) {
325     comments << comment_separator << id_comments_[inst.result_id].str();
326     comment_separator = ", ";
327   }
328 
329   stream_ << line.str();
330 
331   if (!comments.str().empty()) {
332     // Align the comments
333     const uint32_t line_length = GetLineLengthWithoutColor(line.str());
334     uint32_t align = std::max(
335         {line_length + 2, last_instruction_comment_alignment_, kCommentColumn});
336     // Round up the alignment to a multiple of 4 for more niceness.
337     align = (align + 3) & ~0x3u;
338     last_instruction_comment_alignment_ = align;
339 
340     stream_ << std::string(align - line_length, ' ') << "; " << comments.str();
341   } else {
342     last_instruction_comment_alignment_ = 0;
343   }
344 
345   stream_ << "\n";
346 }
347 
GenerateCommentForDecoratedId(const spv_parsed_instruction_t & inst)348 void InstructionDisassembler::GenerateCommentForDecoratedId(
349     const spv_parsed_instruction_t& inst) {
350   assert(comment_);
351   auto opcode = static_cast<spv::Op>(inst.opcode);
352 
353   std::ostringstream partial;
354   uint32_t id = 0;
355   const char* separator = "";
356 
357   switch (opcode) {
358     case spv::Op::OpDecorate:
359       // Take everything after `OpDecorate %id` and associate it with id.
360       id = inst.words[inst.operands[0].offset];
361       for (uint16_t i = 1; i < inst.num_operands; i++) {
362         partial << separator;
363         separator = " ";
364         EmitOperand(partial, inst, i);
365       }
366       break;
367     default:
368       break;
369   }
370 
371   if (id == 0) {
372     return;
373   }
374 
375   // Add the new comment to the comments of this id
376   std::ostringstream& id_comment = id_comments_[id];
377   if (!id_comment.str().empty()) {
378     id_comment << ", ";
379   }
380   id_comment << partial.str();
381 }
382 
EmitSectionComment(const spv_parsed_instruction_t & inst,bool & inserted_decoration_space,bool & inserted_debug_space,bool & inserted_type_space)383 void InstructionDisassembler::EmitSectionComment(
384     const spv_parsed_instruction_t& inst, bool& inserted_decoration_space,
385     bool& inserted_debug_space, bool& inserted_type_space) {
386   auto opcode = static_cast<spv::Op>(inst.opcode);
387   if (comment_ && opcode == spv::Op::OpFunction) {
388     stream_ << std::endl;
389     stream_ << std::string(indent_, ' ');
390     stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
391   }
392   if (comment_ && !inserted_decoration_space && spvOpcodeIsDecoration(opcode)) {
393     inserted_decoration_space = true;
394     stream_ << std::endl;
395     stream_ << std::string(indent_, ' ');
396     stream_ << "; Annotations" << std::endl;
397   }
398   if (comment_ && !inserted_debug_space && spvOpcodeIsDebug(opcode)) {
399     inserted_debug_space = true;
400     stream_ << std::endl;
401     stream_ << std::string(indent_, ' ');
402     stream_ << "; Debug Information" << std::endl;
403   }
404   if (comment_ && !inserted_type_space && spvOpcodeGeneratesType(opcode)) {
405     inserted_type_space = true;
406     stream_ << std::endl;
407     stream_ << std::string(indent_, ' ');
408     stream_ << "; Types, variables and constants" << std::endl;
409   }
410 }
411 
EmitOperand(std::ostream & stream,const spv_parsed_instruction_t & inst,const uint16_t operand_index) const412 void InstructionDisassembler::EmitOperand(std::ostream& stream,
413                                           const spv_parsed_instruction_t& inst,
414                                           const uint16_t operand_index) const {
415   assert(operand_index < inst.num_operands);
416   const spv_parsed_operand_t& operand = inst.operands[operand_index];
417   const uint32_t word = inst.words[operand.offset];
418   switch (operand.type) {
419     case SPV_OPERAND_TYPE_RESULT_ID:
420       assert(false && "<result-id> is not supposed to be handled here");
421       SetBlue(stream);
422       stream << "%" << name_mapper_(word);
423       break;
424     case SPV_OPERAND_TYPE_ID:
425     case SPV_OPERAND_TYPE_TYPE_ID:
426     case SPV_OPERAND_TYPE_SCOPE_ID:
427     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
428       SetYellow(stream);
429       stream << "%" << name_mapper_(word);
430       break;
431     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
432       spv_ext_inst_desc ext_inst;
433       SetRed(stream);
434       if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
435           SPV_SUCCESS) {
436         stream << ext_inst->name;
437       } else {
438         if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
439           assert(false && "should have caught this earlier");
440         } else {
441           // for non-semantic instruction sets we can just print the number
442           stream << word;
443         }
444       }
445     } break;
446     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
447       spv_opcode_desc opcode_desc;
448       if (grammar_.lookupOpcode(spv::Op(word), &opcode_desc))
449         assert(false && "should have caught this earlier");
450       SetRed(stream);
451       stream << opcode_desc->name;
452     } break;
453     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
454     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
455     case SPV_OPERAND_TYPE_LITERAL_FLOAT: {
456       SetRed(stream);
457       EmitNumericLiteral(&stream, inst, operand);
458       ResetColor(stream);
459     } break;
460     case SPV_OPERAND_TYPE_LITERAL_STRING: {
461       stream << "\"";
462       SetGreen(stream);
463 
464       std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
465       for (char const& c : str) {
466         if (c == '"' || c == '\\') stream << '\\';
467         stream << c;
468       }
469       ResetColor(stream);
470       stream << '"';
471     } break;
472     case SPV_OPERAND_TYPE_CAPABILITY:
473     case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
474     case SPV_OPERAND_TYPE_EXECUTION_MODEL:
475     case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
476     case SPV_OPERAND_TYPE_MEMORY_MODEL:
477     case SPV_OPERAND_TYPE_EXECUTION_MODE:
478     case SPV_OPERAND_TYPE_STORAGE_CLASS:
479     case SPV_OPERAND_TYPE_DIMENSIONALITY:
480     case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
481     case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
482     case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
483     case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
484     case SPV_OPERAND_TYPE_LINKAGE_TYPE:
485     case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
486     case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
487     case SPV_OPERAND_TYPE_DECORATION:
488     case SPV_OPERAND_TYPE_BUILT_IN:
489     case SPV_OPERAND_TYPE_GROUP_OPERATION:
490     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
491     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
492     case SPV_OPERAND_TYPE_RAY_FLAGS:
493     case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
494     case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
495     case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
496     case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
497     case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
498     case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
499     case SPV_OPERAND_TYPE_DEBUG_OPERATION:
500     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
501     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
502     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
503     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
504     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
505     case SPV_OPERAND_TYPE_FPDENORM_MODE:
506     case SPV_OPERAND_TYPE_FPOPERATION_MODE:
507     case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
508     case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
509       spv_operand_desc entry;
510       if (grammar_.lookupOperand(operand.type, word, &entry))
511         assert(false && "should have caught this earlier");
512       stream << entry->name;
513     } break;
514     case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
515     case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
516     case SPV_OPERAND_TYPE_LOOP_CONTROL:
517     case SPV_OPERAND_TYPE_IMAGE:
518     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
519     case SPV_OPERAND_TYPE_SELECTION_CONTROL:
520     case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
521     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
522     case SPV_OPERAND_TYPE_RAW_ACCESS_CHAIN_OPERANDS:
523       EmitMaskOperand(stream, operand.type, word);
524       break;
525     default:
526       if (spvOperandIsConcreteMask(operand.type)) {
527         EmitMaskOperand(stream, operand.type, word);
528       } else if (spvOperandIsConcrete(operand.type)) {
529         spv_operand_desc entry;
530         if (grammar_.lookupOperand(operand.type, word, &entry))
531           assert(false && "should have caught this earlier");
532         stream << entry->name;
533       } else {
534         assert(false && "unhandled or invalid case");
535       }
536       break;
537   }
538   ResetColor(stream);
539 }
540 
EmitMaskOperand(std::ostream & stream,const spv_operand_type_t type,const uint32_t word) const541 void InstructionDisassembler::EmitMaskOperand(std::ostream& stream,
542                                               const spv_operand_type_t type,
543                                               const uint32_t word) const {
544   // Scan the mask from least significant bit to most significant bit.  For each
545   // set bit, emit the name of that bit. Separate multiple names with '|'.
546   uint32_t remaining_word = word;
547   uint32_t mask;
548   int num_emitted = 0;
549   for (mask = 1; remaining_word; mask <<= 1) {
550     if (remaining_word & mask) {
551       remaining_word ^= mask;
552       spv_operand_desc entry;
553       if (grammar_.lookupOperand(type, mask, &entry))
554         assert(false && "should have caught this earlier");
555       if (num_emitted) stream << "|";
556       stream << entry->name;
557       num_emitted++;
558     }
559   }
560   if (!num_emitted) {
561     // An operand value of 0 was provided, so represent it by the name
562     // of the 0 value. In many cases, that's "None".
563     spv_operand_desc entry;
564     if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
565       stream << entry->name;
566   }
567 }
568 
ResetColor(std::ostream & stream) const569 void InstructionDisassembler::ResetColor(std::ostream& stream) const {
570   if (color_) stream << spvtools::clr::reset{print_};
571 }
SetGrey(std::ostream & stream) const572 void InstructionDisassembler::SetGrey(std::ostream& stream) const {
573   if (color_) stream << spvtools::clr::grey{print_};
574 }
SetBlue(std::ostream & stream) const575 void InstructionDisassembler::SetBlue(std::ostream& stream) const {
576   if (color_) stream << spvtools::clr::blue{print_};
577 }
SetYellow(std::ostream & stream) const578 void InstructionDisassembler::SetYellow(std::ostream& stream) const {
579   if (color_) stream << spvtools::clr::yellow{print_};
580 }
SetRed(std::ostream & stream) const581 void InstructionDisassembler::SetRed(std::ostream& stream) const {
582   if (color_) stream << spvtools::clr::red{print_};
583 }
SetGreen(std::ostream & stream) const584 void InstructionDisassembler::SetGreen(std::ostream& stream) const {
585   if (color_) stream << spvtools::clr::green{print_};
586 }
587 
ResetColor()588 void InstructionDisassembler::ResetColor() { ResetColor(stream_); }
SetGrey()589 void InstructionDisassembler::SetGrey() { SetGrey(stream_); }
SetBlue()590 void InstructionDisassembler::SetBlue() { SetBlue(stream_); }
SetYellow()591 void InstructionDisassembler::SetYellow() { SetYellow(stream_); }
SetRed()592 void InstructionDisassembler::SetRed() { SetRed(stream_); }
SetGreen()593 void InstructionDisassembler::SetGreen() { SetGreen(stream_); }
594 }  // namespace disassemble
595 
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)596 std::string spvInstructionBinaryToText(const spv_target_env env,
597                                        const uint32_t* instCode,
598                                        const size_t instWordCount,
599                                        const uint32_t* code,
600                                        const size_t wordCount,
601                                        const uint32_t options) {
602   spv_context context = spvContextCreate(env);
603   const AssemblyGrammar grammar(context);
604   if (!grammar.isValid()) {
605     spvContextDestroy(context);
606     return "";
607   }
608 
609   // Generate friendly names for Ids if requested.
610   std::unique_ptr<FriendlyNameMapper> friendly_mapper;
611   NameMapper name_mapper = GetTrivialNameMapper();
612   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
613     friendly_mapper = MakeUnique<FriendlyNameMapper>(context, code, wordCount);
614     name_mapper = friendly_mapper->GetNameMapper();
615   }
616 
617   // Now disassemble!
618   Disassembler disassembler(grammar, options, name_mapper);
619   WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
620   spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
621                  DisassembleTargetInstruction, nullptr);
622 
623   spv_text text = nullptr;
624   std::string output;
625   if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
626     output.assign(text->str, text->str + text->length);
627     // Drop trailing newline characters.
628     while (!output.empty() && output.back() == '\n') output.pop_back();
629   }
630   spvTextDestroy(text);
631   spvContextDestroy(context);
632 
633   return output;
634 }
635 }  // namespace spvtools
636 
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)637 spv_result_t spvBinaryToText(const spv_const_context context,
638                              const uint32_t* code, const size_t wordCount,
639                              const uint32_t options, spv_text* pText,
640                              spv_diagnostic* pDiagnostic) {
641   spv_context_t hijack_context = *context;
642   if (pDiagnostic) {
643     *pDiagnostic = nullptr;
644     spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
645   }
646 
647   const spvtools::AssemblyGrammar grammar(&hijack_context);
648   if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
649 
650   // Generate friendly names for Ids if requested.
651   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
652   spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
653   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
654     friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
655         &hijack_context, code, wordCount);
656     name_mapper = friendly_mapper->GetNameMapper();
657   }
658 
659   // Now disassemble!
660   spvtools::Disassembler disassembler(grammar, options, name_mapper);
661   if (auto error =
662           spvBinaryParse(&hijack_context, &disassembler, code, wordCount,
663                          spvtools::DisassembleHeader,
664                          spvtools::DisassembleInstruction, pDiagnostic)) {
665     return error;
666   }
667 
668   return disassembler.SaveTextResult(pText);
669 }
670