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