1// Copyright (c) 2015-2016 The Khronos Group Inc. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and/or associated documentation files (the 5// "Materials"), to deal in the Materials without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Materials, and to 8// permit persons to whom the Materials are furnished to do so, subject to 9// the following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Materials. 13// 14// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS 15// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS 16// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT 17// https://www.khronos.org/registry/ 18// 19// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 26 27// This file contains a disassembler: It converts a SPIR-V binary 28// to text. 29 30#include <algorithm> 31#include <cassert> 32#include <cstring> 33#include <iomanip> 34#include <memory> 35#include <unordered_map> 36 37#include "assembly_grammar.h" 38#include "binary.h" 39#include "diagnostic.h" 40#include "ext_inst.h" 41#include "name_mapper.h" 42#include "opcode.h" 43#include "print.h" 44#include "spirv-tools/libspirv.h" 45#include "spirv_constant.h" 46#include "spirv_endian.h" 47#include "util/hex_float.h" 48 49namespace { 50 51// A Disassembler instance converts a SPIR-V binary to its assembly 52// representation. 53class Disassembler { 54 public: 55 Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options, 56 libspirv::NameMapper name_mapper) 57 : grammar_(grammar), 58 print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)), 59 color_(print_ && 60 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)), 61 indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options) 62 ? kStandardIndent 63 : 0), 64 text_(), 65 out_(print_ ? out_stream() : out_stream(text_)), 66 stream_(out_.get()), 67 header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)), 68 show_byte_offset_(spvIsInBitfield( 69 SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)), 70 byte_offset_(0), 71 name_mapper_(std::move(name_mapper)) {} 72 73 // Emits the assembly header for the module, and sets up internal state 74 // so subsequent callbacks can handle the cases where the entire module 75 // is either big-endian or little-endian. 76 spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version, 77 uint32_t generator, uint32_t id_bound, 78 uint32_t schema); 79 // Emits the assembly text for the given instruction. 80 spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst); 81 82 // If not printing, populates text_result with the accumulated text. 83 // Returns SPV_SUCCESS on success. 84 spv_result_t SaveTextResult(spv_text* text_result) const; 85 86 private: 87 enum { kStandardIndent = 15 }; 88 89 using out_stream = libspirv::out_stream; 90 91 // Emits an operand for the given instruction, where the instruction 92 // is at offset words from the start of the binary. 93 void EmitOperand(const spv_parsed_instruction_t& inst, 94 const uint16_t operand_index); 95 96 // Emits a mask expression for the given mask word of the specified type. 97 void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word); 98 99 // Resets the output color, if color is turned on. 100 void ResetColor() { 101 if (color_) out_.get() << libspirv::clr::reset(); 102 } 103 // Sets the output to grey, if color is turned on. 104 void SetGrey() { 105 if (color_) out_.get() << libspirv::clr::grey(); 106 } 107 // Sets the output to blue, if color is turned on. 108 void SetBlue() { 109 if (color_) out_.get() << libspirv::clr::blue(); 110 } 111 // Sets the output to yellow, if color is turned on. 112 void SetYellow() { 113 if (color_) out_.get() << libspirv::clr::yellow(); 114 } 115 // Sets the output to red, if color is turned on. 116 void SetRed() { 117 if (color_) out_.get() << libspirv::clr::red(); 118 } 119 // Sets the output to green, if color is turned on. 120 void SetGreen() { 121 if (color_) out_.get() << libspirv::clr::green(); 122 } 123 124 const libspirv::AssemblyGrammar& grammar_; 125 const bool print_; // Should we also print to the standard output stream? 126 const bool color_; // Should we print in colour? 127 const int indent_; // How much to indent. 0 means don't indent 128 spv_endianness_t endian_; // The detected endianness of the binary. 129 std::stringstream text_; // Captures the text, if not printing. 130 out_stream out_; // The Output stream. Either to text_ or standard output. 131 std::ostream& stream_; // The output std::stream. 132 const bool header_; // Should we output header as the leading comment? 133 const bool show_byte_offset_; // Should we print byte offset, in hex? 134 size_t byte_offset_; // The number of bytes processed so far. 135 libspirv::NameMapper name_mapper_; 136}; 137 138spv_result_t Disassembler::HandleHeader(spv_endianness_t endian, 139 uint32_t version, uint32_t generator, 140 uint32_t id_bound, uint32_t schema) { 141 endian_ = endian; 142 143 if (header_) { 144 SetGrey(); 145 const char* generator_tool = 146 spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator)); 147 stream_ << "; SPIR-V\n" 148 << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "." 149 << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n" 150 << "; Generator: " << generator_tool; 151 // For unknown tools, print the numeric tool value. 152 if (0 == strcmp("Unknown", generator_tool)) { 153 stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")"; 154 } 155 // Print the miscellaneous part of the generator word on the same 156 // line as the tool name. 157 stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n" 158 << "; Bound: " << id_bound << "\n" 159 << "; Schema: " << schema << "\n"; 160 ResetColor(); 161 } 162 163 byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t); 164 165 return SPV_SUCCESS; 166} 167 168spv_result_t Disassembler::HandleInstruction( 169 const spv_parsed_instruction_t& inst) { 170 if (inst.result_id) { 171 SetBlue(); 172 const std::string id_name = name_mapper_(inst.result_id); 173 if (indent_) 174 stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size()))); 175 stream_ << "%" << id_name; 176 ResetColor(); 177 stream_ << " = "; 178 } else { 179 stream_ << std::string(indent_, ' '); 180 } 181 182 stream_ << "Op" << spvOpcodeString(static_cast<SpvOp>(inst.opcode)); 183 184 for (uint16_t i = 0; i < inst.num_operands; i++) { 185 const spv_operand_type_t type = inst.operands[i].type; 186 assert(type != SPV_OPERAND_TYPE_NONE); 187 if (type == SPV_OPERAND_TYPE_RESULT_ID) continue; 188 stream_ << " "; 189 EmitOperand(inst, i); 190 } 191 192 if (show_byte_offset_) { 193 SetGrey(); 194 auto saved_flags = stream_.flags(); 195 auto saved_fill = stream_.fill(); 196 stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0') 197 << byte_offset_; 198 stream_.flags(saved_flags); 199 stream_.fill(saved_fill); 200 ResetColor(); 201 } 202 203 byte_offset_ += inst.num_words * sizeof(uint32_t); 204 205 stream_ << "\n"; 206 return SPV_SUCCESS; 207} 208 209void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst, 210 const uint16_t operand_index) { 211 assert(operand_index < inst.num_operands); 212 const spv_parsed_operand_t& operand = inst.operands[operand_index]; 213 const uint32_t word = inst.words[operand.offset]; 214 switch (operand.type) { 215 case SPV_OPERAND_TYPE_RESULT_ID: 216 assert(false && "<result-id> is not supposed to be handled here"); 217 SetBlue(); 218 stream_ << "%" << name_mapper_(word); 219 break; 220 case SPV_OPERAND_TYPE_ID: 221 case SPV_OPERAND_TYPE_TYPE_ID: 222 case SPV_OPERAND_TYPE_SCOPE_ID: 223 case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: 224 SetYellow(); 225 stream_ << "%" << name_mapper_(word); 226 break; 227 case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { 228 spv_ext_inst_desc ext_inst; 229 if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst)) 230 assert(false && "should have caught this earlier"); 231 SetRed(); 232 stream_ << ext_inst->name; 233 } break; 234 case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { 235 spv_opcode_desc opcode_desc; 236 if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc)) 237 assert(false && "should have caught this earlier"); 238 SetRed(); 239 stream_ << opcode_desc->name; 240 } break; 241 case SPV_OPERAND_TYPE_LITERAL_INTEGER: 242 case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: { 243 SetRed(); 244 if (operand.num_words == 1) { 245 switch (operand.number_kind) { 246 case SPV_NUMBER_SIGNED_INT: 247 stream_ << int32_t(word); 248 break; 249 case SPV_NUMBER_UNSIGNED_INT: 250 stream_ << word; 251 break; 252 case SPV_NUMBER_FLOATING: 253 if (operand.number_bit_width == 16) { 254 stream_ << spvutils::FloatProxy<spvutils::Float16>( 255 uint16_t(word & 0xFFFF)); 256 } else { 257 // Assume 32-bit floats. 258 stream_ << spvutils::FloatProxy<float>(word); 259 } 260 break; 261 default: 262 assert(false && "Unreachable"); 263 } 264 } else if (operand.num_words == 2) { 265 // Multi-word numbers are presented with lower order words first. 266 uint64_t bits = 267 uint64_t(word) | (uint64_t(inst.words[operand.offset + 1]) << 32); 268 switch (operand.number_kind) { 269 case SPV_NUMBER_SIGNED_INT: 270 stream_ << int64_t(bits); 271 break; 272 case SPV_NUMBER_UNSIGNED_INT: 273 stream_ << bits; 274 break; 275 case SPV_NUMBER_FLOATING: 276 // Assume only 64-bit floats. 277 stream_ << spvutils::FloatProxy<double>(bits); 278 break; 279 default: 280 assert(false && "Unreachable"); 281 } 282 } else { 283 // TODO(dneto): Support more than 64-bits at a time. 284 assert(false && "Unhandled"); 285 } 286 } break; 287 case SPV_OPERAND_TYPE_LITERAL_STRING: { 288 stream_ << "\""; 289 SetGreen(); 290 // Strings are always little-endian, and null-terminated. 291 // Write out the characters, escaping as needed, and without copying 292 // the entire string. 293 auto c_str = reinterpret_cast<const char*>(inst.words + operand.offset); 294 for (auto p = c_str; *p; ++p) { 295 if (*p == '"' || *p == '\\') stream_ << '\\'; 296 stream_ << *p; 297 } 298 ResetColor(); 299 stream_ << '"'; 300 } break; 301 case SPV_OPERAND_TYPE_CAPABILITY: 302 case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: 303 case SPV_OPERAND_TYPE_EXECUTION_MODEL: 304 case SPV_OPERAND_TYPE_ADDRESSING_MODEL: 305 case SPV_OPERAND_TYPE_MEMORY_MODEL: 306 case SPV_OPERAND_TYPE_EXECUTION_MODE: 307 case SPV_OPERAND_TYPE_STORAGE_CLASS: 308 case SPV_OPERAND_TYPE_DIMENSIONALITY: 309 case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: 310 case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: 311 case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: 312 case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: 313 case SPV_OPERAND_TYPE_LINKAGE_TYPE: 314 case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: 315 case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: 316 case SPV_OPERAND_TYPE_DECORATION: 317 case SPV_OPERAND_TYPE_BUILT_IN: 318 case SPV_OPERAND_TYPE_GROUP_OPERATION: 319 case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: 320 case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: { 321 spv_operand_desc entry; 322 if (grammar_.lookupOperand(operand.type, word, &entry)) 323 assert(false && "should have caught this earlier"); 324 stream_ << entry->name; 325 } break; 326 case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: 327 case SPV_OPERAND_TYPE_FUNCTION_CONTROL: 328 case SPV_OPERAND_TYPE_LOOP_CONTROL: 329 case SPV_OPERAND_TYPE_IMAGE: 330 case SPV_OPERAND_TYPE_MEMORY_ACCESS: 331 case SPV_OPERAND_TYPE_SELECTION_CONTROL: 332 EmitMaskOperand(operand.type, word); 333 break; 334 default: 335 assert(false && "unhandled or invalid case"); 336 } 337 ResetColor(); 338} 339 340void Disassembler::EmitMaskOperand(const spv_operand_type_t type, 341 const uint32_t word) { 342 // Scan the mask from least significant bit to most significant bit. For each 343 // set bit, emit the name of that bit. Separate multiple names with '|'. 344 uint32_t remaining_word = word; 345 uint32_t mask; 346 int num_emitted = 0; 347 for (mask = 1; remaining_word; mask <<= 1) { 348 if (remaining_word & mask) { 349 remaining_word ^= mask; 350 spv_operand_desc entry; 351 if (grammar_.lookupOperand(type, mask, &entry)) 352 assert(false && "should have caught this earlier"); 353 if (num_emitted) stream_ << "|"; 354 stream_ << entry->name; 355 num_emitted++; 356 } 357 } 358 if (!num_emitted) { 359 // An operand value of 0 was provided, so represent it by the name 360 // of the 0 value. In many cases, that's "None". 361 spv_operand_desc entry; 362 if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) 363 stream_ << entry->name; 364 } 365} 366 367spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const { 368 if (!print_) { 369 size_t length = text_.str().size(); 370 char* str = new char[length + 1]; 371 if (!str) return SPV_ERROR_OUT_OF_MEMORY; 372 strncpy(str, text_.str().c_str(), length + 1); 373 spv_text text = new spv_text_t(); 374 if (!text) { 375 delete[] str; 376 return SPV_ERROR_OUT_OF_MEMORY; 377 } 378 text->str = str; 379 text->length = length; 380 *text_result = text; 381 } 382 return SPV_SUCCESS; 383} 384 385spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian, 386 uint32_t /* magic */, uint32_t version, 387 uint32_t generator, uint32_t id_bound, 388 uint32_t schema) { 389 assert(user_data); 390 auto disassembler = static_cast<Disassembler*>(user_data); 391 return disassembler->HandleHeader(endian, version, generator, id_bound, 392 schema); 393} 394 395spv_result_t DisassembleInstruction( 396 void* user_data, const spv_parsed_instruction_t* parsed_instruction) { 397 assert(user_data); 398 auto disassembler = static_cast<Disassembler*>(user_data); 399 return disassembler->HandleInstruction(*parsed_instruction); 400} 401 402} // anonymous namespace 403 404spv_result_t spvBinaryToText(const spv_const_context context, 405 const uint32_t* code, const size_t wordCount, 406 const uint32_t options, spv_text* pText, 407 spv_diagnostic* pDiagnostic) { 408 // Invalid arguments return error codes, but don't necessarily generate 409 // diagnostics. These are programmer errors, not user errors. 410 if (!pDiagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC; 411 const libspirv::AssemblyGrammar grammar(context); 412 if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE; 413 414 // Generate friendly names for Ids if requested. 415 std::unique_ptr<libspirv::FriendlyNameMapper> friendly_mapper; 416 libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper(); 417 if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) { 418 friendly_mapper.reset( 419 new libspirv::FriendlyNameMapper(context, code, wordCount)); 420 name_mapper = friendly_mapper->GetNameMapper(); 421 } 422 423 // Now disassemble! 424 Disassembler disassembler(grammar, options, name_mapper); 425 if (auto error = spvBinaryParse(context, &disassembler, code, wordCount, 426 DisassembleHeader, DisassembleInstruction, 427 pDiagnostic)) { 428 return error; 429 } 430 431 return disassembler.SaveTextResult(pText); 432} 433