574 lines
20 KiB
C++
574 lines
20 KiB
C++
//
|
|
//Copyright (C) 2014-2015 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 MERCHANTABILITY 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 LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
//POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//
|
|
// Disassembler for SPIR-V.
|
|
//
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <iomanip>
|
|
#include <stack>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
|
|
namespace spv {
|
|
// Include C-based headers that don't have a namespace
|
|
#include "GLSL.std.450.h"
|
|
}
|
|
const char* GlslStd450DebugNames[spv::GLSLstd450Count];
|
|
|
|
#include "disassemble.h"
|
|
#include "doc.h"
|
|
|
|
namespace spv {
|
|
|
|
static void Kill(std::ostream& out, const char* message)
|
|
{
|
|
out << std::endl << "Disassembly failed: " << message << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
// used to identify the extended instruction library imported when printing
|
|
enum ExtInstSet {
|
|
GLSL450Inst,
|
|
OpenCLExtInst,
|
|
};
|
|
|
|
// 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:
|
|
SpirvStream(const SpirvStream&);
|
|
SpirvStream& operator=(const 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 outputMask(OperandClass operandClass, unsigned mask);
|
|
void disassembleImmediates(int numOperands);
|
|
void disassembleIds(int numOperands);
|
|
int 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 " << std::hex << stream[word++] << std::endl;
|
|
|
|
// Generator's magic number
|
|
out << "// Generated by (magic number): " << std::hex << stream[word++] << std::dec << 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::outputMask(OperandClass operandClass, unsigned mask)
|
|
{
|
|
if (mask == 0)
|
|
out << "None";
|
|
else {
|
|
for (int m = 0; m < OperandClassParams[operandClass].ceiling; ++m) {
|
|
if (mask & (1 << m))
|
|
out << OperandClassParams[operandClass].getName(m) << " ";
|
|
}
|
|
}
|
|
}
|
|
|
|
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 << " ";
|
|
}
|
|
}
|
|
|
|
// return the number of operands consumed by the string
|
|
int SpirvStream::disassembleString()
|
|
{
|
|
int startWord = word;
|
|
|
|
out << " \"";
|
|
|
|
const char* wordString;
|
|
bool done = false;
|
|
do {
|
|
unsigned int content = stream[word];
|
|
wordString = (const char*)&content;
|
|
for (int charCount = 0; charCount < 4; ++charCount) {
|
|
if (*wordString == 0) {
|
|
done = true;
|
|
break;
|
|
}
|
|
out << *(wordString++);
|
|
}
|
|
++word;
|
|
} while (! done);
|
|
|
|
out << "\"";
|
|
|
|
return word - startWord;
|
|
}
|
|
|
|
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] = (const 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 images specially, so can put out helpful strings.
|
|
if (opCode == OpTypeImage) {
|
|
out << " ";
|
|
disassembleIds(1);
|
|
out << " " << DimensionString((Dim)stream[word++]);
|
|
out << (stream[word++] != 0 ? " depth" : "");
|
|
out << (stream[word++] != 0 ? " array" : "");
|
|
out << (stream[word++] != 0 ? " multi-sampled" : "");
|
|
switch (stream[word++]) {
|
|
case 0: out << " runtime"; break;
|
|
case 1: out << " sampled"; break;
|
|
case 2: out << " nonsampled"; break;
|
|
}
|
|
out << " format:" << ImageFormatString((ImageFormat)stream[word++]);
|
|
|
|
if (numOperands == 8) {
|
|
out << " " << AccessQualifierString(stream[word++]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Handle all the parameterized operands
|
|
for (int op = 0; op < InstructionDesc[opCode].operands.getNum() && numOperands > 0; ++op) {
|
|
out << " ";
|
|
OperandClass operandClass = InstructionDesc[opCode].operands.getClass(op);
|
|
switch (operandClass) {
|
|
case OperandId:
|
|
case OperandScope:
|
|
case OperandMemorySemantics:
|
|
disassembleIds(1);
|
|
--numOperands;
|
|
// Get names for printing "(XXX)" for readability, *after* this id
|
|
if (opCode == OpName)
|
|
idDescriptor[stream[word - 1]] = (const char*)(&stream[word]);
|
|
break;
|
|
case OperandVariableIds:
|
|
disassembleIds(numOperands);
|
|
return;
|
|
case OperandImageOperands:
|
|
outputMask(OperandImageOperands, stream[word++]);
|
|
--numOperands;
|
|
disassembleIds(numOperands);
|
|
return;
|
|
case OperandOptionalLiteral:
|
|
case OperandVariableLiterals:
|
|
if ((opCode == OpDecorate && stream[word - 1] == DecorationBuiltIn) ||
|
|
(opCode == OpMemberDecorate && stream[word - 1] == DecorationBuiltIn)) {
|
|
out << BuiltInString(stream[word++]);
|
|
--numOperands;
|
|
++op;
|
|
}
|
|
disassembleImmediates(numOperands);
|
|
return;
|
|
case OperandVariableIdLiteral:
|
|
while (numOperands > 0) {
|
|
out << std::endl;
|
|
outputResultId(0);
|
|
outputTypeId(0);
|
|
outputIndent();
|
|
out << " Type ";
|
|
disassembleIds(1);
|
|
out << ", member ";
|
|
disassembleImmediates(1);
|
|
numOperands -= 2;
|
|
}
|
|
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);
|
|
--numOperands;
|
|
if (opCode == OpExtInst) {
|
|
ExtInstSet extInstSet = GLSL450Inst;
|
|
if (0 == memcmp("OpenCL", (const char*)(idDescriptor[stream[word-2]].c_str()), 6)) {
|
|
extInstSet = OpenCLExtInst;
|
|
}
|
|
unsigned entrypoint = stream[word - 1];
|
|
if (extInstSet == GLSL450Inst) {
|
|
if (entrypoint < GLSLstd450Count) {
|
|
out << "(" << GlslStd450DebugNames[entrypoint] << ")";
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case OperandOptionalLiteralString:
|
|
case OperandLiteralString:
|
|
numOperands -= disassembleString();
|
|
break;
|
|
default:
|
|
assert(operandClass >= OperandSource && operandClass < OperandOpcode);
|
|
|
|
if (OperandClassParams[operandClass].bitmask)
|
|
outputMask(operandClass, stream[word++]);
|
|
else
|
|
out << OperandClassParams[operandClass].getName(stream[word++]);
|
|
--numOperands;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void GLSLstd450GetDebugNames(const char** names)
|
|
{
|
|
for (int i = 0; i < GLSLstd450Count; ++i)
|
|
names[i] = "Unknown";
|
|
|
|
names[GLSLstd450Round] = "Round";
|
|
names[GLSLstd450RoundEven] = "RoundEven";
|
|
names[GLSLstd450Trunc] = "Trunc";
|
|
names[GLSLstd450FAbs] = "FAbs";
|
|
names[GLSLstd450SAbs] = "SAbs";
|
|
names[GLSLstd450FSign] = "FSign";
|
|
names[GLSLstd450SSign] = "SSign";
|
|
names[GLSLstd450Floor] = "Floor";
|
|
names[GLSLstd450Ceil] = "Ceil";
|
|
names[GLSLstd450Fract] = "Fract";
|
|
names[GLSLstd450Radians] = "Radians";
|
|
names[GLSLstd450Degrees] = "Degrees";
|
|
names[GLSLstd450Sin] = "Sin";
|
|
names[GLSLstd450Cos] = "Cos";
|
|
names[GLSLstd450Tan] = "Tan";
|
|
names[GLSLstd450Asin] = "Asin";
|
|
names[GLSLstd450Acos] = "Acos";
|
|
names[GLSLstd450Atan] = "Atan";
|
|
names[GLSLstd450Sinh] = "Sinh";
|
|
names[GLSLstd450Cosh] = "Cosh";
|
|
names[GLSLstd450Tanh] = "Tanh";
|
|
names[GLSLstd450Asinh] = "Asinh";
|
|
names[GLSLstd450Acosh] = "Acosh";
|
|
names[GLSLstd450Atanh] = "Atanh";
|
|
names[GLSLstd450Atan2] = "Atan2";
|
|
names[GLSLstd450Pow] = "Pow";
|
|
names[GLSLstd450Exp] = "Exp";
|
|
names[GLSLstd450Log] = "Log";
|
|
names[GLSLstd450Exp2] = "Exp2";
|
|
names[GLSLstd450Log2] = "Log2";
|
|
names[GLSLstd450Sqrt] = "Sqrt";
|
|
names[GLSLstd450InverseSqrt] = "InverseSqrt";
|
|
names[GLSLstd450Determinant] = "Determinant";
|
|
names[GLSLstd450MatrixInverse] = "MatrixInverse";
|
|
names[GLSLstd450Modf] = "Modf";
|
|
names[GLSLstd450ModfStruct] = "ModfStruct";
|
|
names[GLSLstd450FMin] = "FMin";
|
|
names[GLSLstd450SMin] = "SMin";
|
|
names[GLSLstd450UMin] = "UMin";
|
|
names[GLSLstd450FMax] = "FMax";
|
|
names[GLSLstd450SMax] = "SMax";
|
|
names[GLSLstd450UMax] = "UMax";
|
|
names[GLSLstd450FClamp] = "FClamp";
|
|
names[GLSLstd450SClamp] = "SClamp";
|
|
names[GLSLstd450UClamp] = "UClamp";
|
|
names[GLSLstd450FMix] = "FMix";
|
|
names[GLSLstd450Step] = "Step";
|
|
names[GLSLstd450SmoothStep] = "SmoothStep";
|
|
names[GLSLstd450Fma] = "Fma";
|
|
names[GLSLstd450Frexp] = "Frexp";
|
|
names[GLSLstd450FrexpStruct] = "FrexpStruct";
|
|
names[GLSLstd450Ldexp] = "Ldexp";
|
|
names[GLSLstd450PackSnorm4x8] = "PackSnorm4x8";
|
|
names[GLSLstd450PackUnorm4x8] = "PackUnorm4x8";
|
|
names[GLSLstd450PackSnorm2x16] = "PackSnorm2x16";
|
|
names[GLSLstd450PackUnorm2x16] = "PackUnorm2x16";
|
|
names[GLSLstd450PackHalf2x16] = "PackHalf2x16";
|
|
names[GLSLstd450PackDouble2x32] = "PackDouble2x32";
|
|
names[GLSLstd450UnpackSnorm2x16] = "UnpackSnorm2x16";
|
|
names[GLSLstd450UnpackUnorm2x16] = "UnpackUnorm2x16";
|
|
names[GLSLstd450UnpackHalf2x16] = "UnpackHalf2x16";
|
|
names[GLSLstd450UnpackSnorm4x8] = "UnpackSnorm4x8";
|
|
names[GLSLstd450UnpackUnorm4x8] = "UnpackUnorm4x8";
|
|
names[GLSLstd450UnpackDouble2x32] = "UnpackDouble2x32";
|
|
names[GLSLstd450Length] = "Length";
|
|
names[GLSLstd450Distance] = "Distance";
|
|
names[GLSLstd450Cross] = "Cross";
|
|
names[GLSLstd450Normalize] = "Normalize";
|
|
names[GLSLstd450FaceForward] = "FaceForward";
|
|
names[GLSLstd450Reflect] = "Reflect";
|
|
names[GLSLstd450Refract] = "Refract";
|
|
names[GLSLstd450FindILsb] = "FindILsb";
|
|
names[GLSLstd450FindSMsb] = "FindSMsb";
|
|
names[GLSLstd450FindUMsb] = "FindUMsb";
|
|
names[GLSLstd450InterpolateAtCentroid] = "InterpolateAtCentroid";
|
|
names[GLSLstd450InterpolateAtSample] = "InterpolateAtSample";
|
|
names[GLSLstd450InterpolateAtOffset] = "InterpolateAtOffset";
|
|
}
|
|
|
|
void Disassemble(std::ostream& out, const std::vector<unsigned int>& stream)
|
|
{
|
|
SpirvStream SpirvStream(out, stream);
|
|
spv::Parameterize();
|
|
GLSLstd450GetDebugNames(GlslStd450DebugNames);
|
|
SpirvStream.validate();
|
|
SpirvStream.processInstructions();
|
|
}
|
|
|
|
}; // end namespace spv
|