https://github.com/shader-slang/slang
Raw File
Tip revision: 768e62f6c7541439e2edc18dad5fb3846d2e05f9 authored by Yong He on 10 October 2022, 22:59:45 UTC
Support multi-level break + single-return conversion + general inline. (#2436)
Tip revision: 768e62f
slang-emit-spirv.cpp
// slang-emit-spirv.cpp

#include "slang-compiler.h"
#include "slang-emit-base.h"

#include "slang-ir.h"
#include "slang-ir-insts.h"
#include "slang-ir-layout.h"
#include "slang-ir-spirv-snippet.h"
#include "slang-ir-spirv-legalize.h"
#include "spirv/unified1/spirv.h"
#include "../core/slang-memory-arena.h"

namespace Slang
{

// Our goal in this file is to convert a module in the Slang IR over to an
// equivalent module in the SPIR-V intermediate language.
//
// The Slang IR is (intentionally) similar to SPIR-V in many ways, and both
// can represent shaders at similar levels of abstraction, so much of the
// translation involves one-to-one translation of Slang IR instructions
// to their SPIR-V equivalents.
//
// SPIR-V differs from Slang IR in some key ways, and the SPIR-V
// specification places many restrictions on how the IR can be encoded.
// In some cases we will rely on earlier IR passes to convert Slang IR
// into a form closer to what SPIR-V expects (e.g., by moving all
// varying entry point parameters to global scope), but other differences
// will be handled during the translation process.
//
// The logic in this file relies on the formal [SPIR-V Specification].
// When we are making use of or enforcing some property from the spec,
// we will try to refer to the relevant section in comments.
//
// [SPIR-V Specification]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html

// [2.3: Physical Layout of a SPIR-V Module and Instruction]
//
// > A SPIR-V module is a single linear stream of words.


// [2.3: Physical Layout of a SPIR-V Module and Instruction]
//
// > All remaining words are a linear sequence of instructions.
// > Each instruction is a stream of words
//
// After a fixed-size header, the contents of a SPIR-V module
// is just a flat sequence of instructions, each of which is
// just a sequence of words.
//
// In principle we could try to emit instructions directly
// in one pass as a stream of words, but there are additional
// constraints placed by the SPIR-V encoding that would make
// a single-pass strategy very hard, so we don't attempt it.
//
// [2.4 Logical Layout of a Module]
//
// SPIR-V imposes some global ordering constraints on instructions,
// such that certain instructions must come before or after others.
// For example, all `OpCapability` instructions must come before any
// `OpEntryPoint` instructions.
//
// While the SPIR-V spec doesn't use such a term, we will take
// the enumeration of the ordering in Section 2.4 and use it to
// define a list of *logical sections* that make up a SPIR-V module.

    /// Logical sections of a SPIR-V module.
enum class SpvLogicalSectionID
{
    Capabilities,
    Extensions,
    ExtIntInstImports,
    MemoryModel,
    EntryPoints,
    ExecutionModes,
    DebugStringsAndSource,
    DebugNames,
    Annotations,
    Types,
    Constants,
    GlobalVariables,
    FunctionDeclarations,
    FunctionDefinitions,

    Count,
};

// While the SPIR-V module is nominally (according to the spec) just
// a flat sequence of instructions, in practice some of the instructions
// are logically in a parent/child relationship.
//
// In particular, functions "own" the instructions between an `OpFunction`
// and the matching `OpFunctionEnd`. We can also think of basic
// blocks within a function as owning the instructions between
// an `OpLabel` (which represents the bloc) and the next label
// or the end of the function.
//
// Furthermore, the common case is SPIR-V is that an instruction
// that defines some value must appear before any instruction
// that uses that value as an operand. This property is often true
// in a Slang IR module, but isn't strictly enforced for things at
// the global scope.
//
// To deal with the above issues, our strategy will be to emit
// SPIR-V instructions into a lightweight intermediate structure
// that simplifies dealing with ordering constraiints on
// instructions.
//
// We will start by forward-declaring the type we will
// use to represent instructions:
//
struct SpvInst;

// Next, we will define a base type that can serve as a parent
// to SPIR-V instructions. Both the logical sections defined
// earlier and instructions such as functions will be used
// as parents.

    /// Base type for SPIR-V instructions and logical sections of a module
    ///
    /// Holds and supports appending to a list of child instructions.
struct SpvInstParent
{
public:
        /// Add an instruction to the end of the list of children
    void addInst(SpvInst* inst);

        /// Dump all children, recursively, to a flattened list of SPIR-V words
    void dumpTo(List<SpvWord>& ioWords);

private:
        /// The first child, if any.
    SpvInst* m_firstChild = nullptr;

        /// A pointer to the null pointer at the end of the linked list.
        ///
        /// If the list of children is empty this points to `m_firstChild`,
        /// while if it is non-empty it points to the `nextSibling` field
        /// of the last instruction.
        ///
    SpvInst** m_link = &m_firstChild;
};

// A SPIR-V instruction is then (in the general case) a potential
// parent to other instructions.

    /// A type to represent a SPIR-V instruction to be emitted.
    ///
    /// This type alows the instruction to be built up across
    /// multiple steps in a mutable fashion.
    ///
struct SpvInst : SpvInstParent
{
    // [2.3: Physical Layout of a SPIR-V Module and Instruction]
    //
    // > Each instruction is a stream of words
    //
    // > Opcode: The 16 high-order bits are the WordCount of the instruction.
    // >         The 16 low-order bits are the opcode enumerant.
    //
    // We will store the "opcode enumerant" directly in our
    // intermediate structure, and compute the word count on
    // the fly when writing an instruction to an output buffer.

        /// The SPIR-V opcode for the instruction
    SpvOp opcode;

    // [2.3: Physical Layout of a SPIR-V Module and Instruction]
    //
    // > Optional instruction type <id> (presence determined by opcode)
    // > Optional instruction Result <id> (presence determined by opcode)
    // > Operand 1 (if needed)
    // > Operand 2 (if needed)
    // > ...
    //
    // We represent the remaining words of the instruction (after
    // the opcode word) as an undifferentiated array. Any code
    // that encodes an instruction is responsible for knowing the
    // opcode-specific data that is required.
    //
    // Our code does not need to process instruction operands after
    // they have been written into a `SpvInst`. If we ever had
    // cases where we needed to do post-processing, then we would
    // need to store a more refined representation here.

        /// The additional words of the instruction after the opcode
    SpvWord* operandWords = nullptr;
        /// The amount of operand words
    uint32_t operandWordsCount = 0;

    // We will store the instructions in a given `SpvInstParent`
    // using an intrusive linked list.

        /// The next instruction in the same `SpvInstParent`
    SpvInst* nextSibling = nullptr;

        /// The result <id> produced by this instruction, or zero if it has no result.
    SpvWord id = 0;

        /// Dump the instruction (and any children, recursively) into the flat array of SPIR-V words.
    void dumpTo(List<SpvWord>& ioWords)
    {
        // [2.2: Terms]
        //
        // > Word Count: The complete number of words taken by an instruction,
        // > including the word holding the word count and opcode, and any optional
        // > operands. An instruction’s word count is the total space taken by the instruction.
        //
        SpvWord wordCount = 1 + SpvWord(operandWordsCount);

        // [2.3: Physical Layout of a SPIR-V Module and Instruction]
        //
        // > Opcode: The 16 high-order bits are the WordCount of the instruction.
        // >         The 16 low-order bits are the opcode enumerant.
        //
        ioWords.add(wordCount << 16 | opcode);

        // The operand words simply follow the opcode word.
        //
        ioWords.addRange(operandWords, operandWordsCount);
        
        // In our representation choice, the children of a
        // parent instruction will always follow the encoded
        // words of a parent:
        //
        // * The instructions inside a function always follow the `OpFunction`
        // * The instructions inside a block always follow the `OpLabel`
        //
        SpvInstParent::dumpTo(ioWords);
    }
};

    /// A logical section of a SPIR-V module
struct SpvLogicalSection : SpvInstParent
{
};

// Now that we've filled in the definition of `SpvInst`, we can
// go back and define the key operations on `SpvInstParent`.

void SpvInstParent::addInst(SpvInst* inst)
{
    SLANG_ASSERT(inst);

    // The user shouldn't be trying to add multiple instructions at once.
    // If they really want that then they probably wanted to give `inst`
    // some children.
    //
    SLANG_ASSERT(!inst->nextSibling);

    *m_link = inst;
    m_link = &inst->nextSibling;
}

void SpvInstParent::dumpTo(List<SpvWord>& ioWords)
{
    for( auto child = m_firstChild; child; child = child->nextSibling )
    {
        child->dumpTo(ioWords);
    }
}

/// The context for inlining a SPV assembly snippet.
struct SpvSnippetEmitContext
{
    SpvInst* resultType;
    IRType* irResultType;
    // True if resultType is float or vector of float.
    bool isResultTypeFloat;
    // True if resultType is signed.
    bool isResultTypeSigned;
    Dictionary<SpvStorageClass, IRInst*> qualifiedResultTypes;
    List<SpvWord> argumentIds;
};

// Now that we've defined the intermediate data structures we will
// use to represent SPIR-V code during emission, we will move on
// to defining the main context type that will drive SPIR-V
// code generation.

    /// Context used for translating a Slang IR module to SPIR-V
struct SPIRVEmitContext
    : public SourceEmitterBase
    , public SPIRVEmitSharedContext
{
        /// The Slang IR module being translated
    IRModule* m_irModule;

    DiagnosticSink* m_sink;

    // [2.2: Terms]
    //
    // > <id>: A numerical name; the name used to refer to an object, a type,
    // > a function, a label, etc. An <id> always consumes one word.
    // > The <id>s defined by a module obey SSA.
    //
    // [2.3: Physical Layout of a SPIR-V Module and Instruction]
    //
    // > Bound; where all <id>s in this module are guaranteed to satisfy
    // > 0 < id < Bound
    // > Bound should be small, smaller is better, with all <id> in a module being densely packed and near 0.
    //
    // Instructions will be referred to by their <id>s.
    // We need to generate <id>s for instructions, and also
    // compute the "bound" value that will be stored in
    // the module header.
    //
    // We will use a single counter and allocate <id>s
    // on demand. There may be some slop where we allocate
    // an <id> for something that never gets referenced,
    // but we expect the amount of slop to be small (and
    // it can be cleaned up by other tools/passes).

        /// The next destination `<id>` to allocate.
    SpvWord m_nextID = 1;

    // We will store the logical sections of the SPIR-V module
    // in a single array so that we can easily look up a
    // section by its `SpvLogicalSectionID`.

        /// The logical sections of the SPIR-V module
    SpvLogicalSection m_sections[int(SpvLogicalSectionID::Count)];

        /// Get a logical section based on its `SpvLogicalSectionID`
    SpvLogicalSection* getSection(SpvLogicalSectionID id)
    {
        return &m_sections[int(id)];
    }

    // At the end of emission we need a single linear stream of words,
    // so we will eventually flatten `m_sections` into a single array.

        /// The final array of SPIR-V words that defines the encoded module
    List<SpvWord> m_words;

        /// Emit the concrete words that make up the binary SPIR-V module.
        ///
        /// This function fills in `m_words` based on the data in `m_sections`.
        /// This function should only be called once.
        ///
    void emitPhysicalLayout()
    {
        // [2.3: Physical Layout of a SPIR-V Module and Instruction]
        //
        // > Magic Number
        //
        m_words.add(SpvMagicNumber);

        // > Version nuumber
        //

        // TODO(JS): 
        // Was previously set to SpvVersion, but that doesn't work since we 
        // upgraded to SPIR-V headers 1.6. (It would lead to validation errors during vk tests)
        // For now mark as version 1.5.0

        static const uint32_t spvVersion1_5_0 = 0x00010500;
        m_words.add(spvVersion1_5_0);

        // > Generator's magic number.
        // > Its value does not affect any semantics, and is allowed to be 0.
        //
        // TODO: We should eventually register a non-zero
        // magic number to represent Slang/slangc.
        //
        m_words.add(0);

        // > Bound
        //
        // As described above, we use `m_nextID` to allocate
        // <id>s, so its value when we are done emitting code
        // can serve as the bound.
        //
        m_words.add(m_nextID);

        // > 0 (Reserved for instruction schema, if needed.)
        //
        m_words.add(0);

        // > First word of instruction stream
        // > All remaining words are a linear sequence of instructions.
        //
        // Once we are done emitting the header, we emit all
        // the instructions in our logical sections.
        // 
        for( int ii = 0; ii < int(SpvLogicalSectionID::Count); ++ii )
        {
            m_sections[ii].dumpTo(m_words);
        }
    }

    // We will often need to refer to an instrcition by its
    // <id>, given only the Slang IR instruction that represents
    // it (e.g., when it is used as an operand of another
    // instruction).
    //
    // To that end we will keep a map of instructions that
    // have been emitted, where a Slang IR instruction maps
    // to the corresponding SPIR-V instruction.

        /// Map a Slang IR instruction to the corresponding SPIR-V instruction
    Dictionary<IRInst*, SpvInst*> m_mapIRInstToSpvInst;

    // Sometimes we need to reserve an ID for an `IRInst` without actually
    // emitting it. We use `m_mapIRInstToSpvID` to hold all reserved SpvIDs.
    // Use `getIRInstSpvID` to obtain an SpvID for an `IRInst` if the
    // `IRInst` may not have been emitted.
    Dictionary<IRInst*, SpvWord> m_mapIRInstToSpvID;

        /// Register that `irInst` maps to `spvInst`
    void registerInst(IRInst* irInst, SpvInst* spvInst)
    {
        m_mapIRInstToSpvInst.Add(irInst, spvInst);

        // If we have reserved an SpvID for `irInst`, make sure to use it.
        SpvWord reservedID = 0;
        m_mapIRInstToSpvID.TryGetValue(irInst, reservedID);

        if (reservedID)
        {
            SLANG_ASSERT(spvInst->id == 0);
            spvInst->id = reservedID;
        }
    }

        /// Get or reserve a SpvID for an IR value.
    SpvWord getIRInstSpvID(IRInst* inst)
    {
        // If we have already emitted an SpvInst for `inst`, return its ID.
        SpvInst* spvInst = nullptr;
        if (m_mapIRInstToSpvInst.TryGetValue(inst, spvInst))
            return getID(spvInst);
        // Check if we have reserved an ID for `inst`.
        SpvWord result = 0;
        if (m_mapIRInstToSpvID.TryGetValue(inst, result))
            return result;
        // Otherwise, reserve a new ID for inst, and register it in `m_mapIRInstToSpvID`.
        result = m_nextID;
        ++m_nextID;
        m_mapIRInstToSpvID[inst] = result;
        return result;
    }

    // When we are emitting an instruction that can produce
    // a result, we will allocate an <id> to it so that other
    // instructions can refer to it.
    //
    // We will allocate <id>s on emand as they are needed.

        /// Get the <id> for `inst`, or assign one if it doesn't have one yet
    SpvWord getID(SpvInst* inst)
    {
        auto id = inst->id;
        if( !id )
        {
            id = m_nextID++;
            inst->id = id;
        }
        return id;
    }

    // We will build up `SpvInst`s in a stateful fashion,
    // mostly for convenience. We could in theory compute
    // the number of words each instruction needs, then allocate
    // the words, then fill them in, but that would make the
    // emit logic more complicated and we'd like to keep it simple
    // until we are sure performance is an issue.
    //
    // Emitting an instruction starts with picking the opcode
    // and allocating the `SpvInst`.

    // Holds a stack of instructions operands *BEFORE* they added to the instruction.
    List<SpvWord> m_operandStack;
    // The current instruction being constructed. Cannot add operands unless it is set.
    SpvInst* m_currentInst = nullptr;

    // Operands can only be added when inside of a InstConstructScope 
    struct InstConstructScope
    {
        SLANG_FORCE_INLINE operator SpvInst*() const { return m_inst; }

        InstConstructScope(SPIRVEmitContext* context, SpvOp opcode, IRInst* irInst = nullptr):
            m_context(context)
        {
            m_context->_beginInst(opcode, irInst, *this);
        }
        ~InstConstructScope()
        {
            m_context->_endInst(*this);
        }

        SpvInst* m_inst;                    ///< The instruction associated with this scope
        SPIRVEmitContext* m_context;        ///< The context
        SpvInst* m_previousInst;            ///< The previously live inst
        Index m_operandsStartIndex;         ///< The start index for operands of m_inst
    };

        /// Holds memory for instructions and operands.
    MemoryArena m_memoryArena;

        /// Begin emitting an instruction with the given SPIR-V `opcode`.
        ///
        /// If `irInst` is non-null, then the resulting SPIR-V instruction
        /// will be registered as corresponding to `irInst`.
        ///
        /// The created instruction is stored in m_currentInst.
        ///
        /// Should not typically be called directly use InstConstructScope to scope construction
    void _beginInst(SpvOp opcode, IRInst* irInst, InstConstructScope& ioScope)
    {
        SLANG_ASSERT(this == ioScope.m_context);

        // Allocate the instruction
        auto spvInst = new (m_memoryArena.allocate(sizeof(SpvInst))) SpvInst();
        spvInst->opcode = opcode;

        if(irInst)
        {
            registerInst(irInst, spvInst);
        }

        // Set up the scope
        ioScope.m_inst = spvInst;
        ioScope.m_previousInst = m_currentInst;
        ioScope.m_operandsStartIndex = m_operandStack.getCount();

        // Set the current instruction
        m_currentInst = spvInst;
    }

        /// End emitting an instruction
        /// Should not typically be called directly use InstConstructScope to scope construction
    void _endInst(const InstConstructScope& scope)
    {
        SLANG_ASSERT(scope.m_inst == m_currentInst);

        const Index operandsStartIndex = scope.m_operandsStartIndex;
        // Work out how many operands were added
        const Index operandsCount = m_operandStack.getCount() - operandsStartIndex;

        
        if (operandsCount)
        {
            // Allocate the operands
            m_currentInst->operandWords = m_memoryArena.allocateAndCopyArray(m_operandStack.getBuffer() + operandsStartIndex, operandsCount);
            // Set the count
            m_currentInst->operandWordsCount = uint32_t(operandsCount);
        }

        // Make the previous inst active
        m_currentInst = scope.m_previousInst;
        // Reset the operand stack
        m_operandStack.setCount(operandsStartIndex);
    }

    /// Ensure that an instruction has been emitted
    SpvInst* ensureInst(IRInst* irInst)
    {
        SpvInst* spvInst = nullptr;
        if (!m_mapIRInstToSpvInst.TryGetValue(irInst, spvInst))
        {
            // If the `irInst` hasn't already been emitted,
            // then we will assume that is is a global instruction
            // (a constant, type, function, etc.) and we should make
            // sure it gets emitted now.
            //
            // Note: this step means that emitting an instruction
            // can be re-entrant/recursive. Because we emit the SPIR-V
            // words for an instruction into an intermediate structure
            // we don't have to worry about the re-entrancy causing
            // the ordering of instruction words to be interleaved.
            //
            spvInst = emitGlobalInst(irInst);
        }
        return spvInst;
    }

    // Whilst an instruction has been created, we append the operand
    // words to it with `emitOperand`. There are a few different
    // case of operands that we handle.
    //
    // The simplest case is when an instruction takes an operand
    // that is just a literal SPIR-V word.

        /// Emit a literal `word` as an operand to the current instruction
    void emitOperand(SpvWord word)
    {
        // Can only add operands if we are constructing an instruction (ie in _beginInst/_endInst)
        SLANG_ASSERT(m_currentInst);
        m_operandStack.add(word);
    }

    // The most common case of operand is an <id> that represents
    // some other instruction. In cases where we already have
    // an <id> we can emit it as a literal and the meaning is
    // the same. If we have a `SpvInst` we can look up or
    // generate an <id> for it.

        /// Emit an operand to the current instruction, which references `src` by its <id>
    void emitOperand(SpvInst* src)
    {
        emitOperand(getID(src));
    }

    // Commonly, we will have an operand in the form of an `IRInst`
    // which might either represent an instruction we've already
    // emitted (e.g., because it came earlier in a function body)
    // or which we have yet to emit (because it is a global-scope
    // instruction that has not been referenced before).

        /// Emit an operand to the current instruction, which references `src` by its <id>
    void emitOperand(IRInst* src)
    {
        SpvInst* spvSrc = ensureInst(src);
        emitOperand(getID(spvSrc));
    }

    // Some instructions take a string as a literal operand,
    // which requires us to follow the SPIR-V rules to
    // encode the string into multiple operand words.

        /// Emit an operand that is encoded as a literal string
    void emitOperand(UnownedStringSlice const& text)
    {
        // Can only emitOperands if we are in an instruction
        SLANG_ASSERT(m_currentInst);
        SLANG_COMPILE_TIME_ASSERT(sizeof(SpvWord) == 4);

        // Assert that `text` doesn't contain any embedded nul bytes, since they
        // could lead to invalid encoded results.
        SLANG_ASSERT(text.indexOf(0) < 0);

        // [Section 2.2.1 : Instructions]
        //
        // > Literal String: A nul-terminated stream of characters consuming
        // > an integral number of words. The character set is Unicode in the
        // > UTF-8 encoding scheme. The UTF-8 octets (8-bit bytes) are packed
        // > four per word, following the little-endian convention (i.e., the
        // > first octet is in the lowest-order 8 bits of the word).
        // > The final word contains the string’s nul-termination character (0), and
        // > all contents past the end of the string in the final word are padded with 0.

        // First work out the amount of words we'll need
        const Index textCount = text.getLength();
        // Calculate the minimum amount of bytes needed - which needs to include terminating 0
        const Index minByteCount = textCount + 1;
        // Calculate the amount of words including padding if necessary
        const Index wordCount = (minByteCount + 3) >> 2;

        // Make space on the operand stack, keeping the free space start in operandStartIndex
        const Index operandStartIndex = m_operandStack.getCount();
        m_operandStack.setCount(operandStartIndex + wordCount);

        // Set dst to the start of the operand memory
        char* dst = (char*)(m_operandStack.getBuffer() + operandStartIndex);

        // Copy the text
        memcpy(dst, text.begin(), textCount);

        // Set terminating 0, and remaining buffer 0s
        memset(dst + textCount, 0, wordCount * sizeof(SpvWord) - textCount);
    }

    // Sometimes we will want to pass down an argument that
    // represents a result <id> operand, but we won't yet
    // have access to the `SpvInst` that will get the <id>.
    // We will use a dummy `enum` type to support this case.

    enum ResultIDToken { kResultID };

    void emitOperand(ResultIDToken)
    {
        SLANG_ASSERT(m_currentInst);

        // A result <id> operand uses the <id> of the instruction itself (which is m_currentInst)
        emitOperand(getID(m_currentInst));
    }

    void emitOperand(SpvDecoration decoration) { emitOperand((SpvWord)decoration); }

    void emitOperand(SpvBuiltIn builtin) { emitOperand((SpvWord)builtin); }
    void emitOperand(SpvStorageClass val) { emitOperand((SpvWord)val); }

    template<typename TConstant>
    struct ConstantValueKey
    {
        IRType* type;
        TConstant value;
        HashCode getHashCode() const
        {
            return combineHash(Slang::getHashCode(type), Slang::getHashCode(value));
        }
        bool operator==(const ConstantValueKey& other) const
        {
            return type == other.type && value == other.value;
        }
    };
    Dictionary<ConstantValueKey<IRIntegerValue>, SpvInst*> m_spvIntConstants;
    Dictionary<ConstantValueKey<IRFloatingPointValue>, SpvInst*> m_spvFloatConstants;
    SpvInst* emitIntConstant(IRIntegerValue val, IRType* type)
    {
        ConstantValueKey<IRIntegerValue> key;
        key.value = val;
        key.type = type;
        SpvInst* result = nullptr;
        if (m_spvIntConstants.TryGetValue(key, result))
            return result;
        SpvWord valWord;
        memcpy(&valWord, &val, sizeof(SpvWord));
        switch (type->getOp())
        {
        case kIROp_Int64Type:
        case kIROp_UInt64Type:
#if SLANG_PTR_IS_64
        case kIROp_PtrType:
        case kIROp_UIntPtrType:
#endif
        {
            SpvWord valHighWord;
            memcpy(&valHighWord, (char*)(&val) + 4, sizeof(SpvWord));
            result = emitInst(
                getSection(SpvLogicalSectionID::Constants),
                nullptr,
                SpvOpConstant,
                type,
                kResultID,
                valWord,
                valHighWord);
            break;
        }
        default:
        {
            result = emitInst(
                getSection(SpvLogicalSectionID::Constants),
                nullptr,
                SpvOpConstant,
                type,
                kResultID,
                valWord);
            break;
        }
        }
        m_spvIntConstants[key] = result;
        return result;
    }
    SpvInst* emitFloatConstant(IRFloatingPointValue val, IRType* type)
    {
        ConstantValueKey<IRFloatingPointValue> key;
        key.value = val;
        key.type = type;
        SpvInst* result = nullptr;
        if (m_spvFloatConstants.TryGetValue(key, result))
            return result;
        SpvWord valWord;
        memcpy(&valWord, &val, sizeof(SpvWord));
        if (type->getOp() == kIROp_DoubleType)
        {
            SpvWord valHighWord;
            memcpy(&valHighWord, (char*)(&val) + 4, sizeof(SpvWord));
            result = emitInst(
                getSection(SpvLogicalSectionID::Constants),
                nullptr,
                SpvOpConstant,
                type,
                kResultID,
                valWord,
                valHighWord);
        }
        else
        {
            result = emitInst(
                getSection(SpvLogicalSectionID::Constants),
                nullptr,
                SpvOpConstant,
                type,
                kResultID,
                valWord);
        }
        m_spvFloatConstants[key] = result;
        return result;
    }
    // As another convenience, there are often cases where
    // we will want to emit all of the operands of some
    // IR instruction as <id> operands of a SPIR-V
    // instruction. This is handy in cases where the
    // Slang IR and SPIR-V instructions agree on the
    // number, order, and meaning of their operands.

        /// Helper type for emitting all the operands of the current IR instruction
    struct OperandsOf
    {
        OperandsOf(IRInst* irInst)
            : irInst(irInst)
        {}

        IRInst* irInst = nullptr;
    };

        /// Emit operand words for all the operands of a given IR instruction
    void emitOperand(OperandsOf const& other)
    {
        auto irInst = other.irInst;
        auto operandCount = irInst->getOperandCount();
        for( UInt ii = 0; ii < operandCount; ++ii )
        {
            emitOperand(irInst->getOperand(ii));
        }
    }

    // With the above routines, code can easily construct a SPIR-V
    // instruction with arbitrary operands over multiple lines of code.
    //
    // In many cases, however, it is desirable to be able to emit
    // an instruction more compactly, and for that we will introduce
    // a number of `emitInst()` helpers that handle creating an
    // instruction, filling in its operands, and adding it to a parent.
    //
    // These routines are overloaded on the number of operands, and
    // also templates to work with any of the types for which
    // `emitOperand()` works.
    //
    // In all of these cases, the caller takes responsibility for
    // correctly matching the SPIR-V encoding rules for the chosen
    // opcode, including whether a type <id> or result <id> is
    // required.

    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename A>
    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        emitOperand(a);
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename A, typename B>
    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        emitOperand(a);
        emitOperand(b);
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename A, typename B, typename C>
    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        emitOperand(a);
        emitOperand(b);
        emitOperand(c);
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename A, typename B, typename C, typename D>
    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c, D const& d)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        emitOperand(a);
        emitOperand(b);
        emitOperand(c);
        emitOperand(d);
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename A, typename B, typename C, typename D, typename E>
    SpvInst* emitInst(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, A const& a, B const& b, C const& c, D const& d, E const& e)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        emitOperand(a);
        emitOperand(b);
        emitOperand(c);
        emitOperand(d);
        emitOperand(e);
        parent->addInst(spvInst);
        return spvInst;
    }

    template<typename OperandEmitFunc>
    SpvInst* emitInstCustomOperandFunc(SpvInstParent* parent, IRInst* irInst, SpvOp opcode, const OperandEmitFunc& f)
    {
        InstConstructScope scopeInst(this, opcode, irInst);
        SpvInst* spvInst = scopeInst;
        f();
        parent->addInst(spvInst);
        return spvInst;
    }

        /// The SPIRV OpExtInstImport inst that represents the GLSL450
        /// extended instruction set.
    SpvInst* m_glsl450ExtInst = nullptr;

    SpvInst* getGLSL450ExtInst()
    {
        if (m_glsl450ExtInst)
            return m_glsl450ExtInst;
        m_glsl450ExtInst = emitInst(
            getSection(SpvLogicalSectionID::ExtIntInstImports),
            nullptr,
            SpvOpExtInstImport,
            UnownedStringSlice("GLSL.std.450"));
        return m_glsl450ExtInst;
    }

    // Now that we've gotten the core infrastructure out of the way,
    // let's start looking at emitting some instructions that make
    // up a SPIR-V module.
    //
    // We will start with certain instructions that are required
    // to appear in a well-formed SPIR-V module for Vulkan, but
    // which do not directly relate to any instruction in the
    // Slang IR.

        /// Emit the mandatory "front-matter" instructions that
        /// the SPIR-V module must include to make it usable.
    void emitFrontMatter()
    {
        // TODO: We should ideally add SPIR-V capabilities to
        // the module as we emit instructions that require them.
        // For now we will always emit the `Shader` capability,
        // since every Vulkan shader module will use it.
        //
        emitInst(getSection(SpvLogicalSectionID::Capabilities), nullptr, SpvOpCapability, SpvCapabilityShader);

        // [2.4: Logical Layout of a Module]
        //
        // > The single required OpMemoryModel instruction.
        //
        // A memory model is always required in SPIR-V module.
        //
        // The Vulkan spec further says:
        //
        // > The `Logical` addressing model must be selected
        //
        // It isn't clear if the GLSL450 memory model is also
        // a requirement, but it is what glslang produces,
        // so we will use it for now.
        //
        emitInst(getSection(SpvLogicalSectionID::MemoryModel),  nullptr, SpvOpMemoryModel, SpvAddressingModelLogical, SpvMemoryModelGLSL450);
    }

    Dictionary<UnownedStringSlice, SpvInst*> m_extensionInsts;
    SpvInst* ensureExtensionDeclaration(UnownedStringSlice name)
    {
        SpvInst* result = nullptr;
        if (m_extensionInsts.TryGetValue(name, result))
            return result;
        result =
            emitInst(getSection(SpvLogicalSectionID::Extensions), nullptr, SpvOpExtension, name);
        m_extensionInsts[name] = result;
        return result;
    }

    struct SpvTypeInstKey
    {
        List<SpvWord> words;
        bool operator==(const SpvTypeInstKey& other)
        {
            if (words.getCount() != other.words.getCount())
                return false;
            for (Index i = 0; i < words.getCount(); i++)
                if (words[i] != other.words[i])
                    return false;
            return true;
        }
        HashCode getHashCode()
        {
            HashCode result = 0;
            for (auto word : words)
                result = combineHash(result, word);
            return result;
        }
    };

    Dictionary<SpvTypeInstKey, SpvInst*> m_spvTypeInsts;

    // Emits a SPV Inst that represents a type, with deduplications since
    // our IR doesn't currently guarantee types are unique in generated SPV.
    SpvInst* emitTypeInst(IRInst* typeInst, SpvOp opcode, ArrayView<SpvWord> operands)
    {
        SpvTypeInstKey key;
        key.words.add((SpvWord)opcode);
        for (auto op : operands)
            key.words.add(op);
        SpvInst* result = nullptr;
        if (m_spvTypeInsts.TryGetValue(key, result))
        {
            return result;
        }
        result = emitInstCustomOperandFunc(
            getSection(SpvLogicalSectionID::Types), typeInst, opcode, [&]() {
                emitOperand(kResultID);
                for (auto op : operands)
                {
                    emitOperand(op);
                }
            });
        m_spvTypeInsts[key] = result;
        return result;
    }

    // Next, let's look at emitting some of the instructions
    // that can occur at global scope.

        /// Emit an instruction that is expected to appear at the global scope of the SPIR-V module.
        ///
        /// Returns the corresponding SPIR-V instruction.
        ///
    SpvInst* emitGlobalInst(IRInst* inst)
    {
        switch( inst->getOp() )
        {
        // [3.32.6: Type-Declaration Instructions]
        //

#define CASE(IROP, SPVOP) \
        case IROP: return emitTypeInst(inst, SPVOP, ArrayView<SpvWord>());

        // > OpTypeVoid
        CASE(kIROp_VoidType, SpvOpTypeVoid);

        // > OpTypeBool
        CASE(kIROp_BoolType, SpvOpTypeBool);

#undef CASE

        // > OpTypeInt

#define CASE(IROP, BITS, SIGNED) \
        case IROP:                                                                     \
        return emitTypeInst(inst, SpvOpTypeInt, makeArray<SpvWord>((SpvWord)BITS, (SpvWord)SIGNED).getView()); 

        CASE(kIROp_IntType,     32, 1);
        CASE(kIROp_UIntType,    32, 0);
        CASE(kIROp_Int64Type,   64, 1);
        CASE(kIROp_UInt64Type,  64, 0);

#undef CASE

        // > OpTypeFloat

#define CASE(IROP, BITS) \
        case IROP:                                                                \
        return emitTypeInst(                                                      \
            inst, SpvOpTypeFloat, makeArray<SpvWord>(BITS).getView()); \

        CASE(kIROp_HalfType,    16);
        CASE(kIROp_FloatType,   32);
        CASE(kIROp_DoubleType,  64);

#undef CASE
        case kIROp_PtrType:
        case kIROp_RefType:
        case kIROp_OutType:
        case kIROp_InOutType:
            {
                SpvStorageClass storageClass = SpvStorageClassFunction;
                auto ptrType = as<IRPtrTypeBase>(inst);
                if (ptrType->hasAddressSpace())
                    storageClass = (SpvStorageClass)ptrType->getAddressSpace();
                if (storageClass == SpvStorageClassStorageBuffer)
                    ensureExtensionDeclaration(UnownedStringSlice("SPV_KHR_storage_buffer_storage_class"));
                auto operands = makeArray<SpvWord>(
                    (SpvWord)storageClass, getID(ensureInst(inst->getOperand(0))));
                return emitTypeInst(
                    inst, SpvOpTypePointer, operands.getView());
            }
        case kIROp_StructType:
            {
                auto spvStructType = emitInstCustomOperandFunc(
                    getSection(SpvLogicalSectionID::Types), inst, SpvOpTypeStruct, [&]() {
                        emitOperand(kResultID);
                        for (auto field : static_cast<IRStructType*>(inst)->getFields())
                        {
                            emitOperand(field->getFieldType());
                            // TODO: decorate offset
                        }
                    });
                emitDecorations(inst, getID(spvStructType));
                return spvStructType;
            }
        case kIROp_VectorType:
            {
                auto vectorType = static_cast<IRVectorType*>(inst);
                return ensureVectorType(
                    static_cast<IRBasicType*>(vectorType->getElementType())->getBaseType(),
                    static_cast<IRIntLit*>(vectorType->getElementCount())->getValue(),
                    vectorType);
            }
        case kIROp_MatrixType:
            {
                auto matrixType = static_cast<IRMatrixType*>(inst);
                auto vectorSpvType = ensureVectorType(
                    static_cast<IRBasicType*>(matrixType->getElementType())->getBaseType(),
                    static_cast<IRIntLit*>(matrixType->getRowCount())->getValue(),
                    nullptr);
                auto matrixSPVType = emitInst(
                    getSection(SpvLogicalSectionID::Types),
                    inst,
                    SpvOpTypeMatrix,
                    kResultID,
                    vectorSpvType,
                    (SpvWord)static_cast<IRIntLit*>(matrixType->getColumnCount())->getValue());
                // TODO: properly compute matrix stride.
                auto columnCount = static_cast<IRIntLit*>(matrixType->getRowCount())->getValue();
                uint32_t stride = 0;
                switch (columnCount)
                {
                case 1:
                    stride = 4;
                    break;
                case 2:
                    stride = 8;
                    break;
                case 3:
                case 4:
                    stride = 16;
                    break;
                default:
                    break;
                }
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    matrixSPVType,
                    SpvDecorationRowMajor,
                    SpvDecorationMatrixStride,
                    stride);
                return matrixSPVType;
            }
        case kIROp_UnsizedArrayType:
            {
                auto elementType = static_cast<IRUnsizedArrayType*>(inst)->getElementType();
                auto runtimeArrayType = emitInst(
                    getSection(SpvLogicalSectionID::Types),
                    nullptr,
                    SpvOpTypeRuntimeArray,
                    kResultID,
                    elementType);
                // TODO: properly decorate stride.
                IRSizeAndAlignment sizeAndAlignment;
                getNaturalSizeAndAlignment(this->m_targetRequest, elementType, &sizeAndAlignment);
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    runtimeArrayType,
                    SpvDecorationArrayStride,
                    (SpvWord)sizeAndAlignment.getStride());
                return runtimeArrayType;
            }
        // > OpTypeImage
        // > OpTypeSampler
        // > OpTypeArray
        // > OpTypeRuntimeArray
        // > OpTypeOpaque
        // > OpTypePointer

        case kIROp_FuncType:
            // > OpTypeFunction
            //
            // Both Slang and SPIR-V encode a function type
            // with the result-type operand coming first,
            // followed by operand sfor all the parameter types.
            //
            return emitInst(getSection(SpvLogicalSectionID::Types), inst, SpvOpTypeFunction, kResultID, OperandsOf(inst));

        case kIROp_RateQualifiedType:
            {
                auto result = emitGlobalInst(as<IRRateQualifiedType>(inst)->getValueType());
                registerInst(inst, result);
                return result;
            }
        // > OpTypeForwardPointer

        case kIROp_Func:
            // [3.32.6: Function Instructions]
            //
            // > OpFunction
            //
            // Functions are complex enough that we'll handle
            // them in a dedicated subroutine.
            //
            return emitFunc(as<IRFunc>(inst));

         case kIROp_BoolLit:
         case kIROp_IntLit:
         case kIROp_FloatLit:
             return emitLit(inst);

        case kIROp_GlobalParam:
             return emitGlobalParam(as<IRGlobalParam>(inst));
        case kIROp_GlobalVar:
            return emitGlobalVar(as<IRGlobalVar>(inst));
        // ...

        default:
            SLANG_UNIMPLEMENTED_X("unhandled instruction opcode");
            UNREACHABLE_RETURN(nullptr);
        }
    }

    // Ensures an SpvInst for the specified vector type is emitted.
    // `inst` represents an optional `IRVectorType` inst representing the vector type, if
    // it is nullptr, this function will create one.
    SpvInst* ensureVectorType(BaseType baseType, IRIntegerValue elementCount, IRVectorType* inst)
    {
        if (!inst)
        {
            IRBuilder builder(m_sharedIRBuilder);
            builder.setInsertInto(m_irModule->getModuleInst());
            inst = builder.getVectorType(
                builder.getBasicType(baseType),
                builder.getIntValue(builder.getIntType(), elementCount));
        }
        auto operands =
            makeArray<SpvWord>(getID(ensureInst(inst->getElementType())), (SpvWord)elementCount);
        auto result = emitTypeInst(inst, SpvOpTypeVector, operands.getView());
        return result;
    }

    void emitVarLayout(SpvInst* varInst, IRVarLayout* layout)
    {
        for (auto rr : layout->getOffsetAttrs())
        {
            UInt index = rr->getOffset();
            UInt space = rr->getSpace();
            switch (rr->getResourceKind())
            {
            case LayoutResourceKind::Uniform:
                break;

            case LayoutResourceKind::VaryingInput:
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationLocation,
                    (SpvWord)index);
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationIndex,
                    (SpvWord)space);
                break;
            case LayoutResourceKind::VaryingOutput:
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationLocation,
                    (SpvWord)index);
                if (space)
                {
                    emitInst(
                        getSection(SpvLogicalSectionID::Annotations),
                        nullptr,
                        SpvOpDecorate,
                        varInst,
                        SpvDecorationIndex,
                        (SpvWord)space);
                }
                break;

            case LayoutResourceKind::SpecializationConstant:
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationSpecId,
                    (SpvWord)index);
                break;

            case LayoutResourceKind::ConstantBuffer:
            case LayoutResourceKind::ShaderResource:
            case LayoutResourceKind::UnorderedAccess:
            case LayoutResourceKind::SamplerState:
            case LayoutResourceKind::DescriptorTableSlot:
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationBinding,
                    (SpvWord)index);
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpDecorate,
                    varInst,
                    SpvDecorationDescriptorSet,
                    (SpvWord)space);
                break;
            default:
                break;
            }
        }
    }
        /// Emit a global parameter definition.
    SpvInst* emitGlobalParam(IRGlobalParam* param)
    {
        auto layout = getVarLayout(param);
        auto storageClass = SpvStorageClassUniform;
        if (auto ptrType = as<IRPtrTypeBase>(param->getDataType()))
        {
            if (ptrType->hasAddressSpace())
                storageClass = (SpvStorageClass)ptrType->getAddressSpace();
        }
        if (auto systemValInst = maybeEmitSystemVal(param))
        {
            registerInst(param, systemValInst);
            return systemValInst;
        }
        auto varInst = emitInst(
            getSection(SpvLogicalSectionID::GlobalVariables),
            param,
            SpvOpVariable,
            param->getDataType(),
            kResultID,
            storageClass);
        emitVarLayout(varInst, layout);
        return varInst;   
    }

        /// Emit a global variable definition.
    SpvInst* emitGlobalVar(IRGlobalVar* globalVar)
    {
        auto layout = getVarLayout(globalVar);
        auto storageClass = SpvStorageClassUniform;
        if (auto ptrType = as<IRPtrTypeBase>(globalVar->getDataType()))
        {
            if (ptrType->hasAddressSpace())
                storageClass = (SpvStorageClass)ptrType->getAddressSpace();
        }
        auto varInst = emitInst(
            getSection(SpvLogicalSectionID::GlobalVariables),
            globalVar,
            SpvOpVariable,
            globalVar->getDataType(),
            kResultID,
            storageClass);
        emitVarLayout(varInst, layout);
        return varInst;
    }

        /// Emit the given `irFunc` to SPIR-V
    SpvInst* emitFunc(IRFunc* irFunc)
    {
        // [2.4: Logical Layout of a Module]
        //
        // > All function declarations ("declarations" are functions
        // > without a body; there is no forward declaration to a
        // > function with a body).
        // > ...
        // > All function definitions (functions with a body).
        //
        // We need to treat functions differently based
        // on whether they have a body or not, since these
        // are encoded differently (and to different sections).
        //
        if( isDefinition(irFunc) )
        {
            return emitFuncDefinition(irFunc);
        }
        else
        {
            return emitFuncDeclaration(irFunc);
        }
    }

        /// Emit a declaration for the given `irFunc`
    SpvInst* emitFuncDeclaration(IRFunc* irFunc)
    {
        // For now we aren't handling function declarations;
        // we expect to deal only with fully linked modules.
        //
        SLANG_UNUSED(irFunc);
        SLANG_UNEXPECTED("function declaration in SPIR-V emit");
        UNREACHABLE_RETURN(nullptr);
    }

        /// Emit a SPIR-V function definition for the Slang IR function `irFunc`.
    SpvInst* emitFuncDefinition(IRFunc* irFunc)
    {
        // [2.4: Logical Layout of a Module]
        //
        // > All function definitions (functions with a body).
        //
        auto section = getSection(SpvLogicalSectionID::FunctionDefinitions);
        //
        // > A function definition is as follows.
        // > * Function definition, using OpFunction.
        // > * Function parameter declarations, using OpFunctionParameter.
        // > * Block
        // > * Block
        // > * ...
        // > * Function end, using OpFunctionEnd.
        //

        // [3.24. Function Control]
        //
        // TODO: We should eventually support emitting the "function control"
        // mask to include inline and other hint bits based on decorations
        // set on `irFunc`.
        //
        SpvFunctionControlMask spvFunctionControl = SpvFunctionControlMaskNone;

        // [3.32.9. Function Instructions]
        //
        // > OpFunction
        //
        // Note that the type <id> of a SPIR-V function uses the
        // *result* type of the function, while the actual function
        // type is given as a later operand. Slan IR instead uses
        // the type of a function instruction store, you know, its *type*.
        //
        SpvInst* spvFunc = emitInst(section, irFunc, SpvOpFunction,
            irFunc->getDataType()->getResultType(),
            kResultID,
            spvFunctionControl,
            irFunc->getDataType());

        // > OpFunctionParameter
        //
        // Unlike Slang, where parameters always belong to blocks,
        // the parameters of a SPIR-V function must appear as direct
        // children of the function instruction, and before any basic blocks.
        //
        for( auto irParam : irFunc->getParams() )
        {
            emitParam(spvFunc, irParam);
        }

        // [3.32.17. Control-Flow Instructions]
        //
        // > OpLabel
        //
        // A Slang `IRBlock` corresponds to a SPIR-V `OpLabel`:
        // each represents a basic block in the control flow
        // graph of a parent function.
        //
        // We will allocate SPIR-V instructions to represent
        // all of the blocks in a function before we emit
        // body instructions into any of them. We do this
        // because it is possible for one block to make
        // forward reference to another (wheras that is
        // not possible for ordinary instructions within
        // the blocks in the Slang IR)
        //
        for( auto irBlock : irFunc->getBlocks() )
        {
            emitInst(spvFunc, irBlock, SpvOpLabel, kResultID);

            // In addition to normal basic blocks,
            // all loops gets a header block.
            for (auto irInst : irBlock->getChildren())
            {
                if (irInst->getOp() == kIROp_loop)
                {
                    emitInst(spvFunc, irInst, SpvOpLabel, kResultID);
                }
            }
        }

        // Once all the basic blocks have had instructions allocated
        // for them, we go through and fill them in with their bodies.
        //
        // Each loop inst results in a loop header block.
        // We will defer the emit of the contents in loop header block
        // until all Phi insts are emitted.
        List<IRLoop*> pendingLoopInsts;
        for( auto irBlock : irFunc->getBlocks() )
        {
            // Note: because we already created the block above,
            // we can be sure that it will have been registred.
            //
            SpvInst* spvBlock = nullptr;
            m_mapIRInstToSpvInst.TryGetValue(irBlock, spvBlock);
            SLANG_ASSERT(spvBlock);

            // [3.32.17. Control-Flow Instructions]
            //
            // > OpPhi
            if (irBlock != irFunc->getFirstBlock())
            {
                for (auto irParam : irBlock->getParams())
                {
                    emitPhi(spvBlock, irParam);
                }
            }
            for( auto irInst : irBlock->getOrdinaryInsts() )
            {
                // Any instructions local to the block will be emitted as children
                // of the block.
                //
                emitLocalInst(spvBlock, irInst);
                if (irInst->getOp() == kIROp_loop)
                    pendingLoopInsts.add(as<IRLoop>(irInst));
            }
        }

        // Finally, we generate the body of loop header blocks.
        for (auto loopInst : pendingLoopInsts)
        {
            SpvInst* headerBlock = nullptr;
            m_mapIRInstToSpvInst.TryGetValue(loopInst, headerBlock);
            SLANG_ASSERT(headerBlock);
            emitLoopHeaderBlock(loopInst, headerBlock);
        }

        // [3.32.9. Function Instructions]
        //
        // > OpFunctionEnd
        //
        // In the SPIR-V encoding a function is logically the parent of any
        // instructions up to a matching `OpFunctionEnd`. In our intermediate
        // structure we will make the `OpFunctionEnd` be the last child of
        // the `OpFunction`.
        //
        emitInst(spvFunc, nullptr, SpvOpFunctionEnd);

        // We will emit any decorations pertinent to the function to the
        // appropriate section of the module.
        //
        emitDecorations(irFunc, getID(spvFunc));

        return spvFunc;
    }

        /// Check if a block is a loop's target block.
    bool isLoopTargetBlock(IRInst* block, IRInst*& loopInst)
    {
        for (auto use = block->firstUse; use; use = use->nextUse)
        {
            if (use->getUser()->getOp() == kIROp_loop &&
                as<IRLoop>(use->getUser())->getTargetBlock() == block)
            {
                loopInst = use->getUser();
                return true;
            }
        }
        return false;
    }

    // The instructions that appear inside the basic blocks of
    // functions are what we will call "local" instructions.
    //
    // When emititng blobal instructions, we usually have to
    // pick the right logical section to emit them into, while
    // for local instructions they will usually emit into
    // a known parent (the basic block that contains them).

        /// Emit an instruction that is local to the body of the given `parent`.
    SpvInst* emitLocalInst(SpvInstParent* parent, IRInst* inst)
    {
        switch( inst->getOp() )
        {
        default:
            SLANG_UNIMPLEMENTED_X("unhandled instruction opcode");
            break;
        case kIROp_Specialize:
            return nullptr;
        case kIROp_Var:
            return emitVar(parent, inst);
        case kIROp_Call:
            return emitCall(parent, inst);
        case kIROp_FieldAddress:
            return emitFieldAddress(parent, as<IRFieldAddress>(inst));
        case kIROp_FieldExtract:
            return emitFieldExtract(parent, as<IRFieldExtract>(inst));
        case kIROp_getElementPtr:
            return emitGetElementPtr(parent, as<IRGetElementPtr>(inst));
        case kIROp_getElement:
            return emitGetElement(parent, as<IRGetElement>(inst));
        case kIROp_Load:
            return emitLoad(parent, as<IRLoad>(inst));
        case kIROp_Store:
            return emitStore(parent, as<IRStore>(inst));
        case kIROp_swizzle:
            return emitSwizzle(parent, as<IRSwizzle>(inst));
        case kIROp_Construct:
            return emitConstruct(parent, inst);
        case kIROp_BitCast:
            return emitInst(
                parent, inst, SpvOpBitcast, inst->getDataType(), kResultID, inst->getOperand(0));
        case kIROp_Add:
        case kIROp_Sub:
        case kIROp_Mul:
        case kIROp_Div:
        case kIROp_IRem:
        case kIROp_FRem:
        case kIROp_Neg:
        case kIROp_Not:
        case kIROp_And:
        case kIROp_Or:
        case kIROp_BitNot:
        case kIROp_BitAnd:
        case kIROp_BitOr:
        case kIROp_BitXor:
        case kIROp_Less:
        case kIROp_Leq:
        case kIROp_Eql:
        case kIROp_Neq:
        case kIROp_Greater:
        case kIROp_Geq:
        case kIROp_Rsh:
        case kIROp_Lsh:
            return emitArithmetic(parent, inst);
        case kIROp_Return:
            if (as<IRReturn>(inst)->getVal()->getOp() == kIROp_VoidLit)
            {
                return emitInst(parent, inst, SpvOpReturn);
            }
            else
            {
                return emitInst(
                    parent, inst, SpvOpReturnValue, as<IRReturn>(inst)->getVal());
            }
        case kIROp_discard:
            return emitInst(parent, inst, SpvOpKill);
        case kIROp_unconditionalBranch:
            {
                // If we are jumping to the main block of a loop,
                // emit a branch to the loop header instead.
                // The SPV id of the resulting loop header block is associated with the loop inst.
                auto targetBlock = as<IRUnconditionalBranch>(inst)->getTargetBlock();
                IRInst* loopInst = nullptr;
                if (isLoopTargetBlock(targetBlock, loopInst))
                {
                    return emitInst(parent, inst, SpvOpBranch, getIRInstSpvID(loopInst));
                }
                // Otherwise, emit a normal branch inst into the target block.
                return emitInst(
                    parent,
                    inst,
                    SpvOpBranch,
                    getIRInstSpvID(targetBlock));
            }
        case kIROp_loop:
            {
                // Return loop header block in its own block.
                auto blockId = getIRInstSpvID(inst);
                SpvInst* block = nullptr;
                m_mapIRInstToSpvInst.TryGetValue(inst, block);
                SLANG_ASSERT(block);

                // Emit a jump to the loop header block.
                // Note: the body of the loop header block is emitted
                // after everything else to ensure Phi instructions (which come
                // from the actual loop target block) are emitted first.
                emitInst(parent, nullptr, SpvOpBranch, blockId);
        
                return block;
            }
        case kIROp_ifElse:
            {
                auto ifelseInst = as<IRIfElse>(inst);
                auto afterBlockID = getIRInstSpvID(ifelseInst->getAfterBlock());
                emitInst(
                    parent,
                    nullptr,
                    SpvOpSelectionMerge,
                    afterBlockID,
                    0);
                auto falseLabel = ifelseInst->getFalseBlock();
                return emitInst(
                    parent,
                    inst,
                    SpvOpBranchConditional,
                    ifelseInst->getCondition(),
                    ifelseInst->getTrueBlock(),
                    falseLabel ? getID(ensureInst(falseLabel)) : afterBlockID);
            }
        case kIROp_Switch:
            {
                auto switchInst = as<IRSwitch>(inst);
                auto mergeBlockID = getIRInstSpvID(switchInst->getBreakLabel());
                emitInst(parent, nullptr, SpvOpSelectionMerge, mergeBlockID, 0);
                return emitInstCustomOperandFunc(parent, inst, SpvOpSwitch, [&]() {
                    emitOperand(switchInst->getCondition());
                    auto defaultLabel = switchInst->getDefaultLabel();
                    emitOperand(defaultLabel ? getID(ensureInst(defaultLabel)) : mergeBlockID);
                    for (UInt c = 0; c < switchInst->getCaseCount(); c++)
                    {
                        auto value = switchInst->getCaseValue(c);
                        auto intLit = as<IRIntLit>(value);
                        SLANG_ASSERT(intLit);
                        emitOperand((SpvWord)intLit->getValue());
                        auto caseLabel = switchInst->getCaseLabel(c);
                        emitOperand(caseLabel ? getID(ensureInst(caseLabel)) : mergeBlockID);
                    }
                });
            }
        case kIROp_Unreachable:
            return emitInst(parent, inst, SpvOpUnreachable);
        case kIROp_conditionalBranch:
            SLANG_UNEXPECTED("Unstructured branching is not supported by SPIRV.");
        }
    }

    SpvInst* emitLit(IRInst* inst)
    {
        switch (inst->getOp())
        {
        case kIROp_IntLit:
            {
                auto value = as<IRIntLit>(inst)->getValue();
                switch (as<IRBasicType>(inst->getDataType())->getBaseType())
                {
                case BaseType::Int64:
                case BaseType::UInt64:
                case BaseType::IntPtr:
                case BaseType::UIntPtr:
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstant,
                        inst->getDataType(),
                        kResultID,
                        (SpvWord)(value & 0xFFFFFFFF),
                        (SpvWord)((value >> 32) & 0xFFFFFFFF));
                default:
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstant,
                        inst->getDataType(),
                        kResultID,
                        (SpvWord)value);
                }
            }
        case kIROp_FloatLit:
            {
                auto value = as<IRConstant>(inst)->value.floatVal;
                switch (as<IRBasicType>(inst->getDataType())->getBaseType())
                {
                case BaseType::Half:
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstant,
                        inst->getDataType(),
                        kResultID,
                        (SpvWord)(FloatToHalf((float)value)));
                case BaseType::Float:
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstant,
                        inst->getDataType(),
                        kResultID,
                        (SpvWord)(FloatAsInt((float)value)));
                case BaseType::Double:
                    {
                        auto ival = DoubleAsInt64(value);
                        return emitInst(
                            getSection(SpvLogicalSectionID::Constants),
                            inst,
                            SpvOpConstant,
                            inst->getDataType(),
                            kResultID,
                            (SpvWord)(ival&0xFFFFFFFF),
                            (SpvWord)(ival>>32));
                    }
                default:
                    return nullptr;
                }
            }
        case kIROp_BoolLit:
            {
                if (as<IRBoolLit>(inst)->getValue())
                {
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstantTrue,
                        inst->getDataType(),
                        kResultID);
                }
                else
                {
                    return emitInst(
                        getSection(SpvLogicalSectionID::Constants),
                        inst,
                        SpvOpConstantFalse,
                        inst->getDataType(),
                        kResultID);
                }
            }
        default:
            return nullptr;
        }
    }

    // Both "local" and "global" instructions can have decorations.
    // When we decide to emit an instruction, we typically also want
    // to emit any decoratons that were attached to it that have
    // a SPIR-V equivalent.

        /// Emit appropriate SPIR-V decorations for the given IR `irInst`.
        ///
        /// The given `dstID` should be the `<id>` of the SPIR-V instruction being decorated,
        /// and should correspond to `irInst`.
        ///
    void emitDecorations(IRInst* irInst, SpvWord dstID)
    {
        for( auto decoration : irInst->getDecorations() )
        {
            emitDecoration(dstID, decoration);
        }
    }

        /// Emit an appropriate SPIR-V decoration for the given IR `decoration`, if necessary and possible.
        ///
        /// The given `dstID` should be the `<id>` of the SPIR-V instruction being decorated,
        /// and should correspond to the parent of `decoration` in the Slang IR.
        ///
    void emitDecoration(SpvWord dstID, IRDecoration* decoration)
    {
        // Unlike in the Slang IR, decorations in SPIR-V are not children
        // of the instruction they decorate, and instead are free-standing
        // instructions at global scope, which reference their target
        // instruction by its `<id>`.
        //
        // The `IRDecoration` hierarchy in Slang also maps to several
        // different categories of instruction in SPIR-V, only a subset
        // of which are officialy called "decorations."
        //
        // We will continue to use the Slang terminology here, since
        // this code path is a catch-all for stuff that only needs to
        // be emitted if the owning instruction gets emitted.

        switch( decoration->getOp() )
        {
        default:
            break;

        // [3.32.2. Debug Instructions]
        //
        // > OpName
        //
        case kIROp_NameHintDecoration:
            {
                auto section = getSection(SpvLogicalSectionID::DebugNames);
                auto nameHint = cast<IRNameHintDecoration>(decoration);
                emitInst(section, decoration, SpvOpName, dstID, nameHint->getName());
            }
            break;

        // [3.32.5. Mode-Setting Instructions]
        //
        // > OpEntryPoint
        // > Declare an entry point, its execution model, and its interface.
        //
        case kIROp_EntryPointDecoration:
            {
                auto section = getSection(SpvLogicalSectionID::EntryPoints);

                // TODO: The `OpEntryPoint` is required to list an varying
                // input or output parameters (by `<id>`) used by the entry point,
                // although these are encoded as global variables in the IR.
                //
                // Currently we have a pass that moves entry-point varying
                // parameters to global scope for the benefit of GLSL output,
                // but we do not maintain a connection between those parameters
                // and the original entry point. That pass should be updated
                // to attach a decoration linking the original entry point
                // to the new globals, which would be used in the SPIR-V emit case.

                auto entryPointDecor = cast<IREntryPointDecoration>(decoration);
                auto spvStage = mapStageToExecutionModel(entryPointDecor->getProfile().getStage());
                auto name = entryPointDecor->getName()->getStringSlice();
                emitInstCustomOperandFunc(section, decoration, SpvOpEntryPoint, [&]() {
                    emitOperand(spvStage);
                    emitOperand(dstID);
                    emitOperand(name);
                    // `interface` part: reference all global variables that are used by this entrypoint.
                    // TODO: we may want to perform more accurate tracking.
                    for (auto globalInst : m_irModule->getModuleInst()->getChildren())
                    {
                        switch (globalInst->getOp())
                        {
                        case kIROp_GlobalVar:
                        case kIROp_GlobalParam:
                            emitOperand(getIRInstSpvID(globalInst));
                            break;
                        }
                    }
                });
            }
            break;

        // > OpExecutionMode

        // [3.6. Execution Mode]: LocalSize
        case kIROp_NumThreadsDecoration:
            {
                auto section = getSection(SpvLogicalSectionID::ExecutionModes);

                // TODO: The `LocalSize` execution mode option requires
                // literal values for the X,Y,Z thread-group sizes.
                // There is a `LocalSizeId` variant that takes `<id>`s
                // for those sizes, and we should consider using that
                // and requiring the appropriate capabilities
                // if any of the operands to the decoration are not
                // literals (in a future where we support non-literals
                // in those positions in the Slang IR).
                //
                auto numThreads = cast<IRNumThreadsDecoration>(decoration);
                emitInst(section, decoration, SpvOpExecutionMode, dstID, SpvExecutionModeLocalSize,
                    SpvWord(numThreads->getX()->getValue()),
                    SpvWord(numThreads->getY()->getValue()),
                    SpvWord(numThreads->getZ()->getValue()));
            }
            break;

        case kIROp_SPIRVBufferBlockDecoration:
            {
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    decoration,
                    SpvOpDecorate,
                    dstID,
                    SpvDecorationBlock);
                emitInst(
                    getSection(SpvLogicalSectionID::Annotations),
                    nullptr,
                    SpvOpMemberDecorate,
                    dstID,
                    0,
                    SpvDecorationOffset,
                    0);
            }
            break;
        // ...
        }
    }

        /// Map a Slang `Stage` to a corresponding SPIR-V execution model
    SpvExecutionModel mapStageToExecutionModel(Stage stage)
    {
        switch( stage )
        {
        default:
            SLANG_UNEXPECTED("unhandled stage");
            UNREACHABLE_RETURN((SpvExecutionModel)0);

#define CASE(STAGE, MODEL) \
        case Stage::STAGE: return SpvExecutionModel##MODEL

        CASE(Vertex,    Vertex);
        CASE(Hull,      TessellationControl);
        CASE(Domain,    TessellationEvaluation);
        CASE(Geometry,  Geometry);
        CASE(Fragment,  Fragment);
        CASE(Compute,   GLCompute);

        // TODO: Extended execution models for ray tracing, etc.

#undef CASE
        }
    }

    Dictionary<SpvBuiltIn, SpvInst*> m_builtinGlobalVars;
    SpvInst* getBuiltinGlobalVar(IRType* type, SpvBuiltIn builtinVal)
    {
        SpvInst* result = nullptr;
        if (m_builtinGlobalVars.TryGetValue(builtinVal, result))
        {
            return result;
        }
        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertBefore(type);
        auto ptrType = as<IRPtrTypeBase>(type);
        SLANG_ASSERT(ptrType && "`getBuiltinGlobalVar`: `type` must be ptr type.");
        auto varInst = emitInst(
            getSection(SpvLogicalSectionID::GlobalVariables),
            nullptr,
            SpvOpVariable,
            type,
            kResultID,
            (SpvStorageClass)ptrType->getAddressSpace());
        emitInst(
            getSection(SpvLogicalSectionID::Annotations),
            nullptr,
            SpvOpDecorate,
            varInst,
            SpvDecorationBuiltIn,
            builtinVal);
        m_builtinGlobalVars[builtinVal] = varInst;
        return varInst;
    }

    SpvInst* maybeEmitSystemVal(IRInst* inst)
    {
        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertBefore(inst);
        if (auto layout = getVarLayout(inst))
        {
            if (auto systemValueAttr = layout->findAttr<IRSystemValueSemanticAttr>())
            {
                String semanticName = systemValueAttr->getName();
                semanticName = semanticName.toLower();
                if (semanticName == "sv_dispatchthreadid")
                {
                    return getBuiltinGlobalVar(inst->getFullType(), SpvBuiltInGlobalInvocationId);
                }
            }
        }
        return nullptr;
    }

    SpvInst* emitParam(SpvInstParent* parent, IRInst* inst)
    {
        return emitInst(parent, inst, SpvOpFunctionParameter, inst->getFullType(), kResultID);
    }

    SpvInst* emitVar(SpvInstParent* parent, IRInst* inst)
    {
        auto ptrType = as<IRPtrTypeBase>(inst->getDataType());
        SLANG_ASSERT(ptrType);
        SpvStorageClass storageClass = SpvStorageClassFunction;
        if (ptrType->hasAddressSpace())
        {
            storageClass = (SpvStorageClass)ptrType->getAddressSpace();
        }
        return emitInst(parent, inst, SpvOpVariable, inst->getFullType(), kResultID, storageClass);
    }

        /// Cached `IRParam` indices in an `IRBlock`. For use in `getParamIndexInBlock`.
    struct BlockParamIndexInfo : public RefObject
    {
        Dictionary<IRParam*, int> mapParamToIndex;
    };
    Dictionary<IRBlock*, RefPtr<BlockParamIndexInfo>> m_mapIRBlockToParamIndexInfo;

        /// Returns the index of an `IRParam` inside a `IRBlock`.
        /// The results are cached in `m_mapIRBlockToParamIndexInfo` to avoid linear search.
    int getParamIndexInBlock(IRBlock* block, IRParam* paramInst)
    {
        RefPtr<BlockParamIndexInfo> info;
        int result = -1;
        if (m_mapIRBlockToParamIndexInfo.TryGetValue(block, info))
        {
            info->mapParamToIndex.TryGetValue(paramInst, result);
            SLANG_ASSERT(result != -1);
            return result;
        }
        info = new BlockParamIndexInfo();
        int paramIndex = 0;
        for (auto param : block->getParams())
        {
            info->mapParamToIndex[param] = paramIndex;
            if (param == paramInst)
                result = paramIndex;
            paramIndex++;
        }
        m_mapIRBlockToParamIndexInfo[block] = info;
        SLANG_ASSERT(result != -1);
        return result;
    }

    bool isGlobalValueInst(IRInst* inst)
    {
        if (as<IRConstant>(inst))
            return true;
        switch (inst->getOp())
        {
        case kIROp_Func:
        case kIROp_GlobalParam:
        case kIROp_GlobalVar:
            return true;
        default:
            return false;
        }
    }

    void emitLoopHeaderBlock(IRLoop* loopInst, SpvInst* loopHeaderBlock)
    {
        SpvWord loopControl = 0;
        if (auto loopControlDecoration = loopInst->findDecoration<IRLoopControlDecoration>())
        {
            switch (loopControlDecoration->getMode())
            {
            case IRLoopControl::kIRLoopControl_Unroll:
                loopControl = 0x1;
                break;
            case IRLoopControl::kIRLoopControl_Loop:
                loopControl = 0x2;
                break;
            default:
                break;
            }
        }
        emitInst(
            loopHeaderBlock,
            nullptr,
            SpvOpLoopMerge,
            getIRInstSpvID(loopInst->getBreakBlock()),
            getIRInstSpvID(loopInst->getContinueBlock()),
            loopControl);
        emitInst(loopHeaderBlock, nullptr, SpvOpBranch, loopInst->getTargetBlock());
    }

    SpvInst* emitPhi(SpvInstParent* parent, IRParam* inst)
    {
        // An `IRParam` in an ordinary `IRBlock` represents a phi value.
        // We can translate them directly to SPIRV's `Phi` instruction.
        // In order to do that, we need to figure out the source values
        // of this `IRParam`, which can be done by looking at the users
        // of current `IRBlock`.

        // First, we find the index of this param.
        IRBlock* block = as<IRBlock>(inst->getParent());
        // Special case: if block is a loop's target block, emit phis into the header block instead.
        IRInst* loopInst = nullptr;
        if (isLoopTargetBlock(block, loopInst))
        {
            SpvInst* loopSpvBlockInst = nullptr;
            m_mapIRInstToSpvInst.TryGetValue(loopInst, loopSpvBlockInst);
            SLANG_ASSERT(loopSpvBlockInst);
            parent = loopSpvBlockInst;
        }

        SLANG_ASSERT(block);
        int paramIndex = getParamIndexInBlock(block, inst);

        // Emit a Phi instruction.
        return emitInstCustomOperandFunc(parent, inst, SpvOpPhi, [&]() {
            emitOperand(inst->getFullType());
            emitOperand(kResultID);
            // Find phi arguments from incoming branch instructions that target `block`.
            for (auto use = block->firstUse; use; use = use->nextUse)
            {
                auto branchInst = use->getUser();
                UInt argStartIndex = 0;
                switch (branchInst->getOp())
                {
                case kIROp_unconditionalBranch:
                    argStartIndex = 1;
                    break;
                case kIROp_loop:
                    argStartIndex = 3;
                    break;
                default:
                    // A phi argument can only come from an unconditional branch inst.
                    // Other uses are not relavent so we should skip.
                    continue;
                }
                SLANG_ASSERT(argStartIndex + paramIndex < branchInst->getOperandCount());
                auto valueInst = branchInst->getOperand(argStartIndex + paramIndex);
                if (isGlobalValueInst(valueInst))
                    ensureInst(valueInst);
                emitOperand(getIRInstSpvID(valueInst));
                auto sourceBlock = as<IRBlock>(branchInst->getParent());
                SLANG_ASSERT(sourceBlock);
                emitOperand(getIRInstSpvID(sourceBlock));
            }
        });
    }

    SpvInst* emitCall(SpvInstParent* parent, IRInst* inst)
    {
        auto funcValue = inst->getOperand(0);

        // Does this function declare any requirements.
        handleRequiredCapabilities(funcValue);

        // We want to detect any call to an intrinsic operation, and inline
        // the SPIRV snippet directly at the call site.
        if (auto targetIntrinsic = Slang::findBestTargetIntrinsicDecoration(
                funcValue, m_targetRequest->getTargetCaps()))
        {
            return emitIntrinsicCallExpr(parent, static_cast<IRCall*>(inst), targetIntrinsic);
        }
        else
        {
            return emitInst(
                parent, inst, SpvOpFunctionCall, inst->getFullType(), kResultID, OperandsOf(inst));
        }
    }

    SpvInst* emitIntrinsicCallExpr(
        SpvInstParent* parent,
        IRCall* inst,
        IRTargetIntrinsicDecoration* intrinsic)
    {
        SpvSnippet* snippet = getParsedSpvSnippet(intrinsic);
        SpvSnippetEmitContext context;
        context.irResultType = inst->getDataType();
        context.resultType = ensureInst(inst->getFullType());
        context.isResultTypeFloat = isFloatType(inst->getDataType());
        context.isResultTypeSigned = isSignedType((IRType*)inst->getDataType());
        for (SlangUInt i = 0; i < inst->getArgCount(); i++)
        {
            auto argInst = ensureInst(inst->getArg(i));
            if (argInst)
            {
                context.argumentIds.add(getID(argInst));
            }
            else
            {
                context.argumentIds.add(0xFFFFFFFF);
            }
        }
        // A SPIRV snippet may refer to the result type of this inst with a
        // different storage-class qualifier. We need to pre-create these
        // storage-class-qualified result pointer types so they can be used
        // during inlining of the snippet.
        if (auto oldPtrType = as<IRPtrTypeBase>(inst->getDataType()))
        {
            for (auto storageClass : snippet->usedResultTypeStorageClasses)
            {
                IRBuilder builder(m_sharedIRBuilder);
                builder.setInsertBefore(inst);
                auto newPtrType = builder.getPtrType(
                    oldPtrType->getOp(), oldPtrType->getValueType(), storageClass);
                context.qualifiedResultTypes[storageClass] = newPtrType;
            }
        }
        return emitSpvSnippet(parent, inst, context, snippet);
    }

    Dictionary<SpvSnippet::ASMConstant, SpvInst*> m_spvSnippetConstantInsts;

    // Emit SPV Inst that represents a constant defined in a SpvSnippet.
    SpvInst* maybeEmitSpvConstant(SpvSnippet::ASMConstant constant)
    {
        SpvInst* result = nullptr;
        if (m_spvSnippetConstantInsts.TryGetValue(constant, result))
            return result;

        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertInto(m_irModule->getModuleInst());
        switch (constant.type)
        {
        case SpvSnippet::ASMType::Float:
            result = emitFloatConstant(constant.floatValues[0], builder.getType(kIROp_FloatType));
            break;
        case SpvSnippet::ASMType::Float2:
            {
                auto floatType = builder.getType(kIROp_FloatType);
                auto element1 = emitFloatConstant(constant.floatValues[0], floatType);
                auto element2 = emitFloatConstant(constant.floatValues[1], floatType);
                result = emitInst(
                    getSection(SpvLogicalSectionID::Constants),
                    nullptr,
                    SpvOpConstantComposite,
                    builder.getVectorType(floatType, builder.getIntValue(builder.getIntType(), 2)),
                    kResultID,
                    element1,
                    element2);
            }
        case SpvSnippet::ASMType::Int:
            result = emitIntConstant((IRIntegerValue)constant.intValues[0], builder.getIntType());
            break;
        case SpvSnippet::ASMType::UInt2:
            {
                auto uintType = builder.getType(kIROp_UIntType);
                auto element1 = emitIntConstant((IRIntegerValue)constant.intValues[0], uintType);
                auto element2 = emitIntConstant((IRIntegerValue)constant.intValues[1], uintType);
                result = emitInst(
                    getSection(SpvLogicalSectionID::Constants),
                    nullptr,
                    SpvOpConstantComposite,
                    builder.getVectorType(uintType, builder.getIntValue(builder.getIntType(), 2)),
                    kResultID,
                    element1,
                    element2);
            }
            break;
        }
        m_spvSnippetConstantInsts[constant] = result;
        return result;
    }

    // Emit SPV Inst that represents a type defined in a SpvSnippet.
    void emitSpvSnippetASMTypeOperand(SpvSnippet::ASMType type)
    {
        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertInto(m_irModule->getModuleInst());
        IRType* irType = nullptr;
        switch (type)
        {
        case SpvSnippet::ASMType::Float:
            irType = builder.getType(kIROp_FloatType);
            break;
        case SpvSnippet::ASMType::Int:
            irType = builder.getIntType();
            break;
        case SpvSnippet::ASMType::Float2:
            irType = builder.getVectorType(
                builder.getType(kIROp_FloatType), builder.getIntValue(builder.getIntType(), 2));
            break;
        case SpvSnippet::ASMType::UInt2:
            irType = builder.getVectorType(
                builder.getType(kIROp_UIntType), builder.getIntValue(builder.getIntType(), 2));
            break;
        default:
            break;
        }
        emitOperand(irType);
    }

    SpvInst* emitSpvSnippet(
        SpvInstParent* parent,
        IRCall* inst,
        const SpvSnippetEmitContext& context,
        SpvSnippet* snippet)
    {
        ShortList<SpvInst*> emittedInsts;
        for (Index i = 0; i < snippet->instructions.getCount(); i++)
        {
            auto& spvSnippetInst = snippet->instructions[i];
            InstConstructScope scopeInst(this, (SpvOp)spvSnippetInst.opCode, nullptr);
            SpvInst* spvInst = scopeInst;
            for (auto operand : spvSnippetInst.operands)
            {
                switch (operand.type)
                {
                case SpvSnippet::ASMOperandType::SpvWord:
                    emitOperand(operand.content);
                    break;
                case SpvSnippet::ASMOperandType::ObjectReference:
                    SLANG_ASSERT(operand.content < (SpvWord)context.argumentIds.getCount());
                    emitOperand(context.argumentIds[operand.content]);
                    break;
                case SpvSnippet::ASMOperandType::ResultId:
                    emitOperand(kResultID);
                    break;
                case SpvSnippet::ASMOperandType::ResultTypeId:
                    if (operand.content != -1)
                    {
                        emitOperand(context.qualifiedResultTypes[(SpvStorageClass)operand.content]
                                        .GetValue());
                    }
                    else
                    {
                        emitOperand(context.resultType);
                    }
                    break;
                case SpvSnippet::ASMOperandType::InstReference:
                    SLANG_ASSERT(operand.content < (SpvWord)emittedInsts.getCount());
                    emitOperand(emittedInsts[operand.content]);
                    break;
                case SpvSnippet::ASMOperandType::GLSL450ExtInstSet:
                    emitOperand(getGLSL450ExtInst());
                    break;
                case SpvSnippet::ASMOperandType::FloatIntegerSelection:
                    if (context.isResultTypeFloat)
                    {
                        emitOperand(operand.content);
                    }
                    else
                    {
                        emitOperand(operand.content2);
                    }
                    break;
                case SpvSnippet::ASMOperandType::FloatUnsignedSignedSelection:
                    if (context.isResultTypeFloat)
                    {
                        emitOperand(operand.content);
                    }
                    else
                    {
                        if (context.isResultTypeSigned)
                        {
                            emitOperand(operand.content3);
                        }
                        else
                        {
                            emitOperand(operand.content2);
                        }
                    }
                    break;
                case SpvSnippet::ASMOperandType::TypeReference:
                    {
                        emitSpvSnippetASMTypeOperand((SpvSnippet::ASMType)operand.content);
                    }
                    break;
                case SpvSnippet::ASMOperandType::ConstantReference:
                    {
                        auto constant = snippet->constants[operand.content];
                        if (constant.type == SpvSnippet::ASMType::FloatOrDouble)
                        {
                            switch (extractBaseType(context.irResultType))
                            {
                            case BaseType::Float:
                                constant.type = SpvSnippet::ASMType::Float;
                                break;
                            case BaseType::Double:
                                constant.type = SpvSnippet::ASMType::Double;
                                break;
                            default:
                                break;
                            }
                        }
                        SpvInst* spvConstant = maybeEmitSpvConstant(constant);
                        emitOperand(spvConstant);
                    }
                    break;
                }
            }
            parent->addInst(spvInst);
            emittedInsts.add(spvInst);
        }
        auto resultInst = emittedInsts.getLast();
        registerInst(inst, resultInst);
        return resultInst;
    }

    struct StructTypeInfo : public RefObject
    {
        Dictionary<IRStructKey*, Index> structFieldIndices;
    };

    Dictionary<IRStructType*, RefPtr<StructTypeInfo>> m_structTypeInfos;

    RefPtr<StructTypeInfo> createStructTypeInfo(IRStructType* structType)
    {
        RefPtr<StructTypeInfo> typeInfo = new StructTypeInfo();
        Index index = 0;
        for (auto field : structType->getFields())
        {
            typeInfo->structFieldIndices[field->getKey()] = index;
            index++;
        }
        return typeInfo;
    }
    Index getStructFieldId(IRStructType* structType, IRStructKey* structFieldKey)
    {
        RefPtr<StructTypeInfo> info;
        if (!m_structTypeInfos.TryGetValue(structType, info))
        {
            info = createStructTypeInfo(structType);
            m_structTypeInfos[structType] = info;
        }
        Index fieldIndex = -1;
        info->structFieldIndices.TryGetValue(structFieldKey, fieldIndex);
        SLANG_ASSERT(fieldIndex != -1);
        return fieldIndex;
    }

    SpvInst* emitFieldAddress(SpvInstParent* parent, IRFieldAddress* fieldAddress)
    {
        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertBefore(fieldAddress);

        auto base = fieldAddress->getBase();
        SpvWord baseId = 0;
        IRStructType* baseStructType = nullptr;

        if (auto ptrLikeType = as<IRPointerLikeType>(base->getDataType()))
        {
            baseStructType = as<IRStructType>(ptrLikeType->getElementType());
            baseId = getID(ensureInst(base));
        }
        else if (auto ptrType = as<IRPtrTypeBase>(base->getDataType()))
        {
            baseStructType = as<IRStructType>(ptrType->getValueType());
            baseId = getID(ensureInst(base));
        }
        else
        {
            baseStructType = as<IRStructType>(base->getDataType());
            
            auto structPtrType = builder.getPtrType(baseStructType);
            auto varInst = emitInst(
                parent, nullptr, SpvOpVariable, structPtrType, kResultID, SpvStorageClassFunction);
            emitInst(parent, nullptr, SpvOpStore, varInst, base);
            baseId = getID(varInst);
        }
        SLANG_ASSERT(baseStructType && "field_address require base to be a struct.");
        auto fieldId = emitIntConstant(
            getStructFieldId(baseStructType, as<IRStructKey>(fieldAddress->getField())),
            builder.getIntType());
        return emitInst(
            parent,
            fieldAddress,
            SpvOpAccessChain,
            fieldAddress->getFullType(),
            kResultID,
            baseId,
            fieldId);
    }

    SpvInst* emitFieldExtract(SpvInstParent* parent, IRFieldExtract* inst)
    {
        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertBefore(inst);

        IRStructType* baseStructType = as<IRStructType>(inst->getBase()->getDataType());
        SLANG_ASSERT(baseStructType && "field_extract require base to be a struct.");
        auto fieldId = emitIntConstant(
            getStructFieldId(baseStructType, as<IRStructKey>(inst->getField())),
            builder.getIntType());
        
        return emitInst(
            parent,
            inst,
            SpvOpCompositeExtract,
            inst->getDataType(),
            kResultID,
            inst->getBase(),
            fieldId);
    }

    SpvInst* emitGetElementPtr(SpvInstParent* parent, IRGetElementPtr* inst)
    {
        auto base = inst->getBase();
        SpvWord baseId = 0;
        IRArrayType* baseArrayType = nullptr;
        // Only used in debug build, but we don't want a warning/error for an unused initialized variable
        SLANG_UNUSED(baseArrayType);

        if (auto ptrLikeType = as<IRPointerLikeType>(base->getDataType()))
        {
            baseArrayType = as<IRArrayType>(ptrLikeType->getElementType());
            baseId = getID(ensureInst(base));
        }
        else if (auto ptrType = as<IRPtrTypeBase>(base->getDataType()))
        {
            baseArrayType = as<IRArrayType>(ptrType->getValueType());
            baseId = getID(ensureInst(base));
        }
        else
        {
            SLANG_ASSERT(!"invalid IR: base of getElementPtr must be a pointer.");
        }
        SLANG_ASSERT(baseArrayType && "getElementPtr require base to be an array.");
        return emitInst(
            parent,
            inst,
            SpvOpAccessChain,
            inst->getFullType(),
            kResultID,
            baseId,
            inst->getIndex());
    }

    SpvInst* emitGetElement(SpvInstParent* parent, IRGetElement* inst)
    {
        auto base = inst->getBase();
        SpvWord baseId = 0;
        IRArrayType* baseArrayType = nullptr;
        // Only used in debug build, but we don't want a warning/error for an unused initialized variable
        SLANG_UNUSED(baseArrayType);

        if (auto ptrLikeType = as<IRPointerLikeType>(base->getDataType()))
        {
            baseArrayType = as<IRArrayType>(ptrLikeType->getElementType());
            baseId = getID(ensureInst(base));
        }
        else if (auto ptrType = as<IRPtrTypeBase>(base->getDataType()))
        {
            baseArrayType = as<IRArrayType>(ptrType->getValueType());
            baseId = getID(ensureInst(base));
        }
        else
        {
            SLANG_ASSERT(!"invalid IR: base of getElement must be a pointer.");
        }
        SLANG_ASSERT(baseArrayType && "getElement require base to be an array.");

        IRBuilder builder(m_sharedIRBuilder);
        builder.setInsertBefore(inst);

        auto ptr = emitInst(
            parent,
            nullptr,
            SpvOpAccessChain,
            builder.getPtrType(inst->getFullType()),
            kResultID,
            baseId,
            inst->getIndex());
        return emitInst(parent, inst, SpvOpLoad, inst->getFullType(), kResultID, ptr);
    }

    SpvInst* emitLoad(SpvInstParent* parent, IRLoad* inst)
    {
        return emitInst(parent, inst, SpvOpLoad, inst->getDataType(), kResultID, inst->getPtr());
    }

    SpvInst* emitStore(SpvInstParent* parent, IRStore* inst)
    {
        return emitInst(parent, inst, SpvOpStore, inst->getPtr(), inst->getVal());
    }

    SpvInst* emitSwizzle(SpvInstParent* parent, IRSwizzle* inst)
    {
        if (inst->getElementCount() == 1)
        {
            return emitInst(
                parent,
                inst,
                SpvOpCompositeExtract,
                inst->getDataType(),
                kResultID,
                inst->getBase(),
                (SpvWord)as<IRIntLit>(inst->getElementIndex(0))->getValue());
        }
        else
        {
            return emitInstCustomOperandFunc(parent, inst, SpvOpVectorShuffle, [&]() {
                emitOperand(inst->getDataType());
                emitOperand(kResultID);
                emitOperand(inst->getBase());
                emitOperand(inst->getBase());
                for (UInt i = 0; i < inst->getElementCount(); i++)
                {
                    auto index = as<IRIntLit>(inst->getElementIndex(i));
                    emitOperand((SpvWord)index->getValue());
                }
            });
        }
    }

    SpvInst* emitConstruct(SpvInstParent* parent, IRInst* inst)
    {
        if (as<IRBasicType>(inst->getDataType()))
        {
            if (inst->getOperandCount() == 1)
            {
                if (inst->getDataType() == inst->getOperand(0)->getDataType())
                    return emitInst(
                        parent,
                        inst,
                        SpvOpCopyObject,
                        inst->getFullType(),
                        kResultID,
                        inst->getOperand(0));
                else
                    return emitInst(
                        parent,
                        inst,
                        SpvOpBitcast,
                        inst->getFullType(),
                        kResultID,
                        inst->getOperand(0));
            }
            else
            {
                SLANG_ASSERT(!"spirv emit: unsupported Construct inst.");
                return nullptr;
            }
        }
        else
        {
            return emitInst(
                parent,
                inst,
                SpvOpCompositeConstruct,
                inst->getDataType(),
                kResultID,
                OperandsOf(inst));
        }
    }

    bool isSignedType(IRType* type)
    {
        switch (type->getOp())
        {
        case kIROp_FloatType:
        case kIROp_DoubleType:
            return true;
        case kIROp_IntType:
        case kIROp_Int16Type:
        case kIROp_Int64Type:
        case kIROp_Int8Type:
            return true;
        case kIROp_VectorType:
            return isSignedType(as<IRVectorType>(type)->getElementType());
        case kIROp_MatrixType:
            return isSignedType(as<IRMatrixType>(type)->getElementType());
        default:
            return false;
        }
    }

    bool isFloatType(IRInst* type)
    {
        switch (type->getOp())
        {
        case kIROp_FloatType:
        case kIROp_DoubleType:
        case kIROp_HalfType:
            return true;
        case kIROp_VectorType:
            return isFloatType(as<IRVectorType>(type)->getElementType());
        case kIROp_MatrixType:
            return isFloatType(as<IRMatrixType>(type)->getElementType());
        default:
            return false;
        }
    }

    SpvInst* emitArithmetic(SpvInstParent* parent, IRInst* inst)
    {
        IRType* elementType = inst->getOperand(0)->getDataType();
        if (auto vectorType = as<IRVectorType>(inst->getDataType()))
        {
            elementType = vectorType->getElementType();
        }
        else if (auto matrixType = as<IRMatrixType>(inst->getDataType()))
        {
            //TODO: implement.
            SLANG_ASSERT(!"unimplemented: matrix arithemetic");
        }
        IRBasicType* basicType = as<IRBasicType>(elementType);
        bool isFloatingPoint = false;
        bool isBool = false;
        switch (basicType->getBaseType())
        {
        case BaseType::Float:
        case BaseType::Double:
            isFloatingPoint = true;
            break;
        case BaseType::Bool:
            isBool = true;
            break;
        default:
            break;
        }
        SpvOp opCode = SpvOpUndef;
        bool isSigned = isSignedType(basicType);
        switch (inst->getOp())
        {
        case kIROp_Add:
            opCode = isFloatingPoint ? SpvOpFAdd : SpvOpIAdd;
            break;
        case kIROp_Sub:
            opCode = isFloatingPoint ? SpvOpFSub : SpvOpISub;
            break;
        case kIROp_Mul:
            opCode = isFloatingPoint ? SpvOpFMul : SpvOpIMul;
            break;
        case kIROp_Div:
            opCode = isFloatingPoint ? SpvOpFDiv : isSigned ? SpvOpSDiv : SpvOpUDiv;
            break;
        case kIROp_IRem:
            opCode = isSigned ? SpvOpSRem : SpvOpUMod;
            break;
        case kIROp_FRem:
            opCode = SpvOpFRem;
            break;
        case kIROp_Less:
            opCode = isFloatingPoint ? SpvOpFOrdLessThan
                                     : isSigned ? SpvOpSLessThan : SpvOpULessThan;
            break;
        case kIROp_Leq:
            opCode = isFloatingPoint ? SpvOpFOrdLessThanEqual
                                     : isSigned ? SpvOpSLessThanEqual : SpvOpULessThanEqual;
            break;
        case kIROp_Eql:
            opCode = isFloatingPoint ? SpvOpFOrdEqual : isBool ? SpvOpLogicalEqual : SpvOpIEqual;
            break;
        case kIROp_Neq:
            opCode = isFloatingPoint ? SpvOpFOrdNotEqual
                                     : isBool ? SpvOpLogicalNotEqual : SpvOpINotEqual;
            break;
        case kIROp_Geq:
            opCode = isFloatingPoint ? SpvOpFOrdGreaterThanEqual
                                     : isSigned ? SpvOpSGreaterThanEqual : SpvOpUGreaterThanEqual;
            break;
        case kIROp_Greater:
            opCode = isFloatingPoint ? SpvOpFOrdGreaterThan
                                     : isSigned ? SpvOpSGreaterThan : SpvOpUGreaterThan;
            break;
        case kIROp_Neg:
            opCode = isFloatingPoint ? SpvOpFNegate : SpvOpSNegate;
            break;
        case kIROp_And:
            opCode = SpvOpLogicalAnd;
            break;
        case kIROp_Or:
            opCode = SpvOpLogicalOr;
            break;
        case kIROp_Not:
            opCode = SpvOpLogicalNot;
            break;
        case kIROp_BitAnd:
            opCode = SpvOpBitwiseAnd;
            break;
        case kIROp_BitOr:
            opCode = SpvOpBitwiseOr;
            break;
        case kIROp_BitXor:
            opCode = SpvOpBitwiseXor;
            break;
        case kIROp_BitNot:
            opCode = SpvOpBitReverse;
            break;
        case kIROp_Rsh:
            opCode = isSigned ? SpvOpShiftRightArithmetic : SpvOpShiftRightLogical;
            break;
        case kIROp_Lsh:
            opCode = SpvOpShiftLeftLogical;
            break;
        default:
            SLANG_ASSERT(!"unknown arithmetic opcode");
            break;
        }
        return emitInst(parent, inst, opCode, inst->getDataType(), kResultID, OperandsOf(inst));
    }

    OrderedHashSet<SpvCapability> m_capabilities;

    void requireSPIRVCapability(SpvCapability capability)
    {
        if (m_capabilities.Add(capability))
        {
            emitInst(
                getSection(SpvLogicalSectionID::Capabilities),
                nullptr,
                SpvOpCapability,
                capability);
        }
    }

    void handleRequiredCapabilitiesImpl(IRInst* inst)
    {
        // TODO: declare required SPV capabilities.

        for (auto decoration : inst->getDecorations())
        {
            switch (decoration->getOp())
            {
            default:
                break;

            case kIROp_RequireGLSLExtensionDecoration:
                {
                    break;
                }
            case kIROp_RequireGLSLVersionDecoration:
                {
                    break;
                }
            case kIROp_RequireSPIRVVersionDecoration:
                {
                    break;
                }
            }
        }
    }

    void diagnoseUnhandledInst(IRInst* inst)
    {
        m_sink->diagnose(
            inst, Diagnostics::unimplemented, "unexpected IR opcode during code emit");
    }

    SPIRVEmitContext(IRModule* module, TargetRequest* target, DiagnosticSink* sink)
        : SPIRVEmitSharedContext(module, target)
        , m_irModule(module)
        , m_sink(sink)
        , m_memoryArena(2048)
    {
    }
};

SlangResult emitSPIRVFromIR(
    CodeGenContext*         codeGenContext,
    IRModule*               irModule,
    const List<IRFunc*>&    irEntryPoints,
    List<uint8_t>&          spirvOut)
{
    spirvOut.clear();

    auto targetRequest = codeGenContext->getTargetReq();
    auto sink = codeGenContext->getSink();

    SPIRVEmitContext context(irModule, targetRequest, sink);
    legalizeIRForSPIRV(&context, irModule, irEntryPoints, sink);

    context.emitFrontMatter();
    for (auto irEntryPoint : irEntryPoints)
    {
        context.ensureInst(irEntryPoint);
    }
    context.emitPhysicalLayout();

    spirvOut.addRange(
        (uint8_t const*) context.m_words.getBuffer(),
        context.m_words.getCount() * sizeof(context.m_words[0]));

    return SLANG_OK;
}


} // namespace Slang
back to top