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: 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. 89 void ResetColor() { 90 if (color_) out_.get() << libspirv::clr::reset(); 91 } 92 // Sets the output to grey, if color is turned on. 93 void SetGrey() { 94 if (color_) out_.get() << libspirv::clr::grey(); 95 } 96 // Sets the output to blue, if color is turned on. 97 void SetBlue() { 98 if (color_) out_.get() << libspirv::clr::blue(); 99 } 100 // Sets the output to yellow, if color is turned on. 101 void SetYellow() { 102 if (color_) out_.get() << libspirv::clr::yellow(); 103 } 104 // Sets the output to red, if color is turned on. 105 void SetRed() { 106 if (color_) out_.get() << libspirv::clr::red(); 107 } 108 // Sets the output to green, if color is turned on. 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 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 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 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 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 316 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 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 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 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