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