• 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     case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
333     case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
334       spv_operand_desc entry;
335       if (grammar_.lookupOperand(operand.type, word, &entry))
336         assert(false && "should have caught this earlier");
337       stream_ << entry->name;
338     } break;
339     case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
340     case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
341     case SPV_OPERAND_TYPE_LOOP_CONTROL:
342     case SPV_OPERAND_TYPE_IMAGE:
343     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
344     case SPV_OPERAND_TYPE_SELECTION_CONTROL:
345     case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
346     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
347       EmitMaskOperand(operand.type, word);
348       break;
349     default:
350       if (spvOperandIsConcreteMask(operand.type)) {
351         EmitMaskOperand(operand.type, word);
352       } else if (spvOperandIsConcrete(operand.type)) {
353         spv_operand_desc entry;
354         if (grammar_.lookupOperand(operand.type, word, &entry))
355           assert(false && "should have caught this earlier");
356         stream_ << entry->name;
357       } else {
358         assert(false && "unhandled or invalid case");
359       }
360       break;
361   }
362   ResetColor();
363 }
364 
EmitMaskOperand(const spv_operand_type_t type,const uint32_t word)365 void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
366                                    const uint32_t word) {
367   // Scan the mask from least significant bit to most significant bit.  For each
368   // set bit, emit the name of that bit. Separate multiple names with '|'.
369   uint32_t remaining_word = word;
370   uint32_t mask;
371   int num_emitted = 0;
372   for (mask = 1; remaining_word; mask <<= 1) {
373     if (remaining_word & mask) {
374       remaining_word ^= mask;
375       spv_operand_desc entry;
376       if (grammar_.lookupOperand(type, mask, &entry))
377         assert(false && "should have caught this earlier");
378       if (num_emitted) stream_ << "|";
379       stream_ << entry->name;
380       num_emitted++;
381     }
382   }
383   if (!num_emitted) {
384     // An operand value of 0 was provided, so represent it by the name
385     // of the 0 value. In many cases, that's "None".
386     spv_operand_desc entry;
387     if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
388       stream_ << entry->name;
389   }
390 }
391 
SaveTextResult(spv_text * text_result) const392 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
393   if (!print_) {
394     size_t length = text_.str().size();
395     char* str = new char[length + 1];
396     if (!str) return SPV_ERROR_OUT_OF_MEMORY;
397     strncpy(str, text_.str().c_str(), length + 1);
398     spv_text text = new spv_text_t();
399     if (!text) {
400       delete[] str;
401       return SPV_ERROR_OUT_OF_MEMORY;
402     }
403     text->str = str;
404     text->length = length;
405     *text_result = text;
406   }
407   return SPV_SUCCESS;
408 }
409 
DisassembleHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)410 spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
411                                uint32_t /* magic */, uint32_t version,
412                                uint32_t generator, uint32_t id_bound,
413                                uint32_t schema) {
414   assert(user_data);
415   auto disassembler = static_cast<Disassembler*>(user_data);
416   return disassembler->HandleHeader(endian, version, generator, id_bound,
417                                     schema);
418 }
419 
DisassembleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)420 spv_result_t DisassembleInstruction(
421     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
422   assert(user_data);
423   auto disassembler = static_cast<Disassembler*>(user_data);
424   return disassembler->HandleInstruction(*parsed_instruction);
425 }
426 
427 // Simple wrapper class to provide extra data necessary for targeted
428 // instruction disassembly.
429 class WrappedDisassembler {
430  public:
WrappedDisassembler(Disassembler * dis,const uint32_t * binary,size_t wc)431   WrappedDisassembler(Disassembler* dis, const uint32_t* binary, size_t wc)
432       : disassembler_(dis), inst_binary_(binary), word_count_(wc) {}
433 
disassembler()434   Disassembler* disassembler() { return disassembler_; }
inst_binary() const435   const uint32_t* inst_binary() const { return inst_binary_; }
word_count() const436   size_t word_count() const { return word_count_; }
437 
438  private:
439   Disassembler* disassembler_;
440   const uint32_t* inst_binary_;
441   const size_t word_count_;
442 };
443 
DisassembleTargetHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)444 spv_result_t DisassembleTargetHeader(void* user_data, spv_endianness_t endian,
445                                      uint32_t /* magic */, uint32_t version,
446                                      uint32_t generator, uint32_t id_bound,
447                                      uint32_t schema) {
448   assert(user_data);
449   auto wrapped = static_cast<WrappedDisassembler*>(user_data);
450   return wrapped->disassembler()->HandleHeader(endian, version, generator,
451                                                id_bound, schema);
452 }
453 
DisassembleTargetInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)454 spv_result_t DisassembleTargetInstruction(
455     void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
456   assert(user_data);
457   auto wrapped = static_cast<WrappedDisassembler*>(user_data);
458   // Check if this is the instruction we want to disassemble.
459   if (wrapped->word_count() == parsed_instruction->num_words &&
460       std::equal(wrapped->inst_binary(),
461                  wrapped->inst_binary() + wrapped->word_count(),
462                  parsed_instruction->words)) {
463     // Found the target instruction. Disassemble it and signal that we should
464     // stop searching so we don't output the same instruction again.
465     if (auto error =
466             wrapped->disassembler()->HandleInstruction(*parsed_instruction))
467       return error;
468     return SPV_REQUESTED_TERMINATION;
469   }
470   return SPV_SUCCESS;
471 }
472 
473 }  // namespace
474 
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)475 spv_result_t spvBinaryToText(const spv_const_context context,
476                              const uint32_t* code, const size_t wordCount,
477                              const uint32_t options, spv_text* pText,
478                              spv_diagnostic* pDiagnostic) {
479   spv_context_t hijack_context = *context;
480   if (pDiagnostic) {
481     *pDiagnostic = nullptr;
482     spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
483   }
484 
485   const spvtools::AssemblyGrammar grammar(&hijack_context);
486   if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
487 
488   // Generate friendly names for Ids if requested.
489   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
490   spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
491   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
492     friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
493         &hijack_context, code, wordCount);
494     name_mapper = friendly_mapper->GetNameMapper();
495   }
496 
497   // Now disassemble!
498   Disassembler disassembler(grammar, options, name_mapper);
499   if (auto error = spvBinaryParse(&hijack_context, &disassembler, code,
500                                   wordCount, DisassembleHeader,
501                                   DisassembleInstruction, pDiagnostic)) {
502     return error;
503   }
504 
505   return disassembler.SaveTextResult(pText);
506 }
507 
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)508 std::string spvtools::spvInstructionBinaryToText(const spv_target_env env,
509                                                  const uint32_t* instCode,
510                                                  const size_t instWordCount,
511                                                  const uint32_t* code,
512                                                  const size_t wordCount,
513                                                  const uint32_t options) {
514   spv_context context = spvContextCreate(env);
515   const spvtools::AssemblyGrammar grammar(context);
516   if (!grammar.isValid()) {
517     spvContextDestroy(context);
518     return "";
519   }
520 
521   // Generate friendly names for Ids if requested.
522   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
523   spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
524   if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
525     friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
526         context, code, wordCount);
527     name_mapper = friendly_mapper->GetNameMapper();
528   }
529 
530   // Now disassemble!
531   Disassembler disassembler(grammar, options, name_mapper);
532   WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
533   spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
534                  DisassembleTargetInstruction, nullptr);
535 
536   spv_text text = nullptr;
537   std::string output;
538   if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
539     output.assign(text->str, text->str + text->length);
540     // Drop trailing newline characters.
541     while (!output.empty() && output.back() == '\n') output.pop_back();
542   }
543   spvTextDestroy(text);
544   spvContextDestroy(context);
545 
546   return output;
547 }
548