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