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
25 #include "assembly_grammar.h"
26 #include "binary.h"
27 #include "diagnostic.h"
28 #include "ext_inst.h"
29 #include "name_mapper.h"
30 #include "opcode.h"
31 #include "parsed_operand.h"
32 #include "print.h"
33 #include "spirv-tools/libspirv.h"
34 #include "spirv_constant.h"
35 #include "spirv_endian.h"
36 #include "util/hex_float.h"
37
38 namespace {
39
40 // A Disassembler instance converts a SPIR-V binary to its assembly
41 // representation.
42 class Disassembler {
43 public:
Disassembler(const libspirv::AssemblyGrammar & grammar,uint32_t options,libspirv::NameMapper name_mapper)44 Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options,
45 libspirv::NameMapper name_mapper)
46 : grammar_(grammar),
47 print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
48 color_(print_ &&
49 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
50 indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
51 ? kStandardIndent
52 : 0),
53 text_(),
54 out_(print_ ? out_stream() : out_stream(text_)),
55 stream_(out_.get()),
56 header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
57 show_byte_offset_(spvIsInBitfield(
58 SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
59 byte_offset_(0),
60 name_mapper_(std::move(name_mapper)) {}
61
62 // Emits the assembly header for the module, and sets up internal state
63 // so subsequent callbacks can handle the cases where the entire module
64 // is either big-endian or little-endian.
65 spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version,
66 uint32_t generator, uint32_t id_bound,
67 uint32_t schema);
68 // Emits the assembly text for the given instruction.
69 spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
70
71 // If not printing, populates text_result with the accumulated text.
72 // Returns SPV_SUCCESS on success.
73 spv_result_t SaveTextResult(spv_text* text_result) const;
74
75 private:
76 enum { kStandardIndent = 15 };
77
78 using out_stream = libspirv::out_stream;
79
80 // Emits an operand for the given instruction, where the instruction
81 // is at offset words from the start of the binary.
82 void EmitOperand(const spv_parsed_instruction_t& inst,
83 const uint16_t operand_index);
84
85 // Emits a mask expression for the given mask word of the specified type.
86 void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
87
88 // Resets the output color, if color is turned on.
ResetColor()89 void ResetColor() {
90 if (color_) out_.get() << libspirv::clr::reset();
91 }
92 // Sets the output to grey, if color is turned on.
SetGrey()93 void SetGrey() {
94 if (color_) out_.get() << libspirv::clr::grey();
95 }
96 // Sets the output to blue, if color is turned on.
SetBlue()97 void SetBlue() {
98 if (color_) out_.get() << libspirv::clr::blue();
99 }
100 // Sets the output to yellow, if color is turned on.
SetYellow()101 void SetYellow() {
102 if (color_) out_.get() << libspirv::clr::yellow();
103 }
104 // Sets the output to red, if color is turned on.
SetRed()105 void SetRed() {
106 if (color_) out_.get() << libspirv::clr::red();
107 }
108 // Sets the output to green, if color is turned on.
SetGreen()109 void SetGreen() {
110 if (color_) out_.get() << libspirv::clr::green();
111 }
112
113 const libspirv::AssemblyGrammar& grammar_;
114 const bool print_; // Should we also print to the standard output stream?
115 const bool color_; // Should we print in colour?
116 const int indent_; // How much to indent. 0 means don't indent
117 spv_endianness_t endian_; // The detected endianness of the binary.
118 std::stringstream text_; // Captures the text, if not printing.
119 out_stream out_; // The Output stream. Either to text_ or standard output.
120 std::ostream& stream_; // The output std::stream.
121 const bool header_; // Should we output header as the leading comment?
122 const bool show_byte_offset_; // Should we print byte offset, in hex?
123 size_t byte_offset_; // The number of bytes processed so far.
124 libspirv::NameMapper name_mapper_;
125 };
126
HandleHeader(spv_endianness_t endian,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)127 spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
128 uint32_t version, uint32_t generator,
129 uint32_t id_bound, uint32_t schema) {
130 endian_ = endian;
131
132 if (header_) {
133 SetGrey();
134 const char* generator_tool =
135 spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
136 stream_ << "; SPIR-V\n"
137 << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
138 << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n"
139 << "; Generator: " << generator_tool;
140 // For unknown tools, print the numeric tool value.
141 if (0 == strcmp("Unknown", generator_tool)) {
142 stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
143 }
144 // Print the miscellaneous part of the generator word on the same
145 // line as the tool name.
146 stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
147 << "; Bound: " << id_bound << "\n"
148 << "; Schema: " << schema << "\n";
149 ResetColor();
150 }
151
152 byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
153
154 return SPV_SUCCESS;
155 }
156
HandleInstruction(const spv_parsed_instruction_t & inst)157 spv_result_t Disassembler::HandleInstruction(
158 const spv_parsed_instruction_t& inst) {
159 if (inst.result_id) {
160 SetBlue();
161 const std::string id_name = name_mapper_(inst.result_id);
162 if (indent_)
163 stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
164 stream_ << "%" << id_name;
165 ResetColor();
166 stream_ << " = ";
167 } else {
168 stream_ << std::string(indent_, ' ');
169 }
170
171 stream_ << "Op" << spvOpcodeString(static_cast<SpvOp>(inst.opcode));
172
173 for (uint16_t i = 0; i < inst.num_operands; i++) {
174 const spv_operand_type_t type = inst.operands[i].type;
175 assert(type != SPV_OPERAND_TYPE_NONE);
176 if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
177 stream_ << " ";
178 EmitOperand(inst, i);
179 }
180
181 if (show_byte_offset_) {
182 SetGrey();
183 auto saved_flags = stream_.flags();
184 auto saved_fill = stream_.fill();
185 stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
186 << byte_offset_;
187 stream_.flags(saved_flags);
188 stream_.fill(saved_fill);
189 ResetColor();
190 }
191
192 byte_offset_ += inst.num_words * sizeof(uint32_t);
193
194 stream_ << "\n";
195 return SPV_SUCCESS;
196 }
197
EmitOperand(const spv_parsed_instruction_t & inst,const uint16_t operand_index)198 void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
199 const uint16_t operand_index) {
200 assert(operand_index < inst.num_operands);
201 const spv_parsed_operand_t& operand = inst.operands[operand_index];
202 const uint32_t word = inst.words[operand.offset];
203 switch (operand.type) {
204 case SPV_OPERAND_TYPE_RESULT_ID:
205 assert(false && "<result-id> is not supposed to be handled here");
206 SetBlue();
207 stream_ << "%" << name_mapper_(word);
208 break;
209 case SPV_OPERAND_TYPE_ID:
210 case SPV_OPERAND_TYPE_TYPE_ID:
211 case SPV_OPERAND_TYPE_SCOPE_ID:
212 case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
213 SetYellow();
214 stream_ << "%" << name_mapper_(word);
215 break;
216 case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
217 spv_ext_inst_desc ext_inst;
218 if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst))
219 assert(false && "should have caught this earlier");
220 SetRed();
221 stream_ << ext_inst->name;
222 } break;
223 case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
224 spv_opcode_desc opcode_desc;
225 if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
226 assert(false && "should have caught this earlier");
227 SetRed();
228 stream_ << opcode_desc->name;
229 } break;
230 case SPV_OPERAND_TYPE_LITERAL_INTEGER:
231 case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
232 SetRed();
233 libspirv::EmitNumericLiteral(&stream_, inst, operand);
234 ResetColor();
235 } break;
236 case SPV_OPERAND_TYPE_LITERAL_STRING: {
237 stream_ << "\"";
238 SetGreen();
239 // Strings are always little-endian, and null-terminated.
240 // Write out the characters, escaping as needed, and without copying
241 // the entire string.
242 auto c_str = reinterpret_cast<const char*>(inst.words + operand.offset);
243 for (auto p = c_str; *p; ++p) {
244 if (*p == '"' || *p == '\\') stream_ << '\\';
245 stream_ << *p;
246 }
247 ResetColor();
248 stream_ << '"';
249 } break;
250 case SPV_OPERAND_TYPE_CAPABILITY:
251 case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
252 case SPV_OPERAND_TYPE_EXECUTION_MODEL:
253 case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
254 case SPV_OPERAND_TYPE_MEMORY_MODEL:
255 case SPV_OPERAND_TYPE_EXECUTION_MODE:
256 case SPV_OPERAND_TYPE_STORAGE_CLASS:
257 case SPV_OPERAND_TYPE_DIMENSIONALITY:
258 case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
259 case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
260 case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
261 case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
262 case SPV_OPERAND_TYPE_LINKAGE_TYPE:
263 case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
264 case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
265 case SPV_OPERAND_TYPE_DECORATION:
266 case SPV_OPERAND_TYPE_BUILT_IN:
267 case SPV_OPERAND_TYPE_GROUP_OPERATION:
268 case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
269 case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: {
270 spv_operand_desc entry;
271 if (grammar_.lookupOperand(operand.type, word, &entry))
272 assert(false && "should have caught this earlier");
273 stream_ << entry->name;
274 } break;
275 case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
276 case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
277 case SPV_OPERAND_TYPE_LOOP_CONTROL:
278 case SPV_OPERAND_TYPE_IMAGE:
279 case SPV_OPERAND_TYPE_MEMORY_ACCESS:
280 case SPV_OPERAND_TYPE_SELECTION_CONTROL:
281 EmitMaskOperand(operand.type, word);
282 break;
283 default:
284 assert(false && "unhandled or invalid case");
285 }
286 ResetColor();
287 }
288
EmitMaskOperand(const spv_operand_type_t type,const uint32_t word)289 void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
290 const uint32_t word) {
291 // Scan the mask from least significant bit to most significant bit. For each
292 // set bit, emit the name of that bit. Separate multiple names with '|'.
293 uint32_t remaining_word = word;
294 uint32_t mask;
295 int num_emitted = 0;
296 for (mask = 1; remaining_word; mask <<= 1) {
297 if (remaining_word & mask) {
298 remaining_word ^= mask;
299 spv_operand_desc entry;
300 if (grammar_.lookupOperand(type, mask, &entry))
301 assert(false && "should have caught this earlier");
302 if (num_emitted) stream_ << "|";
303 stream_ << entry->name;
304 num_emitted++;
305 }
306 }
307 if (!num_emitted) {
308 // An operand value of 0 was provided, so represent it by the name
309 // of the 0 value. In many cases, that's "None".
310 spv_operand_desc entry;
311 if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
312 stream_ << entry->name;
313 }
314 }
315
SaveTextResult(spv_text * text_result) const316 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
317 if (!print_) {
318 size_t length = text_.str().size();
319 char* str = new char[length + 1];
320 if (!str) return SPV_ERROR_OUT_OF_MEMORY;
321 strncpy(str, text_.str().c_str(), length + 1);
322 spv_text text = new spv_text_t();
323 if (!text) {
324 delete[] str;
325 return SPV_ERROR_OUT_OF_MEMORY;
326 }
327 text->str = str;
328 text->length = length;
329 *text_result = text;
330 }
331 return SPV_SUCCESS;
332 }
333
DisassembleHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)334 spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
335 uint32_t /* magic */, uint32_t version,
336 uint32_t generator, uint32_t id_bound,
337 uint32_t schema) {
338 assert(user_data);
339 auto disassembler = static_cast<Disassembler*>(user_data);
340 return disassembler->HandleHeader(endian, version, generator, id_bound,
341 schema);
342 }
343
DisassembleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)344 spv_result_t DisassembleInstruction(
345 void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
346 assert(user_data);
347 auto disassembler = static_cast<Disassembler*>(user_data);
348 return disassembler->HandleInstruction(*parsed_instruction);
349 }
350
351 } // anonymous namespace
352
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)353 spv_result_t spvBinaryToText(const spv_const_context context,
354 const uint32_t* code, const size_t wordCount,
355 const uint32_t options, spv_text* pText,
356 spv_diagnostic* pDiagnostic) {
357 spv_context_t hijack_context = *context;
358 if (pDiagnostic) {
359 *pDiagnostic = nullptr;
360 libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
361 }
362
363 const libspirv::AssemblyGrammar grammar(&hijack_context);
364 if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
365
366 // Generate friendly names for Ids if requested.
367 std::unique_ptr<libspirv::FriendlyNameMapper> friendly_mapper;
368 libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper();
369 if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
370 friendly_mapper.reset(
371 new libspirv::FriendlyNameMapper(&hijack_context, code, wordCount));
372 name_mapper = friendly_mapper->GetNameMapper();
373 }
374
375 // Now disassemble!
376 Disassembler disassembler(grammar, options, name_mapper);
377 if (auto error = spvBinaryParse(&hijack_context, &disassembler, code,
378 wordCount, DisassembleHeader,
379 DisassembleInstruction, pDiagnostic)) {
380 return error;
381 }
382
383 return disassembler.SaveTextResult(pText);
384 }
385