// | |
//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 <stdlib.h> | |
#include <assert.h> | |
#include <iomanip> | |
#include <stack> | |
#include <sstream> | |
#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<unsigned int>& stream) : out(out), stream(stream), word(0), nextNestedControl(0) { } | |
virtual ~SpirvStream() { } | |
void validate(); | |
void processInstructions(); | |
protected: | |
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<unsigned int>& 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 <id> to the instruction that created it | |
Id bound; | |
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) | |
std::vector<std::string> idDescriptor; // the best text string known for explaining the <id> | |
// schema | |
unsigned int schema; | |
// stack of structured-merge points | |
std::stack<Id> 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 <id> 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> | |
Id typeId = 0; | |
if (InstructionDesc[opCode].hasType()) { | |
typeId = stream[word++]; | |
--numOperands; | |
} | |
// Result <id> | |
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 <id>"); | |
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 <id>"); | |
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<unsigned int>& stream) | |
{ | |
SpirvStream SpirvStream(out, stream); | |
SpirvStream.validate(); | |
SpirvStream.processInstructions(); | |
} | |
}; // end namespace spv |