https://github.com/shader-slang/slang
Raw File
Tip revision: d0c9571be3a2167a9f019310aca8f7cd326972c0 authored by jsmall-nvidia on 22 June 2018, 17:09:01 UTC
Expose macros/functionality for defining interfaces (#604)
Tip revision: d0c9571
ir.cpp
// ir.cpp
#include "ir.h"
#include "ir-insts.h"

#include "../core/basic.h"
#include "mangle.h"

namespace Slang
{
    struct IRSpecContext;

    IRGlobalValue* cloneGlobalValueWithMangledName(
        IRSpecContext*  context,
        Name*   mangledName,
        IRGlobalValue*  originalVal);

    struct IROpMapEntry
    {
        IROp        op;
        IROpInfo    info;
    };

    // TODO: We should ideally be speeding up the name->inst
    // mapping by using a dictionary, or even by pre-computing
    // a hash table to be stored as a `static const` array.
    static const IROpMapEntry kIROps[] =
    {
        { kIROp_Invalid, { "invalid", 0, 0 } },

#define INST(ID, MNEMONIC, ARG_COUNT, FLAGS)  \
    { kIROp_##ID, { #MNEMONIC, ARG_COUNT, FLAGS, } },
#define PSEUDO_INST(ID)  \
    { kIRPseudoOp_##ID, { #ID, 0, 0 } },
#include "ir-inst-defs.h"

    };

    //

    IROp findIROp(char const* name)
    {
        for (auto ee : kIROps)
        {
            if (strcmp(name, ee.info.name) == 0)
                return ee.op;
        }

        return IROp(kIROp_Invalid);
    }

    IROpInfo getIROpInfo(IROp op)
    {
        for (auto ee : kIROps)
        {
            if (ee.op == op)
                return ee.info;
        }

        return kIROps[0].info;
    }

    //

    void IRUse::debugValidate()
    {
#ifdef _DEBUG
        auto uv = this->usedValue;
        if(!uv)
        {
            assert(!nextUse);
            assert(!prevLink);
            return;
        }

        auto pp = &uv->firstUse;
        for(auto u = uv->firstUse; u;)
        {
            assert(u->prevLink == pp);

            pp = &u->nextUse;
            u = u->nextUse;
        }
#endif
    }

    void IRUse::init(IRInst* u, IRInst* v)
    {
        clear();

        user = u;
        usedValue = v;
        if(v)
        {
            nextUse = v->firstUse;
            prevLink = &v->firstUse;

            if(nextUse)
            {
                nextUse->prevLink = &this->nextUse;
            }

            v->firstUse = this;
        }

        debugValidate();
    }

    void IRUse::set(IRInst* uv)
    {
        init(user, uv);
    }

    void IRUse::clear()
    {
        // This `IRUse` is part of the linked list
        // of uses for  `usedValue`.

        debugValidate();

        if (usedValue)
        {
            auto uv = usedValue;

            *prevLink = nextUse;
            if(nextUse)
            {
                nextUse->prevLink = prevLink;
            }

            user        = nullptr;
            usedValue   = nullptr;
            nextUse     = nullptr;
            prevLink    = nullptr;

            if(uv->firstUse)
                uv->firstUse->debugValidate();
        }
    }

    //

    IRUse* IRInst::getOperands()
    {
        // We assume that *all* instructions are laid out
        // in memory such that their arguments come right
        // after the first `sizeof(IRInst)` bytes.
        //
        // TODO: we probably need to be careful and make
        // this more robust.

        return (IRUse*)(this + 1);
    }

    IRDecoration* IRInst::findDecorationImpl(IRDecorationOp decorationOp)
    {
        for( auto dd = firstDecoration; dd; dd = dd->next )
        {
            if(dd->op == decorationOp)
                return dd;
        }
        return nullptr;
    }

    // IRConstant

    IRIntegerValue GetIntVal(IRInst* inst)
    {
        switch (inst->op)
        {
        default:
            SLANG_UNEXPECTED("needed a known integer value");
            UNREACHABLE_RETURN(0);

        case kIROp_IntLit:
            return ((IRConstant*)inst)->u.intVal;
            break;
        }
    }

    // IRParam

    IRParam* IRParam::getNextParam()
    {
        return as<IRParam>(getNextInst());
    }

    // IRArrayTypeBase

    IRInst* IRArrayTypeBase::getElementCount()
    {
        if (auto arrayType = as<IRArrayType>(this))
            return arrayType->getElementCount();

        return nullptr;
    }


    // IRBlock

    IRParam* IRBlock::getLastParam()
    {
        IRParam* param = getFirstParam();
        if (!param) return nullptr;

        while (auto nextParam = param->getNextParam())
            param = nextParam;

        return param;
    }

    void IRBlock::addParam(IRParam* param)
    {
        auto lastParam = getLastParam();
        if (lastParam)
        {
            param->insertAfter(lastParam);
        }
        else
        {
            param->insertAtStart(this);
        }
    }

    IRInst* IRBlock::getFirstOrdinaryInst()
    {
        // Find the last parameter (if any) of the block
        auto lastParam = getLastParam();
        if (lastParam)
        {
            // If there is a last parameter, then the
            // instructions after it are the ordinary
            // instructions.
            return lastParam->getNextInst();
        }
        else
        {
            // If there isn't a last parameter, then
            // there must not have been *any* parameters,
            // and so the first instruction in the block
            // is also the first ordinary one.
            return getFirstInst();
        }
    }

    IRInst* IRBlock::getLastOrdinaryInst()
    {
        // Under normal circumstances, the last instruction
        // in the block is also the last ordinary instruction.
        // However, there is the special case of a block with
        // only parameters (which might happen as a temporary
        // state while we are building IR).
        auto inst = getLastInst();

        // If the last instruction is a parameter, then
        // there are no ordinary instructions, so the last
        // one is a null pointer.
        if (as<IRParam>(inst))
            return nullptr;

        // Otherwise the last instruction is the last "ordinary"
        // instruction as well.
        return inst;
    }


    // The predecessors of a block should all show up as users
    // of its value, so rather than explicitly store the CFG,
    // we will recover it on demand from the use-def information.
    //
    // Note: we are really iterating over incoming/outgoing *edges*
    // for a block, because there might be multiple uses of a block,
    // if more than one way of an N-way branch targets the same block.

    // Get the list of successor blocks for an instruction,
    // which we expect to be the last instruction in a block.
    static IRBlock::SuccessorList getSuccessors(IRInst* terminator)
    {
        // If the block somehow isn't terminated, then
        // there is no way to read its successors, so
        // we return an empty list.
        if (!terminator || !as<IRTerminatorInst>(terminator))
            return IRBlock::SuccessorList(nullptr, nullptr);

        // Otherwise, based on the opcode of the terminator
        // instruction, we will build up our list of uses.
        IRUse* begin = nullptr;
        IRUse* end = nullptr;
        UInt stride = 1;

        auto operands = terminator->getOperands();
        switch (terminator->op)
        {
        case kIROp_ReturnVal:
        case kIROp_ReturnVoid:
        case kIROp_unreachable:
        case kIROp_discard:
            break;

        case kIROp_unconditionalBranch:
        case kIROp_loop:
            // unconditonalBranch <block>
            begin = operands + 0;
            end = begin + 1;
            break;

        case kIROp_conditionalBranch:
        case kIROp_ifElse:
            // conditionalBranch <condition> <trueBlock> <falseBlock>
            begin = operands + 1;
            end = begin + 2;
            break;

        case kIROp_switch:
            // switch <val> <break> <default> <caseVal1> <caseBlock1> ...
            begin = operands + 2;

            // TODO: this ends up point one *after* the "one after the end"
            // location, so we should really change the representation
            // so that we don't need to form this pointer...
            end = operands + terminator->getOperandCount() + 1;
            stride = 2;
            break;

        default:
            SLANG_UNEXPECTED("unhandled terminator instruction");
            UNREACHABLE_RETURN(IRBlock::SuccessorList(nullptr, nullptr));
        }

        return IRBlock::SuccessorList(begin, end, stride);
    }

    static IRUse* adjustPredecessorUse(IRUse* use)
    {
        // We will search until we either find a
        // suitable use, or run out of uses.
        for (;use; use = use->nextUse)
        {
            // We only want to deal with uses that represent
            // a "sucessor" operand to some terminator instruction.
            // We will re-use the logic for getting the successor
            // list from such an instruction.

            auto successorList = getSuccessors((IRInst*) use->getUser());

            if(use >= successorList.begin_
                && use < successorList.end_)
            {
                UInt index = (use - successorList.begin_);
                if ((index % successorList.stride) == 0)
                {
                    // This use is in the range of the sucessor list,
                    // and so it represents a real edge between
                    // blocks.
                    return use;
                }
            }
        }

        // If we ran out of uses, then we are at the end
        // of the list of incoming edges.
        return nullptr;
    }

    IRBlock::PredecessorList IRBlock::getPredecessors()
    {
        // We want to iterate over the predecessors of this block.
        // First, we resign ourselves to iterating over the
        // incoming edges, rather than the blocks themselves.
        // This might sound like a trival distinction, but it is
        // possible for there to be multiple edges between two
        // blocks (as for a `switch` with multiple cases that
        // map to the same code). Any client that wants just
        // the unique predecessor blocks needs to deal with
        // the deduplication themselves.
        //
        // Next, we note that for any predecessor edge, there will
        // be a use of this block in the terminator instruction of
        // the predecessor. We basically just want to iterate over
        // the users of this block, then, but we need to be careful
        // to rule out anything that doesn't actually represent
        // an edge. The `adjustPredecessorUse` function will be
        // used to search for a use that actually represents an edge.

        return PredecessorList(
            adjustPredecessorUse(firstUse));
    }

    UInt IRBlock::PredecessorList::getCount()
    {
        UInt count = 0;
        for (auto ii : *this)
        {
            (void)ii;
            count++;
        }
        return count;
    }

    void IRBlock::PredecessorList::Iterator::operator++()
    {
        if (!use) return;
        use = adjustPredecessorUse(use->nextUse);
    }

    IRBlock* IRBlock::PredecessorList::Iterator::operator*()
    {
        if (!use) return nullptr;
        return (IRBlock*)use->getUser()->parent;
    }

    IRBlock::SuccessorList IRBlock::getSuccessors()
    {
        // The successors of a block will all be listed
        // as operands of its terminator instruction.
        // Depending on the terminator, we might have
        // different numbers of operands to deal with.
        //
        // (We might also have to deal with a "stride"
        // in the case where the basic-block operands
        // are mixed up with non-block operands)

        auto terminator = getLastInst();
        return Slang::getSuccessors(terminator);
    }

    UInt IRBlock::SuccessorList::getCount()
    {
        UInt count = 0;
        for (auto ii : *this)
        {
            (void)ii;
            count++;
        }
        return count;
    }

    void IRBlock::SuccessorList::Iterator::operator++()
    {
        use += stride;
    }

    IRBlock* IRBlock::SuccessorList::Iterator::operator*()
    {
        return (IRBlock*)use->get();
    }

    IRParam* IRGlobalValueWithParams::getFirstParam()
    {
        auto entryBlock = getFirstBlock();
        if(!entryBlock) return nullptr;

        return entryBlock->getFirstParam();
    }

    // IRFunc

    IRType* IRFunc::getResultType() { return getDataType()->getResultType(); }
    UInt IRFunc::getParamCount() { return getDataType()->getParamCount(); }
    IRType* IRFunc::getParamType(UInt index) { return getDataType()->getParamType(index); }

    void IRGlobalValueWithCode::addBlock(IRBlock* block)
    {
        block->insertAtEnd(this);
    }

    //

    bool isTerminatorInst(IROp op)
    {
        switch (op)
        {
        default:
            return false;

        case kIROp_ReturnVal:
        case kIROp_ReturnVoid:
        case kIROp_unconditionalBranch:
        case kIROp_conditionalBranch:
        case kIROp_loop:
        case kIROp_ifElse:
        case kIROp_discard:
        case kIROp_switch:
        case kIROp_unreachable:
            return true;
        }
    }

    bool isTerminatorInst(IRInst* inst)
    {
        if (!inst) return false;
        return isTerminatorInst(inst->op);
    }

    //

    IRBlock* IRBuilder::getBlock()
    {
        return as<IRBlock>(insertIntoParent);
    }

    // Get the current function (or other value with code)
    // that we are inserting into (if any).
    IRGlobalValueWithCode* IRBuilder::getFunc()
    {
        auto pp = insertIntoParent;
        if (auto block = as<IRBlock>(pp))
        {
            pp = pp->getParent();
        }
        return as<IRGlobalValueWithCode>(pp);
    }


    void IRBuilder::setInsertInto(IRParentInst* insertInto)
    {
        insertIntoParent = insertInto;
        insertBeforeInst = nullptr;
    }

    void IRBuilder::setInsertBefore(IRInst* insertBefore)
    {
        SLANG_ASSERT(insertBefore);
        insertIntoParent = insertBefore->parent;
        insertBeforeInst = insertBefore;
    }


    // Add an instruction into the current scope
    void IRBuilder::addInst(
        IRInst*     inst)
    {
        if(insertBeforeInst)
        {
            inst->insertBefore(insertBeforeInst);
        }
        else if (insertIntoParent)
        {
            inst->insertAtEnd(insertIntoParent);
        }
        else
        {
            // Don't append the instruction anywhere
        }
    }

    // Given two parent instructions, pick the better one to use as as
    // insertion location for a "hoistable" instruction.
    //
    IRParentInst* mergeCandidateParentsForHoistableInst(IRParentInst* left, IRParentInst* right)
    {
        // If the candidates are both the same, then who cares?
        if(left == right) return left;

        // If either `left` or `right` is a block, then we need to be
        // a bit careful, because blocks can see other values just using
        // the dominance relationship, without a direct parent-child relationship.
        //
        // First, check if each of `left` and `right` is a block.
        //
        auto leftBlock = as<IRBlock>(left);
        auto rightBlock = as<IRBlock>(right);
        //
        // As a special case, if both of these are blocks in the same parent,
        // then we need to pick between them based on dominance.
        //
        if (leftBlock && rightBlock && (leftBlock->getParent() == rightBlock->getParent()))
        {
            // We assume that the order of basic blocks in a function is compatible
            // with the dominance relationship (that is, if A dominates B, then
            // A comes before B in the list of blocks), so it suffices to pick
            // the *later* of the two blocks.
            //
            // There are ways we could try to speed up this search, but no matter
            // what it will be O(n) in the number of blocks, unless we build
            // an explicit dominator tree, which is infeasible during IR building.
            // Thus we just do a simple linear walk here.
            //
            // We will start at `leftBlock` and walk forward, until either...
            //
            for (auto ll = leftBlock; ll; ll = ll->getNextBlock())
            {
                // ... we see `rightBlock` (in which case `rightBlock` came later), or ...
                //
                if (ll == rightBlock) return rightBlock;
            }
            //
            // ... we run out of blocks (in which case `leftBlock` came later).
            //
            return leftBlock;
        }

        //
        // If the special case above doesn't apply, then `left` or `right` might
        // still be a block, but they aren't blocks nested in the same function.
        // We will find the first non-block ancestor of `left` and/or `right`.
        // This will either be the inst itself (it is isn't a block), or
        // its immediate parent (if it *is* a block).
        //
        auto leftNonBlock = leftBlock ? leftBlock->getParent() : left;
        auto rightNonBlock = rightBlock ? rightBlock->getParent() : right;

        // If either side is null, then take the non-null one.
        //
        if (!leftNonBlock) return right;
        if (!rightNonBlock) return left;

        // If the non-block on the left or right is a descendent of
        // the other, then that is what we should use.
        //
        IRParentInst* parentNonBlock = nullptr;
        for (auto ll = leftNonBlock; ll; ll = ll->getParent())
        {
            if (ll == rightNonBlock)
            {
                parentNonBlock = leftNonBlock;
                break;
            }
        }
        for (auto rr = rightNonBlock; rr; rr = rr->getParent())
        {
            if (rr == leftNonBlock)
            {
                SLANG_ASSERT(!parentNonBlock || parentNonBlock == leftNonBlock);
                parentNonBlock = rightNonBlock;
                break;
            }
        }

        // As a matter of validity in the IR, we expect one
        // of the two to be an ancestor (in the non-block case),
        // because otherwise we'd be violating the basic dominance
        // assumptions.
        //
        SLANG_ASSERT(parentNonBlock);

        // As a fallback, try to use the left parent as a default
        // in case things go badly.
        //
        if (!parentNonBlock)
        {
            parentNonBlock = leftNonBlock;
        }

        IRParentInst* parent = parentNonBlock;

        // At this point we've found a non-block parent where we
        // could stick things, but we have to fix things up in
        // case we should be inserting into a block beneath
        // that non-block parent.
        if (leftBlock && (parentNonBlock == leftNonBlock))
        {
            // We have a left block, and have picked its parent.

            // It cannot be the case that there is a right block
            // with the same parent, or else our special case
            // would have triggered at the start.
            SLANG_ASSERT(!rightBlock || (parentNonBlock != rightNonBlock));

            parent = leftBlock;
        }
        else if (rightBlock && (parentNonBlock == rightNonBlock))
        {
            // We have a right block, and have picked its parent.

            // We already tested above, so we know there isn't a
            // matching situation on the left side.

            parent = rightBlock;
        }

        // Okay, we've picked the parent we want to insert into,
        // *but* one last special case arises, because an `IRGlobalValueWithCode`
        // is not actually a suitable place to insert instructions.
        // Furthermore, there is no actual need to insert instructions at
        // that scope, because any parameters, etc. are actually attached
        // to the block(s) within the function.
        if (auto parentFunc = as<IRGlobalValueWithCode>(parent))
        {
            // Insert in the parent of the function (or other value with code).
            // We know that the parent must be able to hold ordinary instructions,
            // because it was able to hold this `IRGlobalValueWithCode`
            parent = parentFunc->getParent();
        }

        return parent;
    }

    // Given an instruction that represents a constant, a type, etc.
    // Try to "hoist" it as far toward the global scope as possible
    // to insert it at a location where it will be maximally visible.
    //
    void addHoistableInst(
        IRBuilder*  builder,
        IRInst*     inst)
    {
        // Start with the assumption that we would insert this instruction
        // into the global scope (the instruction that represents the module)
        IRParentInst* parent = builder->getModule()->getModuleInst();

        // The above decision might be invalid, because there might be
        // one or more operands of the instruction that are defined in
        // more deeply nested parents than the global scope.
        //
        // Therefore, we will scan the operands of the instruction, and
        // look at the parents that define them.
        //
        UInt operandCount = inst->getOperandCount();
        for (UInt ii = 0; ii < operandCount; ++ii)
        {
            auto operand = inst->getOperand(ii);
            if (!operand)
                continue;

            auto operandParent = operand->getParent();

            parent = mergeCandidateParentsForHoistableInst(parent, operandParent);
        }

        // We better have ended up with a place to insert.
        SLANG_ASSERT(parent);

        // If we have chosen to insert into the same parent that the
        // IRBuilder is configured to use, then respect its `insertBeforeInst`
        // setting.
        if (parent == builder->insertIntoParent)
        {
            builder->addInst(inst);
            return;
        }

        // Otherwise, we just want to insert at the end of the chosen parent.
        //
        // TODO: be careful about inserting after the terminator of a block...

        inst->insertAtEnd(parent);
    }

    static void maybeSetSourceLoc(
        IRBuilder*  builder,
        IRInst*     value)
    {
        if(!builder)
            return;

        auto sourceLocInfo = builder->sourceLocInfo;
        if(!sourceLocInfo)
            return;

        // Try to find something with usable location info
        for(;;)
        {
            if(sourceLocInfo->sourceLoc.getRaw())
                break;

            if(!sourceLocInfo->next)
                break;

            sourceLocInfo = sourceLocInfo->next;
        }

        value->sourceLoc = sourceLocInfo->sourceLoc;
    }

    // Create an IR instruction/value and initialize it.
    //
    // In this case `argCount` and `args` represnt the
    // arguments *after* the type (which is a mandatory
    // argument for all instructions).
    template<typename T>
    static T* createInstImpl(
        IRModule*       module,
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            fixedArgCount,
        IRInst* const* fixedArgs,
        UInt                    varArgListCount,
        UInt const*             listArgCounts,
        IRInst* const* const*   listArgs)
    {
        UInt varArgCount = 0;
        for (UInt ii = 0; ii < varArgListCount; ++ii)
        {
            varArgCount += listArgCounts[ii];
        }

        UInt size = sizeof(IRInst) + (fixedArgCount + varArgCount) * sizeof(IRUse);
        if (sizeof(T) > size)
        {
            size = sizeof(T);
        }

        SLANG_ASSERT(module);
        T* inst = (T*)module->memoryPool.allocZero(size);
        new(inst)T();


        inst->operandCount = (uint32_t)(fixedArgCount + varArgCount);

        inst->op = op;

        if (type)
        {
            inst->typeUse.init(inst, type);
        }

        maybeSetSourceLoc(builder, inst);

        auto operand = inst->getOperands();

        for( UInt aa = 0; aa < fixedArgCount; ++aa )
        {
            if (fixedArgs)
            {
                operand->init(inst, fixedArgs[aa]);
            }
            operand++;
        }

        for (UInt ii = 0; ii < varArgListCount; ++ii)
        {
            UInt listArgCount = listArgCounts[ii];
            for (UInt jj = 0; jj < listArgCount; ++jj)
            {
                if (listArgs[ii])
                {
                    operand->init(inst, listArgs[ii][jj]);
                }
                else
                {
                    operand->init(inst, nullptr);
                }
                operand++;
            }
        }
        module->irObjectsToFree.Add(inst);
        return inst;
    }

    template<typename T>
    static T* createInstImpl(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            fixedArgCount,
        IRInst* const* fixedArgs,
        UInt           varArgCount = 0,
        IRInst* const* varArgs = nullptr)
    {
        return createInstImpl<T>(
            builder->getModule(),
            builder,
            op,
            type,
            fixedArgCount,
            fixedArgs,
            1,
            &varArgCount,
            &varArgs);
    }

    template<typename T>
    static T* createInstImpl(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            fixedArgCount,
        IRInst* const*  fixedArgs,
        UInt                    varArgListCount,
        UInt const*             listArgCount,
        IRInst* const* const*   listArgs)
    {
        return createInstImpl<T>(
            builder->getModule(),
            builder,
            op,
            type,
            fixedArgCount,
            fixedArgs,
            varArgListCount,
            listArgCount,
            listArgs);
    }

    template<typename T>
    static T* createInst(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return createInstImpl<T>(
            builder,
            op,
            type,
            argCount,
            args);
    }

    template<typename T>
    static T* createInst(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type)
    {
        return createInstImpl<T>(
            builder,
            op,
            type,
            0,
            nullptr);
    }

    template<typename T>
    static T* createInst(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        IRInst*         arg)
    {
        return createInstImpl<T>(
            builder,
            op,
            type,
            1,
            &arg);
    }

    template<typename T>
    static T* createInst(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        IRInst*         arg1,
        IRInst*         arg2)
    {
        IRInst* args[] = { arg1, arg2 };
        return createInstImpl<T>(
            builder,
            op,
            type,
            2,
            &args[0]);
    }

    template<typename T>
    static T* createInstWithTrailingArgs(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return createInstImpl<T>(
            builder,
            op,
            type,
            argCount,
            args);
    }

    template<typename T>
    static T* createInstWithTrailingArgs(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            fixedArgCount,
        IRInst* const*  fixedArgs,
        UInt            varArgCount,
        IRInst* const*  varArgs)
    {
        return createInstImpl<T>(
            builder,
            op,
            type,
            fixedArgCount,
            fixedArgs,
            varArgCount,
            varArgs);
    }

    template<typename T>
    static T* createInstWithTrailingArgs(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        IRInst*         arg1,
        UInt            varArgCount,
        IRInst* const*  varArgs)
    {
        IRInst* fixedArgs[] = { arg1 };
        UInt fixedArgCount = sizeof(fixedArgs) / sizeof(fixedArgs[0]);

        return createInstImpl<T>(
            builder,
            op,
            type,
            fixedArgCount,
            fixedArgs,
            varArgCount,
            varArgs);
    }
    //

    bool operator==(IRInstKey const& left, IRInstKey const& right)
    {
        if(left.inst->op != right.inst->op) return false;
        if(left.inst->getFullType() != right.inst->getFullType()) return false;
        if(left.inst->operandCount != right.inst->operandCount) return false;

        auto argCount = left.inst->operandCount;
        auto leftArgs = left.inst->getOperands();
        auto rightArgs = right.inst->getOperands();
        for( UInt aa = 0; aa < argCount; ++aa )
        {
            if(leftArgs[aa].get() != rightArgs[aa].get())
                return false;
        }

        return true;
    }

    int IRInstKey::GetHashCode()
    {
        auto code = Slang::GetHashCode(inst->op);
        code = combineHash(code, Slang::GetHashCode(inst->getFullType()));
        code = combineHash(code, Slang::GetHashCode(inst->getOperandCount()));

        auto argCount = inst->getOperandCount();
        auto args = inst->getOperands();
        for( UInt aa = 0; aa < argCount; ++aa )
        {
            code = combineHash(code, Slang::GetHashCode(args[aa].get()));
        }
        return code;
    }

    //

    bool operator==(IRConstantKey const& left, IRConstantKey const& right)
    {
        if(left.inst->op            != right.inst->op)              return false;
        if(left.inst->getFullType() != right.inst->getFullType())       return false;
        if(left.inst->u.ptrData[0]  != right.inst->u.ptrData[0])    return false;
        if(left.inst->u.ptrData[1]  != right.inst->u.ptrData[1])    return false;
        return true;
    }

    int IRConstantKey::GetHashCode()
    {
        auto code = Slang::GetHashCode(inst->op);
        code = combineHash(code, Slang::GetHashCode(inst->getFullType()));
        code = combineHash(code, Slang::GetHashCode(inst->u.ptrData[0]));
        code = combineHash(code, Slang::GetHashCode(inst->u.ptrData[1]));
        return code;
    }

    static IRConstant* findOrEmitConstant(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        UInt            valueSize,
        void const*     value)
    {
        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        keyInst.op = op;
        keyInst.typeUse.usedValue = type;
        memcpy(&keyInst.u, value, valueSize);

        IRConstantKey key;
        key.inst = &keyInst;

        IRConstant* irValue = nullptr;
        if( builder->sharedBuilder->constantMap.TryGetValue(key, irValue) )
        {
            // We found a match, so just use that.
            return irValue;
        }

        // We now know where we want to insert, but there might
        // already be an equivalent instruction in that block.
        //
        // We will check for such an instruction in a slightly hacky
        // way: we will construct a temporary instruction and
        // then use it to look up in a cache of instructions.

        irValue = createInst<IRConstant>(builder, op, type);
        memcpy(&irValue->u, value, valueSize);

        key.inst = irValue;
        builder->sharedBuilder->constantMap.Add(key, irValue);

        addHoistableInst(builder, irValue);

        return irValue;
    }


    //

    IRInst* IRBuilder::getBoolValue(bool inValue)
    {
        IRIntegerValue value = inValue;
        return findOrEmitConstant(
            this,
            kIROp_boolConst,
            getBoolType(),
            sizeof(value),
            &value);
    }

    IRInst* IRBuilder::getIntValue(IRType* type, IRIntegerValue value)
    {
        return findOrEmitConstant(
            this,
            kIROp_IntLit,
            type,
            sizeof(value),
            &value);
    }

    IRInst* IRBuilder::getFloatValue(IRType* type, IRFloatingPointValue value)
    {
        return findOrEmitConstant(
            this,
            kIROp_FloatLit,
            type,
            sizeof(value),
            &value);
    }

    IRInst* findOrEmitHoistableInst(
        IRBuilder*              builder,
        IRType*                 type,
        IROp                    op,
        UInt                    operandListCount,
        UInt const*             listOperandCounts,
        IRInst* const* const*   listOperands)
    {
        UInt operandCount = 0;
        for (UInt ii = 0; ii < operandListCount; ++ii)
        {
            operandCount += listOperandCounts[ii];
        }

        // We are going to create a dummy instruction on the stack,
        // which will be used as a key for lookup, so see if we
        // already have an equivalent instruction available to use.

        size_t keySize = sizeof(IRInst) + operandCount * sizeof(IRUse);
        IRInst* keyInst = (IRInst*) malloc(keySize);
        memset(keyInst, 0, keySize);

        new(keyInst) IRInst();
        keyInst->op = op;
        keyInst->typeUse.usedValue = type;
        keyInst->operandCount = (uint32_t) operandCount;

        IRUse* operand = keyInst->getOperands();
        for (UInt ii = 0; ii < operandListCount; ++ii)
        {
            UInt listOperandCount = listOperandCounts[ii];
            for (UInt jj = 0; jj < listOperandCount; ++jj)
            {
                operand->usedValue = listOperands[ii][jj];
                operand++;
            }
        }

        IRInstKey key;
        key.inst = keyInst;

        IRInst* foundInst = nullptr;
        bool found = builder->sharedBuilder->globalValueNumberingMap.TryGetValue(key, foundInst);

        free((void*)keyInst);

        if (found)
        {
            return foundInst;
        }

        // If no instruction was found, then we need to emit it.

        IRInst* inst = createInstImpl<IRInst>(
            builder,
            op,
            type,
            0,
            nullptr,
            operandListCount,
            listOperandCounts,
            listOperands);
        addHoistableInst(builder, inst);

        key.inst = inst;
        builder->sharedBuilder->globalValueNumberingMap.Add(key, inst);

        return inst;
    }

    IRInst* findOrEmitHoistableInst(
        IRBuilder*      builder,
        IRType*         type,
        IROp            op,
        UInt            operandCount,
        IRInst* const*  operands)
    {
        return findOrEmitHoistableInst(
            builder,
            type,
            op,
            1,
            &operandCount,
            &operands);
    }

    IRInst* findOrEmitHoistableInst(
        IRBuilder*      builder,
        IRType*         type,
        IROp            op,
        IRInst*         operand,
        UInt            operandCount,
        IRInst* const*  operands)
    {
        UInt counts[] = { 1, operandCount };
        IRInst* const* lists[] = { &operand, operands };

        return findOrEmitHoistableInst(
            builder,
            type,
            op,
            2,
            counts,
            lists);
    }


    IRType* IRBuilder::getType(
        IROp            op,
        UInt            operandCount,
        IRInst* const*  operands)
    {
        return (IRType*) findOrEmitHoistableInst(
            this,
            nullptr,
            op,
            operandCount,
            operands);
    }

    IRType* IRBuilder::getType(
        IROp            op)
    {
        return getType(op, 0, nullptr);
    }

    IRBasicType* IRBuilder::getBasicType(BaseType baseType)
    {
        return (IRBasicType*)getType(
            IROp((UInt)kIROp_FirstBasicType + (UInt)baseType));
    }

    IRBasicType* IRBuilder::getVoidType()
    {
        return (IRVoidType*)getType(kIROp_VoidType);
    }

    IRBasicType* IRBuilder::getBoolType()
    {
        return (IRBoolType*)getType(kIROp_BoolType);
    }

    IRBasicType* IRBuilder::getIntType()
    {
        return (IRBasicType*)getType(kIROp_IntType);
    }

    IRBasicBlockType*   IRBuilder::getBasicBlockType()
    {
        return (IRBasicBlockType*)getType(kIROp_BasicBlockType);
    }

    IRTypeKind* IRBuilder::getTypeKind()
    {
        return (IRTypeKind*)getType(kIROp_TypeKind);
    }

    IRGenericKind* IRBuilder::getGenericKind()
    {
        return (IRGenericKind*)getType(kIROp_GenericKind);
    }

    IRPtrType*  IRBuilder::getPtrType(IRType* valueType)
    {
        return (IRPtrType*) getPtrType(kIROp_PtrType, valueType);
    }

    IROutType* IRBuilder::getOutType(IRType* valueType)
    {
        return (IROutType*) getPtrType(kIROp_OutType, valueType);
    }

    IRInOutType* IRBuilder::getInOutType(IRType* valueType)
    {
        return (IRInOutType*) getPtrType(kIROp_InOutType, valueType);
    }

    IRRefType* IRBuilder::getRefType(IRType* valueType)
    {
        return (IRRefType*) getPtrType(kIROp_RefType, valueType);
    }

    IRPtrTypeBase* IRBuilder::getPtrType(IROp op, IRType* valueType)
    {
        IRInst* operands[] = { valueType };
        return (IRPtrTypeBase*) getType(
            op,
            1,
            operands);
    }

    IRArrayTypeBase* IRBuilder::getArrayTypeBase(
        IROp    op,
        IRType* elementType,
        IRInst* elementCount)
    {
        IRInst* operands[] = { elementType, elementCount };
        return (IRArrayTypeBase*)getType(
            op,
            op == kIROp_ArrayType ? 2 : 1,
            operands);
    }

    IRArrayType* IRBuilder::getArrayType(
        IRType* elementType,
        IRInst* elementCount)
    {
        IRInst* operands[] = { elementType, elementCount };
        return (IRArrayType*)getType(
            kIROp_ArrayType,
            sizeof(operands) / sizeof(operands[0]),
            operands);
    }

    IRUnsizedArrayType* IRBuilder::getUnsizedArrayType(
        IRType* elementType)
    {
        IRInst* operands[] = { elementType };
        return (IRUnsizedArrayType*)getType(
            kIROp_UnsizedArrayType,
            sizeof(operands) / sizeof(operands[0]),
            operands);
    }

    IRVectorType* IRBuilder::getVectorType(
        IRType* elementType,
        IRInst* elementCount)
    {
        IRInst* operands[] = { elementType, elementCount };
        return (IRVectorType*)getType(
            kIROp_VectorType,
            sizeof(operands) / sizeof(operands[0]),
            operands);
    }

    IRMatrixType* IRBuilder::getMatrixType(
        IRType* elementType,
        IRInst* rowCount,
        IRInst* columnCount)
    {
        IRInst* operands[] = { elementType, rowCount, columnCount };
        return (IRMatrixType*)getType(
            kIROp_MatrixType,
            sizeof(operands) / sizeof(operands[0]),
            operands);
    }

    IRFuncType* IRBuilder::getFuncType(
        UInt            paramCount,
        IRType* const*  paramTypes,
        IRType*         resultType)
    {
        return (IRFuncType*) findOrEmitHoistableInst(
            this,
            nullptr,
            kIROp_FuncType,
            resultType,
            paramCount,
            (IRInst* const*) paramTypes);
    }

    IRConstExprRate* IRBuilder::getConstExprRate()
    {
        return (IRConstExprRate*)getType(kIROp_ConstExprRate);
    }

    IRGroupSharedRate* IRBuilder::getGroupSharedRate()
    {
        return (IRGroupSharedRate*)getType(kIROp_GroupSharedRate);
    }

    IRRateQualifiedType* IRBuilder::getRateQualifiedType(
        IRRate* rate,
        IRType* dataType)
    {
        IRInst* operands[] = { rate, dataType };
        return (IRRateQualifiedType*)getType(
            kIROp_RateQualifiedType,
            sizeof(operands) / sizeof(operands[0]),
            operands);
    }

    void IRBuilder::setDataType(IRInst* inst, IRType* dataType)
    {
        if (auto oldRateQualifiedType = as<IRRateQualifiedType>(inst->getFullType()))
        {
            // Construct a new rate-qualified type using the same rate.

            auto newRateQualifiedType = getRateQualifiedType(
                oldRateQualifiedType->getRate(),
                dataType);

            inst->setFullType(newRateQualifiedType);
        }
        else
        {
            // No rate? Just clobber the data type.
            inst->setFullType(dataType);
        }
    }


    IRUndefined* IRBuilder::emitUndefined(IRType* type)
    {
        auto inst = createInst<IRUndefined>(
            this,
            kIROp_undefined,
            type);

        addInst(inst);

        return inst;
    }

    IRInst* IRBuilder::emitSpecializeInst(
        IRType*         type,
        IRInst*         genericVal,
        UInt            argCount,
        IRInst* const*  args)
    {
        auto inst = createInstWithTrailingArgs<IRSpecialize>(
            this,
            kIROp_Specialize,
            type,
            1,
            &genericVal,
            argCount,
            args);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitLookupInterfaceMethodInst(
        IRType* type,
        IRInst* witnessTableVal,
        IRInst* interfaceMethodVal)
    {
        auto inst = createInst<IRLookupWitnessMethod>(
            this,
            kIROp_lookup_interface_method,
            type,
            witnessTableVal,
            interfaceMethodVal);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitCallInst(
        IRType*         type,
        IRInst*        pFunc,
        UInt            argCount,
        IRInst* const* args)
    {
        auto inst = createInstWithTrailingArgs<IRCall>(
            this,
            kIROp_Call,
            type,
            1,
            &pFunc,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitIntrinsicInst(
        IRType*         type,
        IROp            op,
        UInt            argCount,
        IRInst* const* args)
    {
        auto inst = createInstWithTrailingArgs<IRInst>(
            this,
            op,
            type,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitConstructorInst(
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        auto inst = createInstWithTrailingArgs<IRInst>(
            this,
            kIROp_Construct,
            type,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitMakeVector(
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return emitIntrinsicInst(type, kIROp_makeVector, argCount, args);
    }

    IRInst* IRBuilder::emitMakeArray(
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return emitIntrinsicInst(type, kIROp_makeArray, argCount, args);
    }

    IRInst* IRBuilder::emitMakeStruct(
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return emitIntrinsicInst(type, kIROp_makeStruct, argCount, args);
    }

    IRModule* IRBuilder::createModule()
    {
        auto module = new IRModule();
        module->session = getSession();

        auto moduleInst = createInstImpl<IRModuleInst>(
            module,
            this,
            kIROp_Module,
            nullptr,
            0,
            nullptr,
            0,
            nullptr,
            nullptr);
        module->moduleInst = moduleInst;
        moduleInst->module = module;

        return module;
    }

    void addGlobalValue(
        IRBuilder*      builder,
        IRGlobalValue*  value)
    {
        // Try to find a suitable parent for the
        // global value we are emitting.
        //
        // We will start out search at the current
        // parent instruction for the builder, and
        // possibly work our way up.
        //
        auto parent = builder->insertIntoParent;
        while(parent)
        {
            // Inserting into the top level of a module?
            // That is fine, and we can stop searching.
            if (as<IRModuleInst>(parent))
                break;

            // Inserting into a basic block inside of
            // a generic? That is okay too.
            if (auto block = as<IRBlock>(parent))
            {
                if (as<IRGeneric>(block->parent))
                    break;
            }

            // Otherwise, move up the chain.
            parent = parent->parent;
        }

        // If we somehow ran out of parents (possibly
        // because an instruction wasn't linked into
        // the full hierarchy yet), then we will
        // fall back to inserting into the overall module.
        if (!parent)
        {
            parent = builder->getModule()->getModuleInst();
        }

        // If it turns out that we are inserting into the
        // current "insert into" parent for the builder, then
        // we need to respect its "insert before" setting
        // as well.
        if (parent == builder->insertIntoParent
            && builder->insertBeforeInst)
        {
            value->insertBefore(builder->insertBeforeInst);
        }
        else
        {
            value->insertAtEnd(parent);
        }
    }

    IRFunc* IRBuilder::createFunc()
    {
        IRFunc* rsFunc = createInst<IRFunc>(
            this,
            kIROp_Func,
            nullptr);
        maybeSetSourceLoc(this, rsFunc);
        addGlobalValue(this, rsFunc);
        return rsFunc;
    }

    IRGlobalVar* IRBuilder::createGlobalVar(
        IRType* valueType)
    {
        auto ptrType = getPtrType(valueType);
        IRGlobalVar* globalVar = createInst<IRGlobalVar>(
            this,
            kIROp_GlobalVar,
            ptrType);
        maybeSetSourceLoc(this, globalVar);
        addGlobalValue(this, globalVar);
        return globalVar;
    }

    IRGlobalConstant* IRBuilder::createGlobalConstant(
        IRType* valueType)
    {
        IRGlobalConstant* globalConstant = createInst<IRGlobalConstant>(
            this,
            kIROp_GlobalConstant,
            valueType);
        maybeSetSourceLoc(this, globalConstant);
        addGlobalValue(this, globalConstant);
        return globalConstant;
    }

    IRWitnessTable* IRBuilder::createWitnessTable()
    {
        IRWitnessTable* witnessTable = createInst<IRWitnessTable>(
            this,
            kIROp_WitnessTable,
            nullptr);
        addGlobalValue(this, witnessTable);
        return witnessTable;
    }

    IRWitnessTableEntry* IRBuilder::createWitnessTableEntry(
        IRWitnessTable* witnessTable,
        IRInst*         requirementKey,
        IRInst*         satisfyingVal)
    {
        IRWitnessTableEntry* entry = createInst<IRWitnessTableEntry>(
            this,
            kIROp_WitnessTableEntry,
            nullptr,
            requirementKey,
            satisfyingVal);

        if (witnessTable)
        {
            entry->insertAtEnd(witnessTable);
        }

        return entry;
    }

    IRStructType* IRBuilder::createStructType()
    {
        IRStructType* structType = createInst<IRStructType>(
            this,
            kIROp_StructType,
            nullptr);
        addGlobalValue(this, structType);
        return structType;
    }

    IRStructKey* IRBuilder::createStructKey()
    {
        IRStructKey* structKey = createInst<IRStructKey>(
            this,
            kIROp_StructKey,
            nullptr);
        addGlobalValue(this, structKey);
        return structKey;
    }

    // Create a field nested in a struct type, declaring that
    // the specified field key maps to a field with the specified type.
    IRStructField*  IRBuilder::createStructField(
        IRStructType*   structType,
        IRStructKey*    fieldKey,
        IRType*         fieldType)
    {
        IRInst* operands[] = { fieldKey, fieldType };
        IRStructField* field = (IRStructField*) createInstWithTrailingArgs<IRInst>(
            this,
            kIROp_StructField,
            nullptr,
            0,
            nullptr,
            2,
            operands);

        if (structType)
        {
            field->insertAtEnd(structType);
        }

        return field;
    }

    IRGeneric* IRBuilder::createGeneric()
    {
        IRGeneric* irGeneric = createInst<IRGeneric>(
            this,
            kIROp_Generic,
            nullptr);
        return irGeneric;
    }

    IRGeneric* IRBuilder::emitGeneric()
    {
        auto irGeneric = createGeneric();
        addGlobalValue(this, irGeneric);
        return irGeneric;
    }


    IRWitnessTable * IRBuilder::lookupWitnessTable(Name* mangledName)
    {
        IRWitnessTable * result;
        if (sharedBuilder->witnessTableMap.TryGetValue(mangledName, result))
            return result;
        return nullptr;
    }


    void IRBuilder::registerWitnessTable(IRWitnessTable * table)
    {
        sharedBuilder->witnessTableMap[table->mangledName] = table;
    }

    IRBlock* IRBuilder::createBlock()
    {
        return createInst<IRBlock>(
            this,
            kIROp_Block,
            getBasicBlockType());
    }

    IRBlock* IRBuilder::emitBlock()
    {
        // Create a block
        auto bb = createBlock();

        // If we are emitting into a function
        // (or another value with code), then
        // append the block to the function and
        // set this block as the new parent for
        // subsequent instructions we insert.
        auto f = getFunc();
        if (f)
        {
            f->addBlock(bb);
            setInsertInto(bb);
        }
        return bb;
    }

    IRParam* IRBuilder::createParam(
        IRType* type)
    {
        auto param = createInst<IRParam>(
            this,
            kIROp_Param,
            type);
        return param;
    }

    IRParam* IRBuilder::emitParam(
        IRType* type)
    {
        auto param = createParam(type);
        if (auto bb = getBlock())
        {
            bb->addParam(param);
        }
        return param;
    }

    IRVar* IRBuilder::emitVar(
        IRType*         type)
    {
        auto allocatedType = getPtrType(type);
        auto inst = createInst<IRVar>(
            this,
            kIROp_Var,
            allocatedType);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitLoad(
        IRInst*    ptr)
    {
        // Note: a `load` operation does not consider the rate
        // (if any) attached to its operand (see the use of `getDataType`
        // below). This means that a load from a rate-qualified
        // variable will still conceptually execute (and return
        // results) at the "default" rate of the parent function,
        // unless a subsequent analysis pass constraints it.

        IRType* valueType = nullptr;
        if(auto ptrType = as<IRPtrTypeBase>(ptr->getDataType()))
        {
            valueType = ptrType->getValueType();
        }
        else if(auto ptrLikeType = as<IRPointerLikeType>(ptr->getDataType()))
        {
            valueType = ptrLikeType->getElementType();
        }
        else
        {
            // Bad!
            SLANG_ASSERT(ptrType);
            return nullptr;
        }

        // Ugly special case: if the front-end created a variable with
        // type `Ptr<@R T>` instead of `@R Ptr<T>`, then the above
        // logic will yield `@R T` instead of `T`, and we need to
        // try and fix that up here.
        //
        // TODO: Lowering to the IR should be fixed to never create
        // that case: rate-qualified types should only be allowed
        // to appear as the type of an instruction, and should not
        // be allowed as operands to type constructors (except
        // in special cases we decide to allow).
        //
        if(auto rateType = as<IRRateQualifiedType>(valueType))
        {
            valueType = rateType->getValueType();
        }

        auto inst = createInst<IRLoad>(
            this,
            kIROp_Load,
            valueType,
            ptr);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitStore(
        IRInst* dstPtr,
        IRInst* srcVal)
    {
        auto inst = createInst<IRStore>(
            this,
            kIROp_Store,
            nullptr,
            dstPtr,
            srcVal);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitFieldExtract(
        IRType* type,
        IRInst* base,
        IRInst* field)
    {
        auto inst = createInst<IRFieldExtract>(
            this,
            kIROp_FieldExtract,
            type,
            base,
            field);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitFieldAddress(
        IRType* type,
        IRInst* base,
        IRInst* field)
    {
        auto inst = createInst<IRFieldAddress>(
            this,
            kIROp_FieldAddress,
            type,
            base,
            field);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitElementExtract(
        IRType* type,
        IRInst* base,
        IRInst* index)
    {
        auto inst = createInst<IRFieldAddress>(
            this,
            kIROp_getElement,
            type,
            base,
            index);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitElementAddress(
        IRType*     type,
        IRInst*    basePtr,
        IRInst*    index)
    {
        auto inst = createInst<IRFieldAddress>(
            this,
            kIROp_getElementPtr,
            type,
            basePtr,
            index);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitSwizzle(
        IRType*         type,
        IRInst*         base,
        UInt            elementCount,
        IRInst* const*  elementIndices)
    {
        auto inst = createInstWithTrailingArgs<IRSwizzle>(
            this,
            kIROp_swizzle,
            type,
            base,
            elementCount,
            elementIndices);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitSwizzle(
        IRType*         type,
        IRInst*         base,
        UInt            elementCount,
        UInt const*     elementIndices)
    {
        auto intType = getBasicType(BaseType::Int);

        IRInst* irElementIndices[4];
        for (UInt ii = 0; ii < elementCount; ++ii)
        {
            irElementIndices[ii] = getIntValue(intType, elementIndices[ii]);
        }

        return emitSwizzle(type, base, elementCount, irElementIndices);
    }


    IRInst* IRBuilder::emitSwizzleSet(
        IRType*         type,
        IRInst*         base,
        IRInst*         source,
        UInt            elementCount,
        IRInst* const*  elementIndices)
    {
        IRInst* fixedArgs[] = { base, source };
        UInt fixedArgCount = sizeof(fixedArgs) / sizeof(fixedArgs[0]);

        auto inst = createInstWithTrailingArgs<IRSwizzleSet>(
            this,
            kIROp_swizzleSet,
            type,
            fixedArgCount,
            fixedArgs,
            elementCount,
            elementIndices);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitSwizzleSet(
        IRType*         type,
        IRInst*         base,
        IRInst*         source,
        UInt            elementCount,
        UInt const*     elementIndices)
    {
        auto intType = getBasicType(BaseType::Int);

        IRInst* irElementIndices[4];
        for (UInt ii = 0; ii < elementCount; ++ii)
        {
            irElementIndices[ii] = getIntValue(intType, elementIndices[ii]);
        }

        return emitSwizzleSet(type, base, source, elementCount, irElementIndices);
    }

    IRInst* IRBuilder::emitSwizzledStore(
        IRInst*         dest,
        IRInst*         source,
        UInt            elementCount,
        IRInst* const*  elementIndices)
    {
        IRInst* fixedArgs[] = { dest, source };
        UInt fixedArgCount = sizeof(fixedArgs) / sizeof(fixedArgs[0]);

        auto inst = createInstImpl<IRSwizzledStore>(
            this,
            kIROp_SwizzledStore,
            nullptr,
            fixedArgCount,
            fixedArgs,
            elementCount,
            elementIndices);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitSwizzledStore(
        IRInst*         dest,
        IRInst*         source,
        UInt            elementCount,
        UInt const*     elementIndices)
    {
        auto intType = getBasicType(BaseType::Int);

        IRInst* irElementIndices[4];
        for (UInt ii = 0; ii < elementCount; ++ii)
        {
            irElementIndices[ii] = getIntValue(intType, elementIndices[ii]);
        }

        return emitSwizzledStore(dest, source, elementCount, irElementIndices);
    }

    IRInst* IRBuilder::emitReturn(
        IRInst*    val)
    {
        auto inst = createInst<IRReturnVal>(
            this,
            kIROp_ReturnVal,
            nullptr,
            val);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitReturn()
    {
        auto inst = createInst<IRReturnVoid>(
            this,
            kIROp_ReturnVoid,
            nullptr);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitUnreachable()
    {
        auto inst = createInst<IRUnreachable>(
            this,
            kIROp_unreachable,
            nullptr);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitDiscard()
    {
        auto inst = createInst<IRDiscard>(
            this,
            kIROp_discard,
            nullptr);
        addInst(inst);
        return inst;
    }


    IRInst* IRBuilder::emitBranch(
        IRBlock*    pBlock)
    {
        auto inst = createInst<IRUnconditionalBranch>(
            this,
            kIROp_unconditionalBranch,
            nullptr,
            pBlock);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitBreak(
        IRBlock*    target)
    {
        return emitBranch(target);
    }

    IRInst* IRBuilder::emitContinue(
        IRBlock*    target)
    {
        return emitBranch(target);
    }

    IRInst* IRBuilder::emitLoop(
        IRBlock*    target,
        IRBlock*    breakBlock,
        IRBlock*    continueBlock)
    {
        IRInst* args[] = { target, breakBlock, continueBlock };
        UInt argCount = sizeof(args) / sizeof(args[0]);

        auto inst = createInst<IRLoop>(
            this,
            kIROp_loop,
            nullptr,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitBranch(
        IRInst*     val,
        IRBlock*    trueBlock,
        IRBlock*    falseBlock)
    {
        IRInst* args[] = { val, trueBlock, falseBlock };
        UInt argCount = sizeof(args) / sizeof(args[0]);

        auto inst = createInst<IRConditionalBranch>(
            this,
            kIROp_conditionalBranch,
            nullptr,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitIfElse(
        IRInst*     val,
        IRBlock*    trueBlock,
        IRBlock*    falseBlock,
        IRBlock*    afterBlock)
    {
        IRInst* args[] = { val, trueBlock, falseBlock, afterBlock };
        UInt argCount = sizeof(args) / sizeof(args[0]);

        auto inst = createInst<IRIfElse>(
            this,
            kIROp_ifElse,
            nullptr,
            argCount,
            args);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitIf(
        IRInst*    val,
        IRBlock*    trueBlock,
        IRBlock*    afterBlock)
    {
        return emitIfElse(val, trueBlock, afterBlock, afterBlock);
    }

    IRInst* IRBuilder::emitLoopTest(
        IRInst*     val,
        IRBlock*    bodyBlock,
        IRBlock*    breakBlock)
    {
        return emitIfElse(val, bodyBlock, breakBlock, bodyBlock);
    }

    IRInst* IRBuilder::emitSwitch(
        IRInst*         val,
        IRBlock*        breakLabel,
        IRBlock*        defaultLabel,
        UInt            caseArgCount,
        IRInst* const*  caseArgs)
    {
        IRInst* fixedArgs[] = { val, breakLabel, defaultLabel };
        UInt fixedArgCount = sizeof(fixedArgs) / sizeof(fixedArgs[0]);

        auto inst = createInstWithTrailingArgs<IRSwitch>(
            this,
            kIROp_switch,
            nullptr,
            fixedArgCount,
            fixedArgs,
            caseArgCount,
            caseArgs);
        addInst(inst);
        return inst;
    }

    IRGlobalGenericParam* IRBuilder::emitGlobalGenericParam()
    {
        IRGlobalGenericParam* irGenericParam = createInst<IRGlobalGenericParam>(
            this,
            kIROp_GlobalGenericParam,
            nullptr);
        addGlobalValue(this, irGenericParam);
        return irGenericParam;
    }

    IRBindGlobalGenericParam* IRBuilder::emitBindGlobalGenericParam(
        IRInst* param,
        IRInst* val)
    {
        auto inst = createInst<IRBindGlobalGenericParam>(
            this,
            kIROp_BindGlobalGenericParam,
            nullptr,
            param,
            val);
        addInst(inst);
        return inst;
    }

    IRHighLevelDeclDecoration* IRBuilder::addHighLevelDeclDecoration(IRInst* inst, Decl* decl)
    {
        auto decoration = addDecoration<IRHighLevelDeclDecoration>(inst, kIRDecorationOp_HighLevelDecl);
        decoration->decl = decl;
        return decoration;
    }

    IRLayoutDecoration* IRBuilder::addLayoutDecoration(IRInst* inst, Layout* layout)
    {
        auto decoration = addDecoration<IRLayoutDecoration>(inst);
        decoration->layout = layout;
        return decoration;
    }

    //


    struct IRDumpContext
    {
        StringBuilder* builder;
        int     indent = 0;

        UInt                        idCounter = 1;
        Dictionary<IRInst*, UInt>  mapValueToID;
    };

    static void dump(
        IRDumpContext*  context,
        char const*     text)
    {
        context->builder->append(text);
    }

    static void dump(
        IRDumpContext*  context,
        UInt            val)
    {
        context->builder->append(val);

//        fprintf(context->file, "%llu", (unsigned long long)val);
    }

    static void dump(
        IRDumpContext*          context,
        IntegerLiteralValue     val)
    {
        context->builder->append(val);

//        fprintf(context->file, "%llu", (unsigned long long)val);
    }

    static void dump(
        IRDumpContext*              context,
        FloatingPointLiteralValue   val)
    {
        context->builder->append(val);

//        fprintf(context->file, "%llu", (unsigned long long)val);
    }

    static void dumpIndent(
        IRDumpContext*  context)
    {
        for (int ii = 0; ii < context->indent; ++ii)
        {
            dump(context, "\t");
        }
    }

    bool opHasResult(IRInst* inst);

    bool instHasUses(IRInst* inst)
    {
        return inst->firstUse != nullptr;
    }

    static UInt getID(
        IRDumpContext*  context,
        IRInst*         value)
    {
        UInt id = 0;
        if (context->mapValueToID.TryGetValue(value, id))
            return id;

        if (opHasResult(value) || instHasUses(value))
        {
            id = context->idCounter++;
        }

        context->mapValueToID.Add(value, id);
        return id;
    }

    static void dumpID(
        IRDumpContext* context,
        IRInst*        inst)
    {
        if (!inst)
        {
            dump(context, "<null>");
            return;
        }

        if (auto globalValue = as<IRGlobalValue>(inst))
        {
            auto mangledName = globalValue->mangledName;
            if(mangledName)
            {
                auto mangledNameText = getText(mangledName);
                if (mangledNameText.Length() > 0)
                {
                    dump(context, "@");
                    dump(context, mangledNameText.Buffer());
                    return;
                }
            }
        }

        UInt id = getID(context, inst);
        if (id)
        {
            dump(context, "%");
            dump(context, id);
        }
        else
        {
            dump(context, "_");
        }
    }

    static void dumpType(
        IRDumpContext*  context,
        IRType*         type);

    static void dumpDeclRef(
        IRDumpContext*          context,
        DeclRef<Decl> const&    declRef);

    static void dumpOperand(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        // TODO: we should have a dedicated value for the `undef` case
        if (!inst)
        {
            dumpID(context, inst);
            return;
        }

        switch (inst->op)
        {
        case kIROp_IntLit:
            dump(context, ((IRConstant*)inst)->u.intVal);
            return;

        case kIROp_FloatLit:
            dump(context, ((IRConstant*)inst)->u.floatVal);
            return;

        case kIROp_boolConst:
            dump(context, ((IRConstant*)inst)->u.intVal ? "true" : "false");
            return;

        default:
            break;
        }

        dumpID(context, inst);
    }

    static void dumpType(
        IRDumpContext*  context,
        IRType*         type)
    {
        if (!type)
        {
            dump(context, "_");
            return;
        }

        // TODO: we should consider some special-case printing
        // for types, so that the IR doesn't get too hard to read
        // (always having to back-reference for what a type expands to)
        dumpOperand(context, type);
    }

    static void dumpInstTypeClause(
        IRDumpContext*  context,
        IRType*         type)
    {
        dump(context, "\t: ");
        dumpType(context, type);

    }

    static void dumpInst(
        IRDumpContext*  context,
        IRInst*         inst);

    static void dumpBlock(
        IRDumpContext*  context,
        IRBlock*        block)
    {
        context->indent--;
        dump(context, "block ");
        dumpID(context, block);

        IRInst* inst = block->getFirstInst();

        // First walk through any `param` instructions,
        // so that we can format them nicely
        if (auto firstParam = as<IRParam>(inst))
        {
            dump(context, "(\n");
            context->indent += 2;

            for(;;)
            {
                auto param = as<IRParam>(inst);
                if (!param)
                    break;

                if (param != firstParam)
                    dump(context, ",\n");

                inst = inst->getNextInst();

                dumpIndent(context);
                dump(context, "param ");
                dumpID(context, param);
                dumpInstTypeClause(context, param->getFullType());
            }
            context->indent -= 2;
            dump(context, ")");
        }
        dump(context, ":\n");
        context->indent++;

        for(; inst; inst = inst->getNextInst())
        {
            dumpInst(context, inst);
        }
    }

    void dumpIRDecorations(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        for( auto dd = inst->firstDecoration; dd; dd = dd->next )
        {
            switch( dd->op )
            {
            case kIRDecorationOp_Target:
                {
                    auto decoration = (IRTargetDecoration*) dd;

                    dump(context, "\n");
                    dumpIndent(context);
                    dump(context, "[target(");
                    dump(context, decoration->targetName.Buffer());
                    dump(context, ")]");
                }
                break;

            }
        }
    }

    void dumpIRGlobalValueWithCode(
        IRDumpContext*          context,
        IRGlobalValueWithCode*  code)
    {
        // TODO: should apply this to all instructions
        dumpIRDecorations(context, code);

        auto opInfo = getIROpInfo(code->op);

        dump(context, "\n");
        dumpIndent(context);
        dump(context, opInfo.name);
        dump(context, " ");
        dumpID(context, code);

        dumpInstTypeClause(context, code->getFullType());

        if (!code->getFirstBlock())
        {
            // Just a declaration.
            dump(context, ";\n");
            return;
        }

        dump(context, "\n");

        dumpIndent(context);
        dump(context, "{\n");
        context->indent++;

        for (auto bb = code->getFirstBlock(); bb; bb = bb->getNextBlock())
        {
            if (bb != code->getFirstBlock())
                dump(context, "\n");
            dumpBlock(context, bb);
        }

        context->indent--;
        dump(context, "}\n");
    }


    String dumpIRFunc(IRFunc* func)
    {
        IRDumpContext dumpContext;
        StringBuilder sbDump;
        dumpContext.builder = &sbDump;
        dumpIRGlobalValueWithCode(&dumpContext, func);
        auto strFunc = sbDump.ToString();
        return strFunc;
    }

    void dumpIRWitnessTableEntry(
        IRDumpContext*          context,
        IRWitnessTableEntry*    entry)
    {
        dump(context, "witness_table_entry(");
        dumpOperand(context, entry->requirementKey.get());
        dump(context, ",");
        dumpOperand(context, entry->satisfyingVal.get());
        dump(context, ")\n");
    }

    void dumpIRParentInst(
        IRDumpContext*  context,
        IRParentInst*   inst)
    {
        // TODO: should apply this to all instructions
        dumpIRDecorations(context, inst);

        auto opInfo = getIROpInfo(inst->op);

        dump(context, "\n");
        dumpIndent(context);
        dump(context, opInfo.name);
        dump(context, " ");
        dumpID(context, inst);

        dumpInstTypeClause(context, inst->getFullType());

        if (!inst->getFirstChild())
        {
            // Empty.
            dump(context, ";\n");
            return;
        }

        dump(context, "\n");

        dumpIndent(context);
        dump(context, "{\n");
        context->indent++;

        for (auto child = inst->getFirstChild(); child; child = child->getNextInst())
        {
            dumpInst(context, child);
        }

        context->indent--;
        dump(context, "}\n");
    }

    void dumpIRGeneric(
        IRDumpContext*  context,
        IRGeneric* witnessTable)
    {
        dump(context, "\n");
        dumpIndent(context);
        dump(context, "ir_witness_table ");
        dumpID(context, witnessTable);
        dump(context, "\n{\n");
        context->indent++;

        for (auto ii : witnessTable->getChildren())
        {
            dumpInst(context, ii);
        }

        context->indent--;
        dump(context, "}\n");
    }

    static void dumpInst(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        if (!inst)
        {
            dumpIndent(context);
            dump(context, "<null>");
            return;
        }

        auto op = inst->op;

        // There are several ops we want to special-case here,
        // so that they will be more pleasant to look at.
        //
        switch (op)
        {
        case kIROp_Func:
        case kIROp_GlobalVar:
        case kIROp_GlobalConstant:
        case kIROp_Generic:
            dumpIRGlobalValueWithCode(context, (IRGlobalValueWithCode*)inst);
            return;

        case kIROp_WitnessTable:
        case kIROp_StructType:
            dumpIRParentInst(context, (IRWitnessTable*)inst);
            return;

        case kIROp_WitnessTableEntry:
            dumpIRWitnessTableEntry(context, (IRWitnessTableEntry*)inst);
            return;

        default:
            break;
        }

        // Okay, we have a seemingly "ordinary" op now
        dumpIndent(context);

        auto opInfo = getIROpInfo(op);
        auto dataType = inst->getDataType();
        auto rate = inst->getRate();

        if(rate)
        {
            dump(context, "@");
            dumpOperand(context, rate);
            dump(context, " ");
        }

        if(opHasResult(inst) || instHasUses(inst))
        {
            dump(context, "let  ");
            dumpID(context, inst);
            dumpInstTypeClause(context, dataType);
            dump(context, "\t= ");
        }
        else
        {
            // No result, okay...
        }

        dump(context, opInfo.name);

        UInt argCount = inst->getOperandCount();
        UInt ii = 0;

        // Special case: make printing of `call` a bit
        // nicer to look at
        if (inst->op == kIROp_Call && argCount > 0)
        {
            dump(context, " ");
            auto argVal = inst->getOperand(ii++);
            dumpOperand(context, argVal);
        }

        bool first = true;
        dump(context, "(");
        for (; ii < argCount; ++ii)
        {
            if (!first)
                dump(context, ", ");

            auto argVal = inst->getOperand(ii);

            dumpOperand(context, argVal);

            first = false;
        }

        // Special cases: literals and other instructions with no real operands
        switch (inst->op)
        {
        case kIROp_IntLit:
        case kIROp_FloatLit:
        case kIROp_boolConst:
            dumpOperand(context, inst);
            break;

        default:
            break;
        }

        dump(context, ")");

        dump(context, "\n");
    }

    void dumpIRModule(
        IRDumpContext*  context,
        IRModule*       module)
    {
        for(auto ii : module->getGlobalInsts())
        {
            dumpInst(context, ii);
        }
    }

    void printSlangIRAssembly(StringBuilder& builder, IRModule* module)
    {
        IRDumpContext context;
        context.builder = &builder;
        context.indent = 0;

        dumpIRModule(&context, module);
    }

    void dumpIR(IRGlobalValue* globalVal)
    {
        StringBuilder sb;

        IRDumpContext context;
        context.builder = &sb;
        context.indent = 0;

        dumpInst(&context, globalVal);

        fprintf(stderr, "%s\n", sb.Buffer());
        fflush(stderr);
    }

    String getSlangIRAssembly(IRModule* module)
    {
        StringBuilder sb;
        printSlangIRAssembly(sb, module);
        return sb;
    }

    void dumpIR(IRModule* module)
    {
        String ir = getSlangIRAssembly(module);
        fprintf(stderr, "%s\n", ir.Buffer());
        fflush(stderr);
    }


    //
    //
    //

    IRRate* IRInst::getRate()
    {
        if(auto rateQualifiedType = as<IRRateQualifiedType>(getFullType()))
            return rateQualifiedType->getRate();

        return nullptr;
    }

    IRType* IRInst::getDataType()
    {
        auto type = getFullType();
        if(auto rateQualifiedType = as<IRRateQualifiedType>(type))
            return rateQualifiedType->getValueType();

        return type;
    }

    void IRInst::replaceUsesWith(IRInst* other)
    {
        // Safety check: don't try to replace something with itself.
        if(other == this)
            return;

        // We will walk through the list of uses for the current
        // instruction, and make them point to the other inst.
        IRUse* ff = firstUse;

        // No uses? Nothing to do.
        if(!ff)
            return;

        ff->debugValidate();

        IRUse* uu = ff;
        for(;;)
        {
            // The uses had better all be uses of this
            // instruction, or invariants are broken.
            SLANG_ASSERT(uu->get() == this);

            // Swap this use over to use the other value.
            uu->usedValue = other;

            // Try to move to the next use, but bail
            // out if we are at the last one.
            IRUse* nn = uu->nextUse;
            if( !nn )
                break;

            uu = nn;
        }

        // We are at the last use (and there must
        // be at least one, because we handled
        // the case of an empty list earlier).
        SLANG_ASSERT(uu);

        // Our job at this point is to splice
        // our list of uses onto the other
        // value's uses.
        //
        // If the value already had uses, then
        // we need to patch our new list onto
        // the front.
        if( auto nn = other->firstUse )
        {
            uu->nextUse = nn;
            nn->prevLink = &uu->nextUse;
        }

        // No matter what, our list of
        // uses will become the start
        // of the list of uses for
        // `other`
        other->firstUse = ff;
        ff->prevLink = &other->firstUse;

        // And `this` will have no uses any more.
        this->firstUse = nullptr;

        ff->debugValidate();
    }

    void IRInst::dispose()
    {
        IRObject::dispose();
    }

    // Insert this instruction into the same basic block
    // as `other`, right before it.
    void IRInst::insertBefore(IRInst* other)
    {
        SLANG_ASSERT(other);
        insertBefore(other, other->parent);
    }

    void IRInst::insertAtStart(IRParentInst* newParent)
    {
        SLANG_ASSERT(newParent);
        insertBefore(newParent->children.first, newParent);
    }

    void IRInst::moveToStart()
    {
        auto p = parent;
        removeFromParent();
        insertAtStart(p);
    }

    void IRInst::insertBefore(IRInst* other, IRParentInst* newParent)
    {
        // Make sure this instruction has been removed from any previous parent
        this->removeFromParent();

        SLANG_ASSERT(other || newParent);
        if (!other) other = newParent->children.first;
        if (!newParent) newParent = other->parent;
        SLANG_ASSERT(newParent);

        auto nn = other;
        auto pp = other ? other->getPrevInst() : nullptr;

        if( pp )
        {
            pp->next = this;
        }
        else
        {
            newParent->children.first = this;
        }

        if (nn)
        {
            nn->prev = this;
        }
        else
        {
            newParent->children.last = this;
        }

        this->prev = pp;
        this->next = nn;
        this->parent = newParent;
    }

    void IRInst::insertAfter(IRInst* other)
    {
        SLANG_ASSERT(other);
        insertAfter(other, other->parent);
    }

    void IRInst::insertAtEnd(IRParentInst* newParent)
    {
        SLANG_ASSERT(newParent);
        insertAfter(newParent->children.last, newParent);
    }

    void IRInst::moveToEnd()
    {
        auto p = parent;
        removeFromParent();
        insertAtEnd(p);
    }

    void IRInst::insertAfter(IRInst* other, IRParentInst* newParent)
    {
        // Make sure this instruction has been removed from any previous parent
        this->removeFromParent();

        SLANG_ASSERT(other || newParent);
        if (!other) other = newParent->children.last;
        if (!newParent) newParent = other->parent;
        SLANG_ASSERT(newParent);

        auto pp = other;
        auto nn = other ? other->next : nullptr;

        if (pp)
        {
            pp->next = this;
        }
        else
        {
            newParent->children.first = this;
        }

        if (nn)
        {
            nn->prev = this;
        }
        else
        {
            newParent->children.last = this;
        }

        this->prev = pp;
        this->next = nn;
        this->parent = newParent;
    }

    // Remove this instruction from its parent block,
    // and then destroy it (it had better have no uses!)
    void IRInst::removeFromParent()
    {
        auto oldParent = getParent();

        // If we don't currently have a parent, then
        // we are doing fine.
        if(!oldParent)
            return;

        auto pp = getPrevInst();
        auto nn = getNextInst();

        if(pp)
        {
            SLANG_ASSERT(pp->getParent() == oldParent);
            pp->next = nn;
        }
        else
        {
            oldParent->children.first = nn;
        }

        if(nn)
        {
            SLANG_ASSERT(nn->getParent() == oldParent);
            nn->prev = pp;
        }
        else
        {
            oldParent->children.last = pp;
        }

        prev = nullptr;
        next = nullptr;
        parent = nullptr;
    }

    void IRInst::removeArguments()
    {
        typeUse.clear();
        for( UInt aa = 0; aa < operandCount; ++aa )
        {
            IRUse& use = getOperands()[aa];
            use.clear();
        }
    }

    // Remove this instruction from its parent block,
    // and then destroy it (it had better have no uses!)
    void IRInst::removeAndDeallocate()
    {
        removeFromParent();
        removeArguments();

        // Run destructor to be sure...
        this->~IRInst();
    }

    bool IRInst::mightHaveSideEffects()
    {
        // TODO: We should drive this based on flags specified
        // in `ir-inst-defs.h` isntead of hard-coding things here,
        // but this is good enough for now if we are conservative:

        if(as<IRType>(this))
            return false;

        if(as<IRGlobalValue>(this))
            return false;

        if(as<IRConstant>(this))
            return false;

        switch(op)
        {
        // By default, assume that we might have side effects,
        // to safely cover all the instructions we haven't had time to think about.
        default:
            return true;

        case kIROp_Call:
            // This is the most interesting.
            return true;

        case kIROp_Nop:
        case kIROp_Specialize:
        case kIROp_lookup_interface_method:
        case kIROp_Construct:
        case kIROp_makeVector:
        case kIROp_makeMatrix:
        case kIROp_makeArray:
        case kIROp_makeStruct:
        case kIROp_Load:    // We are ignoring the possibility of loads from bad addresses, or `volatile` loads
        case kIROp_BufferLoad:
        case kIROp_FieldExtract:
        case kIROp_FieldAddress:
        case kIROp_getElement:
        case kIROp_getElementPtr:
        case kIROp_constructVectorFromScalar:
        case kIROp_swizzle:
        case kIROp_swizzleSet:  // Doesn't actually "set" anything - just returns the resulting vector
        case kIROp_Add:
        case kIROp_Sub:
        case kIROp_Mul:
        //case kIROp_Div:   // TODO: We could split out integer vs. floating-point div/mod and assume the floating-point cases have no side effects
        //case kIROp_Mod:
        case kIROp_Lsh:
        case kIROp_Rsh:
        case kIROp_Eql:
        case kIROp_Neq:
        case kIROp_Greater:
        case kIROp_Less:
        case kIROp_Geq:
        case kIROp_Leq:
        case kIROp_BitAnd:
        case kIROp_BitXor:
        case kIROp_BitOr:
        case kIROp_And:
        case kIROp_Or:
        case kIROp_Neg:
        case kIROp_Not:
        case kIROp_BitNot:
        case kIROp_Select:
        case kIROp_Dot:
        case kIROp_Mul_Vector_Matrix:
        case kIROp_Mul_Matrix_Vector:
        case kIROp_Mul_Matrix_Matrix:
            return false;
        }
    }

    IRModule* IRInst::getModule()
    {
        IRInst* ii = this;
        while(ii)
        {
            if(auto moduleInst = as<IRModuleInst>(ii))
                return moduleInst->module;

            ii = ii->getParent();
        }
        return nullptr;
    }


    //
    // Legalization of entry points for GLSL:
    //

    IRGlobalVar* addGlobalVariable(
        IRModule*   module,
        IRType*     valueType)
    {
        auto session = module->session;

        SharedIRBuilder shared;
        shared.module = module;
        shared.session = session;

        IRBuilder builder;
        builder.sharedBuilder = &shared;
        return builder.createGlobalVar(valueType);
    }

    void moveValueBefore(
        IRGlobalValue*  valueToMove,
        IRGlobalValue*  placeBefore)
    {
        valueToMove->removeFromParent();
        valueToMove->insertBefore(placeBefore);
    }

    // When scalarizing shader inputs/outputs for GLSL, we need a way
    // to refer to a conceptual "value" that might comprise multiple
    // IR-level values. We could in principle introduce tuple types
    // into the IR so that everything stays at the IR level, but
    // it seems easier to just layer it over the top for now.
    //
    // The `ScalarizedVal` type deals with the "tuple or single value?"
    // question, and also the "l-value or r-value?" question.
    struct ScalarizedValImpl : RefObject
    {};
    struct ScalarizedTupleValImpl;
    struct ScalarizedTypeAdapterValImpl;
    struct ScalarizedVal
    {
        enum class Flavor
        {
            // no value (null pointer)
            none,

            // A simple `IRInst*` that represents the actual value
            value,

            // An `IRInst*` that represents the address of the actual value
            address,

            // A `TupleValImpl` that represents zero or more `ScalarizedVal`s
            tuple,

            // A `TypeAdapterValImpl` that wraps a single `ScalarizedVal` and
            // represents an implicit type conversion applied to it on read
            // or write.
            typeAdapter,
        };

        // Create a value representing a simple value
        static ScalarizedVal value(IRInst* irValue)
        {
            ScalarizedVal result;
            result.flavor = Flavor::value;
            result.irValue = irValue;
            return result;
        }


        // Create a value representing an address
        static ScalarizedVal address(IRInst* irValue)
        {
            ScalarizedVal result;
            result.flavor = Flavor::address;
            result.irValue = irValue;
            return result;
        }

        static ScalarizedVal tuple(ScalarizedTupleValImpl* impl)
        {
            ScalarizedVal result;
            result.flavor = Flavor::tuple;
            result.impl = (ScalarizedValImpl*)impl;
            return result;
        }

        static ScalarizedVal typeAdapter(ScalarizedTypeAdapterValImpl* impl)
        {
            ScalarizedVal result;
            result.flavor = Flavor::typeAdapter;
            result.impl = (ScalarizedValImpl*)impl;
            return result;
        }

        Flavor                      flavor = Flavor::none;
        IRInst*                     irValue = nullptr;
        RefPtr<ScalarizedValImpl>   impl;
    };

    // This is the case for a value that is a "tuple" of other values
    struct ScalarizedTupleValImpl : ScalarizedValImpl
    {
        struct Element
        {
            IRStructKey*    key;
            ScalarizedVal   val;
        };

        IRType*         type;
        List<Element>   elements;
    };

    // This is the case for a value that is stored with one type,
    // but needs to present itself as having a different type
    struct ScalarizedTypeAdapterValImpl : ScalarizedValImpl
    {
        ScalarizedVal   val;
        IRType*         actualType;   // the actual type of `val`
        IRType*         pretendType;     // the type this value pretends to have
    };

    struct GlobalVaryingDeclarator
    {
        enum class Flavor
        {
            array,
        };

        Flavor                      flavor;
        IRInst*                     elementCount;
        GlobalVaryingDeclarator*    next;
    };

    struct GLSLSystemValueInfo
    {
        // The name of the built-in GLSL variable
        char const* name;

        // The name of an outer array that wraps
        // the variable, in the case of a GS input
        char const*     outerArrayName;

        // The required type of the built-in variable
        IRType*     requiredType;
    };

    void requireGLSLVersionImpl(
        ExtensionUsageTracker*  tracker,
        ProfileVersion          version);

    void requireGLSLExtension(
        ExtensionUsageTracker*  tracker,
        String const&           name);

    struct GLSLLegalizationContext
    {
        Session*                session;
        ExtensionUsageTracker*  extensionUsageTracker;
        DiagnosticSink*         sink;
        Stage                   stage;

        void requireGLSLExtension(String const& name)
        {
            Slang::requireGLSLExtension(extensionUsageTracker, name);
        }

        void requireGLSLVersion(ProfileVersion version)
        {
            Slang::requireGLSLVersionImpl(extensionUsageTracker, version);
        }

        Stage getStage()
        {
            return stage;
        }

        DiagnosticSink* getSink()
        {
            return sink;
        }

        IRBuilder* builder;
        IRBuilder* getBuilder() { return builder; }
    };

    GLSLSystemValueInfo* getGLSLSystemValueInfo(
        GLSLLegalizationContext*    context,
        VarLayout*                  varLayout,
        LayoutResourceKind          kind,
        Stage                       stage,
        GLSLSystemValueInfo*        inStorage)
    {
        char const* name = nullptr;
        char const* outerArrayName = nullptr;

        auto semanticNameSpelling = varLayout->systemValueSemantic;
        if(semanticNameSpelling.Length() == 0)
            return nullptr;

        auto semanticName = semanticNameSpelling.ToLower();

        IRType* requiredType = nullptr;

        if(semanticName == "sv_position")
        {
            // This semantic can either work like `gl_FragCoord`
            // when it is used as a fragment shader input, or
            // like `gl_Position` when used in other stages.
            //
            // Note: This isn't as simple as testing input-vs-output,
            // because a user might have a VS output `SV_Position`,
            // and then pass it along to a GS that reads it as input.
            //
            if( stage == Stage::Fragment
                && kind == LayoutResourceKind::VaryingInput )
            {
                name = "gl_FragCoord";
            }
            else if( stage == Stage::Geometry
                && kind == LayoutResourceKind::VaryingInput )
            {
                // As a GS input, the correct syntax is `gl_in[...].gl_Position`,
                // but that is not compatible with picking the array dimension later,
                // of course.
                outerArrayName = "gl_in";
                name = "gl_Position";
            }
            else
            {
                name = "gl_Position";
            }
        }
        else if(semanticName == "sv_target")
        {
            // Note: we do *not* need to generate some kind of `gl_`
            // builtin for fragment-shader outputs: they are just
            // ordinary `out` variables, with ordinary `location`s,
            // as far as GLSL is concerned.
            return nullptr;
        }
        else if(semanticName == "sv_clipdistance")
        {
            // TODO: type conversion is required here.
            name = "gl_ClipDistance";
        }
        else if(semanticName == "sv_culldistance")
        {
            context->requireGLSLExtension("ARB_cull_distance");

            // TODO: type conversion is required here.
            name = "gl_CullDistance";
        }
        else if(semanticName == "sv_coverage")
        {
            // TODO: deal with `gl_SampleMaskIn` when used as an input.

            // TODO: type conversion is required here.
            name = "gl_SampleMask";
        }
        else if(semanticName == "sv_depth")
        {
            name = "gl_FragDepth";
        }
        else if(semanticName == "sv_depthgreaterequal")
        {
            // TODO: layout(depth_greater) out float gl_FragDepth;
            name = "gl_FragDepth";
        }
        else if(semanticName == "sv_depthlessequal")
        {
            // TODO: layout(depth_greater) out float gl_FragDepth;
            name = "gl_FragDepth";
        }
        else if(semanticName == "sv_dispatchthreadid")
        {
            name = "gl_GlobalInvocationID";
        }
        else if(semanticName == "sv_domainlocation")
        {
            name = "gl_TessCoord";
        }
        else if(semanticName == "sv_groupid")
        {
            name = "gl_WorkGroupID";
        }
        else if(semanticName == "sv_groupindex")
        {
            name = "gl_LocalInvocationIndex";
        }
        else if(semanticName == "sv_groupthreadid")
        {
            name = "gl_LocalInvocationID";
        }
        else if(semanticName == "sv_gsinstanceid")
        {
            name = "gl_InvocationID";
        }
        else if(semanticName == "sv_instanceid")
        {
            name = "gl_InstanceIndex";
        }
        else if(semanticName == "sv_isfrontface")
        {
            name = "gl_FrontFacing";
        }
        else if(semanticName == "sv_outputcontrolpointid")
        {
            name = "gl_InvocationID";
        }
        else if(semanticName == "sv_primitiveid")
        {
            name = "gl_PrimitiveID";
        }
        else if (semanticName == "sv_rendertargetarrayindex")
        {
            switch (context->getStage())
            {
            case Stage::Geometry:
                context->requireGLSLVersion(ProfileVersion::GLSL_150);
                break;

            case Stage::Fragment:
                context->requireGLSLVersion(ProfileVersion::GLSL_430);
                break;

            default:
                context->requireGLSLVersion(ProfileVersion::GLSL_450);
                context->requireGLSLExtension("GL_ARB_shader_viewport_layer_array");
                break;
            }

            name = "gl_Layer";
            requiredType = context->getBuilder()->getBasicType(BaseType::Int);
        }
        else if (semanticName == "sv_sampleindex")
        {
            name = "gl_SampleID";
        }
        else if (semanticName == "sv_stencilref")
        {
            context->requireGLSLExtension("ARB_shader_stencil_export");
            name = "gl_FragStencilRef";
        }
        else if (semanticName == "sv_tessfactor")
        {
            name = "gl_TessLevelOuter";
        }
        else if (semanticName == "sv_vertexid")
        {
            name = "gl_VertexIndex";
        }
        else if (semanticName == "sv_viewportarrayindex")
        {
            name = "gl_ViewportIndex";
        }
        else if (semanticName == "nv_x_right")
        {
            context->requireGLSLVersion(ProfileVersion::GLSL_450);
            context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");

            // The actual output in GLSL is:
            //
            //    vec4 gl_PositionPerViewNV[];
            //
            // and is meant to support an arbitrary number of views,
            // while the HLSL case just defines a second position
            // output.
            //
            // For now we will hack this by:
            //   1. Mapping an `NV_X_Right` output to `gl_PositionPerViewNV[1]`
            //      (that is, just one element of the output array)
            //   2. Adding logic to copy the traditional `gl_Position` output
            //      over to `gl_PositionPerViewNV[0]`
            //

            name = "gl_PositionPerViewNV[1]";

//            shared->requiresCopyGLPositionToPositionPerView = true;
        }
        else if (semanticName == "nv_viewport_mask")
        {
            context->requireGLSLVersion(ProfileVersion::GLSL_450);
            context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");

            name = "gl_ViewportMaskPerViewNV";
//            globalVarExpr = createGLSLBuiltinRef("gl_ViewportMaskPerViewNV",
//                getUnsizedArrayType(getIntType()));
        }

        if( name )
        {
            inStorage->name = name;
            inStorage->outerArrayName = outerArrayName;
            inStorage->requiredType = requiredType;
            return inStorage;
        }

        context->getSink()->diagnose(varLayout->varDecl.getDecl()->loc, Diagnostics::unknownSystemValueSemantic, semanticNameSpelling);
        return nullptr;
    }

    ScalarizedVal createSimpleGLSLGlobalVarying(
        GLSLLegalizationContext*    context,
        IRBuilder*                  builder,
        IRType*                     inType,
        VarLayout*                  inVarLayout,
        TypeLayout*                 inTypeLayout,
        LayoutResourceKind          kind,
        Stage                       stage,
        UInt                        bindingIndex,
        GlobalVaryingDeclarator*    declarator)
    {
        // Check if we have a system value on our hands.
        GLSLSystemValueInfo systemValueInfoStorage;
        auto systemValueInfo = getGLSLSystemValueInfo(
            context,
            inVarLayout,
            kind,
            stage,
            &systemValueInfoStorage);

        IRType* type = inType;

        // A system-value semantic might end up needing to override the type
        // that the user specified.
        if( systemValueInfo && systemValueInfo->requiredType )
        {
            type = systemValueInfo->requiredType;
        }

        // Construct the actual type and type-layout for the global variable
        //
        RefPtr<TypeLayout> typeLayout = inTypeLayout;
        for( auto dd = declarator; dd; dd = dd->next )
        {
            // We only have one declarator case right now...
            SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);

            auto arrayType = builder->getArrayType(
                type,
                dd->elementCount);

            RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout();
//            arrayTypeLayout->type = arrayType;
            arrayTypeLayout->rules = typeLayout->rules;
            arrayTypeLayout->originalElementTypeLayout =  typeLayout;
            arrayTypeLayout->elementTypeLayout = typeLayout;
            arrayTypeLayout->uniformStride = 0;

            if( auto resInfo = inTypeLayout->FindResourceInfo(kind) )
            {
                // TODO: it is kind of gross to be re-running some
                // of the type layout logic here.

                UInt elementCount = (UInt) GetIntVal(dd->elementCount);
                arrayTypeLayout->addResourceUsage(
                    kind,
                    resInfo->count * elementCount);
            }

            type = arrayType;
            typeLayout = arrayTypeLayout;
        }

        // We need to construct a fresh layout for the variable, even
        // if the original had its own layout, because it might be
        // an `inout` parameter, and we only want to deal with the case
        // described by our `kind` parameter.
        RefPtr<VarLayout> varLayout = new VarLayout();
        varLayout->varDecl = inVarLayout->varDecl;
        varLayout->typeLayout = typeLayout;
        varLayout->flags = inVarLayout->flags;
        varLayout->systemValueSemantic = inVarLayout->systemValueSemantic;
        varLayout->systemValueSemanticIndex = inVarLayout->systemValueSemanticIndex;
        varLayout->semanticName = inVarLayout->semanticName;
        varLayout->semanticIndex = inVarLayout->semanticIndex;
        varLayout->stage = inVarLayout->stage;
        varLayout->AddResourceInfo(kind)->index = bindingIndex;

        // Simple case: just create a global variable of the matching type,
        // and then use the value of the global as a replacement for the
        // value of the original parameter.
        //
        auto globalVariable = addGlobalVariable(builder->getModule(), type);
        moveValueBefore(globalVariable, builder->getFunc());

        ScalarizedVal val = ScalarizedVal::address(globalVariable);

        if( systemValueInfo )
        {
            globalVariable->mangledName = builder->getSession()->getNameObj(systemValueInfo->name);

            if( auto fromType = systemValueInfo->requiredType )
            {
                // We may need to adapt from the declared type to/from
                // the actual type of the GLSL global.
                auto toType = inType;

                if( fromType != toType )
                {
                    RefPtr<ScalarizedTypeAdapterValImpl> typeAdapter = new ScalarizedTypeAdapterValImpl;
                    typeAdapter->actualType = systemValueInfo->requiredType;
                    typeAdapter->pretendType = inType;
                    typeAdapter->val = val;

                    val = ScalarizedVal::typeAdapter(typeAdapter);
                }
            }

            if(auto outerArrayName = systemValueInfo->outerArrayName)
            {
                auto decoration = builder->addDecoration<IRGLSLOuterArrayDecoration>(globalVariable);
                decoration->outerArrayName = outerArrayName;
            }
        }

        builder->addLayoutDecoration(globalVariable, varLayout);

        return val;
    }

    ScalarizedVal createGLSLGlobalVaryingsImpl(
        GLSLLegalizationContext*    context,
        IRBuilder*                  builder,
        IRType*                     type,
        VarLayout*                  varLayout,
        TypeLayout*                 typeLayout,
        LayoutResourceKind          kind,
        Stage                       stage,
        UInt                        bindingIndex,
        GlobalVaryingDeclarator*    declarator)
    {
        if( as<IRBasicType>(type) )
        {
            return createSimpleGLSLGlobalVarying(
                context,
                builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
        }
        else if( as<IRVectorType>(type) )
        {
            return createSimpleGLSLGlobalVarying(
                context,
                builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
        }
        else if( as<IRMatrixType>(type) )
        {
            // TODO: a matrix-type varying should probably be handled like an array of rows
            return createSimpleGLSLGlobalVarying(
                context,
                builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
        }
        else if( auto arrayType = as<IRArrayType>(type) )
        {
            // We will need to SOA-ize any nested types.

            auto elementType = arrayType->getElementType();
            auto elementCount = arrayType->getElementCount();
            auto arrayLayout = dynamic_cast<ArrayTypeLayout*>(typeLayout);
            SLANG_ASSERT(arrayLayout);
            auto elementTypeLayout = arrayLayout->elementTypeLayout;

            GlobalVaryingDeclarator arrayDeclarator;
            arrayDeclarator.flavor = GlobalVaryingDeclarator::Flavor::array;
            arrayDeclarator.elementCount = elementCount;
            arrayDeclarator.next = declarator;

            return createGLSLGlobalVaryingsImpl(
                context,
                builder,
                elementType,
                varLayout,
                elementTypeLayout,
                kind,
                stage,
                bindingIndex,
                &arrayDeclarator);
        }
        else if( auto streamType = as<IRHLSLStreamOutputType>(type))
        {
            auto elementType = streamType->getElementType();
            auto streamLayout = dynamic_cast<StreamOutputTypeLayout*>(typeLayout);
            SLANG_ASSERT(streamLayout);
            auto elementTypeLayout = streamLayout->elementTypeLayout;

            return createGLSLGlobalVaryingsImpl(
                context,
                builder,
                elementType,
                varLayout,
                elementTypeLayout,
                kind,
                stage,
                bindingIndex,
                declarator);
        }
        else if(auto structType = as<IRStructType>(type))
        {
            // We need to recurse down into the individual fields,
            // and generate a variable for each of them.

            auto structTypeLayout = dynamic_cast<StructTypeLayout*>(typeLayout);
            SLANG_ASSERT(structTypeLayout);
            RefPtr<ScalarizedTupleValImpl> tupleValImpl = new ScalarizedTupleValImpl();


            // Construct the actual type for the tuple (including any outer arrays)
            IRType* fullType = type;
            for( auto dd = declarator; dd; dd = dd->next )
            {
                SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
                fullType = builder->getArrayType(
                    fullType,
                    dd->elementCount);
            }

            tupleValImpl->type = fullType;

            // Okay, we want to walk through the fields here, and
            // generate one variable for each.
            UInt fieldCounter = 0;
            for(auto field : structType->getFields())
            {
                UInt fieldIndex = fieldCounter++;

                auto fieldLayout = structTypeLayout->fields[fieldIndex];

                UInt fieldBindingIndex = bindingIndex;
                if(auto fieldResInfo = fieldLayout->FindResourceInfo(kind))
                    fieldBindingIndex += fieldResInfo->index;

                auto fieldVal = createGLSLGlobalVaryingsImpl(
                    context,
                    builder,
                    field->getFieldType(),
                    fieldLayout,
                    fieldLayout->typeLayout,
                    kind,
                    stage,
                    fieldBindingIndex,
                    declarator);

                ScalarizedTupleValImpl::Element element;
                element.val = fieldVal;
                element.key = field->getKey();

                tupleValImpl->elements.Add(element);
            }

            return ScalarizedVal::tuple(tupleValImpl);
        }

        // Default case is to fall back on the simple behavior
        return createSimpleGLSLGlobalVarying(
            context,
            builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
    }

    ScalarizedVal createGLSLGlobalVaryings(
        GLSLLegalizationContext*    context,
        IRBuilder*                  builder,
        IRType*                     type,
        VarLayout*                  layout,
        LayoutResourceKind          kind,
        Stage                       stage)
    {
        UInt bindingIndex = 0;
        if(auto rr = layout->FindResourceInfo(kind))
            bindingIndex = rr->index;
        return createGLSLGlobalVaryingsImpl(
            context,
            builder, type, layout, layout->typeLayout, kind, stage, bindingIndex, nullptr);
    }

    IRType* getFieldType(
        IRType*         baseType,
        IRStructKey*    fieldKey)
    {
        if(auto structType = as<IRStructType>(baseType))
        {
            for(auto ff : structType->getFields())
            {
                if(ff->getKey() == fieldKey)
                    return ff->getFieldType();
            }
        }

        SLANG_UNEXPECTED("no such field");
        UNREACHABLE_RETURN(nullptr);
    }

    ScalarizedVal extractField(
        IRBuilder*              builder,
        ScalarizedVal const&    val,
        UInt                    fieldIndex,
        IRStructKey*            fieldKey)
    {
        switch( val.flavor )
        {
        case ScalarizedVal::Flavor::value:
            return ScalarizedVal::value(
                builder->emitFieldExtract(
                    getFieldType(val.irValue->getDataType(), fieldKey),
                    val.irValue,
                    fieldKey));

        case ScalarizedVal::Flavor::address:
            return ScalarizedVal::address(
                builder->emitFieldAddress(
                    getFieldType(val.irValue->getDataType(), fieldKey),
                    val.irValue,
                    fieldKey));

        case ScalarizedVal::Flavor::tuple:
            {
                auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
                return tupleVal->elements[fieldIndex].val;
            }

        default:
            SLANG_UNEXPECTED("unimplemented");
            UNREACHABLE_RETURN(ScalarizedVal());
        }

    }

    ScalarizedVal adaptType(
        IRBuilder*              builder,
        IRInst*                 val,
        IRType*                 toType,
        IRType*                 /*fromType*/)
    {
        // TODO: actually consider what needs to go on here...
        return ScalarizedVal::value(builder->emitConstructorInst(
            toType,
            1,
            &val));
    }

    ScalarizedVal adaptType(
        IRBuilder*              builder,
        ScalarizedVal const&    val,
        IRType*                 toType,
        IRType*                 fromType)
    {
        switch( val.flavor )
        {
        case ScalarizedVal::Flavor::value:
            return adaptType(builder, val.irValue, toType, fromType);
            break;

        case ScalarizedVal::Flavor::address:
            {
                auto loaded = builder->emitLoad(val.irValue);
                return adaptType(builder, loaded, toType, fromType);
            }
            break;

        default:
            SLANG_UNEXPECTED("unimplemented");
            UNREACHABLE_RETURN(ScalarizedVal());
        }
    }

    void assign(
        IRBuilder*              builder,
        ScalarizedVal const&    left,
        ScalarizedVal const&    right)
    {
        switch( left.flavor )
        {
        case ScalarizedVal::Flavor::address:
            switch( right.flavor )
            {
            case ScalarizedVal::Flavor::value:
                {
                    builder->emitStore(left.irValue, right.irValue);
                }
                break;

            case ScalarizedVal::Flavor::address:
                {
                    auto val = builder->emitLoad(right.irValue);
                    builder->emitStore(left.irValue, val);
                }
                break;

            case ScalarizedVal::Flavor::tuple:
                {
                    // We are assigning from a tuple to a destination
                    // that is not a tuple. We will perform assignment
                    // element-by-element.
                    auto rightTupleVal = right.impl.As<ScalarizedTupleValImpl>();
                    UInt elementCount = rightTupleVal->elements.Count();

                    for( UInt ee = 0; ee < elementCount; ++ee )
                    {
                        auto rightElement = rightTupleVal->elements[ee];
                        auto leftElementVal = extractField(
                            builder,
                            left,
                            ee,
                            rightElement.key);
                        assign(builder, leftElementVal, rightElement.val);
                    }
                }
                break;

            default:
                SLANG_UNEXPECTED("unimplemented");
                break;
            }
            break;

        case ScalarizedVal::Flavor::tuple:
            {
                // We have a tuple, so we are going to need to try and assign
                // to each of its constituent fields.
                auto leftTupleVal = left.impl.As<ScalarizedTupleValImpl>();
                UInt elementCount = leftTupleVal->elements.Count();

                for( UInt ee = 0; ee < elementCount; ++ee )
                {
                    auto rightElementVal = extractField(
                        builder,
                        right,
                        ee,
                        leftTupleVal->elements[ee].key);
                    assign(builder, leftTupleVal->elements[ee].val, rightElementVal);
                }
            }
            break;

        case ScalarizedVal::Flavor::typeAdapter:
            {
                // We are trying to assign to something that had its type adjusted,
                // so we will need to adjust the type of the right-hand side first.
                //
                // In this case we are converting to the actual type of the GLSL variable,
                // from the "pretend" type that it had in the IR before.
                auto typeAdapter = left.impl.As<ScalarizedTypeAdapterValImpl>();
                auto adaptedRight = adaptType(builder, right, typeAdapter->actualType, typeAdapter->pretendType);
                assign(builder, typeAdapter->val, adaptedRight);
            }
            break;

        default:
            SLANG_UNEXPECTED("unimplemented");
            break;
        }
    }

    ScalarizedVal getSubscriptVal(
        IRBuilder*      builder,
        IRType*         elementType,
        ScalarizedVal   val,
        IRInst*         indexVal)
    {
        switch( val.flavor )
        {
        case ScalarizedVal::Flavor::value:
            return ScalarizedVal::value(
                builder->emitElementExtract(
                    elementType,
                    val.irValue,
                    indexVal));

        case ScalarizedVal::Flavor::address:
            return ScalarizedVal::address(
                builder->emitElementAddress(
                    builder->getPtrType(elementType),
                    val.irValue,
                    indexVal));

        case ScalarizedVal::Flavor::tuple:
            {
                auto inputTuple = val.impl.As<ScalarizedTupleValImpl>();

                RefPtr<ScalarizedTupleValImpl> resultTuple = new ScalarizedTupleValImpl();
                resultTuple->type = elementType;

                UInt elementCount = inputTuple->elements.Count();
                UInt elementCounter = 0;

                auto structType = as<IRStructType>(elementType);
                for(auto field : structType->getFields())
                {
                    auto tupleElementType = field->getFieldType();

                    UInt elementIndex = elementCounter++;

                    SLANG_RELEASE_ASSERT(elementIndex < elementCount);
                    auto inputElement = inputTuple->elements[elementIndex];

                    ScalarizedTupleValImpl::Element resultElement;
                    resultElement.key = inputElement.key;
                    resultElement.val = getSubscriptVal(
                        builder,
                        tupleElementType,
                        inputElement.val,
                        indexVal);

                    resultTuple->elements.Add(resultElement);
                }
                SLANG_RELEASE_ASSERT(elementCounter == elementCount);

                return ScalarizedVal::tuple(resultTuple);
            }

        default:
            SLANG_UNEXPECTED("unimplemented");
            UNREACHABLE_RETURN(ScalarizedVal());
        }
    }

    ScalarizedVal getSubscriptVal(
        IRBuilder*      builder,
        IRType*         elementType,
        ScalarizedVal   val,
        UInt            index)
    {
        return getSubscriptVal(
            builder,
            elementType,
            val,
            builder->getIntValue(
                builder->getIntType(),
                index));
    }

    IRInst* materializeValue(
        IRBuilder*              builder,
        ScalarizedVal const&    val);

    IRInst* materializeTupleValue(
        IRBuilder*      builder,
        ScalarizedVal   val)
    {
        auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
        SLANG_ASSERT(tupleVal);

        UInt elementCount = tupleVal->elements.Count();
        auto type = tupleVal->type;

        if( auto arrayType = as<IRArrayType>(type))
        {
            // The tuple represent an array, which means that the
            // individual elements are expected to yield arrays as well.
            //
            // We will extract a value for each array element, and
            // then use these to construct our result.

            List<IRInst*> arrayElementVals;
            UInt arrayElementCount = (UInt) GetIntVal(arrayType->getElementCount());

            for( UInt ii = 0; ii < arrayElementCount; ++ii )
            {
                auto arrayElementPseudoVal = getSubscriptVal(
                    builder,
                    arrayType->getElementType(),
                    val,
                    ii);

                auto arrayElementVal = materializeValue(
                    builder,
                    arrayElementPseudoVal);

                arrayElementVals.Add(arrayElementVal);
            }

            return builder->emitMakeArray(
                arrayType,
                arrayElementVals.Count(),
                arrayElementVals.Buffer());
        }
        else
        {
            // The tuple represents a value of some aggregate type,
            // so we can simply materialize the elements and then
            // construct a value of that type.
            //
            // TODO: this should be using a `makeStruct` instruction.

            List<IRInst*> elementVals;
            for( UInt ee = 0; ee < elementCount; ++ee )
            {
                auto elementVal = materializeValue(builder, tupleVal->elements[ee].val);
                elementVals.Add(elementVal);
            }

            return builder->emitConstructorInst(
                tupleVal->type,
                elementVals.Count(),
                elementVals.Buffer());
        }
    }

    IRInst* materializeValue(
        IRBuilder*              builder,
        ScalarizedVal const&    val)
    {
        switch( val.flavor )
        {
        case ScalarizedVal::Flavor::value:
            return val.irValue;

        case ScalarizedVal::Flavor::address:
            {
                auto loadInst = builder->emitLoad(val.irValue);
                return loadInst;
            }
            break;

        case ScalarizedVal::Flavor::tuple:
            {
                auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
                return materializeTupleValue(builder, val);
            }
            break;

        case ScalarizedVal::Flavor::typeAdapter:
            {
                // Somebody is trying to use a value where its actual type
                // doesn't match the type it pretends to have. To make this
                // work we need to adapt the type from its actual type over
                // to its pretend type.
                auto typeAdapter = val.impl.As<ScalarizedTypeAdapterValImpl>();
                auto adapted = adaptType(builder, typeAdapter->val, typeAdapter->pretendType, typeAdapter->actualType);
                return materializeValue(builder, adapted);
            }
            break;

        default:
            SLANG_UNEXPECTED("unimplemented");
            break;
        }
    }

    IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(
        IRInst*        val,
        String const&   targetName)
    {
        for( auto dd = val->firstDecoration; dd; dd = dd->next )
        {
            if(dd->op != kIRDecorationOp_TargetIntrinsic)
                continue;

            auto decoration = (IRTargetIntrinsicDecoration*) dd;
            if(decoration->targetName == targetName)
                return decoration;
        }

        return nullptr;
    }

    void legalizeEntryPointForGLSL(
        Session*                session,
        IRModule*               module,
        IRFunc*                 func,
        EntryPointLayout*       entryPointLayout,
        DiagnosticSink*         sink,
        ExtensionUsageTracker*  extensionUsageTracker)
    {
        GLSLLegalizationContext context;
        context.session = session;
        context.stage = entryPointLayout->profile.GetStage();
        context.sink = sink;
        context.extensionUsageTracker = extensionUsageTracker;

        Stage stage = entryPointLayout->profile.GetStage();

        // We require that the entry-point function has no uses,
        // because otherwise we'd invalidate the signature
        // at all existing call sites.
        //
        // TODO: the right thing to do here is to split any
        // function that both gets called as an entry point
        // and as an ordinary function.
        SLANG_ASSERT(!func->firstUse);

        // We create a dummy IR builder, since some of
        // the functions require it.
        //
        // TODO: make some of these free functions...
        //
        SharedIRBuilder shared;
        shared.module = module;
        shared.session = session;
        IRBuilder builder;
        builder.sharedBuilder = &shared;
        builder.setInsertInto(func);

        context.builder = &builder;

        // We will start by looking at the return type of the
        // function, because that will enable us to do an
        // early-out check to avoid more work.
        //
        // Specifically, we need to check if the function has
        // a `void` return type, because there is no work
        // to be done on its return value in that case.
        auto resultType = func->getResultType();
        if(as<IRVoidType>(resultType))
        {
            // In this case, the function doesn't return a value
            // so we don't need to transform its `return` sites.
            //
            // We can also use this opportunity to quickly
            // check if the function has any parameters, and if
            // it doesn't use the chance to bail out immediately.
            if( func->getParamCount() == 0 )
            {
                // This function is already legal for GLSL
                // (at least in terms of parameter/result signature),
                // so we won't bother doing anything at all.
                return;
            }

            // If the function does have parameters, then we need
            // to let the logic later in this function handle them.
        }
        else
        {
            // Function returns a value, so we need
            // to introduce a new global variable
            // to hold that value, and then replace
            // any `returnVal` instructions with
            // code to write to that variable.

            auto resultGlobal = createGLSLGlobalVaryings(
                &context,
                &builder,
                resultType,
                entryPointLayout->resultLayout,
                LayoutResourceKind::VaryingOutput,
                stage);

            for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                // TODO: This is silly, because we are looking at every instruction,
                // when we know that a `returnVal` should only ever appear as a
                // terminator...
                for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
                {
                    if(ii->op != kIROp_ReturnVal)
                        continue;

                    IRReturnVal* returnInst = (IRReturnVal*) ii;
                    IRInst* returnValue = returnInst->getVal();

                    // Make sure we add these instructions to the right block
                    builder.setInsertInto(bb);

                    // Write to our global variable(s) from the value being returned.
                    assign(&builder, resultGlobal, ScalarizedVal::value(returnValue));

                    // Emit a `returnVoid` to end the block
                    auto returnVoid = builder.emitReturn();

                    // Remove the old `returnVal` instruction.
                    returnInst->removeAndDeallocate();

                    // Make sure to resume our iteration at an
                    // appropriate instruciton, since we deleted
                    // the one we had been using.
                    ii = returnVoid;
                }
            }
        }

        // Next we will walk through any parameters of the entry-point function,
        // and turn them into global variables.
        if( auto firstBlock = func->getFirstBlock() )
        {
            // Any initialization code we insert for parameters needs
            // to be at the start of the "ordinary" instructions in the block:
            builder.setInsertBefore(firstBlock->getFirstOrdinaryInst());

            UInt paramCounter = 0;
            for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
            {
                UInt paramIndex = paramCounter++;

                // We assume that the entry-point layout includes information
                // on each parameter, and that these arrays are kept aligned.
                // Note that this means that any transformations that mess
                // with function signatures will need to also update layout info...
                //
                SLANG_ASSERT(entryPointLayout->fields.Count() > paramIndex);
                auto paramLayout = entryPointLayout->fields[paramIndex];

                // We need to create a global variable that will replace the parameter.
                // It seems superficially obvious that the variable should have
                // the same type as the parameter.
                // However, if the parameter was a pointer, in order to
                // support `out` or `in out` parameter passing, we need
                // to be sure to allocate a variable of the pointed-to
                // type instead.
                //
                // We also need to replace uses of the parameter with
                // uses of the variable, and the exact logic there
                // will differ a bit between the pointer and non-pointer
                // cases.
                auto paramType = pp->getDataType();

                // First we will special-case stage input/outputs that
                // don't fit into the standard varying model.
                // For right now we are only doing special-case handling
                // of geometry shader output streams.
                if( auto paramPtrType = as<IROutTypeBase>(paramType) )
                {
                    auto valueType = paramPtrType->getValueType();
                    if( auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) )
                    {
                        // An output stream type like `TriangleStream<Foo>` should
                        // more or less translate into `out Foo` (plus scalarization).

                        auto globalOutputVal = createGLSLGlobalVaryings(
                            &context,
                            &builder,
                            valueType,
                            paramLayout,
                            LayoutResourceKind::VaryingOutput,
                            stage);

                        // TODO: a GS output stream might be passed into other
                        // functions, so that we should really be modifying
                        // any function that has one of these in its parameter
                        // list (and in the limit we should be leagalizing any
                        // type that nests these...).
                        //
                        // For now we will just try to deal with `Append` calls
                        // directly in this function.



                        for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
                        {
                            for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
                            {
                                // Is it a call?
                                if(ii->op != kIROp_Call)
                                    continue;

                                // Is it calling the append operation?
                                auto callee = ii->getOperand(0);
                                for(;;)
                                {
                                    // If the instruction is `specialize(X,...)` then
                                    // we want to look at `X`, and if it is `generic { ... return R; }`
                                    // then we want to look at `R`. We handle this
                                    // iteratively here.
                                    //
                                    // TODO: This idiom seems to come up enough that we
                                    // should probably have a dedicated convenience routine
                                    // for this.
                                    //
                                    // Alternatively, we could switch the IR encoding so
                                    // that decorations are added to the generic instead of the
                                    // value it returns.
                                    //
                                    switch(callee->op)
                                    {
                                    case kIROp_Specialize:
                                        {
                                            callee = cast<IRSpecialize>(callee)->getOperand(0);
                                            continue;
                                        }

                                    case kIROp_Generic:
                                        {
                                            auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee));
                                            if(genericResult)
                                            {
                                                callee = genericResult;
                                                continue;
                                            }
                                        }

                                    default:
                                        break;
                                    }
                                    break;
                                }
                                if(callee->op != kIROp_Func)
                                    continue;

                                // HACK: we will identify the operation based
                                // on the target-intrinsic definition that was
                                // given to it.
                                auto decoration = findTargetIntrinsicDecoration(callee, "glsl");
                                if(!decoration)
                                    continue;

                                if(decoration->definition != "EmitVertex()")
                                {
                                    continue;
                                }

                                // Okay, we have a declaration, and we want to modify it!

                                builder.setInsertBefore(ii);

                                assign(&builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2)));
                            }
                        }

                        continue;
                    }
                }


                // Is the parameter type a special pointer type
                // that indicates the parameter is used for `out`
                // or `inout` access?
                if(auto paramPtrType = as<IROutTypeBase>(paramType) )
                {
                    // Okay, we have the more interesting case here,
                    // where the parameter was being passed by reference.
                    // We are going to create a local variable of the appropriate
                    // type, which will replace the parameter, along with
                    // one or more global variables for the actual input/output.

                    auto valueType = paramPtrType->getValueType();

                    auto localVariable = builder.emitVar(valueType);
                    auto localVal = ScalarizedVal::address(localVariable);

                    if( auto inOutType = as<IRInOutType>(paramPtrType) )
                    {
                        // In the `in out` case we need to declare two
                        // sets of global variables: one for the `in`
                        // side and one for the `out` side.
                        auto globalInputVal = createGLSLGlobalVaryings(
                            &context,
                            &builder, valueType, paramLayout, LayoutResourceKind::VaryingInput, stage);

                        assign(&builder, localVal, globalInputVal);
                    }

                    // Any places where the original parameter was used inside
                    // the function body should instead use the new local variable.
                    // Since the parameter was a pointer, we use the variable instruction
                    // itself (which is an `alloca`d pointer) directly:
                    pp->replaceUsesWith(localVariable);

                    // We also need one or more global variabels to write the output to
                    // when the function is done. We create them here.
                    auto globalOutputVal = createGLSLGlobalVaryings(
                            &context,
                            &builder, valueType, paramLayout, LayoutResourceKind::VaryingOutput, stage);

                    // Now we need to iterate over all the blocks in the function looking
                    // for any `return*` instructions, so that we can write to the output variable
                    for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
                    {
                        auto terminatorInst = bb->getLastInst();
                        if(!terminatorInst)
                            continue;

                        switch( terminatorInst->op )
                        {
                        default:
                            continue;

                        case kIROp_ReturnVal:
                        case kIROp_ReturnVoid:
                            break;
                        }

                        // We dont' re-use `builder` here because we don't want to
                        // disrupt the source location it is using for inserting
                        // temporary variables at the top of the function.
                        //
                        IRBuilder terminatorBuilder;
                        terminatorBuilder.sharedBuilder = builder.sharedBuilder;
                        terminatorBuilder.setInsertBefore(terminatorInst);

                        // Assign from the local variabel to the global output
                        // variable before the actual `return` takes place.
                        assign(&terminatorBuilder, globalOutputVal, localVal);
                    }
                }
                else
                {
                    // This is the "easy" case where the parameter wasn't
                    // being passed by reference. We start by just creating
                    // one or more global variables to represent the parameter,
                    // and attach the required layout information to it along
                    // the way.

                    auto globalValue = createGLSLGlobalVaryings(
                        &context,
                        &builder, paramType, paramLayout, LayoutResourceKind::VaryingInput, stage);

                    // Next we need to replace uses of the parameter with
                    // references to the variable(s). We are going to do that
                    // somewhat naively, by simply materializing the
                    // variables at the start.
                    IRInst* materialized = materializeValue(&builder, globalValue);

                    pp->replaceUsesWith(materialized);
                }
            }

            // At this point we should have eliminated all uses of the
            // parameters of the entry block. Also, our control-flow
            // rules mean that the entry block cannot be the target
            // of any branches in the code, so there can't be
            // any control-flow ops that try to match the parameter
            // list.
            //
            // We can safely go through and destroy the parameters
            // themselves, and then clear out the parameter list.

            for( auto pp = firstBlock->getFirstParam(); pp; )
            {
                auto next = pp->getNextParam();
                pp->removeAndDeallocate();
                pp = next;
            }
        }

        // Finally, we need to patch up the type of the entry point,
        // because it is no longer accurate.

        IRFuncType* voidFuncType = builder.getFuncType(
            0,
            nullptr,
            builder.getVoidType());
        func->setFullType(voidFuncType);

        // TODO: we should technically be constructing
        // a new `EntryPointLayout` here to reflect
        // the way that things have been moved around.
    }

    // Needed for lookup up entry-point layouts.
    //
    // TODO: maybe arrange so that codegen is driven from the layout layer
    // instead of the input/request layer.
    EntryPointLayout* findEntryPointLayout(
        ProgramLayout*      programLayout,
        EntryPointRequest*  entryPointRequest);

    struct IRSpecSymbol : RefObject
    {
        IRGlobalValue*          irGlobalValue;
        RefPtr<IRSpecSymbol>    nextWithSameName;
    };

    struct IRSpecEnv
    {
        IRSpecEnv*  parent = nullptr;

        // A map from original values to their cloned equivalents.
        typedef Dictionary<IRInst*, IRInst*> ClonedValueDictionary;
        ClonedValueDictionary clonedValues;
    };

    struct IRSharedSpecContext
    {
        // The code-generation target in use
        CodeGenTarget target;

        // The specialized module we are building
        RefPtr<IRModule>   module;

        // The original, unspecialized module we are copying
        IRModule*   originalModule;

        // A map from mangled symbol names to zero or
        // more global IR values that have that name,
        // in the *original* module.
        typedef Dictionary<Name*, RefPtr<IRSpecSymbol>> SymbolDictionary;
        SymbolDictionary symbols;

        SharedIRBuilder sharedBuilderStorage;
        IRBuilder builderStorage;

        // The "global" specialization environment.
        IRSpecEnv globalEnv;
    };

    struct IRSharedGenericSpecContext : IRSharedSpecContext
    {
        // Instructions to be processed (for generic specialization context)
        List<IRInst*> workList;
        HashSet<IRInst*> workListSet;
        void addToWorkList(IRInst* inst)
        {
            if(!workListSet.Contains(inst))
            {
                workList.Add(inst);
                workListSet.Add(inst);
            }
        }
        IRInst* popWorkList()
        {
            UInt count = workList.Count();
            if(count != 0)
            {
                IRInst* inst = workList[count - 1];
                workList.FastRemoveAt(count - 1);
                workListSet.Remove(inst);
                return inst;
            }
            return nullptr;
        }
    };

    struct IRSpecContextBase
    {
        // A map from the mangled name of a global variable
        // to the layout to use for it.
        Dictionary<Name*, VarLayout*> globalVarLayouts;

        IRSharedSpecContext* shared;

        IRSharedSpecContext* getShared() { return shared; }

        IRModule* getModule() { return getShared()->module; }

        IRModule* getOriginalModule() { return getShared()->originalModule; }

        IRSharedSpecContext::SymbolDictionary& getSymbols() { return getShared()->symbols; }

        // The current specialization environment to use.
        IRSpecEnv* env = nullptr;
        IRSpecEnv* getEnv()
        {
            // TODO: need to actually establish environments on contexts we create.
            //
            // Or more realistically we need to change the whole approach
            // to specialization and cloning so that we don't try to share
            // logic between two very different cases.


            return env;
        }

        // The IR builder to use for creating nodes
        IRBuilder*  builder;

        // A callback to be used when a value that is not registerd in `clonedValues`
        // is needed during cloning. This gives the subtype a chance to intercept
        // the operation and clone (or not) as needed.
        virtual IRInst* maybeCloneValue(IRInst* originalVal)
        {
            return originalVal;
        }
    };

    void registerClonedValue(
        IRSpecContextBase*  context,
        IRInst*    clonedValue,
        IRInst*    originalValue)
    {
        if(!originalValue)
            return;

        // TODO: now that things are scoped using environments, we
        // shouldn't be running into the cases where a value with
        // the same key already exists. This should be changed to
        // an `Add()` call.
        //
        context->getEnv()->clonedValues[originalValue] = clonedValue;
    }

    // Information on values to use when registering a cloned value
    struct IROriginalValuesForClone
    {
        IRInst*        originalVal = nullptr;
        IRSpecSymbol*   sym = nullptr;

        IROriginalValuesForClone() {}

        IROriginalValuesForClone(IRInst* originalValue)
            : originalVal(originalValue)
        {}

        IROriginalValuesForClone(IRSpecSymbol* symbol)
            : sym(symbol)
        {}
    };

    void registerClonedValue(
        IRSpecContextBase*              context,
        IRInst*                        clonedValue,
        IROriginalValuesForClone const& originalValues)
    {
        registerClonedValue(context, clonedValue, originalValues.originalVal);
        for( auto s = originalValues.sym; s; s = s->nextWithSameName )
        {
            registerClonedValue(context, clonedValue, s->irGlobalValue);
        }
    }

    void cloneDecorations(
        IRSpecContextBase*  context,
        IRInst*        clonedValue,
        IRInst*        originalValue)
    {
        for (auto dd = originalValue->firstDecoration; dd; dd = dd->next)
        {
            switch (dd->op)
            {
            case kIRDecorationOp_HighLevelDecl:
                {
                    auto originalDecoration = (IRHighLevelDeclDecoration*)dd;

                    context->builder->addHighLevelDeclDecoration(clonedValue, originalDecoration->decl);
                }
                break;

            case kIRDecorationOp_LoopControl:
                {
                    auto originalDecoration = (IRLoopControlDecoration*)dd;
                    auto newDecoration = context->builder->addDecoration<IRLoopControlDecoration>(clonedValue);
                    newDecoration->mode = originalDecoration->mode;
                }
                break;

            case kIRDecorationOp_TargetIntrinsic:
                {
                    auto originalDecoration = (IRTargetIntrinsicDecoration*)dd;
                    auto newDecoration = context->builder->addDecoration<IRTargetIntrinsicDecoration>(clonedValue);
                    newDecoration->targetName = originalDecoration->targetName;
                    newDecoration->definition = originalDecoration->definition;
                }
                break;

            case kIRDecorationOp_Semantic:
                {
                    auto originalDecoration = (IRSemanticDecoration*)dd;
                    auto newDecoration = context->builder->addDecoration<IRSemanticDecoration>(clonedValue);
                    newDecoration->semanticName = originalDecoration->semanticName;
                }
                break;

            case kIRDecorationOp_InterpolationMode:
                {
                    auto originalDecoration = (IRInterpolationModeDecoration*)dd;
                    auto newDecoration = context->builder->addDecoration<IRInterpolationModeDecoration>(clonedValue);
                    newDecoration->mode = originalDecoration->mode;
                }
                break;

            case kIRDecorationOp_NameHint:
                {
                    auto originalDecoration = (IRNameHintDecoration*)dd;
                    auto newDecoration = context->builder->addDecoration<IRNameHintDecoration>(clonedValue);
                    newDecoration->name = originalDecoration->name;
                }
                break;

            default:
                // Don't clone any decorations we don't understand.
                break;
            }
        }

        // We will also clone the location here, just because this is a convenient bottleneck
        clonedValue->sourceLoc = originalValue->sourceLoc;
    }

    // We use an `IRSpecContext` for the case where we are cloning
    // code from one or more input modules to create a "linked" output
    // module. Along the way, we will resolve profile-specific functions
    // to the best definition for a given target.
    //
    struct IRSpecContext : IRSpecContextBase
    {
        // Override the "maybe clone" logic so that we always clone
        virtual IRInst* maybeCloneValue(IRInst* originalVal) override;
    };


    IRGlobalValue* cloneGlobalValue(IRSpecContext* context, IRGlobalValue* originalVal);

    IRInst* cloneValue(
        IRSpecContextBase*  context,
        IRInst*        originalValue);

    IRType* cloneType(
        IRSpecContextBase*  context,
        IRType*             originalType);

    IRInst* IRSpecContext::maybeCloneValue(IRInst* originalValue)
    {
        if (auto globalValue = as<IRGlobalValue>(originalValue))
        {
            return cloneGlobalValue(this, globalValue);
        }

        switch (originalValue->op)
        {
        case kIROp_boolConst:
            {
                IRConstant* c = (IRConstant*)originalValue;
                return builder->getBoolValue(c->u.intVal != 0);
            }
            break;


        case kIROp_IntLit:
            {
                IRConstant* c = (IRConstant*)originalValue;
                return builder->getIntValue(cloneType(this, c->getDataType()), c->u.intVal);
            }
            break;

        case kIROp_FloatLit:
            {
                IRConstant* c = (IRConstant*)originalValue;
                return builder->getFloatValue(cloneType(this, c->getDataType()), c->u.floatVal);
            }
            break;

        default:
            {
                // In the deafult case, assume that we have some sort of "hoistable"
                // instruction that requires us to create a clone of it.

                UInt argCount = originalValue->getOperandCount();
                IRInst* clonedValue = createInstWithTrailingArgs<IRInst>(
                    builder,
                    originalValue->op,
                    cloneType(this, originalValue->getFullType()),
                    0, nullptr,
                    argCount, nullptr);
                registerClonedValue(this, clonedValue, originalValue);
                for (UInt aa = 0; aa < argCount; ++aa)
                {
                    IRInst* originalArg = originalValue->getOperand(aa);
                    IRInst* clonedArg = cloneValue(this, originalArg);
                    clonedValue->getOperands()[aa].init(clonedValue, clonedArg);
                }
                cloneDecorations(this, clonedValue, originalValue);

                addHoistableInst(builder, clonedValue);

                return clonedValue;
            }
            break;
        }
    }

    IRInst* cloneValue(
        IRSpecContextBase*  context,
        IRInst*        originalValue);

    // Find a pre-existing cloned value, or return null if none is available.
    IRInst* findClonedValue(
        IRSpecContextBase*  context,
        IRInst*        originalValue)
    {
        IRInst* clonedValue = nullptr;
        for (auto env = context->getEnv(); env; env = env->parent)
        {
            if (env->clonedValues.TryGetValue(originalValue, clonedValue))
            {
                return clonedValue;
            }
        }

        return nullptr;
    }

    IRInst* cloneValue(
        IRSpecContextBase*  context,
        IRInst*        originalValue)
    {
        if (!originalValue)
            return nullptr;

        if (IRInst* clonedValue = findClonedValue(context, originalValue))
            return clonedValue;

        return context->maybeCloneValue(originalValue);
    }

    IRType* cloneType(
        IRSpecContextBase*  context,
        IRType*             originalType)
    {
        return (IRType*)cloneValue(context, originalType);
    }

    IRInst* maybeCloneValueWithMangledName(
        IRSpecContextBase*  context,
        IRGlobalValue*      originalValue)
    {
        for(auto ii : context->shared->module->getGlobalInsts())
        {
            auto gv = as<IRGlobalValue>(ii);
            if (!gv)
                continue;

            if (gv->mangledName == originalValue->mangledName)
                return gv;
        }
        return cloneValue(context, originalValue);
    }

    IRInst* cloneInst(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRInst*                         originalInst,
        IROriginalValuesForClone const& originalValues);

    IRInst* cloneInst(
        IRSpecContextBase*  context,
        IRBuilder*          builder,
        IRInst*             originalInst)
    {
        return cloneInst(context, builder, originalInst, originalInst);
    }

    void cloneGlobalValueWithCodeCommon(
        IRSpecContextBase*      context,
        IRGlobalValueWithCode*  clonedValue,
        IRGlobalValueWithCode*  originalValue);

    IRRate* cloneRate(
        IRSpecContextBase*  context,
        IRRate*             rate)
    {
        return (IRRate*) cloneType(context, rate);
    }

    void maybeSetClonedRate(
        IRSpecContextBase*  context,
        IRBuilder*          builder,
        IRInst*             clonedValue,
        IRInst*             originalValue)
    {
        if(auto rate = originalValue->getRate() )
        {
            clonedValue->setFullType(builder->getRateQualifiedType(
                cloneRate(context, rate),
                clonedValue->getFullType()));
        }
    }

    IRGlobalVar* cloneGlobalVarImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRGlobalVar*                    originalVar,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedVar = builder->createGlobalVar(
            cloneType(context, originalVar->getDataType()->getValueType()));

        maybeSetClonedRate(context, builder, clonedVar, originalVar);

        registerClonedValue(context, clonedVar, originalValues);

        auto mangledName = originalVar->mangledName;
        clonedVar->mangledName = mangledName;

        cloneDecorations(context, clonedVar, originalVar);

        VarLayout* layout = nullptr;
        if (context->globalVarLayouts.TryGetValue(mangledName, layout))
        {
            builder->addLayoutDecoration(clonedVar, layout);
        }

        // Clone any code in the body of the variable, since this
        // represents the initializer.
        cloneGlobalValueWithCodeCommon(
            context,
            clonedVar,
            originalVar);

        return clonedVar;
    }

    IRGlobalConstant* cloneGlobalConstantImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRGlobalConstant*               originalVal,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedVal = builder->createGlobalConstant(
            cloneType(context, originalVal->getFullType()));
        registerClonedValue(context, clonedVal, originalValues);

        auto mangledName = originalVal->mangledName;
        clonedVal->mangledName = mangledName;

        cloneDecorations(context, clonedVal, originalVal);

        // Clone any code in the body of the constant, since this
        // represents the initializer.
        cloneGlobalValueWithCodeCommon(
            context,
            clonedVal,
            originalVal);

        return clonedVal;
    }

    IRGeneric* cloneGenericImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRGeneric*                      originalVal,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedVal = builder->emitGeneric();
        registerClonedValue(context, clonedVal, originalValues);

        auto mangledName = originalVal->mangledName;
        clonedVal->mangledName = mangledName;

        cloneDecorations(context, clonedVal, originalVal);

        // Clone any code in the body of the generic, since this
        // computes its result value.
        cloneGlobalValueWithCodeCommon(
            context,
            clonedVal,
            originalVal);

        return clonedVal;
    }

    void cloneSimpleGlobalValueImpl(
        IRSpecContextBase*              context,
        IRGlobalValue*                  originalInst,
        IROriginalValuesForClone const& originalValues,
        IRGlobalValue*                  clonedInst,
        bool                            registerValue = true)
    {
        if (registerValue)
            registerClonedValue(context, clonedInst, originalValues);

        auto mangledName = originalInst->mangledName;
        clonedInst->mangledName = mangledName;

        cloneDecorations(context, clonedInst, originalInst);

        // Set up an IR builder for inserting into the inst
        IRBuilder builderStorage = *context->builder;
        IRBuilder* builder = &builderStorage;
        builder->setInsertInto(clonedInst);

        // Clone any children of the instruction
        for (auto child : originalInst->getChildren())
        {
            cloneInst(context, builder, child);
        }
    }

    IRStructKey* cloneStructKeyImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRStructKey*                    originalVal,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedVal = builder->createStructKey();
        cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
        return clonedVal;
    }

    IRGlobalGenericParam* cloneGlobalGenericParamImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRGlobalGenericParam*           originalVal,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedVal = builder->emitGlobalGenericParam();
        cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
        return clonedVal;
    }


    IRWitnessTable* cloneWitnessTableImpl(
        IRSpecContextBase*  context,
        IRBuilder*          builder,
        IRWitnessTable* originalTable,
        IROriginalValuesForClone const& originalValues,
        IRWitnessTable* dstTable = nullptr,
        bool registerValue = true)
    {
        auto clonedTable = dstTable ? dstTable : builder->createWitnessTable();
        cloneSimpleGlobalValueImpl(context, originalTable, originalValues, clonedTable, registerValue);
        return clonedTable;
    }

    IRWitnessTable* cloneWitnessTableWithoutRegistering(
        IRSpecContextBase*  context,
        IRBuilder*          builder,
        IRWitnessTable* originalTable,
        IRWitnessTable* dstTable = nullptr)
    {
        return cloneWitnessTableImpl(context, builder, originalTable, IROriginalValuesForClone(), dstTable, false);
    }

    IRStructType* cloneStructTypeImpl(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRStructType*                   originalStruct,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedStruct = builder->createStructType();
        cloneSimpleGlobalValueImpl(context, originalStruct, originalValues, clonedStruct);
        return clonedStruct;
    }

    void cloneGlobalValueWithCodeCommon(
        IRSpecContextBase*      context,
        IRGlobalValueWithCode*  clonedValue,
        IRGlobalValueWithCode*  originalValue)
    {
        // Next we are going to clone the actual code.
        IRBuilder builderStorage = *context->builder;
        IRBuilder* builder = &builderStorage;
        builder->setInsertInto(clonedValue);


        // We will walk through the blocks of the function, and clone each of them.
        //
        // We need to create the cloned blocks first, and then walk through them,
        // because blocks might be forward referenced (this is not possible
        // for other cases of instructions).
        for (auto originalBlock = originalValue->getFirstBlock();
            originalBlock;
            originalBlock = originalBlock->getNextBlock())
        {
            IRBlock* clonedBlock = builder->createBlock();
            clonedValue->addBlock(clonedBlock);
            registerClonedValue(context, clonedBlock, originalBlock);

#if 0
            // We can go ahead and clone parameters here, while we are at it.
            builder->curBlock = clonedBlock;
            for (auto originalParam = originalBlock->getFirstParam();
                originalParam;
                originalParam = originalParam->getNextParam())
            {
                IRParam* clonedParam = builder->emitParam(
                    context->maybeCloneType(
                        originalParam->getFullType()));
                cloneDecorations(context, clonedParam, originalParam);
                registerClonedValue(context, clonedParam, originalParam);
            }
#endif
        }

        // Okay, now we are in a good position to start cloning
        // the instructions inside the blocks.
        {
            IRBlock* ob = originalValue->getFirstBlock();
            IRBlock* cb = clonedValue->getFirstBlock();
            while (ob)
            {
                SLANG_ASSERT(cb);

                builder->setInsertInto(cb);
                for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst())
                {
                    cloneInst(context, builder, oi);
                }

                ob = ob->getNextBlock();
                cb = cb->getNextBlock();
            }
        }

    }

    void checkIRDuplicate(IRInst* inst, IRParentInst* moduleInst, Name* mangledName)
    {
#ifdef _DEBUG
        for (auto child : moduleInst->getChildren())
        {
            if (child == inst)
                continue;

            if (child->op == kIROp_Func)
            {
                auto extName = ((IRGlobalValue*)child)->mangledName;
                if (extName == mangledName ||
                    (extName && mangledName &&
                        extName->text == mangledName->text))
                    SLANG_UNEXPECTED("duplicate global var");
            }
        }
#else
        SLANG_UNREFERENCED_PARAMETER(inst);
        SLANG_UNREFERENCED_PARAMETER(moduleInst);
        SLANG_UNREFERENCED_PARAMETER(mangledName);
#endif
    }

    void cloneFunctionCommon(
        IRSpecContextBase*  context,
        IRFunc*         clonedFunc,
        IRFunc*         originalFunc,
        bool checkDuplicate = true)
    {
        // First clone all the simple properties.
        clonedFunc->mangledName = originalFunc->mangledName;
        clonedFunc->setFullType(cloneType(context, originalFunc->getFullType()));

        cloneDecorations(context, clonedFunc, originalFunc);

        cloneGlobalValueWithCodeCommon(
            context,
            clonedFunc,
            originalFunc);

        // Shuffle the function to the end of the list, because
        // it needs to follow its dependencies.
        //
        // TODO: This isn't really a good requirement to place on the IR...
        clonedFunc->moveToEnd();
        if (checkDuplicate)
            checkIRDuplicate(clonedFunc, context->getModule()->getModuleInst(), clonedFunc->mangledName);
    }

    IRFunc* specializeIRForEntryPoint(
        IRSpecContext*  context,
        EntryPointRequest*  entryPointRequest,
        EntryPointLayout*   entryPointLayout)
    {
        // Look up the IR symbol by name
        auto mangledName = context->getModule()->session->getNameObj(getMangledName(entryPointRequest->decl));
        RefPtr<IRSpecSymbol> sym;
        if (!context->getSymbols().TryGetValue(mangledName, sym))
        {
            SLANG_UNEXPECTED("no matching IR symbol");
            return nullptr;
        }

        // TODO: deal with the case where we might
        // have multiple versions...

        auto globalValue = sym->irGlobalValue;
        if (globalValue->op != kIROp_Func)
        {
            SLANG_UNEXPECTED("expected an IR function");
            return nullptr;
        }
        auto originalFunc = (IRFunc*)globalValue;

        // Create a clone for the IR function
        auto clonedFunc = context->builder->createFunc();

        // Note: we do *not* register this cloned declaration
        // as the cloned value for the original symbol.
        // This is kind of a kludge, but it ensures that
        // in the unlikely case that the function is both
        // used as an entry point and a callable function
        // (yes, this would imply recursion...) we actually
        // have two copies, which lets us arbitrarily
        // transform the entry point to meet target requirements.
        //
        // TODO: The above statement is kind of bunk, though,
        // because both versions of the function would have
        // the same mangled name... :(

        // We need to clone all the properties of the original
        // function, including any blocks, their parameters,
        // and their instructions.
        cloneFunctionCommon(context, clonedFunc, originalFunc);

        // We need to attach the layout information for
        // the entry point to this declaration, so that
        // we can use it to inform downstream code emit.
        context->builder->addLayoutDecoration(
            clonedFunc,
            entryPointLayout);

        // We will also go on and attach layout information
        // to the function parameters, so that we have it
        // available directly on the parameters, rather
        // than having to look it up on the original entry-point layout.
        if( auto firstBlock = clonedFunc->getFirstBlock() )
        {
            UInt paramLayoutCount = entryPointLayout->fields.Count();
            UInt paramCounter = 0;
            for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
            {
                UInt paramIndex = paramCounter++;
                if( paramIndex < paramLayoutCount )
                {
                    auto paramLayout = entryPointLayout->fields[paramIndex];
                    context->builder->addLayoutDecoration(
                        pp,
                        paramLayout);
                }
                else
                {
                    SLANG_UNEXPECTED("too many parameters");
                }
            }
        }

        return clonedFunc;
    }

    IRFunc* cloneSimpleFuncWithoutRegistering(IRSpecContextBase* context, IRFunc* originalFunc)
    {
        auto clonedFunc = context->builder->createFunc();
        cloneFunctionCommon(context, clonedFunc, originalFunc, false);
        return clonedFunc;
    }

    // Get a string form of the target so that we can
    // use it to match against target-specialization modifiers
    //
    // TODO: We shouldn't be using strings for this.
    String getTargetName(IRSpecContext* context)
    {
        switch( context->shared->target )
        {
        case CodeGenTarget::HLSL:
            return "hlsl";

        case CodeGenTarget::GLSL:
            return "glsl";

        default:
            SLANG_UNEXPECTED("unhandled case");
            UNREACHABLE_RETURN("unknown");
        }
    }

    // How specialized is a given declaration for the chosen target?
    enum class TargetSpecializationLevel
    {
        specializedForOtherTarget = 0,
        notSpecialized,
        specializedForTarget,
    };

    TargetSpecializationLevel getTargetSpecialiationLevel(
        IRGlobalValue*  val,
        String const&   targetName)
    {
        TargetSpecializationLevel result = TargetSpecializationLevel::notSpecialized;
        for( auto dd = val->firstDecoration; dd; dd = dd->next )
        {
            if(dd->op != kIRDecorationOp_Target)
                continue;

            auto decoration = (IRTargetDecoration*) dd;
            if(decoration->targetName == targetName)
                return TargetSpecializationLevel::specializedForTarget;

            result = TargetSpecializationLevel::specializedForOtherTarget;
        }

        return result;
    }

    IRInst* findGenericReturnVal(IRGeneric* generic)
    {
        auto lastBlock = generic->getLastBlock();
        if (!lastBlock)
            return nullptr;

        auto returnInst = as<IRReturnVal>(lastBlock->getTerminator());
        if (!returnInst)
            return nullptr;

        auto val = returnInst->getVal();
        return val;
    }

    bool isDefinition(
        IRGlobalValue* inVal)
    {
        IRInst* val = inVal;
        // unwrap any generic declarations to see
        // the value they return.
        for(;;)
        {
            auto genericInst = as<IRGeneric>(val);
            if(!genericInst)
                break;

            auto returnVal = findGenericReturnVal(genericInst);
            if(!returnVal)
                break;

            val = returnVal;
        }

        switch (val->op)
        {
        case kIROp_WitnessTable:
        case kIROp_GlobalVar:
        case kIROp_GlobalConstant:
        case kIROp_Func:
        case kIROp_Generic:
            return ((IRParentInst*)val)->getFirstChild() != nullptr;

        case kIROp_StructType:
            return true;

        default:
            return false;
        }
    }

    // Is `newVal` marked as being a better match for our
    // chosen code-generation target?
    //
    // TODO: there is a missing step here where we need
    // to check if things are even available in the first place...
    bool isBetterForTarget(
        IRSpecContext*  context,
        IRGlobalValue*  newVal,
        IRGlobalValue*  oldVal)
    {
        String targetName = getTargetName(context);

        // For right now every declaration might have zero or more
        // modifiers, representing the targets for which it is specialized.
        // Each modifier has a single string "tag" to represent a target.
        // We thus decide that a declaration is "more specialized" by:
        //
        // - Does it have a modifier with a tag with the string for the current target?
        //   If yes, it is the most specialized it can be.
        //
        // - Does it have a no tags? Then it is "unspecialized" and that is okay.
        //
        // - Does it have a modifier with a tag for a *different* target?
        //   If yes, then it shouldn't even be usable on this target.
        //
        // Longer term a better approach is to think of this in terms
        // of a "disjunction of conjunctions" that is:
        //
        //     (A and B and C) or (A and D) or (E) or (F and G) ...
        //
        // A code generation target would then consist of a
        // conjunction of invidual tags:
        //
        //    (HLSL and SM_4_0 and Vertex and ...)
        //
        // A declaration is *applicable* on a target if one of
        // its conjunctions of tags is a subset of the target's.
        //
        // One declaration is *better* than another on a target
        // if it is applicable and its tags are a superset
        // of the other's.

        auto newLevel = getTargetSpecialiationLevel(newVal, targetName);
        auto oldLevel = getTargetSpecialiationLevel(oldVal, targetName);
        if(newLevel != oldLevel)
            return UInt(newLevel) > UInt(oldLevel);

        // All other factors being equal, a definition is
        // better than a declaration.
        auto newIsDef = isDefinition(newVal);
        auto oldIsDef = isDefinition(oldVal);
        if (newIsDef != oldIsDef)
            return newIsDef;

        return false;
    }

    IRFunc* cloneFuncImpl(
        IRSpecContextBase*  context,
        IRBuilder*          builder,
        IRFunc*             originalFunc,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedFunc = builder->createFunc();
        registerClonedValue(context, clonedFunc, originalValues);
        cloneFunctionCommon(context, clonedFunc, originalFunc);
        return clonedFunc;
    }


    IRInst* cloneInst(
        IRSpecContextBase*              context,
        IRBuilder*                      builder,
        IRInst*                         originalInst,
        IROriginalValuesForClone const& originalValues)
    {
        switch (originalInst->op)
        {
            // We need to special-case any instruction that is not
            // allocated like an ordinary `IRInst` with trailing args.
        case kIROp_Func:
            return cloneFuncImpl(context, builder, cast<IRFunc>(originalInst), originalValues);

        case kIROp_GlobalVar:
            return cloneGlobalVarImpl(context, builder, cast<IRGlobalVar>(originalInst), originalValues);

        case kIROp_GlobalConstant:
            return cloneGlobalConstantImpl(context, builder, cast<IRGlobalConstant>(originalInst), originalValues);

        case kIROp_WitnessTable:
            return cloneWitnessTableImpl(context, builder, cast<IRWitnessTable>(originalInst), originalValues);

        case kIROp_StructType:
            return cloneStructTypeImpl(context, builder, cast<IRStructType>(originalInst), originalValues);

        case kIROp_Generic:
            return cloneGenericImpl(context, builder, cast<IRGeneric>(originalInst), originalValues);

        case kIROp_StructKey:
            return cloneStructKeyImpl(context, builder, cast<IRStructKey>(originalInst), originalValues);

        case kIROp_GlobalGenericParam:
            return cloneGlobalGenericParamImpl(context, builder, cast<IRGlobalGenericParam>(originalInst), originalValues);

        default:
            break;
        }

        // The common case is that we just need to construct a cloned
        // instruction with the right number of operands, intialize
        // it, and then add it to the sequence.
        UInt argCount = originalInst->getOperandCount();
        IRInst* clonedInst = createInstWithTrailingArgs<IRInst>(
            builder, originalInst->op,
            cloneType(context, originalInst->getFullType()),
            0, nullptr,
            argCount, nullptr);
        registerClonedValue(context, clonedInst, originalValues);
        auto oldBuilder = context->builder;
        context->builder = builder;
        for (UInt aa = 0; aa < argCount; ++aa)
        {
            IRInst* originalArg = originalInst->getOperand(aa);
            IRInst* clonedArg = cloneValue(context, originalArg);
            clonedInst->getOperands()[aa].init(clonedInst, clonedArg);
        }
        builder->addInst(clonedInst);
        context->builder = oldBuilder;
        cloneDecorations(context, clonedInst, originalInst);

        return clonedInst;
    }

    IRGlobalValue* cloneGlobalValueImpl(
        IRSpecContext*                  context,
        IRGlobalValue*                  originalInst,
        IROriginalValuesForClone const& originalValues)
    {
        auto clonedValue = cloneInst(context, &context->shared->builderStorage, originalInst, originalValues);
        clonedValue->moveToEnd();
        return cast<IRGlobalValue>(clonedValue);
    }


    // Clone a global value, which has the given `mangledName`.
    // The `originalVal` is a known global IR value with that name, if one is available.
    // (It is okay for this parameter to be null).
    IRGlobalValue* cloneGlobalValueWithMangledName(
        IRSpecContext*  context,
        Name*           mangledName,
        IRGlobalValue*  originalVal)
    {
        // If the global value being cloned is already in target module, don't clone
        // Why checking this?
        //   When specializing a generic function G (which is already in target module),
        //   where G calls a normal function F (which is already in target module),
        //   then when we are making a copy of G via cloneFuncCommom(), it will recursively clone F,
        //   however we don't want to make a duplicate of F in the target module.
        if (originalVal->getParent() == context->getModule()->getModuleInst())
            return originalVal;

        // Check if we've already cloned this value, for the case where
        // an original value has already been established.
        if (originalVal)
        {
            if (IRInst* clonedVal = findClonedValue(context, originalVal))
            {
                return cast<IRGlobalValue>(clonedVal);
            }
        }

        if(getText(mangledName).Length() == 0)
        {
            // If there is no mangled name, then we assume this is a local symbol,
            // and it can't possibly have multiple declarations.
            return cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone());
        }

        //
        // We will scan through all of the available declarations
        // with the same mangled name as `originalVal` and try
        // to pick the "best" one for our target.

        RefPtr<IRSpecSymbol> sym;
        if( !context->getSymbols().TryGetValue(mangledName, sym) )
        {
            if(!originalVal)
                return nullptr;

            // This shouldn't happen!
            SLANG_UNEXPECTED("no matching values registered");
            UNREACHABLE_RETURN(cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone()));
        }

        // We will try to track the "best" declaration we can find.
        //
        // Generally, one declaration wil lbe better than another if it is
        // more specialized for the chosen target. Otherwise, we simply favor
        // definitions over declarations.
        //
        IRGlobalValue* bestVal = sym->irGlobalValue;
        for( auto ss = sym->nextWithSameName; ss; ss = ss->nextWithSameName )
        {
            IRGlobalValue* newVal = ss->irGlobalValue;
            if(isBetterForTarget(context, newVal, bestVal))
                bestVal = newVal;
        }

        // Check if we've already cloned this value, for the case where
        // we didn't have an original value (just a name), but we've
        // now found a representative value.
        if (!originalVal)
        {
            if (IRInst* clonedVal = findClonedValue(context, bestVal))
            {
                return cast<IRGlobalValue>(clonedVal);
            }
        }

        return cloneGlobalValueImpl(context, bestVal, IROriginalValuesForClone(sym));
    }

    IRGlobalValue* cloneGlobalValueWithMangledName(IRSpecContext* context, Name* mangledName)
    {
        return cloneGlobalValueWithMangledName(context, mangledName, nullptr);
    }

    // Clone a global value, where `originalVal` is one declaration/definition, but we might
    // have to consider others, in order to find the "best" version of the symbol.
    IRGlobalValue* cloneGlobalValue(IRSpecContext* context, IRGlobalValue* originalVal)
    {
        // We are being asked to clone a particular global value, but in
        // the IR that comes out of the front-end there could still
        // be multiple, target-specific, declarations of any given
        // global value, all of which share the same mangled name.
        return cloneGlobalValueWithMangledName(
            context,
            originalVal->mangledName,
            originalVal);
    }

    StructTypeLayout* getGlobalStructLayout(
        ProgramLayout*  programLayout);

    void insertGlobalValueSymbol(
        IRSharedSpecContext*    sharedContext,
        IRGlobalValue*          gv)
    {
        auto mangledName = gv->mangledName;

        // Don't try to register a symbol for global values
        // with no mangled name, since these represent symbols
        // that shouldn't get "linkage"
        if (!getText(mangledName).Length())
            return;

        RefPtr<IRSpecSymbol> sym = new IRSpecSymbol();
        sym->irGlobalValue = gv;

        RefPtr<IRSpecSymbol> prev;
        if (sharedContext->symbols.TryGetValue(mangledName, prev))
        {
            sym->nextWithSameName = prev->nextWithSameName;
            prev->nextWithSameName = sym;
        }
        else
        {
            sharedContext->symbols.Add(mangledName, sym);
        }
    }

    void insertGlobalValueSymbols(
        IRSharedSpecContext*    sharedContext,
        IRModule*               originalModule)
    {
        if (!originalModule)
            return;

        for(auto ii : originalModule->getGlobalInsts())
        {
            auto gv = as<IRGlobalValue>(ii);
            if (!gv)
                continue;
            insertGlobalValueSymbol(sharedContext, gv);
        }
    }

    void initializeSharedSpecContext(
        IRSharedSpecContext*    sharedContext,
        Session*                session,
        IRModule*               module,
        IRModule*               originalModule,
        CodeGenTarget           target)
    {

        SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage;
        sharedBuilder->module = nullptr;
        sharedBuilder->session = session;

        IRBuilder* builder = &sharedContext->builderStorage;
        builder->sharedBuilder = sharedBuilder;

        if( !module )
        {
            module = builder->createModule();
        }

        sharedBuilder->module = module;
        sharedContext->module = module;
        sharedContext->originalModule = originalModule;
        sharedContext->target = target;
        // We will populate a map with all of the IR values
        // that use the same mangled name, to make lookup easier
        // in other steps.
        insertGlobalValueSymbols(sharedContext, originalModule);
    }

    // implementation provided in parameter-binding.cpp
    RefPtr<ProgramLayout> specializeProgramLayout(
        TargetRequest * targetReq,
        ProgramLayout* programLayout,
        SubstitutionSet typeSubst);

    struct IRSpecializationState
    {
        ProgramLayout*      programLayout;
        CodeGenTarget       target;
        TargetRequest*      targetReq;

        IRModule* irModule = nullptr;
        RefPtr<ProgramLayout> newProgramLayout;

        IRSharedSpecContext sharedContextStorage;
        IRSpecContext contextStorage;

        IRSpecEnv globalEnv;

        IRSharedSpecContext* getSharedContext() { return &sharedContextStorage; }
        IRSpecContext* getContext() { return &contextStorage; }

        IRSpecializationState()
        {
            contextStorage.env = &globalEnv;
        }

        ~IRSpecializationState()
        {
            newProgramLayout = nullptr;
            contextStorage = IRSpecContext();
            sharedContextStorage = IRSharedSpecContext();
        }
    };

    IRSpecializationState* createIRSpecializationState(
        EntryPointRequest*  entryPointRequest,
        ProgramLayout*      programLayout,
        CodeGenTarget       target,
        TargetRequest*      targetReq)
    {
        IRSpecializationState* state = new IRSpecializationState();

        state->programLayout = programLayout;
        state->target = target;
        state->targetReq = targetReq;


        auto compileRequest = entryPointRequest->compileRequest;
        auto translationUnit = entryPointRequest->getTranslationUnit();
        auto originalIRModule = translationUnit->irModule;

        auto sharedContext = state->getSharedContext();
        initializeSharedSpecContext(
            sharedContext,
            compileRequest->mSession,
            nullptr,
            originalIRModule,
            target);

        state->irModule = sharedContext->module;

        // We also need to attach the IR definitions for symbols from
        // any loaded modules:
        for (auto loadedModule : compileRequest->loadedModulesList)
        {
            insertGlobalValueSymbols(sharedContext, loadedModule->irModule);
        }

        auto context = state->getContext();
        context->shared = sharedContext;
        context->builder = &sharedContext->builderStorage;

        // Now specialize the program layout using the substitution
        //
        // TODO: The specialization of the layout is conceptually an AST-level operations,
        // and shouldn't be done here in the IR at all.
        //
        RefPtr<ProgramLayout> newProgramLayout = specializeProgramLayout(
            targetReq,
            programLayout,
            SubstitutionSet(entryPointRequest->globalGenericSubst));

        // TODO: we need to register the (IR-level) arguments of the global generic parameters as the
        // substitutions for the generic parameters in the original IR.

        // applyGlobalGenericParamSubsitution(...);


        state->newProgramLayout = newProgramLayout;

        // Next, we want to optimize lookup for layout infromation
        // associated with global declarations, so that we can
        // look things up based on the IR values (using mangled names)
        auto globalStructLayout = getGlobalStructLayout(newProgramLayout);
        for (auto globalVarLayout : globalStructLayout->fields)
        {
            auto mangledName = compileRequest->mSession->getNameObj(getMangledName(globalVarLayout->varDecl));
            context->globalVarLayouts.AddIfNotExists(mangledName, globalVarLayout);
        }

        // for now, clone all unreferenced witness tables
        for (auto sym :context->getSymbols())
        {
            if (sym.Value->irGlobalValue->op == kIROp_WitnessTable)
                cloneGlobalValue(context, (IRWitnessTable*)sym.Value->irGlobalValue);
        }
        return state;
    }

    void destroyIRSpecializationState(IRSpecializationState* state)
    {
        delete state;
    }

    IRModule* getIRModule(IRSpecializationState* state)
    {
        return state->irModule;
    }

    IRGlobalValue* getSpecializedGlobalValueForDeclRef(
        IRSpecializationState*  state,
        DeclRef<Decl> const&    declRef)
    {
        // We will start be ensuring that we have code for
        // the declaration itself.
        auto decl = declRef.getDecl();
        auto mangledDeclName = getMangledName(decl);

        IRGlobalValue* irDeclVal = cloneGlobalValueWithMangledName(
            state->getContext(),
            state->getContext()->getModule()->session->getNameObj(mangledDeclName));
        if(!irDeclVal)
            return nullptr;

        // Now we need to deal with specializing the given
        // IR value based on the substitutions applied to
        // our declaration reference.

        if(!declRef.substitutions)
            return irDeclVal;

        SLANG_UNEXPECTED("unhandled");
        UNREACHABLE_RETURN(nullptr);
    }

    void specializeIRForEntryPoint(
        IRSpecializationState*  state,
        EntryPointRequest*  entryPointRequest,
        ExtensionUsageTracker*  extensionUsageTracker)
    {
        auto target = state->target;

        auto compileRequest = entryPointRequest->compileRequest;
        auto session = compileRequest->mSession;
        auto translationUnit = entryPointRequest->getTranslationUnit();
        auto originalIRModule = translationUnit->irModule;
        if (!originalIRModule)
        {
            // We should already have emitted IR for the original
            // translation unit, and it we don't have it, then
            // we are now in trouble.
            return;
        }

        auto context = state->getContext();
        auto newProgramLayout = state->newProgramLayout;

        auto entryPointLayout = findEntryPointLayout(newProgramLayout, entryPointRequest);


        // Next, we make sure to clone the global value for
        // the entry point function itself, and rely on
        // this step to recursively copy over anything else
        // it might reference.
        auto irEntryPoint = specializeIRForEntryPoint(context, entryPointRequest, entryPointLayout);

        // HACK: right now the bindings for global generic parameters are coming in
        // as part of the original IR module, and we need to make sure these get
        // copied over, even if they aren't referenced.
        //
        for(auto inst : originalIRModule->getGlobalInsts())
        {
            auto bindInst = as<IRBindGlobalGenericParam>(inst);
            if(!bindInst)
                continue;

            cloneValue(context, bindInst);
        }


        // TODO: *technically* we should consider the case where
        // we have global variables with initializers, since
        // these should get run whether or not the entry point
        // references them.

        // For GLSL only, we will need to perform "legalization" of
        // the entry point and any entry-point parameters.
        switch (target)
        {
        case CodeGenTarget::GLSL:
            {
                legalizeEntryPointForGLSL(
                    session,
                    context->getModule(),
                    irEntryPoint,
                    entryPointLayout,
                    &compileRequest->mSink,
                    extensionUsageTracker);
            }
            break;

        default:
            break;
        }
    }

    struct IRGenericSpecContext : IRSpecContextBase
    {
        IRSpecContextBase* parent = nullptr;

        IRSharedSpecContext* getShared() { return shared; }

        // Override the "maybe clone" logic so that we always clone
        virtual IRInst* maybeCloneValue(IRInst* originalVal) override;
    };

    IRInst* IRGenericSpecContext::maybeCloneValue(IRInst* originalVal)
    {
        if (parent)
        {
            return parent->maybeCloneValue(originalVal);
        }
        else
        {
            return originalVal;
        }
    }

    // See the work list for the generic spec context with
    // every relevant instruction from `inst` through its
    // descendents.
    void addToSpecializationWorkListRec(
        IRSharedGenericSpecContext* sharedContext,
        IRInst*                     inst)
    {
        if(auto genericInst = as<IRGeneric>(inst))
        {
            // We do *not* consider generics, or instructions nested under them.
            return;
        }
        else if(auto parentInst = as<IRParentInst>(inst))
        {
            // For a parent instruction, we will scan through its contents,
            // since that will be where the `specialize` instructions are

            for(auto child : parentInst->children)
            {
                addToSpecializationWorkListRec(sharedContext, child);
            }
        }
        else
        {
            // Default case: consider this instruction for specialization.
            sharedContext->addToWorkList(inst);
        }
    }

    IRInst* specializeGeneric(
        IRSharedGenericSpecContext* sharedContext,
        IRSpecContextBase*          parentContext,
        IRGeneric*                  genericVal,
        IRSpecialize*               specializeInst)
    {
        // First, we want to see if an existing specialization
        // has already been made. To do that we will need to
        // compute the mangled name of the specialized value,
        // so that we can look for existing declarations.
        String specMangledName = mangleSpecializedFuncName(getText(genericVal->mangledName), specializeInst);
        auto specMangledNameObj = sharedContext->module->session->getNameObj(specMangledName);

        // Now look up an existing symbol with a matching name
        RefPtr<IRSpecSymbol> symb;
        if (sharedContext->symbols.TryGetValue(specMangledNameObj, symb))
        {
            return symb->irGlobalValue;
        }

        // TODO: This is a terrible linear search, and we should
        // avoid it by building a dictionary ahead of time,
        // as is being done for the `IRSpecContext` used above.
        // We can probalby use the same basic context, actually.
        for(auto ii : sharedContext->module->getGlobalInsts())
        {
            auto gv = as<IRGlobalValue>(ii);
            if (!gv)
                continue;

            if (gv->mangledName == specMangledNameObj)
                return gv;
        }

        // If we get to this point, then we need to construct a
        // new IR value to represent the result of specialization.

        // We need to establish a new mapping from inst->inst to
        // handle the specialization, because we don't want the
        // clones we register in this pass to cause confusion
        // in later steps that might clone the same code.

        IRSpecEnv env;
        env.parent = &sharedContext->globalEnv;
        if (parentContext)
        {
            env.parent = parentContext->getEnv();
        }

        // The result of specialization should be inserted
        // into the global scope, at the same location as
        // the original generic.
        IRBuilder builderStorage;
        IRBuilder* builder = &builderStorage;
        builder->sharedBuilder = &sharedContext->sharedBuilderStorage;
        builder->setInsertBefore(genericVal);

        IRGenericSpecContext context;
        context.shared = sharedContext;
        context.parent = parentContext;
        context.builder = builder;
        context.env = &env;

        // Register the arguments of the `specialize` instruction to be used
        // as the "cloned" value for each of the parameters of the generic.
        //
        UInt argCounter = 0;
        for (auto param = genericVal->getFirstParam(); param; param = param->getNextParam())
        {
            UInt argIndex = argCounter++;
            SLANG_ASSERT(argIndex < specializeInst->getArgCount());

            IRInst* arg = specializeInst->getArg(argIndex);

            registerClonedValue(&context, arg, param);
        }

        // Okay, now we want to run through the body of the generic
        // and clone stuff into the parent scope (which had
        // better be the global scope).
        for (auto bb : genericVal->getBlocks())
        {
            // We expect a generic to only ever contain a single block.
            SLANG_ASSERT(bb == genericVal->getFirstBlock());

            for (auto ii : bb->getChildren())
            {
                // Skip parameters, since they were handled earlier.
                if (auto param = as<IRParam>(ii))
                    continue;

                // The last block of the generic is expected to end with
                // a `return` instruction for the specialized value that
                // comes out of the abstraction.
                //
                // We thus use that cloned value as the result of the
                // specialization step.
                if (auto returnValInst = as<IRReturnVal>(ii))
                {
                    auto clonedResult = cloneValue(&context, returnValInst->getVal());
                    if (auto clonedGlobalValue = as<IRGlobalValue>(clonedResult))
                    {
                        clonedGlobalValue->mangledName = specMangledNameObj;

                        // TODO: create a symbol for it and add it to the map.
                    }

                    return clonedResult;
                }

                // Otherwise, clone the instruction into the global scope
                IRInst* clonedInst = cloneInst(&context, context.builder, ii);

                // Now that we've cloned the instruction to a location outside
                // of a generic, we should consider whether it can now be specialized.
                addToSpecializationWorkListRec(sharedContext, clonedInst);
            }
        }

        // If we reach this point, something went wrong, because we
        // never encountered a `return` inside the body of the generic.
        SLANG_UNEXPECTED("no return from generic");
        UNREACHABLE_RETURN(nullptr);
    }

    // Find the value in the given witness table that
    // satisfies the given requirement (or return
    // null if not found).
    IRInst* findWitnessVal(
        IRWitnessTable* witnessTable,
        IRInst*         requirementKey)
    {
        // For now we will do a dumb linear search
        for( auto entry : witnessTable->getEntries() )
        {
            // If the keys matched, then we use the value from this entry.
            if (requirementKey == entry->requirementKey.get())
            {
                auto satisfyingVal = entry->satisfyingVal.get();
                return satisfyingVal;
            }
        }

        // No matching entry found.
        return nullptr;
    }

    static bool canSpecializeGeneric(
        IRGeneric*  generic)
    {
        IRGeneric* g = generic;
        for(;;)
        {
            auto val = findGenericReturnVal(g);
            if(!val)
                return false;

            if (auto nestedGeneric = as<IRGeneric>(val))
            {
                // The outer generic returns an *inner* generic
                // (so that multiple calls to `specialize` are
                // needed to resolve it). We should look at
                // what the nested generic returns to figure
                // out whether specialization is allowed.
                g = nestedGeneric;
                continue;
            }

            // We've found the leaf value that will be produced after
            // all of the specialization is done. Now we want to know
            // if that is a value suitable for actually specializing

            if (auto globalValue = as<IRGlobalValue>(val))
            {
                if (isDefinition(globalValue))
                    return true;
                return false;
            }
            else
            {
                // There might be other cases with a declaration-vs-definition
                // thing that we need to handle.

                return true;
            }
        }
    }

    // Add any instruction that uses `inst` to the work list,
    // so that it can be evaluated (or re-evaluated) for specialization.
    void addUsesToWorkList(
        IRSharedGenericSpecContext* sharedContext,
        IRInst*                     inst)
    {
        for(auto u = inst->firstUse; u; u = u->nextUse)
        {
            sharedContext->addToWorkList(u->getUser());
        }
    }

    void specializeGenericsForInst(
        IRSharedGenericSpecContext* sharedContext,
        IRInst*                     inst)
    {
        switch(inst->op)
        {
        default:
            // The default behavior is to do nothing.
            // An instruction is specialize-able once its operands
            // are specialized, and after that it is also safe
            // to consider the instruction specialized.
            break;

        case kIROp_Specialize:
            {
                // We have a `specialize` instruction, so lets see
                // whether we have an opportunity to perform the
                // specialization here and now.
                IRSpecialize* specInst = cast<IRSpecialize>(inst);

                // Look at the base of the `specialize`, and see if
                // it directly names a generic, so that we can apply
                // specialization here and now.
                auto baseVal = specInst->getBase();
                if(auto genericVal = as<IRGeneric>(baseVal))
                {
                    if (canSpecializeGeneric(genericVal))
                    {
                        // Okay, we have a candidate for specialization here.
                        //
                        // We will apply the specialization logic to the body of the generic,
                        // which will yield, e.g., a specialized `IRFunc`.
                        //
                        auto specializedVal = specializeGeneric(sharedContext, nullptr, genericVal, specInst);
                        //
                        // Then we will replace the use sites for the `specialize`
                        // instruction with uses of the specialized value.
                        //
                        addUsesToWorkList(sharedContext, specInst);
                        specInst->replaceUsesWith(specializedVal);
                        specInst->removeAndDeallocate();
                    }
                }
            }
            break;

        case kIROp_lookup_interface_method:
            {
                // We have a `lookup_interface_method` instruction,
                // so let's see whether it is a lookup in a known
                // witness table.
                IRLookupWitnessMethod* lookupInst = cast<IRLookupWitnessMethod>(inst);

                // We only want to deal with the case where the witness-table
                // argument points to a concrete global table (and not, e.g., a
                // `specialize` instruction that will yield a table)
                auto witnessTable = as<IRWitnessTable>(lookupInst->witnessTable.get());
                if(!witnessTable)
                    break;

                // Use the witness table to look up the value that
                // satisfies the requirement.
                auto requirementKey = lookupInst->getRequirementKey();
                auto satisfyingVal = findWitnessVal(witnessTable, requirementKey);
                // We expect to always find something, but lets just
                // be careful here.
                if(!satisfyingVal)
                    break;

                // If we get through all of the above checks, then we
                // have a (more) concrete method that implements the interface,
                // and so we should dispatch to that directly, rather than
                // use the `lookup_interface_method` instruction.
                addUsesToWorkList(sharedContext, lookupInst);
                lookupInst->replaceUsesWith(satisfyingVal);
                lookupInst->removeAndDeallocate();
            }
            break;
        }
    }

    static bool isInstSpecialized(
        IRSharedGenericSpecContext* sharedContext,
        IRInst*                     inst)
    {
        // If an instruction is still on our work list, then
        // it isn't specialized, and conversely we say that
        // if it *isn't* on the work list, it must be specialized.
        //
        // Note: if we end up with bugs in this logic, we could
        // maintain an explicit set of specialized insts instead.
        //
        return !sharedContext->workListSet.Contains(inst);
    }

    static bool canSpecializeInst(
        IRSharedGenericSpecContext* sharedContext,
        IRInst*                     inst)
    {
        // We can specialize an instruction once all its
        // operands are specialized.

        UInt operandCount = inst->getOperandCount();
        for(UInt ii = 0; ii < operandCount; ++ii)
        {
            IRInst* operand = inst->getOperand(ii);
            if(!isInstSpecialized(sharedContext, operand))
                return false;
        }
        return true;
    }

    // Go through the code in the module and try to identify
    // calls to generic functions where the generic arguments
    // are known, and specialize the callee based on those
    // known values.
    void specializeGenerics(
        IRModule*   module,
        CodeGenTarget target)
    {
        IRSharedGenericSpecContext sharedContextStorage;
        auto sharedContext = &sharedContextStorage;

        initializeSharedSpecContext(
            sharedContext,
            module->session,
            module,
            module,
            target);

        auto moduleInst = module->getModuleInst();

        // First things first, let's deal with any bindings for global generic parameters.
        for(auto inst : moduleInst->getChildren())
        {
            auto bindInst = as<IRBindGlobalGenericParam>(inst);
            if(!bindInst)
                continue;

            // HACK: Our current front-end emit logic can end up emitting multiple
            // `bindGlobalGeneric` instructions for the same parameter. This is
            // a buggy behavior, but a real fix would require refactoring the way
            // global generic arguments are specified today.
            //
            // For now we will do a sanity check to detect parameters that
            // have already been specialized.
            if( !as<IRGlobalGenericParam>(bindInst->getOperand(0)) )
            {
                // parameter operand is no longer a parameter, so it
                // seems things must have been specialized already.
                continue;
            }

            auto param = bindInst->getParam();
            auto val = bindInst->getVal();

            param->replaceUsesWith(val);
        }
        {
            // Now we will do a second pass to clean up the
            // generic parameters and their bindings.
            IRInst* next = nullptr;
            for(auto inst = moduleInst->getFirstChild(); inst; inst = next)
            {
                next = inst->getNextInst();

                switch(inst->op)
                {
                default:
                    break;

                case kIROp_GlobalGenericParam:
                case kIROp_BindGlobalGenericParam:
                    // A "bind" instruction should have no uses in the
                    // first place, and all the global generic parameters
                    // should have had their uses replaced.
                    SLANG_ASSERT(!inst->firstUse);
                    inst->removeAndDeallocate();
                    break;
                }
            }
        }

        // Our goal here is to find `specialize` instructions that
        // can be replaced with references to, e.g., a suitably
        // specialized function, and to resolve any `lookup_interface_method`
        // instructions to the concrete value fetched from a witness
        // table.
        //
        // We need to be careful of a few things:
        //
        // * It would not in general make sense to consider specialize-able
        //   instructions under an `IRGeneric`, since that could mean "specialziing"
        //   code to parameter values that are still unknown.
        //
        // * We *also* need to be careful not to specialize something when one
        //   or more of its inputs is also a `specialize` or `lookup_interface_method`
        //   instruction, because then we'd be propagating through non-concrete
        //   values.
        //
        // The approach we use here is to build a work list of instructions
        // that *can* become fully specialized, but aren't yet. Any
        // instruction on the work list will be considered to be "unspecialized"
        // and any instruction not on the work list is considered specialized.
        //
        // We will start by recursively walking all the instructions to add
        // the appropriate ones to  our work list:
        //
        addToSpecializationWorkListRec(sharedContext, moduleInst);

        // Now we are going to repeatedly walk our work list, and filter
        // it to create a new work list.
        List<IRInst*> workListCopy;
        for(;;)
        {
            // Swap out the work list on the context so we can
            // process it here without worrying about concurrent
            // modifications.
            workListCopy.Clear();
            workListCopy.SwapWith(sharedContext->workList);

            if(workListCopy.Count() == 0)
                break;

            for(auto inst : workListCopy)
            {
                // We need to check whether it is possible to specialize
                // the instruction yet (it might not be because its
                // operands haven't been specialized)
                if(!canSpecializeInst(sharedContext, inst))
                {
                    // Put it back on the fresh work list, so that
                    // we can re-consider it in another iteration.
                    sharedContext->workList.Add(inst);
                }
                else
                {
                    // Okay, perform any specialization step on this
                    // instruction that makes sense (which might be
                    // doing nothing).
                    specializeGenericsForInst(sharedContext, inst);

                    // Remove the instruction from consideration.
                    sharedContext->workListSet.Remove(inst);
                }
            }
        }

        // Once the work list has gone dry, we should have the invariant
        // that there are no `specialize` instructions inside of non-generic
        // functions that in turn reference a generic function, *except*
        // in the case where that generic is for a builtin function, in
        // which case we wouldn't want to specialize it anyway.
    }

    void applyGlobalGenericParamSubstitution(
        IRSpecContext*  /*context*/)
    {
        // TODO: we need to figure out how to apply this
    }


    void markConstExpr(
        IRBuilder*  builder,
        IRInst*     irValue)
    {
        // We will take an IR value with type `T`,
        // and turn it into one with type `@ConstExpr T`.

        // TODO: need to be careful if the value already has a rate
        // qualifier set.

        irValue->setFullType(
            builder->getRateQualifiedType(
                builder->getConstExprRate(),
                irValue->getDataType()));
    }
}
back to top