• 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 
197 constexpr int kStandardIndent = 15;
198 }  // namespace
199 
200 namespace disassemble {
InstructionDisassembler(const AssemblyGrammar & grammar,std::ostream & stream,uint32_t options,NameMapper name_mapper)201 InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
202                                                  std::ostream& stream,
203                                                  uint32_t options,
204                                                  NameMapper name_mapper)
205     : grammar_(grammar),
206       stream_(stream),
207       print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
208       color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
209       indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
210                   ? kStandardIndent
211                   : 0),
212       comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
213       show_byte_offset_(
214           spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
215       name_mapper_(std::move(name_mapper)) {}
216 
EmitHeaderSpirv()217 void InstructionDisassembler::EmitHeaderSpirv() { stream_ << "; SPIR-V\n"; }
218 
EmitHeaderVersion(uint32_t version)219 void InstructionDisassembler::EmitHeaderVersion(uint32_t version) {
220   stream_ << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
221           << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n";
222 }
223 
EmitHeaderGenerator(uint32_t generator)224 void InstructionDisassembler::EmitHeaderGenerator(uint32_t generator) {
225   const char* generator_tool =
226       spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
227   stream_ << "; Generator: " << generator_tool;
228   // For unknown tools, print the numeric tool value.
229   if (0 == strcmp("Unknown", generator_tool)) {
230     stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
231   }
232   // Print the miscellaneous part of the generator word on the same
233   // line as the tool name.
234   stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n";
235 }
236 
EmitHeaderIdBound(uint32_t id_bound)237 void InstructionDisassembler::EmitHeaderIdBound(uint32_t id_bound) {
238   stream_ << "; Bound: " << id_bound << "\n";
239 }
240 
EmitHeaderSchema(uint32_t schema)241 void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
242   stream_ << "; Schema: " << schema << "\n";
243 }
244 
EmitInstruction(const spv_parsed_instruction_t & inst,size_t inst_byte_offset)245 void InstructionDisassembler::EmitInstruction(
246     const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
247   auto opcode = static_cast<SpvOp>(inst.opcode);
248 
249   if (inst.result_id) {
250     SetBlue();
251     const std::string id_name = name_mapper_(inst.result_id);
252     if (indent_)
253       stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
254     stream_ << "%" << id_name;
255     ResetColor();
256     stream_ << " = ";
257   } else {
258     stream_ << std::string(indent_, ' ');
259   }
260 
261   stream_ << "Op" << spvOpcodeString(opcode);
262 
263   for (uint16_t i = 0; i < inst.num_operands; i++) {
264     const spv_operand_type_t type = inst.operands[i].type;
265     assert(type != SPV_OPERAND_TYPE_NONE);
266     if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
267     stream_ << " ";
268     EmitOperand(inst, i);
269   }
270 
271   if (comment_ && opcode == SpvOpName) {
272     const spv_parsed_operand_t& operand = inst.operands[0];
273     const uint32_t word = inst.words[operand.offset];
274     stream_ << "  ; id %" << word;
275   }
276 
277   if (show_byte_offset_) {
278     SetGrey();
279     auto saved_flags = stream_.flags();
280     auto saved_fill = stream_.fill();
281     stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
282             << inst_byte_offset;
283     stream_.flags(saved_flags);
284     stream_.fill(saved_fill);
285     ResetColor();
286   }
287   stream_ << "\n";
288 }
289 
EmitSectionComment(const spv_parsed_instruction_t & inst,bool & inserted_decoration_space,bool & inserted_debug_space,bool & inserted_type_space)290 void InstructionDisassembler::EmitSectionComment(
291     const spv_parsed_instruction_t& inst, bool& inserted_decoration_space,
292     bool& inserted_debug_space, bool& inserted_type_space) {
293   auto opcode = static_cast<SpvOp>(inst.opcode);
294   if (comment_ && opcode == SpvOpFunction) {
295     stream_ << std::endl;
296     stream_ << std::string(indent_, ' ');
297     stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
298   }
299   if (comment_ && !inserted_decoration_space && spvOpcodeIsDecoration(opcode)) {
300     inserted_decoration_space = true;
301     stream_ << std::endl;
302     stream_ << std::string(indent_, ' ');
303     stream_ << "; Annotations" << std::endl;
304   }
305   if (comment_ && !inserted_debug_space && spvOpcodeIsDebug(opcode)) {
306     inserted_debug_space = true;
307     stream_ << std::endl;
308     stream_ << std::string(indent_, ' ');
309     stream_ << "; Debug Information" << std::endl;
310   }
311   if (comment_ && !inserted_type_space && spvOpcodeGeneratesType(opcode)) {
312     inserted_type_space = true;
313     stream_ << std::endl;
314     stream_ << std::string(indent_, ' ');
315     stream_ << "; Types, variables and constants" << std::endl;
316   }
317 }
318 
EmitOperand(const spv_parsed_instruction_t & inst,const uint16_t operand_index)319 void InstructionDisassembler::EmitOperand(const spv_parsed_instruction_t& inst,
320                                           const uint16_t operand_index) {
321   assert(operand_index < inst.num_operands);
322   const spv_parsed_operand_t& operand = inst.operands[operand_index];
323   const uint32_t word = inst.words[operand.offset];
324   switch (operand.type) {
325     case SPV_OPERAND_TYPE_RESULT_ID:
326       assert(false && "<result-id> is not supposed to be handled here");
327       SetBlue();
328       stream_ << "%" << name_mapper_(word);
329       break;
330     case SPV_OPERAND_TYPE_ID:
331     case SPV_OPERAND_TYPE_TYPE_ID:
332     case SPV_OPERAND_TYPE_SCOPE_ID:
333     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
334       SetYellow();
335       stream_ << "%" << name_mapper_(word);
336       break;
337     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
338       spv_ext_inst_desc ext_inst;
339       SetRed();
340       if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
341           SPV_SUCCESS) {
342         stream_ << ext_inst->name;
343       } else {
344         if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
345           assert(false && "should have caught this earlier");
346         } else {
347           // for non-semantic instruction sets we can just print the number
348           stream_ << word;
349         }
350       }
351     } break;
352     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
353       spv_opcode_desc opcode_desc;
354       if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
355         assert(false && "should have caught this earlier");
356       SetRed();
357       stream_ << opcode_desc->name;
358     } break;
359     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
360     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
361       SetRed();
362       EmitNumericLiteral(&stream_, inst, operand);
363       ResetColor();
364     } break;
365     case SPV_OPERAND_TYPE_LITERAL_STRING: {
366       stream_ << "\"";
367       SetGreen();
368 
369       std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
370       for (char const& c : str) {
371         if (c == '"' || c == '\\') stream_ << '\\';
372         stream_ << c;
373       }
374       ResetColor();
375       stream_ << '"';
376     } break;
377     case SPV_OPERAND_TYPE_CAPABILITY:
378     case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
379     case SPV_OPERAND_TYPE_EXECUTION_MODEL:
380     case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
381     case SPV_OPERAND_TYPE_MEMORY_MODEL:
382     case SPV_OPERAND_TYPE_EXECUTION_MODE:
383     case SPV_OPERAND_TYPE_STORAGE_CLASS:
384     case SPV_OPERAND_TYPE_DIMENSIONALITY:
385     case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
386     case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
387     case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
388     case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
389     case SPV_OPERAND_TYPE_LINKAGE_TYPE:
390     case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
391     case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
392     case SPV_OPERAND_TYPE_DECORATION:
393     case SPV_OPERAND_TYPE_BUILT_IN:
394     case SPV_OPERAND_TYPE_GROUP_OPERATION:
395     case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
396     case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
397     case SPV_OPERAND_TYPE_RAY_FLAGS:
398     case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
399     case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
400     case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
401     case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
402     case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
403     case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
404     case SPV_OPERAND_TYPE_DEBUG_OPERATION:
405     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
406     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
407     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
408     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
409     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
410     case SPV_OPERAND_TYPE_FPDENORM_MODE:
411     case SPV_OPERAND_TYPE_FPOPERATION_MODE:
412     case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
413     case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
414       spv_operand_desc entry;
415       if (grammar_.lookupOperand(operand.type, word, &entry))
416         assert(false && "should have caught this earlier");
417       stream_ << entry->name;
418     } break;
419     case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
420     case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
421     case SPV_OPERAND_TYPE_LOOP_CONTROL:
422     case SPV_OPERAND_TYPE_IMAGE:
423     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
424     case SPV_OPERAND_TYPE_SELECTION_CONTROL:
425     case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
426     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
427       EmitMaskOperand(operand.type, word);
428       break;
429     default:
430       if (spvOperandIsConcreteMask(operand.type)) {
431         EmitMaskOperand(operand.type, word);
432       } else if (spvOperandIsConcrete(operand.type)) {
433         spv_operand_desc entry;
434         if (grammar_.lookupOperand(operand.type, word, &entry))
435           assert(false && "should have caught this earlier");
436         stream_ << entry->name;
437       } else {
438         assert(false && "unhandled or invalid case");
439       }
440       break;
441   }
442   ResetColor();
443 }
444 
EmitMaskOperand(const spv_operand_type_t type,const uint32_t word)445 void InstructionDisassembler::EmitMaskOperand(const spv_operand_type_t type,
446                                               const uint32_t word) {
447   // Scan the mask from least significant bit to most significant bit.  For each
448   // set bit, emit the name of that bit. Separate multiple names with '|'.
449   uint32_t remaining_word = word;
450   uint32_t mask;
451   int num_emitted = 0;
452   for (mask = 1; remaining_word; mask <<= 1) {
453     if (remaining_word & mask) {
454       remaining_word ^= mask;
455       spv_operand_desc entry;
456       if (grammar_.lookupOperand(type, mask, &entry))
457         assert(false && "should have caught this earlier");
458       if (num_emitted) stream_ << "|";
459       stream_ << entry->name;
460       num_emitted++;
461     }
462   }
463   if (!num_emitted) {
464     // An operand value of 0 was provided, so represent it by the name
465     // of the 0 value. In many cases, that's "None".
466     spv_operand_desc entry;
467     if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
468       stream_ << entry->name;
469   }
470 }
471 
ResetColor()472 void InstructionDisassembler::ResetColor() {
473   if (color_) stream_ << spvtools::clr::reset{print_};
474 }
SetGrey()475 void InstructionDisassembler::SetGrey() {
476   if (color_) stream_ << spvtools::clr::grey{print_};
477 }
SetBlue()478 void InstructionDisassembler::SetBlue() {
479   if (color_) stream_ << spvtools::clr::blue{print_};
480 }
SetYellow()481 void InstructionDisassembler::SetYellow() {
482   if (color_) stream_ << spvtools::clr::yellow{print_};
483 }
SetRed()484 void InstructionDisassembler::SetRed() {
485   if (color_) stream_ << spvtools::clr::red{print_};
486 }
SetGreen()487 void InstructionDisassembler::SetGreen() {
488   if (color_) stream_ << spvtools::clr::green{print_};
489 }
490 }  // namespace disassemble
491 
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)492 std::string spvInstructionBinaryToText(const spv_target_env env,
493                                        const uint32_t* instCode,
494                                        const size_t instWordCount,
495                                        const uint32_t* code,
496                                        const size_t wordCount,
497                                        const uint32_t options) {
498   spv_context context = spvContextCreate(env);
499   const AssemblyGrammar grammar(context);
500   if (!grammar.isValid()) {
501     spvContextDestroy(context);
502     return "";
503   }
504 
505   // Generate friendly names for Ids if requested.
506   std::unique_ptr<FriendlyNameMapper> friendly_mapper;
507   NameMapper name_mapper = GetTrivialNameMapper();
508   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
509     friendly_mapper = MakeUnique<FriendlyNameMapper>(context, code, wordCount);
510     name_mapper = friendly_mapper->GetNameMapper();
511   }
512 
513   // Now disassemble!
514   Disassembler disassembler(grammar, options, name_mapper);
515   WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
516   spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
517                  DisassembleTargetInstruction, nullptr);
518 
519   spv_text text = nullptr;
520   std::string output;
521   if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
522     output.assign(text->str, text->str + text->length);
523     // Drop trailing newline characters.
524     while (!output.empty() && output.back() == '\n') output.pop_back();
525   }
526   spvTextDestroy(text);
527   spvContextDestroy(context);
528 
529   return output;
530 }
531 }  // namespace spvtools
532 
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)533 spv_result_t spvBinaryToText(const spv_const_context context,
534                              const uint32_t* code, const size_t wordCount,
535                              const uint32_t options, spv_text* pText,
536                              spv_diagnostic* pDiagnostic) {
537   spv_context_t hijack_context = *context;
538   if (pDiagnostic) {
539     *pDiagnostic = nullptr;
540     spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
541   }
542 
543   const spvtools::AssemblyGrammar grammar(&hijack_context);
544   if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
545 
546   // Generate friendly names for Ids if requested.
547   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
548   spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
549   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
550     friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
551         &hijack_context, code, wordCount);
552     name_mapper = friendly_mapper->GetNameMapper();
553   }
554 
555   // Now disassemble!
556   spvtools::Disassembler disassembler(grammar, options, name_mapper);
557   if (auto error =
558           spvBinaryParse(&hijack_context, &disassembler, code, wordCount,
559                          spvtools::DisassembleHeader,
560                          spvtools::DisassembleInstruction, pDiagnostic)) {
561     return error;
562   }
563 
564   return disassembler.SaveTextResult(pText);
565 }
566