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