summaryrefslogtreecommitdiff
path: root/source/slang/slang-ir.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2019-05-31 17:20:37 -0400
committerGitHub <noreply@github.com>2019-05-31 17:20:37 -0400
commit6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch)
tree5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-ir.cpp
parentb81ff3ef968d1cc4e954b31a1812b3c391d17b02 (diff)
Use slang- prefix on slang compiler and core source (#973)
* Prefixing source files in source/slang with slang- * Prefix source in source/slang with slang- prefix. * Rename core source files with slang- prefix. * Update project files. * Fix problems from automatic merge.
Diffstat (limited to 'source/slang/slang-ir.cpp')
-rw-r--r--source/slang/slang-ir.cpp4511
1 files changed, 4511 insertions, 0 deletions
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
new file mode 100644
index 000000000..4975ac824
--- /dev/null
+++ b/source/slang/slang-ir.cpp
@@ -0,0 +1,4511 @@
+// 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;
+
+ 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"
+
+ // Pseudo ops
+#define INST(ID, MNEMONIC, ARG_COUNT, FLAGS) /* empty */
+#define PSEUDO_INST(ID) \
+ { kIRPseudoOp_##ID, { #ID, 0, 0 } },
+
+ // First is 'invalid'
+ { kIROp_Invalid,{ "invalid", 0, 0 } },
+ // Then all the other psuedo ops
+#include "slang-ir-inst-defs.h"
+
+ };
+
+ IROpInfo getIROpInfo(IROp opIn)
+ {
+ const int op = opIn & kIROpMeta_PseudoOpMask;
+ if ((op & kIROpMeta_IsPseudoOp) && op < kIRPseudoOp_LastPlusOne)
+ {
+ // It's a pseudo op
+ const int index = op - kIRPseudoOp_First;
+ // Pseudo ops start from kIROpcount
+ const auto& entry = kIROps[kIROpCount + index];
+ SLANG_ASSERT(entry.op == op);
+ return entry.info;
+ }
+ else 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;
+ }
+
+ // 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->getDataType();
+ }
+
+ // 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);
+ }
+ }
+
+ 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();
+ }
+
+
+ // 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)
+ {
+ 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 resultType = func->getResultType();
+
+ auto funcType = builder.getFuncType(paramTypes, resultType);
+ builder.setDataType(func, funcType);
+ }
+
+ //
+
+ 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);
+ }
+
+ //
+
+ 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;
+
+ if (type)
+ {
+ inst->typeUse.init(inst, type);
+ }
+
+ maybeSetSourceLoc(builder, inst);
+
+ auto operand = inst->getOperands();
+
+ for( UInt aa = 0; aa < fixedArgCount; ++aa )
+ {
+ if (fixedArgs)
+ {
+ operand->init(inst, fixedArgs[aa]);
+ }
+ operand++;
+ }
+
+ for (UInt ii = 0; ii < varArgListCount; ++ii)
+ {
+ UInt listArgCount = listArgCounts[ii];
+ for (UInt jj = 0; jj < listArgCount; ++jj)
+ {
+ if (listArgs[ii])
+ {
+ operand->init(inst, listArgs[ii][jj]);
+ }
+ else
+ {
+ operand->init(inst, nullptr);
+ }
+ operand++;
+ }
+ }
+ 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;
+ }
+
+ int IRInstKey::GetHashCode()
+ {
+ auto code = Slang::GetHashCode(inst->op);
+ code = combineHash(code, Slang::GetHashCode(inst->getFullType()));
+ code = combineHash(code, Slang::GetHashCode(inst->getOperandCount()));
+
+ auto argCount = inst->getOperandCount();
+ auto args = inst->getOperands();
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ code = combineHash(code, Slang::GetHashCode(args[aa].get()));
+ }
+ return code;
+ }
+
+ 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::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();
+ }
+
+ int 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.size()));
+ }
+ 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.size();
+ 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.size());
+
+ 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* findOrEmitHoistableInst(
+ IRBuilder* builder,
+ IRType* type,
+ IROp op,
+ UInt operandListCount,
+ UInt const* listOperandCounts,
+ IRInst* const* const* listOperands)
+ {
+ UInt operandCount = 0;
+ for (UInt ii = 0; ii < operandListCount; ++ii)
+ {
+ operandCount += listOperandCounts[ii];
+ }
+
+ auto& memoryArena = builder->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 = builder->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(builder, 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(builder, inst);
+
+ return inst;
+ }
+
+ IRInst* findOrEmitHoistableInst(
+ IRBuilder* builder,
+ IRType* type,
+ IROp op,
+ UInt operandCount,
+ IRInst* const* operands)
+ {
+ return findOrEmitHoistableInst(
+ builder,
+ type,
+ op,
+ 1,
+ &operandCount,
+ &operands);
+ }
+
+ IRInst* findOrEmitHoistableInst(
+ IRBuilder* builder,
+ IRType* type,
+ IROp op,
+ IRInst* operand,
+ UInt operandCount,
+ IRInst* const* operands)
+ {
+ UInt counts[] = { 1, operandCount };
+ IRInst* const* lists[] = { &operand, operands };
+
+ return findOrEmitHoistableInst(
+ builder,
+ type,
+ op,
+ 2,
+ counts,
+ lists);
+ }
+
+
+ IRType* IRBuilder::getType(
+ IROp op,
+ UInt operandCount,
+ IRInst* const* operands)
+ {
+ return (IRType*) findOrEmitHoistableInst(
+ this,
+ nullptr,
+ op,
+ operandCount,
+ operands);
+ }
+
+ IRType* IRBuilder::getType(
+ IROp op)
+ {
+ return getType(op, 0, nullptr);
+ }
+
+ IRBasicType* IRBuilder::getBasicType(BaseType baseType)
+ {
+ return (IRBasicType*)getType(
+ IROp((UInt)kIROp_FirstBasicType + (UInt)baseType));
+ }
+
+ IRBasicType* IRBuilder::getVoidType()
+ {
+ return (IRVoidType*)getType(kIROp_VoidType);
+ }
+
+ IRBasicType* IRBuilder::getBoolType()
+ {
+ return (IRBoolType*)getType(kIROp_BoolType);
+ }
+
+ IRBasicType* IRBuilder::getIntType()
+ {
+ return (IRBasicType*)getType(kIROp_IntType);
+ }
+
+ IRStringType* IRBuilder::getStringType()
+ {
+ return (IRStringType*)getType(kIROp_StringType);
+ }
+
+ IRBasicBlockType* IRBuilder::getBasicBlockType()
+ {
+ return (IRBasicBlockType*)getType(kIROp_BasicBlockType);
+ }
+
+ IRTypeKind* IRBuilder::getTypeKind()
+ {
+ return (IRTypeKind*)getType(kIROp_TypeKind);
+ }
+
+ IRGenericKind* IRBuilder::getGenericKind()
+ {
+ return (IRGenericKind*)getType(kIROp_GenericKind);
+ }
+
+ IRPtrType* IRBuilder::getPtrType(IRType* valueType)
+ {
+ return (IRPtrType*) getPtrType(kIROp_PtrType, valueType);
+ }
+
+ IROutType* IRBuilder::getOutType(IRType* valueType)
+ {
+ return (IROutType*) getPtrType(kIROp_OutType, valueType);
+ }
+
+ IRInOutType* IRBuilder::getInOutType(IRType* valueType)
+ {
+ return (IRInOutType*) getPtrType(kIROp_InOutType, valueType);
+ }
+
+ IRRefType* IRBuilder::getRefType(IRType* valueType)
+ {
+ return (IRRefType*) getPtrType(kIROp_RefType, valueType);
+ }
+
+ IRPtrTypeBase* IRBuilder::getPtrType(IROp op, IRType* valueType)
+ {
+ IRInst* operands[] = { valueType };
+ return (IRPtrTypeBase*) getType(
+ op,
+ 1,
+ operands);
+ }
+
+ IRArrayTypeBase* IRBuilder::getArrayTypeBase(
+ IROp op,
+ IRType* elementType,
+ IRInst* elementCount)
+ {
+ IRInst* operands[] = { elementType, elementCount };
+ return (IRArrayTypeBase*)getType(
+ op,
+ op == kIROp_ArrayType ? 2 : 1,
+ operands);
+ }
+
+ IRArrayType* IRBuilder::getArrayType(
+ IRType* elementType,
+ IRInst* elementCount)
+ {
+ IRInst* operands[] = { elementType, elementCount };
+ return (IRArrayType*)getType(
+ kIROp_ArrayType,
+ sizeof(operands) / sizeof(operands[0]),
+ operands);
+ }
+
+ IRUnsizedArrayType* IRBuilder::getUnsizedArrayType(
+ IRType* elementType)
+ {
+ IRInst* operands[] = { elementType };
+ return (IRUnsizedArrayType*)getType(
+ kIROp_UnsizedArrayType,
+ sizeof(operands) / sizeof(operands[0]),
+ operands);
+ }
+
+ IRVectorType* IRBuilder::getVectorType(
+ IRType* elementType,
+ IRInst* elementCount)
+ {
+ IRInst* operands[] = { elementType, elementCount };
+ return (IRVectorType*)getType(
+ kIROp_VectorType,
+ sizeof(operands) / sizeof(operands[0]),
+ operands);
+ }
+
+ IRMatrixType* IRBuilder::getMatrixType(
+ IRType* elementType,
+ IRInst* rowCount,
+ IRInst* columnCount)
+ {
+ IRInst* operands[] = { elementType, rowCount, columnCount };
+ return (IRMatrixType*)getType(
+ kIROp_MatrixType,
+ sizeof(operands) / sizeof(operands[0]),
+ operands);
+ }
+
+ IRFuncType* IRBuilder::getFuncType(
+ UInt paramCount,
+ IRType* const* paramTypes,
+ IRType* resultType)
+ {
+ return (IRFuncType*) findOrEmitHoistableInst(
+ this,
+ nullptr,
+ kIROp_FuncType,
+ resultType,
+ paramCount,
+ (IRInst* const*) paramTypes);
+ }
+
+ 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(
+ this,
+ 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 = getPtrType(kIROp_ExistentialBoxType, concreteType);
+ return ptrType;
+ }
+ }
+
+ return (IRType*) findOrEmitHoistableInst(
+ this,
+ 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);
+ }
+ }
+
+
+ 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();
+ 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::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::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::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;
+ }
+
+ IRGlobalConstant* IRBuilder::createGlobalConstant(
+ IRType* valueType)
+ {
+ IRGlobalConstant* globalConstant = createInst<IRGlobalConstant>(
+ this,
+ kIROp_GlobalConstant,
+ valueType);
+ maybeSetSourceLoc(this, globalConstant);
+ addGlobalValue(this, globalConstant);
+ return globalConstant;
+ }
+
+ IRGlobalParam* IRBuilder::createGlobalParam(
+ IRType* valueType)
+ {
+ IRGlobalParam* inst = createInst<IRGlobalParam>(
+ this,
+ kIROp_GlobalParam,
+ valueType);
+ maybeSetSourceLoc(this, inst);
+ addGlobalValue(this, inst);
+ return inst;
+ }
+
+ IRWitnessTable* IRBuilder::createWitnessTable()
+ {
+ IRWitnessTable* witnessTable = createInst<IRWitnessTable>(
+ this,
+ kIROp_WitnessTable,
+ nullptr);
+ addGlobalValue(this, witnessTable);
+ return witnessTable;
+ }
+
+ IRWitnessTableEntry* IRBuilder::createWitnessTableEntry(
+ IRWitnessTable* witnessTable,
+ IRInst* requirementKey,
+ IRInst* satisfyingVal)
+ {
+ IRWitnessTableEntry* entry = createInst<IRWitnessTableEntry>(
+ this,
+ kIROp_WitnessTableEntry,
+ nullptr,
+ requirementKey,
+ satisfyingVal);
+
+ if (witnessTable)
+ {
+ entry->insertAtEnd(witnessTable);
+ }
+
+ return entry;
+ }
+
+ IRStructType* IRBuilder::createStructType()
+ {
+ IRStructType* structType = createInst<IRStructType>(
+ this,
+ kIROp_StructType,
+ nullptr);
+ addGlobalValue(this, structType);
+ return structType;
+ }
+
+ IRInterfaceType* IRBuilder::createInterfaceType()
+ {
+ IRInterfaceType* interfaceType = createInst<IRInterfaceType>(
+ this,
+ kIROp_InterfaceType,
+ nullptr);
+ 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;
+ }
+
+ 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::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()
+ {
+ IRGlobalGenericParam* irGenericParam = createInst<IRGlobalGenericParam>(
+ this,
+ kIROp_GlobalGenericParam,
+ nullptr);
+ addGlobalValue(this, irGenericParam);
+ return irGenericParam;
+ }
+
+ IRBindGlobalGenericParam* IRBuilder::emitBindGlobalGenericParam(
+ IRInst* param,
+ IRInst* val)
+ {
+ auto inst = createInst<IRBindGlobalGenericParam>(
+ this,
+ kIROp_BindGlobalGenericParam,
+ nullptr,
+ param,
+ val);
+ addInst(inst);
+ return inst;
+ }
+
+ IRInst* IRBuilder::emitBindGlobalExistentialSlots(
+ UInt argCount,
+ IRInst* const* args)
+ {
+ auto inst = createInstWithTrailingArgs<IRInst>(
+ this,
+ kIROp_BindGlobalExistentialSlots,
+ getVoidType(),
+ 0,
+ nullptr,
+ argCount,
+ args);
+ 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;
+ }
+
+ //
+ // 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(addRefObjectToFree(decl));
+ addDecoration(inst, kIROp_HighLevelDeclDecoration, ptrConst);
+ }
+
+ void IRBuilder::addLayoutDecoration(IRInst* inst, Layout* layout)
+ {
+ auto ptrConst = getPtrValue(addRefObjectToFree(layout));
+ addDecoration(inst, kIROp_LayoutDecoration, ptrConst);
+ }
+
+ //
+
+
+ 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())
+ {
+ // Certain decorations aren't helpful to appear
+ // in output dumps, so we will only include them
+ // in the "detailed" dumping mode.
+ //
+ // For all other modes, we will check the opcode
+ // and skip selected decorations.
+ //
+ if(context->mode != IRDumpMode::Detailed)
+ {
+ switch(dd->op)
+ {
+ default:
+ break;
+
+ case kIROp_HighLevelDeclDecoration:
+ case kIROp_LayoutDecoration:
+ continue;
+ }
+ }
+
+ 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, "}");
+ }
+
+
+ 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());
+
+ 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);
+
+ 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, ")");
+
+ }
+
+ 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_GlobalConstant:
+ 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();
+ }
+
+ 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;
+ }
+
+ static 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_PseudoOpMask);
+ const IROp opB = IROp(b->op & kIROpMeta_PseudoOpMask);
+
+ 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;
+
+ 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_Func:
+ case kIROp_Generic:
+ case kIROp_GlobalVar:
+ case kIROp_GlobalConstant:
+ case kIROp_GlobalParam:
+ case kIROp_StructKey:
+ case kIROp_GlobalGenericParam:
+ case kIROp_WitnessTable:
+ case kIROp_WitnessTableEntry:
+ case kIROp_Block:
+ return false;
+
+ case kIROp_Nop:
+ case kIROp_Specialize:
+ case kIROp_lookup_interface_method:
+ case kIROp_Construct:
+ case kIROp_makeVector:
+ case kIROp_MakeMatrix:
+ case kIROp_makeArray:
+ case kIROp_makeStruct:
+ case kIROp_Load: // We are ignoring the possibility of loads from bad addresses, or `volatile` loads
+ case kIROp_FieldExtract:
+ case kIROp_FieldAddress:
+ case kIROp_getElement:
+ case kIROp_getElementPtr:
+ case kIROp_constructVectorFromScalar:
+ case kIROp_swizzle:
+ case kIROp_swizzleSet: // Doesn't actually "set" anything - just returns the resulting vector
+ case kIROp_Add:
+ case kIROp_Sub:
+ case kIROp_Mul:
+ //case kIROp_Div: // TODO: We could split out integer vs. floating-point div/mod and assume the floating-point cases have no side effects
+ //case kIROp_Mod:
+ case kIROp_Lsh:
+ case kIROp_Rsh:
+ case kIROp_Eql:
+ case kIROp_Neq:
+ case kIROp_Greater:
+ case kIROp_Less:
+ case kIROp_Geq:
+ case kIROp_Leq:
+ case kIROp_BitAnd:
+ case kIROp_BitXor:
+ case kIROp_BitOr:
+ case kIROp_And:
+ case kIROp_Or:
+ case kIROp_Neg:
+ case kIROp_Not:
+ case kIROp_BitNot:
+ case kIROp_Select:
+ case kIROp_Dot:
+ case kIROp_Mul_Vector_Matrix:
+ case kIROp_Mul_Matrix_Vector:
+ case kIROp_Mul_Matrix_Matrix:
+ case kIROp_MakeExistential:
+ case kIROp_ExtractExistentialType:
+ case kIROp_ExtractExistentialValue:
+ case kIROp_ExtractExistentialWitnessTable:
+ case kIROp_WrapExistential:
+ 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* 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(;;)
+ {
+ auto genericInst = as<IRGeneric>(val);
+ if(!genericInst)
+ break;
+
+ auto returnVal = findGenericReturnVal(genericInst);
+ if(!returnVal)
+ break;
+
+ val = returnVal;
+ }
+
+ // TODO: the logic here should probably
+ // be that anything with an `IRImportDecoration`
+ // is considered to be a declaration rather than definition.
+
+ switch (val->op)
+ {
+ case kIROp_WitnessTable:
+ case kIROp_GlobalConstant:
+ case kIROp_Func:
+ case kIROp_Generic:
+ return val->getFirstChild() != nullptr;
+
+ case kIROp_StructType:
+ case kIROp_GlobalVar:
+ case kIROp_GlobalParam:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ 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()));
+ }
+}