John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 1 | //
|
| 2 | //Copyright (C) 2014 LunarG, Inc.
|
| 3 | //
|
| 4 | //All rights reserved.
|
| 5 | //
|
| 6 | //Redistribution and use in source and binary forms, with or without
|
| 7 | //modification, are permitted provided that the following conditions
|
| 8 | //are met:
|
| 9 | //
|
| 10 | // Redistributions of source code must retain the above copyright
|
| 11 | // notice, this list of conditions and the following disclaimer.
|
| 12 | //
|
| 13 | // Redistributions in binary form must reproduce the above
|
| 14 | // copyright notice, this list of conditions and the following
|
| 15 | // disclaimer in the documentation and/or other materials provided
|
| 16 | // with the distribution.
|
| 17 | //
|
| 18 | // Neither the name of 3Dlabs Inc. Ltd. nor the names of its
|
| 19 | // contributors may be used to endorse or promote products derived
|
| 20 | // from this software without specific prior written permission.
|
| 21 | //
|
| 22 | //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 23 | //"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 24 | //LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTAstreamITY AND FITNESS
|
| 25 | //FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
| 26 | //COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
| 27 | //INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
| 28 | //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 29 | //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 30 | //CAUSED AND ON ANY THEORY OF LIAstreamITY, WHETHER IN CONTRACT, STRICT
|
| 31 | //LIAstreamITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
| 32 | //ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 33 | //POSSIstreamITY OF SUCH DAMAGE.
|
| 34 |
|
| 35 | //
|
| 36 | // Author: John Kessenich, LunarG
|
| 37 | //
|
| 38 |
|
| 39 | //
|
| 40 | // Disassembler for SPIR-V.
|
| 41 | //
|
| 42 |
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 43 | #include <stdlib.h>
|
| 44 | #include <assert.h>
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 45 | #include <iomanip>
|
| 46 | #include <stack>
|
| 47 | #include <sstream>
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 48 |
|
| 49 | #include "GLSL450Lib.h"
|
| 50 | extern const char* GlslStd450DebugNames[GLSL_STD_450::Count];
|
| 51 |
|
| 52 | #include "disassemble.h"
|
| 53 | #include "doc.h"
|
| 54 |
|
| 55 | namespace spv {
|
| 56 |
|
| 57 | void Kill(std::ostream& out, const char* message)
|
| 58 | {
|
| 59 | out << std::endl << "Disassembly failed: " << message << std::endl;
|
| 60 | exit(1);
|
| 61 | }
|
| 62 |
|
| 63 | // Container class for a single instance of a SPIR-V stream, with methods for disassembly.
|
| 64 | class SpirvStream {
|
| 65 | public:
|
| 66 | SpirvStream(std::ostream& out, const std::vector<unsigned int>& stream) : out(out), stream(stream), word(0), nextNestedControl(0) { }
|
| 67 | virtual ~SpirvStream() { }
|
| 68 |
|
| 69 | void validate();
|
| 70 | void processInstructions();
|
| 71 |
|
| 72 | protected:
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 73 | Op getOpCode(int id) const { return idInstruction[id] ? (Op)(stream[idInstruction[id]] & OpCodeMask) : OpNop; }
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 74 |
|
| 75 | // Output methods
|
| 76 | void outputIndent();
|
| 77 | void formatId(Id id, std::stringstream&);
|
| 78 | void outputResultId(Id id);
|
| 79 | void outputTypeId(Id id);
|
| 80 | void outputId(Id id);
|
| 81 | void disassembleImmediates(int numOperands);
|
| 82 | void disassembleIds(int numOperands);
|
| 83 | void disassembleString();
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 84 | void disassembleInstruction(Id resultId, Id typeId, Op opCode, int numOperands);
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 85 |
|
| 86 | // Data
|
| 87 | std::ostream& out; // where to write the disassembly
|
| 88 | const std::vector<unsigned int>& stream; // the actual word stream
|
| 89 | int size; // the size of the word stream
|
| 90 | int word; // the next word of the stream to read
|
| 91 |
|
| 92 | // map each <id> to the instruction that created it
|
| 93 | Id bound;
|
| 94 | std::vector<unsigned int> idInstruction; // the word offset into the stream where the instruction for result [id] starts; 0 if not yet seen (forward reference or function parameter)
|
| 95 |
|
| 96 | std::vector<std::string> idDescriptor; // the best text string known for explaining the <id>
|
| 97 |
|
| 98 | // schema
|
| 99 | unsigned int schema;
|
| 100 |
|
| 101 | // stack of structured-merge points
|
| 102 | std::stack<Id> nestedControl;
|
| 103 | Id nextNestedControl; // need a slight delay for when we are nested
|
| 104 | };
|
| 105 |
|
| 106 | void SpirvStream::validate()
|
| 107 | {
|
| 108 | size = (int)stream.size();
|
| 109 | if (size < 4)
|
| 110 | Kill(out, "stream is too short");
|
| 111 |
|
| 112 | // Magic number
|
| 113 | if (stream[word++] != MagicNumber) {
|
| 114 | out << "Bad magic number";
|
| 115 | return;
|
| 116 | }
|
| 117 |
|
| 118 | // Version
|
| 119 | out << "// Module Version " << stream[word++] << std::endl;
|
| 120 |
|
| 121 | // Generator's magic number
|
| 122 | out << "// Generated by (magic number): " << std::setbase(16) << stream[word++] << std::setbase(10) << std::endl;
|
| 123 |
|
| 124 | // Result <id> bound
|
| 125 | bound = stream[word++];
|
| 126 | idInstruction.resize(bound);
|
| 127 | idDescriptor.resize(bound);
|
| 128 | out << "// Id's are bound by " << bound << std::endl;
|
| 129 | out << std::endl;
|
| 130 |
|
| 131 | // Reserved schema, must be 0 for now
|
| 132 | schema = stream[word++];
|
| 133 | if (schema != 0)
|
| 134 | Kill(out, "bad schema, must be 0");
|
| 135 | }
|
| 136 |
|
| 137 | // Loop over all the instructions, in order, processing each.
|
| 138 | // Boiler plate for each is handled here directly, the rest is dispatched.
|
| 139 | void SpirvStream::processInstructions()
|
| 140 | {
|
| 141 | // Instructions
|
| 142 | while (word < size) {
|
| 143 | int instructionStart = word;
|
| 144 |
|
| 145 | // Instruction wordCount and opcode
|
| 146 | unsigned int firstWord = stream[word];
|
| 147 | unsigned wordCount = firstWord >> WordCountShift;
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 148 | Op opCode = (Op)(firstWord & OpCodeMask);
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 149 | int nextInst = word + wordCount;
|
| 150 | ++word;
|
| 151 |
|
| 152 | // Presence of full instruction
|
| 153 | if (nextInst > size)
|
| 154 | Kill(out, "stream instruction terminated too early");
|
| 155 |
|
| 156 | // Base for computing number of operands; will be updated as more is learned
|
| 157 | unsigned numOperands = wordCount - 1;
|
| 158 |
|
| 159 | // Type <id>
|
| 160 | Id typeId = 0;
|
| 161 | if (InstructionDesc[opCode].hasType()) {
|
| 162 | typeId = stream[word++];
|
| 163 | --numOperands;
|
| 164 | }
|
| 165 |
|
| 166 | // Result <id>
|
| 167 | Id resultId = 0;
|
| 168 | if (InstructionDesc[opCode].hasResult()) {
|
| 169 | resultId = stream[word++];
|
| 170 | --numOperands;
|
| 171 |
|
| 172 | // save instruction for future reference
|
| 173 | idInstruction[resultId] = instructionStart;
|
| 174 | }
|
| 175 |
|
| 176 | outputResultId(resultId);
|
| 177 | outputTypeId(typeId);
|
| 178 | outputIndent();
|
| 179 |
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 180 | // Hand off the Op and all its operands
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 181 | disassembleInstruction(resultId, typeId, opCode, numOperands);
|
| 182 | if (word != nextInst) {
|
| 183 | out << " ERROR, incorrect number of operands consumed. At " << word << " instead of " << nextInst << " instruction start was " << instructionStart;
|
| 184 | word = nextInst;
|
| 185 | }
|
| 186 | out << std::endl;
|
| 187 | }
|
| 188 | }
|
| 189 |
|
| 190 | void SpirvStream::outputIndent()
|
| 191 | {
|
| 192 | for (int i = 0; i < (int)nestedControl.size(); ++i)
|
| 193 | out << " ";
|
| 194 | }
|
| 195 |
|
| 196 | void SpirvStream::formatId(Id id, std::stringstream& idStream)
|
| 197 | {
|
| 198 | if (id >= bound)
|
| 199 | Kill(out, "Bad <id>");
|
| 200 |
|
| 201 | if (id != 0) {
|
| 202 | idStream << id;
|
| 203 | if (idDescriptor[id].size() > 0)
|
| 204 | idStream << "(" << idDescriptor[id] << ")";
|
| 205 | }
|
| 206 | }
|
| 207 |
|
| 208 | void SpirvStream::outputResultId(Id id)
|
| 209 | {
|
| 210 | const int width = 16;
|
| 211 | std::stringstream idStream;
|
| 212 | formatId(id, idStream);
|
| 213 | out << std::setw(width) << std::right << idStream.str();
|
| 214 | if (id != 0)
|
| 215 | out << ":";
|
| 216 | else
|
| 217 | out << " ";
|
| 218 |
|
| 219 | if (nestedControl.size() && id == nestedControl.top())
|
| 220 | nestedControl.pop();
|
| 221 | }
|
| 222 |
|
| 223 | void SpirvStream::outputTypeId(Id id)
|
| 224 | {
|
| 225 | const int width = 12;
|
| 226 | std::stringstream idStream;
|
| 227 | formatId(id, idStream);
|
| 228 | out << std::setw(width) << std::right << idStream.str() << " ";
|
| 229 | }
|
| 230 |
|
| 231 | void SpirvStream::outputId(Id id)
|
| 232 | {
|
| 233 | if (id >= bound)
|
| 234 | Kill(out, "Bad <id>");
|
| 235 |
|
| 236 | out << id;
|
| 237 | if (idDescriptor[id].size() > 0)
|
| 238 | out << "(" << idDescriptor[id] << ")";
|
| 239 | }
|
| 240 |
|
| 241 | void SpirvStream::disassembleImmediates(int numOperands)
|
| 242 | {
|
| 243 | for (int i = 0; i < numOperands; ++i) {
|
| 244 | out << stream[word++];
|
| 245 | if (i < numOperands - 1)
|
| 246 | out << " ";
|
| 247 | }
|
| 248 | }
|
| 249 |
|
| 250 | void SpirvStream::disassembleIds(int numOperands)
|
| 251 | {
|
| 252 | for (int i = 0; i < numOperands; ++i) {
|
| 253 | outputId(stream[word++]);
|
| 254 | if (i < numOperands - 1)
|
| 255 | out << " ";
|
| 256 | }
|
| 257 | }
|
| 258 |
|
| 259 | void SpirvStream::disassembleString()
|
| 260 | {
|
| 261 | out << " \"";
|
| 262 |
|
| 263 | char* wordString;
|
| 264 | bool done = false;
|
| 265 | do {
|
| 266 | unsigned int content = stream[word];
|
| 267 | wordString = (char*)&content;
|
| 268 | for (int charCount = 0; charCount < 4; ++charCount) {
|
| 269 | if (*wordString == 0) {
|
| 270 | done = true;
|
| 271 | break;
|
| 272 | }
|
| 273 | out << *(wordString++);
|
| 274 | }
|
| 275 | ++word;
|
| 276 | } while (! done);
|
| 277 |
|
| 278 | out << "\"";
|
| 279 | }
|
| 280 |
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 281 | void SpirvStream::disassembleInstruction(Id resultId, Id typeId, Op opCode, int numOperands)
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 282 | {
|
| 283 | // Process the opcode
|
| 284 |
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 285 | out << (OpcodeString(opCode) + 2); // leave out the "Op"
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 286 |
|
| 287 | if (opCode == OpLoopMerge || opCode == OpSelectionMerge)
|
| 288 | nextNestedControl = stream[word];
|
| 289 | else if (opCode == OpBranchConditional || opCode == OpSwitch) {
|
| 290 | if (nextNestedControl) {
|
| 291 | nestedControl.push(nextNestedControl);
|
| 292 | nextNestedControl = 0;
|
| 293 | }
|
| 294 | } else if (opCode == OpExtInstImport)
|
| 295 | idDescriptor[resultId] = (char*)(&stream[word]);
|
| 296 | else {
|
| 297 | if (idDescriptor[resultId].size() == 0) {
|
| 298 | switch (opCode) {
|
| 299 | case OpTypeInt:
|
| 300 | idDescriptor[resultId] = "int";
|
| 301 | break;
|
| 302 | case OpTypeFloat:
|
| 303 | idDescriptor[resultId] = "float";
|
| 304 | break;
|
| 305 | case OpTypeBool:
|
| 306 | idDescriptor[resultId] = "bool";
|
| 307 | break;
|
| 308 | case OpTypeStruct:
|
| 309 | idDescriptor[resultId] = "struct";
|
| 310 | break;
|
| 311 | case OpTypePointer:
|
| 312 | idDescriptor[resultId] = "ptr";
|
| 313 | break;
|
| 314 | case OpTypeVector:
|
| 315 | if (idDescriptor[stream[word]].size() > 0)
|
| 316 | idDescriptor[resultId].append(idDescriptor[stream[word]].begin(), idDescriptor[stream[word]].begin() + 1);
|
| 317 | idDescriptor[resultId].append("vec");
|
| 318 | switch (stream[word + 1]) {
|
| 319 | case 2: idDescriptor[resultId].append("2"); break;
|
| 320 | case 3: idDescriptor[resultId].append("3"); break;
|
| 321 | case 4: idDescriptor[resultId].append("4"); break;
|
| 322 | case 8: idDescriptor[resultId].append("8"); break;
|
| 323 | case 16: idDescriptor[resultId].append("16"); break;
|
| 324 | case 32: idDescriptor[resultId].append("32"); break;
|
| 325 | default: break;
|
| 326 | }
|
| 327 | break;
|
| 328 | default:
|
| 329 | break;
|
| 330 | }
|
| 331 | }
|
| 332 | }
|
| 333 |
|
| 334 | // Process the operands. Note, a new context-dependent set could be
|
| 335 | // swapped in mid-traversal.
|
| 336 |
|
| 337 | // Handle textures specially, so can put out helpful strings.
|
| 338 | if (opCode == OpTypeSampler) {
|
| 339 | disassembleIds(1);
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 340 | out << " " << DimensionString((Dim)stream[word++]);
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 341 | switch (stream[word++]) {
|
| 342 | case 0: out << " texture"; break;
|
| 343 | case 1: out << " image"; break;
|
| 344 | case 2: out << " filter+texture"; break;
|
| 345 | }
|
| 346 | out << (stream[word++] != 0 ? " array" : "");
|
| 347 | out << (stream[word++] != 0 ? " depth" : "");
|
| 348 | out << (stream[word++] != 0 ? " multi-sampled" : "");
|
| 349 | return;
|
| 350 | }
|
| 351 |
|
| 352 | // Handle all the parameterized operands
|
| 353 | for (int op = 0; op < InstructionDesc[opCode].operands.getNum(); ++op) {
|
| 354 | out << " ";
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 355 | OperandClass operandClass = InstructionDesc[opCode].operands.getClass(op);
|
| 356 | switch (operandClass) {
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 357 | case OperandId:
|
| 358 | disassembleIds(1);
|
| 359 | // Get names for printing "(XXX)" for readability, *after* this id
|
| 360 | if (opCode == OpName)
|
| 361 | idDescriptor[stream[word - 1]] = (char*)(&stream[word]);
|
| 362 | break;
|
| 363 | case OperandOptionalId:
|
| 364 | case OperandVariableIds:
|
| 365 | disassembleIds(numOperands);
|
| 366 | return;
|
| 367 | case OperandVariableLiterals:
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 368 | if (opCode == OpDecorate && stream[word - 1] == DecorationBuiltIn) {
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 369 | out << BuiltInString(stream[word++]);
|
| 370 | --numOperands;
|
| 371 | ++op;
|
| 372 | }
|
| 373 | disassembleImmediates(numOperands);
|
| 374 | return;
|
| 375 | case OperandVariableLiteralId:
|
| 376 | while (numOperands > 0) {
|
| 377 | out << std::endl;
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 378 | outputResultId(0);
|
| 379 | outputTypeId(0);
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 380 | outputIndent();
|
| 381 | out << " case ";
|
| 382 | disassembleImmediates(1);
|
| 383 | out << ": ";
|
| 384 | disassembleIds(1);
|
| 385 | numOperands -= 2;
|
| 386 | }
|
| 387 | return;
|
| 388 | case OperandLiteralNumber:
|
| 389 | disassembleImmediates(1);
|
| 390 | if (opCode == OpExtInst) {
|
| 391 | unsigned entrypoint = stream[word - 1];
|
| 392 | if (entrypoint < GLSL_STD_450::Count)
|
| 393 | out << "(" << GlslStd450DebugNames[entrypoint] << ")";
|
| 394 | }
|
| 395 | break;
|
| 396 | case OperandLiteralString:
|
| 397 | disassembleString();
|
| 398 | return;
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 399 | default:
|
John Kessenich | b40d6ac | 2015-03-30 17:41:16 +0000 | [diff] [blame] | 400 | assert(operandClass >= OperandSource && operandClass < OperandOpcode);
|
| 401 |
|
| 402 | if (OperandClassParams[operandClass].bitmask) {
|
| 403 | unsigned int mask = stream[word++];
|
| 404 | if (mask == 0)
|
| 405 | out << "None";
|
| 406 | else {
|
| 407 | for (int m = 0; m < OperandClassParams[operandClass].ceiling; ++m) {
|
| 408 | if (mask & (1 << m))
|
| 409 | out << OperandClassParams[operandClass].getName(m) << " ";
|
| 410 | }
|
| 411 | }
|
| 412 | break;
|
| 413 | } else
|
| 414 | out << OperandClassParams[operandClass].getName(stream[word++]);
|
| 415 |
|
John Kessenich | acba772 | 2015-03-04 03:48:38 +0000 | [diff] [blame] | 416 | break;
|
| 417 | }
|
| 418 | --numOperands;
|
| 419 | }
|
| 420 |
|
| 421 | return;
|
| 422 | }
|
| 423 |
|
| 424 | void Disassemble(std::ostream& out, const std::vector<unsigned int>& stream)
|
| 425 | {
|
| 426 | SpirvStream SpirvStream(out, stream);
|
| 427 | SpirvStream.validate();
|
| 428 | SpirvStream.processInstructions();
|
| 429 | }
|
| 430 |
|
| 431 | }; // end namespace spv
|