//
// 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 <cstdint>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <iomanip>
#include <stack>
#include <sstream>
#include <cstring>
#include <utility>

#include "disassemble.h"
#include "doc.h"
#include "spvUtil.h"

namespace spv {
    extern "C" {
        // Include C-based headers that don't have a namespace
        #include "GLSL.std.450.h"
        #include "GLSL.ext.AMD.h"
        #include "GLSL.ext.NV.h"
        #include "GLSL.ext.ARM.h"
        #include "NonSemanticShaderDebugInfo.h"
        #include "GLSL.ext.QCOM.h"
    }
}
static const char* GlslStd450DebugNames[spv::GLSLstd450Count];

namespace spv {

static const char* GLSLextAMDGetDebugNames(const char*, unsigned);
static const char* GLSLextNVGetDebugNames(const char*, unsigned);
static const char* NonSemanticShaderDebugInfoGetDebugNames(unsigned);

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,
    GLSLextAMDInst,
    GLSLextNVInst,
    OpenCLExtInst,
    NonSemanticDebugPrintfExtInst,
    NonSemanticDebugBreakExtInst,
    NonSemanticShaderDebugInfo
};

// 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) : Op::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);
    std::pair<int, std::string> decodeString();
    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[enumCast(opCode)].hasType()) {
            typeId = stream[word++];
            --numOperands;
        }

        // Result <id>
        Id resultId = 0;
        if (InstructionDesc[enumCast(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 != 0) {
        // On instructions with no IDs, this is called with "0", which does not
        // have to be within ID bounds on null shaders.
        if (id >= bound)
            Kill(out, "Bad <id>");

        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 << " ";
    }
}

// decode string from words at current position (non-consuming)
std::pair<int, std::string> SpirvStream::decodeString()
{
    std::string res;
    int wordPos = word;
    char c;
    bool done = false;

    do {
        unsigned int content = stream[wordPos];
        for (int charCount = 0; charCount < 4; ++charCount) {
            c = content & 0xff;
            content >>= 8;
            if (c == '\0') {
                done = true;
                break;
            }
            res += c;
        }
        ++wordPos;
    } while(! done);

    return std::make_pair(wordPos - word, res);
}

// return the number of operands consumed by the string
int SpirvStream::disassembleString()
{
    out << " \"";

    std::pair<int, std::string> decoderes = decodeString();

    out << decoderes.second;
    out << "\"";

    word += decoderes.first;

    return decoderes.first;
}

static uint32_t popcount(uint32_t mask)
{
    uint32_t count = 0;
    while (mask) {
        if (mask & 1) {
            count++;
        }
        mask >>= 1;
    }
    return count;
}

void SpirvStream::disassembleInstruction(Id resultId, Id /*typeId*/, Op opCode, int numOperands)
{
    // Process the opcode

    out << (OpcodeString((int)opCode) + 2);  // leave out the "Op"

    if (opCode == Op::OpLoopMerge || opCode == Op::OpSelectionMerge)
        nextNestedControl = stream[word];
    else if (opCode == Op::OpBranchConditional || opCode == Op::OpSwitch) {
        if (nextNestedControl) {
            nestedControl.push(nextNestedControl);
            nextNestedControl = 0;
        }
    } else if (opCode == Op::OpExtInstImport) {
        idDescriptor[resultId] = decodeString().second;
    }
    else {
        if (resultId != 0 && idDescriptor[resultId].size() == 0) {
            switch (opCode) {
            case Op::OpTypeInt:
                switch (stream[word]) {
                case 8:  idDescriptor[resultId] = "int8_t"; break;
                case 16: idDescriptor[resultId] = "int16_t"; break;
                default: assert(0); [[fallthrough]];
                case 32: idDescriptor[resultId] = "int"; break;
                case 64: idDescriptor[resultId] = "int64_t"; break;
                }
                break;
            case Op::OpTypeFloat:
                switch (stream[word]) {
                case 4:
                case 6:
                case 8:
                case 16:
                    if (numOperands > 1) {
                        switch (stream[word+1]) {
                        default:
                            assert(0); [[fallthrough]];
                        case (int)spv::FPEncoding::BFloat16KHR:
                            idDescriptor[resultId] = "bfloat16_t";
                            break;
                        case (int)spv::FPEncoding::Float8E4M3EXT:
                            idDescriptor[resultId] = "floate4m3_t";
                            break;
                        case (int)spv::FPEncoding::Float8E5M2EXT:
                            idDescriptor[resultId] = "floate5m2_t";
                            break;
                        case (int)spv::FPEncoding::Float4E2M1EXT:
                            idDescriptor[resultId] = "floate2m1_t";
                            break;
                        case (int)spv::FPEncoding::Float6E3M2EXT:
                            idDescriptor[resultId] = "floate3m2_t";
                            break;
                        case (int)spv::FPEncoding::Float6E2M3EXT:
                            idDescriptor[resultId] = "floate2m3_t";
                            break;
                        case (int)spv::FPEncoding::Float8UnsignedE8M0EXT:
                            idDescriptor[resultId] = "floatue8m0_t";
                            break;
                        case (int)spv::FPEncoding::MXInt8EXT:
                            idDescriptor[resultId] = "floatmxint8_t";
                            break;
                        }
                    } else {
                        idDescriptor[resultId] = "float16_t";
                    }
                    break;
                default: assert(0); [[fallthrough]];
                case 32: idDescriptor[resultId] = "float"; break;
                case 64: idDescriptor[resultId] = "float64_t"; break;
                }
                break;
            case Op::OpTypeBool:
                idDescriptor[resultId] = "bool";
                break;
            case Op::OpTypeStruct:
                idDescriptor[resultId] = "struct";
                break;
            case Op::OpTypePointer:
            case Op::OpTypeUntypedPointerKHR:
                idDescriptor[resultId] = "ptr";
                break;
            case Op::OpTypeVector:
                if (idDescriptor[stream[word]].size() > 0) {
                    if (idDescriptor[stream[word]].substr(0,2) == "bf") {
                        idDescriptor[resultId].append(idDescriptor[stream[word]].begin(), idDescriptor[stream[word]].begin() + 2);
                    } else {
                        idDescriptor[resultId].append(idDescriptor[stream[word]].begin(), idDescriptor[stream[word]].begin() + 1);
                    }
                    if (strstr(idDescriptor[stream[word]].c_str(), "8")) {
                        idDescriptor[resultId].append("8");
                    }
                    if (strstr(idDescriptor[stream[word]].c_str(), "16")) {
                        idDescriptor[resultId].append("16");
                    }
                    if (strstr(idDescriptor[stream[word]].c_str(), "64")) {
                        idDescriptor[resultId].append("64");
                    }
                }
                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 == Op::OpTypeImage) {
        out << " ";
        disassembleIds(1);
        out << " " << DimensionString((int)(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((int)(ImageFormat)stream[word++]);

        if (numOperands == 8) {
            out << " " << AccessQualifierString(stream[word++]);
        }
        return;
    }

    // Handle all the parameterized operands
    for (int op = 0; op < InstructionDesc[enumCast(opCode)].operands.getNum() && numOperands > 0; ++op) {
        out << " ";
        OperandClass operandClass = InstructionDesc[enumCast(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 == Op::OpName)
                idDescriptor[stream[word - 1]] = decodeString().second;
            break;
        case OperandVariableIds:
            disassembleIds(numOperands);
            return;
        case OperandImageOperands:
            outputMask(OperandImageOperands, stream[word++]);
            --numOperands;
            disassembleIds(numOperands);
            return;
        case OperandOptionalLiteral:
        case OperandVariableLiterals:
            if ((opCode == Op::OpDecorate && stream[word - 1] == Decoration::BuiltIn) ||
                (opCode == Op::OpMemberDecorate && stream[word - 1] == Decoration::BuiltIn)) {
                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 == Op::OpExtInst) {
                ExtInstSet extInstSet = GLSL450Inst;
                const char* name = idDescriptor[stream[word - 2]].c_str();
                if (strcmp("OpenCL.std", name) == 0) {
                    extInstSet = OpenCLExtInst;
                } else if (strcmp("OpenCL.DebugInfo.100", name) == 0) {
                    extInstSet = OpenCLExtInst;
                } else if (strcmp("NonSemantic.DebugPrintf", name) == 0) {
                    extInstSet = NonSemanticDebugPrintfExtInst;
                } else if (strcmp("NonSemantic.DebugBreak", name) == 0) {
                    extInstSet = NonSemanticDebugBreakExtInst;
                } else if (strncmp("NonSemantic.Shader.DebugInfo.", name, strlen("NonSemantic.Shader.DebugInfo.")) ==
                           0) {
                    extInstSet = NonSemanticShaderDebugInfo;
                } else if (strcmp(spv::E_SPV_AMD_shader_ballot, name) == 0 ||
                           strcmp(spv::E_SPV_AMD_shader_trinary_minmax, name) == 0 ||
                           strcmp(spv::E_SPV_AMD_shader_explicit_vertex_parameter, name) == 0 ||
                           strcmp(spv::E_SPV_AMD_gcn_shader, name) == 0) {
                    extInstSet = GLSLextAMDInst;
                } else if (strcmp(spv::E_SPV_NV_sample_mask_override_coverage, name) == 0 ||
                          strcmp(spv::E_SPV_NV_geometry_shader_passthrough, name) == 0 ||
                          strcmp(spv::E_SPV_NV_viewport_array2, name) == 0 ||
                          strcmp(spv::E_SPV_NVX_multiview_per_view_attributes, name) == 0 ||
                          strcmp(spv::E_SPV_NV_fragment_shader_barycentric, name) == 0 ||
                          strcmp(spv::E_SPV_NV_mesh_shader, name) == 0) {
                    extInstSet = GLSLextNVInst;
                }
                unsigned entrypoint = stream[word - 1];
                if (extInstSet == GLSL450Inst) {
                    if (entrypoint < GLSLstd450Count) {
                        out << "(" << GlslStd450DebugNames[entrypoint] << ")";
                    }
                } else if (extInstSet == GLSLextAMDInst) {
                    out << "(" << GLSLextAMDGetDebugNames(name, entrypoint) << ")";
                }
                else if (extInstSet == GLSLextNVInst) {
                    out << "(" << GLSLextNVGetDebugNames(name, entrypoint) << ")";
                } else if (extInstSet == NonSemanticDebugPrintfExtInst) {
                    out << "(DebugPrintf)";
                } else if (extInstSet == NonSemanticDebugBreakExtInst) {
                    out << "(DebugBreak)";
                } else if (extInstSet == NonSemanticShaderDebugInfo) {
                    out << "(" << NonSemanticShaderDebugInfoGetDebugNames(entrypoint) << ")";
                }
            }
            break;
        case OperandOptionalLiteralString:
        case OperandLiteralString:
            numOperands -= disassembleString();
            break;
        case OperandVariableLiteralStrings:
            while (numOperands > 0)
                numOperands -= disassembleString();
            return;
        case OperandMemoryAccess:
            {
                outputMask(OperandMemoryAccess, stream[word++]);
                --numOperands;
                // Put a space after "None" if there are any remaining operands
                if (numOperands && stream[word-1] == 0) {
                    out << " ";
                }
                uint32_t mask = stream[word-1];
                // Aligned is the only memory access operand that uses an immediate
                // value, and it is also the first operand that uses a value at all.
                if (mask & (uint32_t)MemoryAccessMask::Aligned) {
                    disassembleImmediates(1);
                    numOperands--;
                    if (numOperands)
                        out << " ";
                }

                uint32_t bitCount = popcount(mask & (uint32_t)(MemoryAccessMask::MakePointerAvailable | MemoryAccessMask::MakePointerVisible));
                disassembleIds(bitCount);
                numOperands -= bitCount;
            }
            break;
        case OperandTensorAddressingOperands:
            {
                outputMask(OperandTensorAddressingOperands, stream[word++]);
                --numOperands;
                // Put a space after "None" if there are any remaining operands
                if (numOperands && stream[word-1] == 0) {
                    out << " ";
                }
                uint32_t bitCount = popcount(stream[word-1]);
                disassembleIds(bitCount);
                numOperands -= bitCount;
            }
            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";
    names[GLSLstd450NMin]                    = "NMin";
    names[GLSLstd450NMax]                    = "NMax";
    names[GLSLstd450NClamp]                  = "NClamp";
}

static const char* GLSLextAMDGetDebugNames(const char* name, unsigned entrypoint)
{
    if (strcmp(name, spv::E_SPV_AMD_shader_ballot) == 0) {
        switch (entrypoint) {
        case SwizzleInvocationsAMD:         return "SwizzleInvocationsAMD";
        case SwizzleInvocationsMaskedAMD:   return "SwizzleInvocationsMaskedAMD";
        case WriteInvocationAMD:            return "WriteInvocationAMD";
        case MbcntAMD:                      return "MbcntAMD";
        default:                            return "Bad";
        }
    } else if (strcmp(name, spv::E_SPV_AMD_shader_trinary_minmax) == 0) {
        switch (entrypoint) {
        case FMin3AMD:      return "FMin3AMD";
        case UMin3AMD:      return "UMin3AMD";
        case SMin3AMD:      return "SMin3AMD";
        case FMax3AMD:      return "FMax3AMD";
        case UMax3AMD:      return "UMax3AMD";
        case SMax3AMD:      return "SMax3AMD";
        case FMid3AMD:      return "FMid3AMD";
        case UMid3AMD:      return "UMid3AMD";
        case SMid3AMD:      return "SMid3AMD";
        default:            return "Bad";
        }
    } else if (strcmp(name, spv::E_SPV_AMD_shader_explicit_vertex_parameter) == 0) {
        switch (entrypoint) {
        case InterpolateAtVertexAMD:    return "InterpolateAtVertexAMD";
        default:                        return "Bad";
        }
    }
    else if (strcmp(name, spv::E_SPV_AMD_gcn_shader) == 0) {
        switch (entrypoint) {
        case CubeFaceIndexAMD:      return "CubeFaceIndexAMD";
        case CubeFaceCoordAMD:      return "CubeFaceCoordAMD";
        case TimeAMD:               return "TimeAMD";
        default:
            break;
        }
    }

    return "Bad";
}

static const char* GLSLextNVGetDebugNames(const char* name, unsigned entrypoint)
{
    if (strcmp(name, spv::E_SPV_NV_sample_mask_override_coverage) == 0 ||
        strcmp(name, spv::E_SPV_NV_geometry_shader_passthrough) == 0 ||
        strcmp(name, spv::E_ARB_shader_viewport_layer_array) == 0 ||
        strcmp(name, spv::E_SPV_NV_viewport_array2) == 0 ||
        strcmp(name, spv::E_SPV_NVX_multiview_per_view_attributes) == 0 ||
        strcmp(name, spv::E_SPV_NV_fragment_shader_barycentric) == 0 ||
        strcmp(name, spv::E_SPV_NV_mesh_shader) == 0 ||
        strcmp(name, spv::E_SPV_NV_shader_image_footprint) == 0) {
        switch (entrypoint) {
        // NV builtins
        case (unsigned)BuiltIn::ViewportMaskNV:                 return "ViewportMaskNV";
        case (unsigned)BuiltIn::SecondaryPositionNV:            return "SecondaryPositionNV";
        case (unsigned)BuiltIn::SecondaryViewportMaskNV:        return "SecondaryViewportMaskNV";
        case (unsigned)BuiltIn::PositionPerViewNV:              return "PositionPerViewNV";
        case (unsigned)BuiltIn::ViewportMaskPerViewNV:          return "ViewportMaskPerViewNV";
        case (unsigned)BuiltIn::BaryCoordNV:                    return "BaryCoordNV";
        case (unsigned)BuiltIn::BaryCoordNoPerspNV:             return "BaryCoordNoPerspNV";
        case (unsigned)BuiltIn::TaskCountNV:                    return "TaskCountNV";
        case (unsigned)BuiltIn::PrimitiveCountNV:               return "PrimitiveCountNV";
        case (unsigned)BuiltIn::PrimitiveIndicesNV:             return "PrimitiveIndicesNV";
        case (unsigned)BuiltIn::ClipDistancePerViewNV:          return "ClipDistancePerViewNV";
        case (unsigned)BuiltIn::CullDistancePerViewNV:          return "CullDistancePerViewNV";
        case (unsigned)BuiltIn::LayerPerViewNV:                 return "LayerPerViewNV";
        case (unsigned)BuiltIn::MeshViewCountNV:                return "MeshViewCountNV";
        case (unsigned)BuiltIn::MeshViewIndicesNV:              return "MeshViewIndicesNV";

        // NV Capabilities
        case (unsigned)Capability::GeometryShaderPassthroughNV: return "GeometryShaderPassthroughNV";
        case (unsigned)Capability::ShaderViewportMaskNV:        return "ShaderViewportMaskNV";
        case (unsigned)Capability::ShaderStereoViewNV:          return "ShaderStereoViewNV";
        case (unsigned)Capability::PerViewAttributesNV:         return "PerViewAttributesNV";
        case (unsigned)Capability::FragmentBarycentricNV:       return "FragmentBarycentricNV";
        case (unsigned)Capability::MeshShadingNV:               return "MeshShadingNV";
        case (unsigned)Capability::ImageFootprintNV:            return "ImageFootprintNV";
        case (unsigned)Capability::SampleMaskOverrideCoverageNV:return "SampleMaskOverrideCoverageNV";

        // NV Decorations
        case (unsigned)Decoration::OverrideCoverageNV:          return "OverrideCoverageNV";
        case (unsigned)Decoration::PassthroughNV:               return "PassthroughNV";
        case (unsigned)Decoration::ViewportRelativeNV:          return "ViewportRelativeNV";
        case (unsigned)Decoration::SecondaryViewportRelativeNV: return "SecondaryViewportRelativeNV";
        case (unsigned)Decoration::PerVertexNV:                 return "PerVertexNV";
        case (unsigned)Decoration::PerPrimitiveNV:              return "PerPrimitiveNV";
        case (unsigned)Decoration::PerViewNV:                   return "PerViewNV";
        case (unsigned)Decoration::PerTaskNV:                   return "PerTaskNV";

        default:                                    return "Bad";
        }
    }
    return "Bad";
}

static const char* NonSemanticShaderDebugInfoGetDebugNames(unsigned entrypoint)
{
    switch (entrypoint) {
        case NonSemanticShaderDebugInfoDebugInfoNone:                        return "DebugInfoNone";
        case NonSemanticShaderDebugInfoDebugCompilationUnit:                 return "DebugCompilationUnit";
        case NonSemanticShaderDebugInfoDebugTypeBasic:                       return "DebugTypeBasic";
        case NonSemanticShaderDebugInfoDebugTypePointer:                     return "DebugTypePointer";
        case NonSemanticShaderDebugInfoDebugTypeQualifier:                   return "DebugTypeQualifier";
        case NonSemanticShaderDebugInfoDebugTypeArray:                       return "DebugTypeArray";
        case NonSemanticShaderDebugInfoDebugTypeVector:                      return "DebugTypeVector";
        case NonSemanticShaderDebugInfoDebugTypedef:                         return "DebugTypedef";
        case NonSemanticShaderDebugInfoDebugTypeFunction:                    return "DebugTypeFunction";
        case NonSemanticShaderDebugInfoDebugTypeEnum:                        return "DebugTypeEnum";
        case NonSemanticShaderDebugInfoDebugTypeComposite:                   return "DebugTypeComposite";
        case NonSemanticShaderDebugInfoDebugTypeMember:                      return "DebugTypeMember";
        case NonSemanticShaderDebugInfoDebugTypeInheritance:                 return "DebugTypeInheritance";
        case NonSemanticShaderDebugInfoDebugTypePtrToMember:                 return "DebugTypePtrToMember";
        case NonSemanticShaderDebugInfoDebugTypeTemplate:                    return "DebugTypeTemplate";
        case NonSemanticShaderDebugInfoDebugTypeTemplateParameter:           return "DebugTypeTemplateParameter";
        case NonSemanticShaderDebugInfoDebugTypeTemplateTemplateParameter:   return "DebugTypeTemplateTemplateParameter";
        case NonSemanticShaderDebugInfoDebugTypeTemplateParameterPack:       return "DebugTypeTemplateParameterPack";
        case NonSemanticShaderDebugInfoDebugGlobalVariable:                  return "DebugGlobalVariable";
        case NonSemanticShaderDebugInfoDebugFunctionDeclaration:             return "DebugFunctionDeclaration";
        case NonSemanticShaderDebugInfoDebugFunction:                        return "DebugFunction";
        case NonSemanticShaderDebugInfoDebugLexicalBlock:                    return "DebugLexicalBlock";
        case NonSemanticShaderDebugInfoDebugLexicalBlockDiscriminator:       return "DebugLexicalBlockDiscriminator";
        case NonSemanticShaderDebugInfoDebugScope:                           return "DebugScope";
        case NonSemanticShaderDebugInfoDebugNoScope:                         return "DebugNoScope";
        case NonSemanticShaderDebugInfoDebugInlinedAt:                       return "DebugInlinedAt";
        case NonSemanticShaderDebugInfoDebugLocalVariable:                   return "DebugLocalVariable";
        case NonSemanticShaderDebugInfoDebugInlinedVariable:                 return "DebugInlinedVariable";
        case NonSemanticShaderDebugInfoDebugDeclare:                         return "DebugDeclare";
        case NonSemanticShaderDebugInfoDebugValue:                           return "DebugValue";
        case NonSemanticShaderDebugInfoDebugOperation:                       return "DebugOperation";
        case NonSemanticShaderDebugInfoDebugExpression:                      return "DebugExpression";
        case NonSemanticShaderDebugInfoDebugMacroDef:                        return "DebugMacroDef";
        case NonSemanticShaderDebugInfoDebugMacroUndef:                      return "DebugMacroUndef";
        case NonSemanticShaderDebugInfoDebugImportedEntity:                  return "DebugImportedEntity";
        case NonSemanticShaderDebugInfoDebugSource:                          return "DebugSource";
        case NonSemanticShaderDebugInfoDebugFunctionDefinition:              return "DebugFunctionDefinition";
        case NonSemanticShaderDebugInfoDebugSourceContinued:                 return "DebugSourceContinued";
        case NonSemanticShaderDebugInfoDebugLine:                            return "DebugLine";
        case NonSemanticShaderDebugInfoDebugNoLine:                          return "DebugNoLine";
        case NonSemanticShaderDebugInfoDebugBuildIdentifier:                 return "DebugBuildIdentifier";
        case NonSemanticShaderDebugInfoDebugStoragePath:                     return "DebugStoragePath";
        case NonSemanticShaderDebugInfoDebugEntryPoint:                      return "DebugEntryPoint";
        case NonSemanticShaderDebugInfoDebugTypeMatrix:                      return "DebugTypeMatrix";
        case NonSemanticShaderDebugInfoDebugTypeVectorIdEXT:                 return "DebugTypeVectorIdEXT";
        case NonSemanticShaderDebugInfoDebugTypeCooperativeMatrixKHR:        return "DebugTypeCooperativeMatrixKHR";
        default:                                                                return "Bad";
    }

    return "Bad";
}

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
