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