https://github.com/shader-slang/slang
Raw File
Tip revision: 3d063a7024e54340b6fed2af964ea2790056a3e3 authored by Tim Foley on 23 September 2020, 19:24:16 UTC
Fix a bug around byte-address buffer loads of vectors (#1557)
Tip revision: 3d063a7
slang-ir.cpp
// slang-ir.cpp
#include "slang-ir.h"
#include "slang-ir-insts.h"

#include "../core/slang-basic.h"

#include "slang-mangle.h"

namespace Slang
{
    struct IRSpecContext;

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!! DiagnosticSink Impls !!!!!!!!!!!!!!!!!!!!!

    SourceLoc const& getDiagnosticPos(IRInst* inst)
    {
        return inst->sourceLoc;
    }

    void printDiagnosticArg(StringBuilder& sb, IRInst* irObject)
    {
        if (auto nameHint = irObject->findDecoration<IRNameHintDecoration>())
            sb << nameHint->getName();
    }

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!! DiagnosticSink Impls !!!!!!!!!!!!!!!!!!!!!

    IRInst* cloneGlobalValueWithLinkage(
        IRSpecContext*          context,
        IRInst*                 originalVal,
        IRLinkageDecoration*    originalLinkage);

    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.
    //
    // NOTE! That this array is now constructed in such a way that looking up 
    // an entry from an op is fast, by keeping blocks of main, and pseudo ops in same order
    // as the ops themselves. Care must be taken to keep this constraint.
    static const IROpMapEntry kIROps[] =
    {
        
    // Main ops in order
#define INST(ID, MNEMONIC, ARG_COUNT, FLAGS)  \
    { kIROp_##ID, { #MNEMONIC, ARG_COUNT, FLAGS, } },
#include "slang-ir-inst-defs.h"

    // Invalid op sentinel value comes after all the valid ones
    { kIROp_Invalid,{ "invalid", 0, 0 } },
    };

    IROpInfo getIROpInfo(IROp opIn)
    {
        const int op = opIn & kIROpMeta_OpMask;
        if (op < kIROpCount)
        {
            // It's a main op
            const auto& entry = kIROps[op];
            SLANG_ASSERT(entry.op == op);
            return entry.info;
        }

        // Don't know what this is
        SLANG_ASSERT(!"Invalid op");
        SLANG_ASSERT(kIROps[kIROpCount].op == kIROp_Invalid);
        return kIROps[kIROpCount].info;
    }

    IROp findIROp(const UnownedStringSlice& name)
    {
        for (auto ee : kIROps)
        {
            if (name == ee.info.name)
                return ee.op;
        }

        return IROp(kIROp_Invalid);
    }

    

    //

    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();
        }
    }

    // IRInstListBase

    void IRInstListBase::Iterator::operator++()
    {
        if (inst)
        {
            inst = inst->next;
        }
    }

    IRInstListBase::Iterator IRInstListBase::begin() { return Iterator(first); }
    IRInstListBase::Iterator IRInstListBase::end() { return Iterator(last ? last->next : nullptr); }

    //

    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(IROp decorationOp)
    {
        for(auto dd : getDecorations())
        {
            if(dd->op == decorationOp)
                return dd;
        }
        return nullptr;
    }

    IROperandListBase IRInst::getAllAttrs()
    {
        // We assume as an invariant that all attributes appear at the end of the operand
        // list, after all the non-attribute operands.
        //
        // We will therefore define a range that ends at the end of the operand list ...
        //
        IRUse* end = getOperands() + getOperandCount();
        //
        // ... and begins after the last non-attribute operand.
        //
        IRUse* cursor = getOperands();
        while(cursor != end && !as<IRAttr>(cursor->get()))
            cursor++;

        return IROperandListBase(cursor, end);
    }

    // IRConstant

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

        case kIROp_IntLit:
            return static_cast<IRConstant*>(inst)->value.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;
    }

    // IRPtrTypeBase

    IRType* tryGetPointedToType(
        IRBuilder*  builder,
        IRType*     type)
    {
        if( auto rateQualType = as<IRRateQualifiedType>(type) )
        {
            type = rateQualType->getValueType();
        }

        // The "true" pointers and the pointer-like stdlib types are the easy cases.
        if( auto ptrType = as<IRPtrTypeBase>(type) )
        {
            return ptrType->getValueType();
        }
        else if( auto ptrLikeType = as<IRPointerLikeType>(type) )
        {
            return ptrLikeType->getElementType();
        }
        //
        // A more interesting case arises when we have a `BindExistentials<P<T>, ...>`
        // where `P<T>` is a pointer(-like) type.
        //
        else if( auto bindExistentials = as<IRBindExistentialsType>(type) )
        {
            // We know that `BindExistentials` won't introduce its own
            // existential type parameters, nor will any of the pointer(-like)
            // type constructors `P`.
            //
            // Thus we know that the type that is pointed to should be
            // the same as `BindExistentials<T, ...>`.
            //
            auto baseType = bindExistentials->getBaseType();
            if( auto baseElementType = tryGetPointedToType(builder, baseType) )
            {
                UInt existentialArgCount = bindExistentials->getExistentialArgCount();
                List<IRInst*> existentialArgs;
                for( UInt ii = 0; ii < existentialArgCount; ++ii )
                {
                    existentialArgs.add(bindExistentials->getExistentialArg(ii));
                }
                return builder->getBindExistentialsType(
                    baseElementType,
                    existentialArgCount,
                    existentialArgs.getBuffer());
            }
        }

        // TODO: We may need to handle other cases here.

        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)
    {
        // If there are any existing parameters,
        // then insert after the last of them.
        //
        if (auto lastParam = getLastParam())
        {
            param->insertAfter(lastParam);
        }
        //
        // Otherwise, if there are any existing
        // "ordinary" instructions, insert before
        // the first of them.
        //
        else if(auto firstOrdinary = getFirstOrdinaryInst())
        {
            param->insertBefore(firstOrdinary);
        }
        //
        // Otherwise the block currently has neither
        // parameters nor orindary instructions,
        // so we can safely insert at the end of
        // the list of (raw) children.
        //
        else
        {
            param->insertAtEnd(this);
        }
    }

    // Similar to addParam, but instead of appending `param` to the end
    // of the parameter list, this function inserts `param` before the
    // head of the list.
    void IRBlock::insertParamAtHead(IRParam* param)
    {
        if (auto firstParam = getFirstParam())
        {
            param->insertBefore(firstParam);
        }
        else if (auto firstOrdinary = getFirstOrdinaryInst())
        {
            param->insertBefore(firstOrdinary);
        }
        else
        {
            param->insertAtEnd(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_MissingReturn:
        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;
    }

    bool IRBlock::PredecessorList::isEmpty()
    {
        return !(begin() != end());
    }


    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();
    }

    UInt IRUnconditionalBranch::getArgCount()
    {
        switch(op)
        {
        case kIROp_unconditionalBranch:
            return getOperandCount() - 1;

        case kIROp_loop:
            return getOperandCount() - 3;

        default:
            SLANG_UNEXPECTED("unhandled unconditional branch opcode");
            UNREACHABLE_RETURN(0);
        }
    }

    IRUse* IRUnconditionalBranch::getArgs()
    {
        switch(op)
        {
        case kIROp_unconditionalBranch:
            return getOperands() + 1;

        case kIROp_loop:
            return getOperands() + 3;

        default:
            SLANG_UNEXPECTED("unhandled unconditional branch opcode");
            UNREACHABLE_RETURN(0);
        }
    }

    IRInst* IRUnconditionalBranch::getArg(UInt index)
    {
        return getArgs()[index].usedValue;
    }

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

        return entryBlock->getFirstParam();
    }

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

        return entryBlock->getLastParam();
    }

    IRInstList<IRParam> IRGlobalValueWithParams::getParams()
    {
        auto entryBlock = getFirstBlock();
        if(!entryBlock) return IRInstList<IRParam>();

        return entryBlock->getParams();
    }

    IRInst* IRGlobalValueWithParams::getFirstOrdinaryInst()
    {
        auto firstBlock = getFirstBlock();
        if (!firstBlock)
            return nullptr;
        return firstBlock->getFirstOrdinaryInst();
    }

    // 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);
    }

    void fixUpFuncType(IRFunc* func, IRType* resultType)
    {
        SLANG_ASSERT(func);

        auto irModule = func->getModule();
        SLANG_ASSERT(irModule);

        SharedIRBuilder sharedBuilder;
        sharedBuilder.module = irModule;

        IRBuilder builder;
        builder.sharedBuilder = &sharedBuilder;

        builder.setInsertBefore(func);

        List<IRType*> paramTypes;
        for(auto param : func->getParams())
        {
            paramTypes.add(param->getFullType());
        }

        auto funcType = builder.getFuncType(paramTypes, resultType);
        builder.setDataType(func, funcType);
    }

    void fixUpFuncType(IRFunc* func)
    {
        fixUpFuncType(func, func->getResultType());
    }

    //

    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:
        case kIROp_MissingReturn:
            return true;
        }
    }

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

    //
    // IRTypeLayout
    //

    IRTypeSizeAttr* IRTypeLayout::findSizeAttr(LayoutResourceKind kind)
    {
        // TODO: If we could assume the attributes were sorted
        // by `kind`, then we could use a binary search here
        // instead of linear.
        //
        // In practice, the number of entries will be very small,
        // so the cost of the linear search should not be too bad.

        for( auto sizeAttr : getSizeAttrs() )
        {
            if(sizeAttr->getResourceKind() == kind)
                return sizeAttr;
        }
        return nullptr;
    }

    IRTypeLayout* IRTypeLayout::unwrapArray()
    {
        auto typeLayout = this;
        while(auto arrayTypeLayout = as<IRArrayTypeLayout>(typeLayout))
            typeLayout = arrayTypeLayout->getElementTypeLayout();
        return typeLayout;
    }

    IRTypeLayout* IRTypeLayout::getPendingDataTypeLayout()
    {
        if(auto attr = findAttr<IRPendingLayoutAttr>())
            return cast<IRTypeLayout>(attr->getLayout());
        return nullptr;
    }

    IROperandList<IRTypeSizeAttr> IRTypeLayout::getSizeAttrs()
    {
        return findAttrs<IRTypeSizeAttr>();
    }

    IRTypeLayout::Builder::Builder(IRBuilder* irBuilder)
        : m_irBuilder(irBuilder)
    {}

    void IRTypeLayout::Builder::addResourceUsage(
        LayoutResourceKind  kind,
        LayoutSize          size)
    {
        auto& resInfo = m_resInfos[Int(kind)];
        resInfo.kind = kind;
        resInfo.size += size;
    }

    void IRTypeLayout::Builder::addResourceUsage(IRTypeSizeAttr* sizeAttr)
    {
        addResourceUsage(
            sizeAttr->getResourceKind(),
            sizeAttr->getSize());
    }

    void IRTypeLayout::Builder::addResourceUsageFrom(IRTypeLayout* typeLayout)
    {
        for( auto sizeAttr : typeLayout->getSizeAttrs() )
        {
            addResourceUsage(sizeAttr);
        }
    }

    IRTypeLayout* IRTypeLayout::Builder::build()
    {
        IRBuilder* irBuilder = getIRBuilder();

        List<IRInst*> operands;

        addOperands(operands);
        addAttrs(operands);

        return irBuilder->getTypeLayout(
            getOp(),
            operands);
    }

    void IRTypeLayout::Builder::addOperands(List<IRInst*>& operands)
    {
        addOperandsImpl(operands);
    }

    void IRTypeLayout::Builder::addAttrs(List<IRInst*>& operands)
    {
        auto irBuilder = getIRBuilder();

        for(auto resInfo : m_resInfos)
        {
            if(resInfo.kind == LayoutResourceKind::None)
                continue;

            IRInst* sizeAttr = irBuilder->getTypeSizeAttr(
                resInfo.kind,
                resInfo.size);
            operands.add(sizeAttr);
        }

        if( auto pendingTypeLayout = m_pendingTypeLayout )
        {
            operands.add(irBuilder->getPendingLayoutAttr(
                pendingTypeLayout));
        }

        addAttrsImpl(operands);
    }

    //
    // IRParameterGroupTypeLayout
    //

    void IRParameterGroupTypeLayout::Builder::addOperandsImpl(List<IRInst*>& ioOperands)
    {
        ioOperands.add(m_containerVarLayout);
        ioOperands.add(m_elementVarLayout);
        ioOperands.add(m_offsetElementTypeLayout);
    }

    IRParameterGroupTypeLayout* IRParameterGroupTypeLayout::Builder::build()
    {
        return cast<IRParameterGroupTypeLayout>(Super::Builder::build());
    }

    //
    // IRStructTypeLayout
    //

    void IRStructTypeLayout::Builder::addAttrsImpl(List<IRInst*>& ioOperands)
    {
        auto irBuilder = getIRBuilder();
        for(auto field : m_fields)
        {
            ioOperands.add(
                irBuilder->getFieldLayoutAttr(field.key, field.layout));
        }
    }

    //
    // IRArrayTypeLayout
    //

    void IRArrayTypeLayout::Builder::addOperandsImpl(List<IRInst*>& ioOperands)
    {
        ioOperands.add(m_elementTypeLayout);
    }

    //
    // IRStreamOutputTypeLayout
    //

    void IRStreamOutputTypeLayout::Builder::addOperandsImpl(List<IRInst*>& ioOperands)
    {
        ioOperands.add(m_elementTypeLayout);
    }

    //
    // IRMatrixTypeLayout
    //

    IRMatrixTypeLayout::Builder::Builder(IRBuilder* irBuilder, MatrixLayoutMode mode)
        : Super::Builder(irBuilder)
    {
        m_modeInst = irBuilder->getIntValue(irBuilder->getIntType(), IRIntegerValue(mode));
    }

    void IRMatrixTypeLayout::Builder::addOperandsImpl(List<IRInst*>& ioOperands)
    {
        ioOperands.add(m_modeInst);
    }

    //
    // IRTaggedUnionTypeLayout
    //

    IRTaggedUnionTypeLayout::Builder::Builder(IRBuilder* irBuilder, LayoutSize tagOffset)
        : Super::Builder(irBuilder)
    {
        m_tagOffset = irBuilder->getIntValue(irBuilder->getIntType(), tagOffset.raw);
    }

    void IRTaggedUnionTypeLayout::Builder::addCaseTypeLayout(IRTypeLayout* typeLayout)
    {
        m_caseTypeLayoutAttrs.add(getIRBuilder()->getCaseTypeLayoutAttr(typeLayout));
    }

    void IRTaggedUnionTypeLayout::Builder::addOperandsImpl(List<IRInst*>& ioOperands)
    {
        ioOperands.add(m_tagOffset);
    }

    void IRTaggedUnionTypeLayout::Builder::addAttrsImpl(List<IRInst*>& ioOperands)
    {
        for(auto attr : m_caseTypeLayoutAttrs)
            ioOperands.add(attr);
    }

    //
    // IRVarLayout
    //

    bool IRVarLayout::usesResourceKind(LayoutResourceKind kind)
    {
        // TODO: basing this check on whether or not the
        // var layout has an entry for `kind` means that
        // we can't just optimize away any entry where
        // the offset is zero (which might be a small
        // but nice optimization). We could consider shifting
        // this test to use the entries on the type layout
        // instead (since non-zero resource consumption
        // should be an equivalent test).

        return findOffsetAttr(kind) != nullptr;
    }

    IRSystemValueSemanticAttr* IRVarLayout::findSystemValueSemanticAttr()
    {
        return findAttr<IRSystemValueSemanticAttr>();
    }

    IRVarOffsetAttr* IRVarLayout::findOffsetAttr(LayoutResourceKind kind)
    {
        for( auto offsetAttr : getOffsetAttrs() )
        {
            if(offsetAttr->getResourceKind() == kind)
                return offsetAttr;
        }
        return nullptr;
    }

    IROperandList<IRVarOffsetAttr> IRVarLayout::getOffsetAttrs()
    {
        return findAttrs<IRVarOffsetAttr>();
    }

    Stage IRVarLayout::getStage()
    {
        if(auto stageAttr = findAttr<IRStageAttr>())
            return stageAttr->getStage();
        return Stage::Unknown;
    }

    IRVarLayout* IRVarLayout::getPendingVarLayout()
    {
        if( auto pendingLayoutAttr = findAttr<IRPendingLayoutAttr>() )
        {
            return cast<IRVarLayout>(pendingLayoutAttr->getLayout());
        }
        return nullptr;
    }

    IRVarLayout::Builder::Builder(
        IRBuilder*      irBuilder,
        IRTypeLayout*   typeLayout)
        : m_irBuilder(irBuilder)
        , m_typeLayout(typeLayout)
    {}

    bool IRVarLayout::Builder::usesResourceKind(LayoutResourceKind kind)
    {
        return m_resInfos[Int(kind)].kind != LayoutResourceKind::None;
    }

    IRVarLayout::Builder::ResInfo* IRVarLayout::Builder::findOrAddResourceInfo(LayoutResourceKind kind)
    {
        auto& resInfo = m_resInfos[Int(kind)];
        resInfo.kind = kind;
        return &resInfo;
    }

    void IRVarLayout::Builder::setSystemValueSemantic(String const& name, UInt index)
    {
        m_systemValueSemantic = getIRBuilder()->getSystemValueSemanticAttr(name, index);
    }

    void IRVarLayout::Builder::setUserSemantic(String const& name, UInt index)
    {
        m_userSemantic = getIRBuilder()->getUserSemanticAttr(name, index);
    }

    void IRVarLayout::Builder::setStage(Stage stage)
    {
        m_stageAttr = getIRBuilder()->getStageAttr(stage);
    }

    void IRVarLayout::Builder::cloneEverythingButOffsetsFrom(
        IRVarLayout* that)
    {
        if(auto systemValueSemantic = that->findAttr<IRSystemValueSemanticAttr>())
            m_systemValueSemantic = systemValueSemantic;

        if(auto userSemantic = that->findAttr<IRUserSemanticAttr>())
            m_userSemantic = userSemantic;

        if(auto stageAttr = that->findAttr<IRStageAttr>())
            m_stageAttr = stageAttr;
    }

    IRVarLayout* IRVarLayout::Builder::build()
    {
        SLANG_ASSERT(m_typeLayout);

        IRBuilder* irBuilder = getIRBuilder();

        List<IRInst*> operands;

        operands.add(m_typeLayout);

        for(auto resInfo : m_resInfos)
        {
            if(resInfo.kind == LayoutResourceKind::None)
                continue;

            IRInst* varOffsetAttr = irBuilder->getVarOffsetAttr(
                resInfo.kind,
                resInfo.offset,
                resInfo.space);
            operands.add(varOffsetAttr);
        }

        if(auto semanticAttr = m_userSemantic)
            operands.add(semanticAttr);

        if(auto semanticAttr = m_systemValueSemantic)
            operands.add(semanticAttr);

        if(auto stageAttr = m_stageAttr)
            operands.add(stageAttr);

        if(auto pendingVarLayout = m_pendingVarLayout)
        {
            IRInst* pendingLayoutAttr = irBuilder->getPendingLayoutAttr(
                pendingVarLayout);
            operands.add(pendingLayoutAttr);
        }

        return irBuilder->getVarLayout(operands);
    }

    //
    // IREntryPointLayout
    //

    IRStructTypeLayout* getScopeStructLayout(IREntryPointLayout* scopeLayout)
    {
        auto scopeTypeLayout = scopeLayout->getParamsLayout()->getTypeLayout();

        if( auto constantBufferTypeLayout = as<IRParameterGroupTypeLayout>(scopeTypeLayout) )
        {
            scopeTypeLayout = constantBufferTypeLayout->getOffsetElementTypeLayout();
        }

        if( auto structTypeLayout = as<IRStructTypeLayout>(scopeTypeLayout) )
        {
            return structTypeLayout;
        }

        SLANG_UNEXPECTED("uhandled global-scope binding layout");
        UNREACHABLE_RETURN(nullptr);
    }

    //

    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(IRInst* 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.
    //
    IRInst* mergeCandidateParentsForHoistableInst(IRInst* left, IRInst* 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.
        //
        IRInst* 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;
        }

        IRInst* 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;
    }

    IRInst* createEmptyInst(
        IRModule*   module,
        IROp        op,
        int         totalArgCount)
    {
        size_t size = sizeof(IRInst) + (totalArgCount) * sizeof(IRUse);

        SLANG_ASSERT(module);
        IRInst* inst = (IRInst*)module->memoryArena.allocateAndZero(size);

        inst->operandCount = uint32_t(totalArgCount);
        inst->op = op;

        return inst;
    }

    IRInst* createEmptyInstWithSize(
        IRModule*   module,
        IROp        op,
        size_t      totalSizeInBytes)
    {
        SLANG_ASSERT(totalSizeInBytes >= sizeof(IRInst));

        SLANG_ASSERT(module);
        IRInst* inst = (IRInst*)module->memoryArena.allocateAndZero(totalSizeInBytes);

        inst->operandCount = 0;
        inst->op = op;

        return inst;
    }

    // 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)
        IRInst* 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` represent 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->memoryArena.allocateAndZero(size);

        // TODO: Do we need to run ctor after zeroing?
        new(inst)T();

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

        inst->op = op;

        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]);
            }
            else
            {
                operand->init(inst, nullptr);
            }
            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++;
            }
        }
        return inst;
    }

    static IRInst* createInstWithSizeImpl(
        IRBuilder*      builder,
        IROp            op,
        IRType*         type,
        size_t          sizeInBytes)
    {
        auto module = builder->getModule();
        IRInst* inst = (IRInst*)module->memoryArena.allocate(sizeInBytes);
        // Zero only the 'type'
        memset(inst, 0, sizeof(IRInst));
        // TODO: Do we need to run ctor after zeroing?
        new (inst) IRInst;

        inst->op = op;
        if (type)
        {
            inst->typeUse.init(inst, type);
        }
        maybeSetSourceLoc(builder, 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;
    }

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

    UnownedStringSlice IRConstant::getStringSlice()
    {
        assert(op == kIROp_StringLit);
        // If the transitory decoration is set, then this is uses the transitoryStringVal for the text storage.
        // This is typically used when we are using a transitory IRInst held on the stack (such that it can be looked up in cached), 
        // that just points to a string elsewhere, and NOT the typical normal style, where the string is held after the instruction in memory.
        //
        if(findDecorationImpl(kIROp_TransitoryDecoration))
        {
            return UnownedStringSlice(value.transitoryStringVal.chars, value.transitoryStringVal.numChars);
        }
        else
        {
            return UnownedStringSlice(value.stringVal.chars, value.stringVal.numChars);
        }
    }

    bool IRConstant::isFinite() const
    {
        SLANG_ASSERT(op == kIROp_FloatLit);
        
        // Lets check we can analyze as double, at least in principal
        SLANG_COMPILE_TIME_ASSERT(sizeof(IRFloatingPointValue) == sizeof(double));
        // We are in effect going to type pun (yay!), lets make sure they are the same size
        SLANG_COMPILE_TIME_ASSERT(sizeof(IRIntegerValue) == sizeof(IRFloatingPointValue));

        const uint64_t i = uint64_t(value.intVal);
        int e = int(i >> 52) & 0x7ff;
        return (e != 0x7ff);
    }

    IRConstant::FloatKind IRConstant::getFloatKind() const
    {
        SLANG_ASSERT(op == kIROp_FloatLit);

        const uint64_t i = uint64_t(value.intVal);
        int e = int(i >> 52) & 0x7ff;
        if ( e == 0x7ff)
        {
            if (i << 12)
            {
                return FloatKind::Nan;
            }
            // Sign bit (top bit) will indicate positive or negative nan
            return value.intVal < 0 ? FloatKind::NegativeInfinity : FloatKind::PositiveInfinity;
        }
        return FloatKind::Finite;
    }

    bool IRConstant::isValueEqual(IRConstant* rhs)
    {
        // If they are literally the same thing.. 
        if (this == rhs)
        {
            return true;
        }
        // Check the type and they are the same op & same type
        if (op != rhs->op)
        {
            return false;
        }

        switch (op)
        {
            case kIROp_BoolLit:
            case kIROp_FloatLit:
            case kIROp_IntLit:
            {
                SLANG_COMPILE_TIME_ASSERT(sizeof(IRFloatingPointValue) == sizeof(IRIntegerValue));
                // ... we can just compare as bits
                return value.intVal == rhs->value.intVal;
            }
            case kIROp_PtrLit:
            {
                return value.ptrVal == rhs->value.ptrVal;
            }
            case kIROp_StringLit:
            {
                return getStringSlice() == rhs->getStringSlice();
            }
            default: break;
        }

        SLANG_ASSERT(!"Unhandled type");
        return false;
    }

    /// True if constants are equal
    bool IRConstant::equal(IRConstant* rhs)
    {
        // TODO(JS): Only equal if pointer types are identical (to match how getHashCode works below)
        return isValueEqual(rhs) && getFullType() == rhs->getFullType();
    }

    HashCode IRConstant::getHashCode()
    {
        auto code = Slang::getHashCode(op);
        code = combineHash(code, Slang::getHashCode(getFullType()));

        switch (op)
        {
            case kIROp_BoolLit:
            case kIROp_FloatLit:
            case kIROp_IntLit:
            {
                SLANG_COMPILE_TIME_ASSERT(sizeof(IRFloatingPointValue) == sizeof(IRIntegerValue));
                // ... we can just compare as bits
                return combineHash(code, Slang::getHashCode(value.intVal));
            }
            case kIROp_PtrLit:
            {
                return combineHash(code, Slang::getHashCode(value.ptrVal));
            }
            case kIROp_StringLit:
            {
                const UnownedStringSlice slice = getStringSlice();
                return combineHash(code, Slang::getHashCode(slice.begin(), slice.getLength()));
            }
            default:
            {
                SLANG_ASSERT(!"Invalid type");
                return 0;
            }
        }
    }

    static IRConstant* findOrEmitConstant(
        IRBuilder*      builder,
        IRConstant&     keyInst)
    {
        // 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.
        // The 'fake' instruction is passed in as keyInst.

        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;
        }
    
        // Calculate the minimum object size (ie not including the payload of value)    
        const size_t prefixSize = SLANG_OFFSET_OF(IRConstant, value);

        switch (keyInst.op)
        {
        default:
            SLANG_UNEXPECTED("missing case for IR constant");
            break;

            case kIROp_BoolLit:
            case kIROp_IntLit:
            {
                irValue = static_cast<IRConstant*>(createInstWithSizeImpl(builder, keyInst.op, keyInst.getFullType(), prefixSize + sizeof(IRIntegerValue)));
                irValue->value.intVal = keyInst.value.intVal;
                break; 
            }
            case kIROp_FloatLit:
            {
                irValue = static_cast<IRConstant*>(createInstWithSizeImpl(builder, keyInst.op, keyInst.getFullType(), prefixSize + sizeof(IRFloatingPointValue)));
                irValue->value.floatVal = keyInst.value.floatVal;
                break;
            }
            case kIROp_PtrLit:
            {
                irValue = static_cast<IRConstant*>(createInstWithSizeImpl(builder, keyInst.op, keyInst.getFullType(), prefixSize + sizeof(void*)));
                irValue->value.ptrVal = keyInst.value.ptrVal;
                break;
            }
            case kIROp_StringLit:
            {
                const UnownedStringSlice slice = keyInst.getStringSlice();

                const size_t sliceSize = slice.getLength();
                const size_t instSize = prefixSize + offsetof(IRConstant::StringValue, chars) + sliceSize; 

                irValue = static_cast<IRConstant*>(createInstWithSizeImpl(builder, keyInst.op, keyInst.getFullType(), instSize));

                IRConstant::StringValue& dstString = irValue->value.stringVal;

                dstString.numChars = uint32_t(sliceSize);
                // Turn into pointer to avoid warning of array overrun
                char* dstChars = dstString.chars;
                // Copy the chars
                memcpy(dstChars, slice.begin(), sliceSize); 

                break;
            }
        }

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

        addHoistableInst(builder, irValue);

        return irValue;
    }

    //

    IRInst* IRBuilder::getBoolValue(bool inValue)
    {
        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        keyInst.op = kIROp_BoolLit;
        keyInst.typeUse.usedValue = getBoolType();
        keyInst.value.intVal = IRIntegerValue(inValue);
        return findOrEmitConstant(this, keyInst);
    }

    IRInst* IRBuilder::getIntValue(IRType* type, IRIntegerValue inValue)
    {
        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        keyInst.op = kIROp_IntLit;
        keyInst.typeUse.usedValue = type;
        keyInst.value.intVal = inValue;
        return findOrEmitConstant(this, keyInst);
    }

    IRInst* IRBuilder::getFloatValue(IRType* type, IRFloatingPointValue inValue)
    {
        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        keyInst.op = kIROp_FloatLit;
        keyInst.typeUse.usedValue = type;
        keyInst.value.floatVal = inValue;
        return findOrEmitConstant(this, keyInst);
    }

    IRStringLit* IRBuilder::getStringValue(const UnownedStringSlice& inSlice)
    {
        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        
        // Mark that this is on the stack...
        IRDecoration stackDecoration;
        memset(&stackDecoration, 0, sizeof(stackDecoration));
        stackDecoration.op = kIROp_TransitoryDecoration;
        stackDecoration.insertAtEnd(&keyInst);
            
        keyInst.op = kIROp_StringLit;
        keyInst.typeUse.usedValue = getStringType();
        
        IRConstant::StringSliceValue& dstSlice = keyInst.value.transitoryStringVal;
        dstSlice.chars = const_cast<char*>(inSlice.begin());
        dstSlice.numChars = uint32_t(inSlice.getLength());

        return static_cast<IRStringLit*>(findOrEmitConstant(this, keyInst));
    }

    IRPtrLit* IRBuilder::getPtrValue(void* value)
    {
        IRType* type = getPtrType(getVoidType());

        IRConstant keyInst;
        memset(&keyInst, 0, sizeof(keyInst));
        keyInst.op = kIROp_PtrLit;
        keyInst.typeUse.usedValue = type;
        keyInst.value.ptrVal = value;
        return (IRPtrLit*) findOrEmitConstant(this, keyInst);
    }

    IRInst* IRBuilder::findOrEmitHoistableInst(
        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];
        }

        auto& memoryArena = getModule()->memoryArena;
        void* cursor = memoryArena.getCursor();

        // We are going to create a 'dummy' instruction on the memoryArena
        // which can 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* inst = (IRInst*) memoryArena.allocateAndZero(keySize);
        
        void* endCursor = memoryArena.getCursor();
        // Mark as 'unused' cos it is unused on release builds. 
        SLANG_UNUSED(endCursor);

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

        // Don't link up as we may free (if we already have this key)
        {
            IRUse* operand = inst->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++;
                }
            }
        }

        // Find or add the key/inst
        {
            IRInstKey key = { inst };

            // Ideally we would add if not found, else return if was found instead of testing & then adding.
            IRInst** found = sharedBuilder->globalValueNumberingMap.TryGetValueOrAdd(key, inst);
            SLANG_ASSERT(endCursor == memoryArena.getCursor());
            // If it's found, just return, and throw away the instruction
            if (found)
            {
                memoryArena.rewindToCursor(cursor);
                return *found;
            }
        }

        // Make the lookup 'inst' instruction into 'proper' instruction. Equivalent to
        // IRInst* inst = createInstImpl<IRInst>(builder, op, type, 0, nullptr, operandListCount, listOperandCounts, listOperands);
        {
            if (type)
            {
                inst->typeUse.usedValue = nullptr;
                inst->typeUse.init(inst, type);
            }

            maybeSetSourceLoc(this, inst);

            IRUse*const operands = inst->getOperands();
            for (UInt i = 0; i < operandCount; ++i)
            {
                IRUse& operand = operands[i];
                auto value = operand.usedValue;

                operand.usedValue = nullptr;
                operand.init(inst, value);
            }
        }

        addHoistableInst(this, inst);

        return inst;
    }

    IRInst* IRBuilder::findOrAddInst(
        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];
        }

        auto& memoryArena = getModule()->memoryArena;
        void* cursor = memoryArena.getCursor();

        // We are going to create a 'dummy' instruction on the memoryArena
        // which can 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* inst = (IRInst*)memoryArena.allocateAndZero(keySize);

        void* endCursor = memoryArena.getCursor();
        // Mark as 'unused' cos it is unused on release builds. 
        SLANG_UNUSED(endCursor);

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

        // Don't link up as we may free (if we already have this key)
        {
            IRUse* operand = inst->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++;
                }
            }
        }

        // Find or add the key/inst
        {
            IRInstKey key = { inst };

            // Ideally we would add if not found, else return if was found instead of testing & then adding.
            IRInst** found = sharedBuilder->globalValueNumberingMap.TryGetValueOrAdd(key, inst);
            SLANG_ASSERT(endCursor == memoryArena.getCursor());
            // If it's found, just return, and throw away the instruction
            if (found)
            {
                memoryArena.rewindToCursor(cursor);
                return *found;
            }
        }

        // Make the lookup 'inst' instruction into 'proper' instruction. Equivalent to
        // IRInst* inst = createInstImpl<IRInst>(builder, op, type, 0, nullptr, operandListCount, listOperandCounts, listOperands);
        {
            if (type)
            {
                inst->typeUse.usedValue = nullptr;
                inst->typeUse.init(inst, type);
            }

            maybeSetSourceLoc(this, inst);

            IRUse*const operands = inst->getOperands();
            for (UInt i = 0; i < operandCount; ++i)
            {
                IRUse& operand = operands[i];
                auto value = operand.usedValue;

                operand.usedValue = nullptr;
                operand.init(inst, value);
            }
        }

        addInst(inst);
        return inst;
    }


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

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

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


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

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

    IRType* IRBuilder::getType(
        IROp            op,
        IRInst*         operand0)
    {
        return getType(op, 1, &operand0);
    }

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

    IRBasicType* IRBuilder::getUIntType()
    {
        return (IRBasicType*)getType(kIROp_UIntType);
    }

    IRStringType* IRBuilder::getStringType()
    {
        return (IRStringType*)getType(kIROp_StringType);
    }

    IRDynamicType* IRBuilder::getDynamicType() { return (IRDynamicType*)getType(kIROp_DynamicType); }

    IRAssociatedType* IRBuilder::getAssociatedType(ArrayView<IRInterfaceType*> constraintTypes)
    {
        return (IRAssociatedType*)getType(kIROp_AssociatedType,
            constraintTypes.getCount(),
            (IRInst**)constraintTypes.getBuffer());
    }

    IRThisType* IRBuilder::getThisType(IRInterfaceType* interfaceType)
    {
        return (IRThisType*)getType(kIROp_ThisType, interfaceType);
    }

    IRRawPointerType* IRBuilder::getRawPointerType()
    {
        return (IRRawPointerType*)getType(kIROp_RawPointerType);
    }

    IRRTTIPointerType* IRBuilder::getRTTIPointerType(IRInst* rttiPtr)
    {
        return (IRRTTIPointerType*)getType(kIROp_RTTIPointerType, rttiPtr);
    }

    IRRTTIType* IRBuilder::getRTTIType()
    {
        return (IRRTTIType*)getType(kIROp_RTTIType);
    }

    IRAnyValueType* IRBuilder::getAnyValueType(IRIntegerValue size)
    {
        return (IRAnyValueType*)getType(kIROp_AnyValueType,
            getIntValue(getIntType(), size));
    }

    IRAnyValueType* IRBuilder::getAnyValueType(IRInst* size)
    {
        return (IRAnyValueType*)getType(kIROp_AnyValueType, size);
    }

    IRTupleType* IRBuilder::getTupleType(UInt count, IRType* const* types)
    {
        return (IRTupleType*)getType(kIROp_TupleType, count, (IRInst*const*)types);
    }

    IRTupleType* IRBuilder::getTupleType(IRType* type0, IRType* type1)
    {
        IRType* operands[] = { type0, type1 };
        return getTupleType(2, operands);
    }

    IRTupleType* IRBuilder::getTupleType(IRType* type0, IRType* type1, IRType* type2)
    {
        IRType* operands[] = { type0, type1, type2 };
        return getTupleType(3, operands);
    }

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

    IRExistentialBoxType* IRBuilder::getExistentialBoxType(IRType* concreteType, IRType* interfaceType)
    {
        IRInst* operands[] = {concreteType, interfaceType};
        return (IRExistentialBoxType*)getType(kIROp_ExistentialBoxType, 2, 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(
            nullptr,
            kIROp_FuncType,
            resultType,
            paramCount,
            (IRInst* const*) paramTypes);
    }

    IRWitnessTableType* IRBuilder::getWitnessTableType(
        IRType* baseType)
    {
        return (IRWitnessTableType*)findOrEmitHoistableInst(
            nullptr,
            kIROp_WitnessTableType,
            1,
            (IRInst* const*)&baseType);
    }

    IRConstantBufferType* IRBuilder::getConstantBufferType(IRType* elementType)
    {
        IRInst* operands[] = { elementType };
        return (IRConstantBufferType*) getType(
            kIROp_ConstantBufferType,
            1,
            operands);
    }

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

    IRType* IRBuilder::getTaggedUnionType(
        UInt            caseCount,
        IRType* const*  caseTypes)
    {
        return (IRType*) findOrEmitHoistableInst(
            getTypeKind(),
            kIROp_TaggedUnionType,
            caseCount,
            (IRInst* const*) caseTypes);
    }

    IRType* IRBuilder::getBindExistentialsType(
        IRInst*         baseType,
        UInt            slotArgCount,
        IRInst* const*  slotArgs)
    {
        if(slotArgCount == 0)
            return (IRType*) baseType;

        // If we are trying to bind an interface type, then
        // we will go ahead and simplify the instruction
        // away impmediately.
        // 
        if(as<IRInterfaceType>(baseType))
        {
            if(slotArgCount >= 1)
            {
                // We are being asked to emit `BindExistentials(someInterface, someConcreteType, ...)`
                // so we just want to return `ExistentialBox<someConcreteType>`.
                //
                auto concreteType = (IRType*) slotArgs[0];
                auto ptrType = getExistentialBoxType(concreteType, (IRType*)baseType);
                return ptrType;
            }
        }

        return (IRType*) findOrEmitHoistableInst(
            getTypeKind(),
            kIROp_BindExistentialsType,
            baseType,
            slotArgCount,
            (IRInst* const*) slotArgs);
    }

    IRType* IRBuilder::getBindExistentialsType(
        IRInst*         baseType,
        UInt            slotArgCount,
        IRUse const*    slotArgUses)
    {
        if(slotArgCount == 0)
            return (IRType*) baseType;

        List<IRInst*> slotArgs;
        for( UInt ii = 0; ii < slotArgCount; ++ii )
        {
            slotArgs.add(slotArgUses[ii].get());
        }
        return getBindExistentialsType(
            baseType,
            slotArgCount,
            slotArgs.getBuffer());
    }



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

    IRInst* IRBuilder::emitGetValueFromExistentialBox(IRType* type, IRInst* existentialBox)
    {
        auto inst =
            createInst<IRInst>(this, kIROp_GetValueFromExistentialBox, type, 1, &existentialBox);
        addInst(inst);
        return inst;
    }


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

        addInst(inst);

        return inst;
    }

    IRInst* IRBuilder::emitExtractExistentialValue(
        IRType* type,
        IRInst* existentialValue)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_ExtractExistentialValue,
            type,
            1,
            &existentialValue);
        addInst(inst);
        return inst;
    }

    IRType* IRBuilder::emitExtractExistentialType(
        IRInst* existentialValue)
    {
        auto type = getTypeKind();
        auto inst = createInst<IRInst>(
            this,
            kIROp_ExtractExistentialType,
            type,
            1,
            &existentialValue);
        addInst(inst);
        return (IRType*) inst;
    }

    IRInst* IRBuilder::emitExtractExistentialWitnessTable(
        IRInst* existentialValue)
    {
        auto type = getWitnessTableType(existentialValue->getDataType());
        auto inst = createInst<IRInst>(
            this,
            kIROp_ExtractExistentialWitnessTable,
            type,
            1,
            &existentialValue);
        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::emitAlloca(IRInst* type, IRInst* rttiObjPtr)
    {
        auto inst = createInst<IRAlloca>(
            this,
            kIROp_Alloca,
            (IRType*)type,
            rttiObjPtr);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitCopy(IRInst* dst, IRInst* src, IRInst* rttiObjPtr)
    {
        IRInst* args[] = { dst, src, rttiObjPtr };
        auto inst = createInst<IRCopy>(
            this,
            kIROp_Copy,
            getVoidType(),
            3,
            args);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitPackAnyValue(IRType* type, IRInst* value)
    {
        auto inst = createInst<IRPackAnyValue>(
            this,
            kIROp_PackAnyValue,
            type,
            value);

        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitUnpackAnyValue(IRType* type, IRInst* value)
    {
        auto inst = createInst<IRPackAnyValue>(
            this,
            kIROp_UnpackAnyValue,
            type,
            value);

        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::createIntrinsicInst(
        IRType*         type,
        IROp            op,
        UInt            argCount,
        IRInst* const* args)
    {
        return createInstWithTrailingArgs<IRInst>(
            this,
            op,
            type,
            argCount,
            args);
    }


    IRInst* IRBuilder::emitIntrinsicInst(
        IRType*         type,
        IROp            op,
        UInt            argCount,
        IRInst* const* args)
    {
        auto inst = createIntrinsicInst(
            type,
            op,
            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::emitMakeRTTIObject(IRInst* typeInst)
    {
        auto inst = createInst<IRRTTIObject>(
            this,
            kIROp_RTTIObject,
            getRTTIType(),
            typeInst);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitMakeTuple(IRType* type, UInt count, IRInst* const* args)
    {
        return emitIntrinsicInst(type, kIROp_MakeTuple, count, args);
    }

    IRInst* IRBuilder::emitGetTupleElement(IRType* type, IRInst* tuple, UInt element)
    {
        IRInst* args[] = { tuple, getIntValue(getIntType(), element) };
        return emitIntrinsicInst(type, kIROp_GetTupleElement, 2, args);
    }

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

    IRInst* IRBuilder::emitMakeMatrix(
        IRType*         type,
        UInt            argCount,
        IRInst* const* args)
    {
        return emitIntrinsicInst(type, kIROp_MakeMatrix, 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);
    }

    IRInst* IRBuilder::emitMakeExistential(
        IRType* type,
        IRInst* value,
        IRInst* witnessTable)
    {
        IRInst* args[] = {value, witnessTable};
        return emitIntrinsicInst(type, kIROp_MakeExistential, SLANG_COUNT_OF(args), args);
    }

    IRInst* IRBuilder::emitMakeExistentialWithRTTI(
        IRType* type,
        IRInst* value,
        IRInst* witnessTable,
        IRInst* rtti)
    {
        IRInst* args[] = { value, witnessTable, rtti };
        return emitIntrinsicInst(type, kIROp_MakeExistentialWithRTTI, SLANG_COUNT_OF(args), args);
    }

    IRInst* IRBuilder::emitWrapExistential(
        IRType*         type,
        IRInst*         value,
        UInt            slotArgCount,
        IRInst* const*  slotArgs)
    {
        if(slotArgCount == 0)
            return value;

        // If we are wrapping a single concrete value into
        // an interface type, then this is really a `makeExistential`
        //
        // TODO: We may want to check for a `specialize` of a generic interface as well.
        //
        if(as<IRInterfaceType>(type))
        {
            if(slotArgCount >= 2)
            {
                // We are being asked to emit `wrapExistential(value, concreteType, witnessTable, ...) : someInterface`
                //
                // We also know that a concrete value being wrapped will always be an existential box,
                // so we expect that `value : ExistentialBox<T>` for some `T`.
                //
                // We want to emit `makeExistential(load(value), witnessTable)`.
                //
                auto deref = emitLoad(value);
                return emitMakeExistential(type, deref, slotArgs[1]);
            }
        }

        IRInst* fixedArgs[] = {value};
        auto inst = createInstImpl<IRInst>(
            this,
            kIROp_WrapExistential,
            type,
            SLANG_COUNT_OF(fixedArgs),
            fixedArgs,
            slotArgCount,
            slotArgs);
        addInst(inst);
        return inst;
    }

    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,
        IRInst*     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;
    }

    IRGlobalParam* IRBuilder::createGlobalParam(
        IRType* valueType)
    {
        IRGlobalParam* inst = createInst<IRGlobalParam>(
            this,
            kIROp_GlobalParam,
            valueType);
        maybeSetSourceLoc(this, inst);
        addGlobalValue(this, inst);
        return inst;
    }

    IRWitnessTable* IRBuilder::createWitnessTable(IRType* baseType)
    {
        IRWitnessTable* witnessTable = createInst<IRWitnessTable>(
            this,
            kIROp_WitnessTable,
            getWitnessTableType(baseType));
        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;
    }

    IRInterfaceRequirementEntry* IRBuilder::createInterfaceRequirementEntry(
        IRInst* requirementKey,
        IRInst* requirementVal)
    {
        IRInterfaceRequirementEntry* entry = createInst<IRInterfaceRequirementEntry>(
            this,
            kIROp_InterfaceRequirementEntry,
            nullptr,
            requirementKey,
            requirementVal);
        addGlobalValue(this, entry);
        return entry;
    }

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

    IRInterfaceType* IRBuilder::createInterfaceType(UInt operandCount, IRInst* const* operands)
    {
        IRInterfaceType* interfaceType = createInst<IRInterfaceType>(
            this,
            kIROp_InterfaceType,
            getTypeKind(),
            operandCount,
            operands);
        addGlobalValue(this, interfaceType);
        return interfaceType;
    }

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

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

    void IRBuilder::insertBlock(IRBlock* block)
    {
        // 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.
        //
        // TODO: This should probably insert the block
        // after the current "insert into" block if
        // there is one. Right now we are always
        // adding the block to the end of the list,
        // which is technically valid (the ordering
        // of blocks doesn't affect the CFG topology),
        // but some later passes might assume the ordering
        // is significant in representing the intent
        // of the original code.
        //
        auto f = getFunc();
        if (f)
        {
            f->addBlock(block);
            setInsertInto(block);
        }
    }

    IRBlock* IRBuilder::emitBlock()
    {
        auto block = createBlock();
        insertBlock(block);
        return block;
    }

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

    IRParam* IRBuilder::emitParamAtHead(
        IRType* type)
    {
        auto param = createParam(type);
        if (auto bb = getBlock())
        {
            bb->insertParamAtHead(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(
        IRType* type,
        IRInst* ptr)
    {
        auto inst = createInst<IRLoad>(
            this,
            kIROp_Load,
            type,
            ptr);

        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 = tryGetPointedToType(this, ptr->getFullType());
        SLANG_ASSERT(valueType);

        // 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();
        }

        return emitLoad(valueType, ptr);
    }

    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::emitGetAddress(
        IRType* type,
        IRInst* value)
    {
        auto inst = createInst<IRGetAddress>(
            this,
            kIROp_getAddr,
            type,
            value);

        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::emitMissingReturn()
    {
        auto inst = createInst<IRMissingReturn>(
            this,
            kIROp_MissingReturn,
            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(
        IRType* type)
    {
        IRGlobalGenericParam* irGenericParam = createInst<IRGlobalGenericParam>(
            this,
            kIROp_GlobalGenericParam,
            type);
        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;
    }

    IRDecoration* IRBuilder::addBindExistentialSlotsDecoration(
        IRInst*         value,
        UInt            argCount,
        IRInst* const*  args)
    {
        auto decoration = createInstWithTrailingArgs<IRDecoration>(
            this,
            kIROp_BindExistentialSlotsDecoration,
            getVoidType(),
            0,
            nullptr,
            argCount,
            args);

        decoration->insertAtStart(value);

        return decoration;
    }

    IRInst* IRBuilder::emitExtractTaggedUnionTag(
        IRInst* val)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_ExtractTaggedUnionTag,
            getBasicType(BaseType::UInt),
            val);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitExtractTaggedUnionPayload(
        IRType* type,
        IRInst* val,
        IRInst* tag)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_ExtractTaggedUnionPayload,
            type,
            val,
            tag);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitBitCast(
        IRType* type,
        IRInst* val)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_BitCast,
            type,
            val);
        addInst(inst);
        return inst;
    }

    IRGlobalConstant* IRBuilder::emitGlobalConstant(
        IRType* type)
    {
        auto inst = createInst<IRGlobalConstant>(
            this,
            kIROp_GlobalConstant,
            type);
        addInst(inst);
        return inst;
    }

    IRGlobalConstant* IRBuilder::emitGlobalConstant(
        IRType* type,
        IRInst* val)
    {
        auto inst = createInst<IRGlobalConstant>(
            this,
            kIROp_GlobalConstant,
            type,
            val);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitWaveMaskBallot(IRType* type, IRInst* mask, IRInst* condition)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_WaveMaskBallot,
            type,
            mask,
            condition);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitWaveMaskMatch(IRType* type, IRInst* mask, IRInst* value)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_WaveMaskMatch,
            type,
            mask,
            value);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitBitAnd(IRType* type, IRInst* left, IRInst* right)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_BitAnd,
            type,
            left,
            right);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitBitNot(IRType* type, IRInst* value)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_BitNot,
            type,
            value);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitAdd(IRType* type, IRInst* left, IRInst* right)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_Add,
            type,
            left,
            right);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitMul(IRType* type, IRInst* left, IRInst* right)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_Mul,
            type,
            left,
            right);
        addInst(inst);
        return inst;
    }

    IRInst* IRBuilder::emitGpuForeach(List<IRInst*> args)
    {
        auto inst = createInst<IRInst>(
            this,
            kIROp_GpuForeach,
            getVoidType(),
            args.getCount(),
            args.getBuffer());
        addInst(inst);
        return inst;
    }

    //
    // Decorations
    //

    IRDecoration* IRBuilder::addDecoration(IRInst* value, IROp op, IRInst* const* operands, Int operandCount)
    {
        auto decoration = createInstWithTrailingArgs<IRDecoration>(
            this,
            op,
            getVoidType(),
            operandCount,
            operands);

        // Decoration order should not, in general, be semantically
        // meaningful, so we will elect to insert a new decoration
        // at the start of an instruction (constant time) rather
        // than at the end of any existing list of deocrations
        // (which would take time linear in the number of decorations).
        //
        // TODO: revisit this if maintaining decoration ordering
        // from input source code is desirable.
        //
        decoration->insertAtStart(value);

        return decoration;
    }


    void IRBuilder::addHighLevelDeclDecoration(IRInst* inst, Decl* decl)
    {
        auto ptrConst = getPtrValue(decl);
        addDecoration(inst, kIROp_HighLevelDeclDecoration, ptrConst);
    }

    void IRBuilder::addLayoutDecoration(IRInst* value, IRLayout* layout)
    {
        addDecoration(value, kIROp_LayoutDecoration, layout);
    }

    IRTypeSizeAttr* IRBuilder::getTypeSizeAttr(
        LayoutResourceKind kind,
        LayoutSize size)
    {
        auto kindInst = getIntValue(getIntType(), IRIntegerValue(kind));
        auto sizeInst = getIntValue(getIntType(), IRIntegerValue(size.raw));

        IRInst* operands[] = { kindInst, sizeInst };

        return cast<IRTypeSizeAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_TypeSizeAttr,
            SLANG_COUNT_OF(operands),
            operands));
    }

    IRVarOffsetAttr* IRBuilder::getVarOffsetAttr(
        LayoutResourceKind  kind,
        UInt                offset,
        UInt                space)
    {
        IRInst* operands[3];
        UInt operandCount = 0;

        auto kindInst = getIntValue(getIntType(), IRIntegerValue(kind));
        operands[operandCount++] = kindInst;

        auto offsetInst = getIntValue(getIntType(), IRIntegerValue(offset));
        operands[operandCount++] = offsetInst;

        if(space)
        {
            auto spaceInst = getIntValue(getIntType(), IRIntegerValue(space));
            operands[operandCount++] = spaceInst;
        }

        return cast<IRVarOffsetAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_VarOffsetAttr,
            operandCount,
            operands));
    }

    IRPendingLayoutAttr* IRBuilder::getPendingLayoutAttr(
        IRLayout* pendingLayout)
    {
        IRInst* operands[] = { pendingLayout };

        return cast<IRPendingLayoutAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_PendingLayoutAttr,
            SLANG_COUNT_OF(operands),
            operands));
    }

    IRStructFieldLayoutAttr* IRBuilder::getFieldLayoutAttr(
        IRInst*         key,
        IRVarLayout*    layout)
    {
        IRInst* operands[] = { key, layout };

        return cast<IRStructFieldLayoutAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_StructFieldLayoutAttr,
            SLANG_COUNT_OF(operands),
            operands));
    }

    IRCaseTypeLayoutAttr* IRBuilder::getCaseTypeLayoutAttr(
        IRTypeLayout*   layout)
    {
        IRInst* operands[] = { layout };

        return cast<IRCaseTypeLayoutAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_CaseTypeLayoutAttr,
            SLANG_COUNT_OF(operands),
            operands));
    }

    IRSemanticAttr* IRBuilder::getSemanticAttr(
        IROp            op,
        String const&   name,
        UInt            index)
    {
        auto nameInst = getStringValue(name.getUnownedSlice());
        auto indexInst = getIntValue(getIntType(), index);

        IRInst* operands[] = { nameInst, indexInst };

        return cast<IRSemanticAttr>(findOrEmitHoistableInst(
            getVoidType(),
            op,
            SLANG_COUNT_OF(operands),
            operands));
    }

    IRStageAttr* IRBuilder::getStageAttr(Stage stage)
    {
        auto stageInst = getIntValue(getIntType(), IRIntegerValue(stage));
        IRInst* operands[] = { stageInst };
        return cast<IRStageAttr>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_StageAttr,
            SLANG_COUNT_OF(operands),
            operands));
    }


    IRTypeLayout* IRBuilder::getTypeLayout(IROp op, List<IRInst*> const& operands)
    {
        return cast<IRTypeLayout>(findOrEmitHoistableInst(
            getVoidType(),
            op,
            operands.getCount(),
            operands.getBuffer()));
    }

    IRVarLayout* IRBuilder::getVarLayout(List<IRInst*> const& operands)
    {
        return cast<IRVarLayout>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_VarLayout,
            operands.getCount(),
            operands.getBuffer()));
    }

    IREntryPointLayout* IRBuilder::getEntryPointLayout(
        IRVarLayout* paramsLayout,
        IRVarLayout* resultLayout)
    {
        IRInst* operands[] = { paramsLayout, resultLayout };

        return cast<IREntryPointLayout>(findOrEmitHoistableInst(
            getVoidType(),
            kIROp_EntryPointLayout,
            SLANG_COUNT_OF(operands),
            operands));
    }

    //

    struct IRDumpContext
    {
        StringBuilder*  builder = nullptr;
        int             indent  = 0;
        IRDumpMode      mode    = IRDumpMode::Simplified;

        Dictionary<IRInst*, String> mapValueToName;
        Dictionary<String, UInt>    uniqueNameCounters;
        UInt                        uniqueIDCounter = 1;
    };

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

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

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

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

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

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

    bool opHasResult(IRInst* inst)
    {
        auto type = inst->getDataType();
        if (!type) return false;

        // As a bit of a hack right now, we need to check whether
        // the function returns the distinguished `Void` type,
        // since that is conceptually the same as "not returning
        // a value."
        if(type->op == kIROp_VoidType)
            return false;

        return true;
    }

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

    static void scrubName(
        String const& name,
        StringBuilder&  sb)
    {
        // Note: this function duplicates a lot of the logic
        // in `EmitVisitor::scrubName`, so we should consider
        // whether they can share code at some point.
        //
        // There is no requirement that assembly dumps and output
        // code follow the same model, though, so this is just
        // a nice-to-have rather than a maintenance problem
        // waiting to happen.

        // Allow an empty nam
        // Special case a name that is the empty string, just in case.
        if(name.getLength() == 0)
        {
            sb.append('_');
        }

        int prevChar = -1;
        for(auto c : name)
        {
            if(c == '.')
            {
                c = '_';
            }

            if(((c >= 'a') && (c <= 'z'))
                || ((c >= 'A') && (c <= 'Z')))
            {
                // Ordinary ASCII alphabetic characters are assumed
                // to always be okay.
            }
            else if((c >= '0') && (c <= '9'))
            {
                // We don't want to allow a digit as the first
                // byte in a name.
                if(prevChar == -1)
                {
                    sb.append('_');
                }
            }
            else
            {
                // If we run into a character that wouldn't normally
                // be allowed in an identifier, we need to translate
                // it into something that *is* valid.
                //
                // Our solution for now will be very clumsy: we will
                // emit `x` and then the hexadecimal version of
                // the byte we were given.
                sb.append("x");
                sb.append(uint32_t((unsigned char) c), 16);

                // We don't want to apply the default handling below,
                // so skip to the top of the loop now.
                prevChar = c;
                continue;
            }

            sb.append(c);
            prevChar = c;
        }

        // If the whole thing ended with a digit, then add
        // a final `_` just to make sure that we can append
        // a unique ID suffix without risk of collisions.
        if(('0' <= prevChar) && (prevChar <= '9'))
        {
            sb.append('_');
        }
    }

    static String createName(
        IRDumpContext*  context,
        IRInst*         value)
    {
        if(auto nameHintDecoration = value->findDecoration<IRNameHintDecoration>())
        {
            String nameHint = nameHintDecoration->getName();

            StringBuilder sb;
            scrubName(nameHint, sb);

            String key = sb.ProduceString();
            UInt count = 0;
            context->uniqueNameCounters.TryGetValue(key, count);

            context->uniqueNameCounters[key] = count+1;

            if(count)
            {
                sb.append(count);
            }
            return sb.ProduceString();
        }
        else
        {
            StringBuilder sb;
            auto id = context->uniqueIDCounter++;
            sb.append(id);
            return sb.ProduceString();
        }
    }

    static String getName(
        IRDumpContext*  context,
        IRInst*         value)
    {
        String name;
        if (context->mapValueToName.TryGetValue(value, name))
            return name;

        name = createName(context, value);
        context->mapValueToName.Add(value, name);
        return name;
    }

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

        if( opHasResult(inst) || instHasUses(inst) )
        {
            dump(context, "%");
            dump(context, getName(context, inst));
        }
        else
        {
            dump(context, "_");
        }
    }


    
    struct StringEncoder
    {
        static char getHexChar(int v)
        {
            return (v <= 9) ? char(v + '0') : char(v - 10 + 'A');
        }

        void flush(const char* pos)
        {
            if (pos > m_runStart)
            {
                m_builder->append(m_runStart, pos);
            }
            m_runStart = pos + 1;
        }

        void appendEscapedChar(const char* pos, char encodeChar)
        {
            flush(pos);
            const char chars[] = { '\\', encodeChar };
            m_builder->Append(chars, 2);
        }
        
        void appendAsHex(const char* pos)
        {
            flush(pos);

            const int v = *(const uint8_t*)pos;

            char buf[5];
            buf[0] = '\\';
            buf[1] = 'x';
            buf[2] = '0';

            buf[3] = getHexChar(v >> 4);
            buf[4] = getHexChar(v & 0xf);

            m_builder->Append(buf, 5);
        }

        StringEncoder(StringBuilder* builder, const char* start):
            m_runStart(start),
            m_builder(builder)
        {}

        StringBuilder* m_builder;
        const char* m_runStart;
    };

    static void dumpEncodeString(
        IRDumpContext*  context, 
        const UnownedStringSlice& slice)
    {
        // https://msdn.microsoft.com/en-us/library/69ze775t.aspx

        StringBuilder& builder = *context->builder;
        builder.Append('"');
        
        {
            const char* cur = slice.begin();
            StringEncoder encoder(&builder, cur);
            const char* end = slice.end();

            for (; cur < end; cur++)
            {
                const int8_t c = uint8_t(*cur);
                switch (c)
                {
                    case '\\':
                        encoder.appendEscapedChar(cur, '\\');
                        break;
                    case '"':
                        encoder.appendEscapedChar(cur, '"');
                        break;
                    case '\n': 
                        encoder.appendEscapedChar(cur, 'n');
                        break;
                    case '\t':
                        encoder.appendEscapedChar(cur, 't');
                        break;
                    case '\r':
                        encoder.appendEscapedChar(cur, 'r');
                        break;
                    case '\0':
                        encoder.appendEscapedChar(cur, '0');
                        break;
                    default:
                    {
                        if (c < 32)
                        {
                            encoder.appendAsHex(cur);
                        }
                        break;
                    }
                }
            }
            encoder.flush(end);
        }
        
        builder.Append('"');
    }

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

    static bool shouldFoldInstIntoUses(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        // Never fold an instruction into its use site
        // in the "detailed" mode, so that we always
        // accurately reflect the structure of the IR.
        //
        if(context->mode == IRDumpMode::Detailed)
            return false;

        if(as<IRConstant>(inst))
            return true;

        // We are going to have a general rule that
        // a type should be folded into its use site,
        // which improves output in most cases, but
        // we would like to not apply that rule to
        // "nominal" types like `struct`s.
        //
        switch( inst->op )
        {
        case kIROp_StructType:
        case kIROp_InterfaceType:
            return false;

        default:
            break;
        }

        if(as<IRType>(inst))
            return true;

        return false;
    }

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

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

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

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

        if(shouldFoldInstIntoUses(context, inst))
        {
            dumpInstExpr(context, inst);
            return;
        }

        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);

    }

    void dumpIRDecorations(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        for(auto dd : inst->getDecorations())
        {
            dump(context, "[");
            dumpInstBody(context, dd);
            dump(context, "]\n");

            dumpIndent(context);
        }
    }

    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);
                dumpIRDecorations(context, param);
                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 dumpIRGlobalValueWithCode(
        IRDumpContext*          context,
        IRGlobalValueWithCode*  code)
    {
        auto opInfo = getIROpInfo(code->op);

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

    static void dumpInstOperandList(
        IRDumpContext* context,
        IRInst* inst)
    {
        UInt argCount = inst->getOperandCount();

        if (argCount == 0)
            return;

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

        dump(context, ")");
    }

    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,
        IRInst*         inst)
    {
        auto opInfo = getIROpInfo(inst->op);

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

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

        dumpInstOperandList(context, inst);

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

        dump(context, "\n");

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

        for(auto child : inst->getChildren())
        {
            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 dumpInstExpr(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        if (!inst)
        {
            dump(context, "<null>");
            return;
        }

        auto op = inst->op;
        auto opInfo = getIROpInfo(op);

        // Special-case the literal instructions.
        if(auto irConst = as<IRConstant>(inst))
        {
            switch (op)
            {
            case kIROp_IntLit:
                dump(context, irConst->value.intVal);
                return;

            case kIROp_FloatLit:
                dump(context, irConst->value.floatVal);
                return;

            case kIROp_BoolLit:
                dump(context, irConst->value.intVal ? "true" : "false");
                return;

            case kIROp_StringLit:
                dumpEncodeString(context, irConst->getStringSlice());
                return;

            case kIROp_PtrLit:
                dump(context, "<ptr>");
                return;

            default:
                break;
            }
        }

        dump(context, opInfo.name);

        dumpInstOperandList(context, inst);
    }

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

        auto op = inst->op;

        dumpIRDecorations(context, inst);

        // 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_Generic:
            dumpIRGlobalValueWithCode(context, (IRGlobalValueWithCode*)inst);
            return;

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

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

        default:
            break;
        }

        // Okay, we have a seemingly "ordinary" op now
        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...
        }

        dumpInstExpr(context, inst);
    }

    static void dumpInst(
        IRDumpContext*  context,
        IRInst*         inst)
    {
        if(shouldFoldInstIntoUses(context, inst))
            return;

        dumpIndent(context);
        dumpInstBody(context, inst);
        dump(context, "\n");
    }

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

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

        dumpIRModule(&context, module);
    }

    void dumpIR(IRInst* globalVal, ISlangWriter* writer, IRDumpMode mode)
    {
        StringBuilder sb;

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

        dumpInst(&context, globalVal);

        writer->write(sb.getBuffer(), sb.getLength());
        writer->flush();
    }

    void dumpIR(IRModule* module, ISlangWriter* slangWriter, char const* label)
    {
        WriterHelper writer(slangWriter);

        if (label)
        {
            writer.put("### ");
            writer.put(label);
            writer.put(":\n");
        }

        dumpIR(module, slangWriter, IRDumpMode::Simplified);

        if (label)
        {
            writer.put("###\n");
        }
    }

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

    void dumpIR(IRModule* module, ISlangWriter* writer, IRDumpMode mode)
    {
        String ir = getSlangIRAssembly(module, mode);
        writer->write(ir.getBuffer(), ir.getLength());
        writer->flush();
    }

    // Pre-declare
    static bool _isTypeOperandEqual(IRInst* a, IRInst* b);

    static bool _areTypeOperandsEqual(IRInst* a, IRInst* b)
    {
        // Must have same number of operands
        const auto operandCountA = Index(a->getOperandCount());
        if (operandCountA != Index(b->getOperandCount()))
        {
            return false;
        }

        // All the operands must be equal
        for (Index i = 0; i < operandCountA; ++i)
        {
            IRInst* operandA = a->getOperand(i);
            IRInst* operandB = b->getOperand(i);

            if (!_isTypeOperandEqual(operandA, operandB))
            {
                return false;
            }
        }

        return true;
    }

    bool isNominalOp(IROp op)
    {
        // True if the op identity is 'nominal'
        switch (op)
        {
            case kIROp_StructType:
            case kIROp_InterfaceType:
            case kIROp_Generic:
            case kIROp_Param:
            {
                return true;
            }
        }
        return false;
    }

    // True if a type operand is equal. Operands are 'IRInst' - but it's only a restricted set that
    // can be operands of IRType instructions
    static bool _isTypeOperandEqual(IRInst* a, IRInst* b)
    {
        if (a == b)
        {
            return true;
        }

        if (a == nullptr || b == nullptr)
        {
            return false;
        }

        const IROp opA = IROp(a->op & kIROpMeta_OpMask);
        const IROp opB = IROp(b->op & kIROpMeta_OpMask);

        if (opA != opB)
        {
            return false;
        }

        // If the type is nominal - it can only be the same if the pointer is the same.
        if (isNominalOp(opA))
        {
            // The pointer isn't the same (as that was already tested), so cannot be equal
            return false;
        }

        // Both are types
        if (IRType::isaImpl(opA))
        {
            if (IRBasicType::isaImpl(opA))
            {
                // If it's a basic type, then their op being the same means we are done
                return true;
            }

            // We don't care about the parent or positioning
            // We also don't care about 'type' - because these instructions are defining the type.
            // 
            // We may want to care about decorations.

            // If it's a resource type - special case the handling of the resource flavor 
            if (IRResourceTypeBase::isaImpl(opA) &&
                static_cast<const IRResourceTypeBase*>(a)->getFlavor() != static_cast<const IRResourceTypeBase*>(b)->getFlavor())
            {
                return false;
            }

            // TODO(JS): There is a question here about what to do about decorations.
            // For now we ignore decorations. Are two types potentially different if there decorations different?
            // If decorations play a part in difference in types - the order of decorations presumably is not important.

            // All the operands of the types must be equal
            return _areTypeOperandsEqual(a, b);
        }
       
        // If it's a constant...
        if (IRConstant::isaImpl(opA))
        {
            // TODO: This is contrived in that we want two types that are the same, but are different
            // pointers to match here.
            // If we make getHashCode for IRType* compatible with isTypeEqual, then we should probably use that.
            return static_cast<IRConstant*>(a)->isValueEqual(static_cast<IRConstant*>(b)) &&
                isTypeEqual(a->getFullType(), b->getFullType());
        }

        SLANG_ASSERT(!"Unhandled comparison");

        // We can't equate any other type..
        return false;
    }

    bool isTypeEqual(IRType* a, IRType* b)
    {
        // _isTypeOperandEqual handles comparison of types so can defer to it
        return _isTypeOperandEqual(a, b);
    }
     
    void findAllInstsBreadthFirst(IRInst* inst, List<IRInst*>& outInsts)
    {
        Index index = outInsts.getCount();

        outInsts.add(inst);

        while (index < outInsts.getCount())
        {
            IRInst* cur = outInsts[index++];

            IRInstListBase childrenList = cur->getDecorationsAndChildren();
            for (IRInst* child : childrenList)
            {
                outInsts.add(child);
            }
        }
    }

    IRDecoration* IRInst::getFirstDecoration()
    {
        return as<IRDecoration>(getFirstDecorationOrChild());
    }

    IRDecoration* IRInst::getLastDecoration()
    {
        IRDecoration* decoration = getFirstDecoration();
        if (!decoration) return nullptr;

        while (auto nextDecoration = decoration->getNextDecoration())
            decoration = nextDecoration;

        return decoration;
    }

    IRInstList<IRDecoration> IRInst::getDecorations()
    {
        return IRInstList<IRDecoration>(
            getFirstDecoration(),
            getLastDecoration());
    }

    IRInst* IRInst::getFirstChild()
    {
        // The children come after any decorations,
        // so if there are any decorations, then the
        // first child is right after the last decoration.
        //
        if(auto lastDecoration = getLastDecoration())
            return lastDecoration->getNextInst();
        //
        // Otherwise, there must be no decorations, so
        // that the first "child or decoration" is a child.
        //
        return getFirstDecorationOrChild();
    }

    IRInst* IRInst::getLastChild()
    {
        // The children come after any decorations, so
        // that the last item in the list of children
        // and decorations is the last child *unless*
        // it is a decoration, in which case there are
        // no children.
        //
        auto lastChild = getLastDecorationOrChild();
        return as<IRDecoration>(lastChild) ? nullptr : lastChild;
    }


    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();
    }

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

    void IRInst::insertAtStart(IRInst* newParent)
    {
        SLANG_ASSERT(newParent);
        _insertAt(nullptr, newParent->getFirstDecorationOrChild(), newParent);
    }

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

    void IRInst::_insertAt(IRInst* inPrev, IRInst* inNext, IRInst* inParent)
    {
        // Make sure this instruction has been removed from any previous parent
        this->removeFromParent();

        SLANG_ASSERT(inParent);
        SLANG_ASSERT(!inPrev || (inPrev->getNextInst() == inNext) && (inPrev->getParent() == inParent));
        SLANG_ASSERT(!inNext || (inNext->getPrevInst() == inPrev) && (inNext->getParent() == inParent));

        if( inPrev )
        {
            inPrev->next = this;
        }
        else
        {
            inParent->m_decorationsAndChildren.first = this;
        }

        if (inNext)
        {
            inNext->prev = this;
        }
        else
        {
            inParent->m_decorationsAndChildren.last = this;
        }

        this->prev = inPrev;
        this->next = inNext;
        this->parent = inParent;
    }

    void IRInst::insertAfter(IRInst* other)
    {
        SLANG_ASSERT(other);
        removeFromParent();
        _insertAt(other, other->getNextInst(), other->getParent());
    }

    void IRInst::insertAtEnd(IRInst* newParent)
    {
        SLANG_ASSERT(newParent);
        removeFromParent();
        _insertAt(newParent->getLastDecorationOrChild(), nullptr, newParent);
    }

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

    // 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->m_decorationsAndChildren.first = nn;
        }

        if(nn)
        {
            SLANG_ASSERT(nn->getParent() == oldParent);
            nn->prev = pp;
        }
        else
        {
            oldParent->m_decorationsAndChildren.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();
        removeAndDeallocateAllDecorationsAndChildren();

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

    void IRInst::removeAndDeallocateAllDecorationsAndChildren()
    {
        IRInst* nextChild = nullptr;
        for( IRInst* child = getFirstDecorationOrChild(); child; child = nextChild )
        {
            nextChild = child->getNextInst();
            child->removeAndDeallocate();
        }
    }

    void IRInst::transferDecorationsTo(IRInst* target)
    {
        while( auto decoration = getFirstDecoration() )
        {
            decoration->removeFromParent();
            decoration->insertAtStart(target);
        }
    }

    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<IRConstant>(this))
            return false;

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

        if(as<IRAttr>(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:
            {
                // In the general case, a function call must be assumed to
                // have almost arbitrary side effects.
                //
                // However, it is possible that the callee can be identified,
                // and it may be a function with an attribute that explicitly
                // limits the side effects it is allowed to have.
                //
                // For now, we will explicitly check for the `[__readNone]`
                // attribute, which was used to mark functions that compute
                // their result strictly as a function of the arguments (and
                // not anything they point to, or other non-argument state).
                // Calls to such functions cannot have side effects (except
                // for things like stack overflow that abstract language models
                // tend to ignore), and can be subject to dead code elimination,
                // common subexpression elimination, etc.
                //
                auto call = cast<IRCall>(this);
                auto callee = getResolvedInstForDecorations(call->getCallee());
                if(callee->findDecoration<IRReadNoneDecoration>())
                {
                    return false;
                }
            }
            return true;

            // All of the cases for "global values" are side-effect-free.
        case kIROp_StructType:
        case kIROp_StructField:
        case kIROp_RTTIPointerType:
        case kIROp_RTTIObject:
        case kIROp_RTTIType:
        case kIROp_Func:
        case kIROp_Generic:
        case kIROp_Var:
        case kIROp_GlobalVar: // Note: the IRGlobalVar represents the *address*, so only a load/store would have side effects
        case kIROp_GlobalConstant:
        case kIROp_GlobalParam:
        case kIROp_StructKey:
        case kIROp_GlobalGenericParam:
        case kIROp_WitnessTable:
        case kIROp_WitnessTableEntry:
        case kIROp_InterfaceRequirementEntry:
        case kIROp_Block:
            return false;

        case kIROp_Nop:
        case kIROp_undefined:
        case kIROp_DefaultConstruct:
        case kIROp_Specialize:
        case kIROp_lookup_interface_method:
        case kIROp_getAddr:
        case kIROp_GetValueFromExistentialBox:
        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_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_Rem:
        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_MakeExistential:
        case kIROp_ExtractExistentialType:
        case kIROp_ExtractExistentialValue:
        case kIROp_ExtractExistentialWitnessTable:
        case kIROp_WrapExistential:
        case kIROp_BitCast:
            return false;
        }
    }

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

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

    //
    // IRType
    //

    IRType* unwrapArray(IRType* type)
    {
        IRType* t = type;
        while( auto arrayType = as<IRArrayTypeBase>(t) )
        {
            t = arrayType->getElementType();
        }
        return t;
    }

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

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

        return nullptr;
    }

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

    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;
    }
    
    IRInst* findInnerMostGenericReturnVal(IRGeneric* generic)
    {
        IRInst* inst = generic;
        while (auto genericInst = as<IRGeneric>(inst))
            inst = findGenericReturnVal(genericInst);
        return inst;
    }

    IRGeneric* findSpecializedGeneric(IRSpecialize* specialize)
    {
        return as<IRGeneric>(specialize->getBase());
    }


    IRInst* findSpecializeReturnVal(IRSpecialize* specialize)
    {
        auto generic = findSpecializedGeneric(specialize);
        if(!generic)
            return nullptr;

        return findGenericReturnVal(generic);
    }

    IRInst* getResolvedInstForDecorations(IRInst* inst)
    {
        IRInst* candidate = inst;
        while(auto specInst = as<IRSpecialize>(candidate))
        {
            auto genericInst = as<IRGeneric>(specInst->getBase());
            if(!genericInst)
                break;

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

            candidate = returnVal;
        }
        return candidate;
    }

    bool isDefinition(
        IRInst* inVal)
    {
        IRInst* val = inVal;
        // unwrap any generic declarations to see
        // the value they return.
        for(;;)
        {
            // An instruciton marked `[import(...)]` cannot
            // be a definition, since it is claiming that
            // the actual body comes from another module.
            //
            if(val->findDecoration<IRImportDecoration>())
                return false;

            auto genericInst = as<IRGeneric>(val);
            if(!genericInst)
                break;

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

            val = returnVal;
        }

        // Some cases of instructions have structural
        // rules about when they are considered to have
        // a definition (e.g., a function must have a body).
        //
        switch (val->op)
        {
        case kIROp_Func:
        case kIROp_Generic:
            return val->getFirstChild() != nullptr;

        case kIROp_GlobalConstant:
            return cast<IRGlobalConstant>(val)->getValue() != nullptr;

        default:
            break;
        }

        // In all other cases, if we have an instruciton
        // that has *not* been marked for import, then
        // we consider it to be a definition.

        return true;
    }

    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()));
    }

    bool isPointerOfType(IRInst* ptrType, IRInst* elementType)
    {
        return ptrType && ptrType->op == kIROp_PtrType && ptrType->getOperand(0) == elementType;
    }

    bool isPointerOfType(IRInst* ptrType, IROp opCode)
    {
        return ptrType && ptrType->op == kIROp_PtrType && ptrType->getOperand(0) &&
            ptrType->getOperand(0)->op == opCode;
    }
    bool isBuiltin(IRInst* inst)
    {
        return inst->findDecoration<IRBuiltinDecoration>() != nullptr;
    }
} // namespace Slang

back to top