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