// //Copyright (C) 2014 LunarG, Inc. // //All rights reserved. // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions //are met: // // Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // Neither the name of 3Dlabs Inc. Ltd. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS //"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT //LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTAstreamITY AND FITNESS //FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE //COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER //CAUSED AND ON ANY THEORY OF LIAstreamITY, WHETHER IN CONTRACT, STRICT //LIAstreamITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN //ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //POSSIstreamITY OF SUCH DAMAGE. // // Author: John Kessenich, LunarG // // // Disassembler for SPIR-V. // #include #include #include #include #include #include "GLSL450Lib.h" extern const char* GlslStd450DebugNames[GLSL_STD_450::Count]; #include "disassemble.h" #include "doc.h" namespace spv { void Kill(std::ostream& out, const char* message) { out << std::endl << "Disassembly failed: " << message << std::endl; exit(1); } // Container class for a single instance of a SPIR-V stream, with methods for disassembly. class SpirvStream { public: SpirvStream(std::ostream& out, const std::vector& stream) : out(out), stream(stream), word(0), nextNestedControl(0) { } virtual ~SpirvStream() { } void validate(); void processInstructions(); protected: SpirvStream(SpirvStream&); SpirvStream& operator=(SpirvStream&); Op getOpCode(int id) const { return idInstruction[id] ? (Op)(stream[idInstruction[id]] & OpCodeMask) : OpNop; } // Output methods void outputIndent(); void formatId(Id id, std::stringstream&); void outputResultId(Id id); void outputTypeId(Id id); void outputId(Id id); void disassembleImmediates(int numOperands); void disassembleIds(int numOperands); void disassembleString(); void disassembleInstruction(Id resultId, Id typeId, Op opCode, int numOperands); // Data std::ostream& out; // where to write the disassembly const std::vector& stream; // the actual word stream int size; // the size of the word stream int word; // the next word of the stream to read // map each to the instruction that created it Id bound; std::vector idInstruction; // the word offset into the stream where the instruction for result [id] starts; 0 if not yet seen (forward reference or function parameter) std::vector idDescriptor; // the best text string known for explaining the // schema unsigned int schema; // stack of structured-merge points std::stack nestedControl; Id nextNestedControl; // need a slight delay for when we are nested }; void SpirvStream::validate() { size = (int)stream.size(); if (size < 4) Kill(out, "stream is too short"); // Magic number if (stream[word++] != MagicNumber) { out << "Bad magic number"; return; } // Version out << "// Module Version " << stream[word++] << std::endl; // Generator's magic number out << "// Generated by (magic number): " << std::setbase(16) << stream[word++] << std::setbase(10) << std::endl; // Result bound bound = stream[word++]; idInstruction.resize(bound); idDescriptor.resize(bound); out << "// Id's are bound by " << bound << std::endl; out << std::endl; // Reserved schema, must be 0 for now schema = stream[word++]; if (schema != 0) Kill(out, "bad schema, must be 0"); } // Loop over all the instructions, in order, processing each. // Boiler plate for each is handled here directly, the rest is dispatched. void SpirvStream::processInstructions() { // Instructions while (word < size) { int instructionStart = word; // Instruction wordCount and opcode unsigned int firstWord = stream[word]; unsigned wordCount = firstWord >> WordCountShift; Op opCode = (Op)(firstWord & OpCodeMask); int nextInst = word + wordCount; ++word; // Presence of full instruction if (nextInst > size) Kill(out, "stream instruction terminated too early"); // Base for computing number of operands; will be updated as more is learned unsigned numOperands = wordCount - 1; // Type Id typeId = 0; if (InstructionDesc[opCode].hasType()) { typeId = stream[word++]; --numOperands; } // Result Id resultId = 0; if (InstructionDesc[opCode].hasResult()) { resultId = stream[word++]; --numOperands; // save instruction for future reference idInstruction[resultId] = instructionStart; } outputResultId(resultId); outputTypeId(typeId); outputIndent(); // Hand off the Op and all its operands disassembleInstruction(resultId, typeId, opCode, numOperands); if (word != nextInst) { out << " ERROR, incorrect number of operands consumed. At " << word << " instead of " << nextInst << " instruction start was " << instructionStart; word = nextInst; } out << std::endl; } } void SpirvStream::outputIndent() { for (int i = 0; i < (int)nestedControl.size(); ++i) out << " "; } void SpirvStream::formatId(Id id, std::stringstream& idStream) { if (id >= bound) Kill(out, "Bad "); if (id != 0) { idStream << id; if (idDescriptor[id].size() > 0) idStream << "(" << idDescriptor[id] << ")"; } } void SpirvStream::outputResultId(Id id) { const int width = 16; std::stringstream idStream; formatId(id, idStream); out << std::setw(width) << std::right << idStream.str(); if (id != 0) out << ":"; else out << " "; if (nestedControl.size() && id == nestedControl.top()) nestedControl.pop(); } void SpirvStream::outputTypeId(Id id) { const int width = 12; std::stringstream idStream; formatId(id, idStream); out << std::setw(width) << std::right << idStream.str() << " "; } void SpirvStream::outputId(Id id) { if (id >= bound) Kill(out, "Bad "); out << id; if (idDescriptor[id].size() > 0) out << "(" << idDescriptor[id] << ")"; } void SpirvStream::disassembleImmediates(int numOperands) { for (int i = 0; i < numOperands; ++i) { out << stream[word++]; if (i < numOperands - 1) out << " "; } } void SpirvStream::disassembleIds(int numOperands) { for (int i = 0; i < numOperands; ++i) { outputId(stream[word++]); if (i < numOperands - 1) out << " "; } } void SpirvStream::disassembleString() { out << " \""; char* wordString; bool done = false; do { unsigned int content = stream[word]; wordString = (char*)&content; for (int charCount = 0; charCount < 4; ++charCount) { if (*wordString == 0) { done = true; break; } out << *(wordString++); } ++word; } while (! done); out << "\""; } void SpirvStream::disassembleInstruction(Id resultId, Id /*typeId*/, Op opCode, int numOperands) { // Process the opcode out << (OpcodeString(opCode) + 2); // leave out the "Op" if (opCode == OpLoopMerge || opCode == OpSelectionMerge) nextNestedControl = stream[word]; else if (opCode == OpBranchConditional || opCode == OpSwitch) { if (nextNestedControl) { nestedControl.push(nextNestedControl); nextNestedControl = 0; } } else if (opCode == OpExtInstImport) idDescriptor[resultId] = (char*)(&stream[word]); else { if (idDescriptor[resultId].size() == 0) { switch (opCode) { case OpTypeInt: idDescriptor[resultId] = "int"; break; case OpTypeFloat: idDescriptor[resultId] = "float"; break; case OpTypeBool: idDescriptor[resultId] = "bool"; break; case OpTypeStruct: idDescriptor[resultId] = "struct"; break; case OpTypePointer: idDescriptor[resultId] = "ptr"; break; case OpTypeVector: if (idDescriptor[stream[word]].size() > 0) idDescriptor[resultId].append(idDescriptor[stream[word]].begin(), idDescriptor[stream[word]].begin() + 1); idDescriptor[resultId].append("vec"); switch (stream[word + 1]) { case 2: idDescriptor[resultId].append("2"); break; case 3: idDescriptor[resultId].append("3"); break; case 4: idDescriptor[resultId].append("4"); break; case 8: idDescriptor[resultId].append("8"); break; case 16: idDescriptor[resultId].append("16"); break; case 32: idDescriptor[resultId].append("32"); break; default: break; } break; default: break; } } } // Process the operands. Note, a new context-dependent set could be // swapped in mid-traversal. // Handle textures specially, so can put out helpful strings. if (opCode == OpTypeSampler) { disassembleIds(1); out << " " << DimensionString((Dim)stream[word++]); switch (stream[word++]) { case 0: out << " texture"; break; case 1: out << " image"; break; case 2: out << " filter+texture"; break; } out << (stream[word++] != 0 ? " array" : ""); out << (stream[word++] != 0 ? " depth" : ""); out << (stream[word++] != 0 ? " multi-sampled" : ""); return; } // Handle all the parameterized operands for (int op = 0; op < InstructionDesc[opCode].operands.getNum(); ++op) { out << " "; OperandClass operandClass = InstructionDesc[opCode].operands.getClass(op); switch (operandClass) { case OperandId: disassembleIds(1); // Get names for printing "(XXX)" for readability, *after* this id if (opCode == OpName) idDescriptor[stream[word - 1]] = (char*)(&stream[word]); break; case OperandOptionalId: case OperandVariableIds: disassembleIds(numOperands); return; case OperandVariableLiterals: if (opCode == OpDecorate && stream[word - 1] == DecorationBuiltIn) { out << BuiltInString(stream[word++]); --numOperands; ++op; } disassembleImmediates(numOperands); return; case OperandVariableLiteralId: while (numOperands > 0) { out << std::endl; outputResultId(0); outputTypeId(0); outputIndent(); out << " case "; disassembleImmediates(1); out << ": "; disassembleIds(1); numOperands -= 2; } return; case OperandLiteralNumber: disassembleImmediates(1); if (opCode == OpExtInst) { unsigned entrypoint = stream[word - 1]; if (entrypoint < GLSL_STD_450::Count) out << "(" << GlslStd450DebugNames[entrypoint] << ")"; } break; case OperandLiteralString: disassembleString(); return; default: assert(operandClass >= OperandSource && operandClass < OperandOpcode); if (OperandClassParams[operandClass].bitmask) { unsigned int mask = stream[word++]; if (mask == 0) out << "None"; else { for (int m = 0; m < OperandClassParams[operandClass].ceiling; ++m) { if (mask & (1 << m)) out << OperandClassParams[operandClass].getName(m) << " "; } } break; } else out << OperandClassParams[operandClass].getName(stream[word++]); break; } --numOperands; } return; } void Disassemble(std::ostream& out, const std::vector& stream) { SpirvStream SpirvStream(out, stream); SpirvStream.validate(); SpirvStream.processInstructions(); } }; // end namespace spv