summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-ir-lower-binding-query.cpp
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2024-10-29 14:49:26 +0800
committerGitHub <noreply@github.com>2024-10-29 14:49:26 +0800
commitf65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch)
treeea1d61342cd29368e19135000ec2948813096205 /source/slang/slang-ir-lower-binding-query.cpp
parenta729c15e9dce9f5116a38afc66329ab2ca4cea54 (diff)
format
* format * Minor test fixes * enable checking cpp format in ci
Diffstat (limited to 'source/slang/slang-ir-lower-binding-query.cpp')
-rw-r--r--source/slang/slang-ir-lower-binding-query.cpp912
1 files changed, 452 insertions, 460 deletions
diff --git a/source/slang/slang-ir-lower-binding-query.cpp b/source/slang/slang-ir-lower-binding-query.cpp
index 116dcff61..e54430cfa 100644
--- a/source/slang/slang-ir-lower-binding-query.cpp
+++ b/source/slang/slang-ir-lower-binding-query.cpp
@@ -1,8 +1,8 @@
// slang-ir-lower-tuple-types.cpp
+#include "slang-ir-insts.h"
#include "slang-ir-lower-tuple-types.h"
#include "slang-ir.h"
-#include "slang-ir-insts.h"
// The pass in this file lowers the `getRegisterIndex()` and
// `getSpaceIndex()` intrinsics, by replacing them with literal
@@ -16,7 +16,7 @@
// being referenced was passed down into the current function from
// a caller. We thus introduce new function parameters after the
// resource in question, transforming, e.g., this:
-//
+//
// void doThings(
// float a,
// Texture2D t,
@@ -29,7 +29,7 @@
// doThings(myTexture);
//
// into this:
-//
+//
// void doThings(
// float a,
// Texture2D t,
@@ -51,567 +51,559 @@
namespace Slang
{
- // There are a ton of passes we've implemented now that use
- // some basic work-list structures, and it seems a bit silly
- // to be writing that code intermixed with the actual algorithm.
- //
- // For this file, we break the common work-list functionality
- // out into a base type that we can re-use in specific passes.
- //
- struct WorkListPass
+// There are a ton of passes we've implemented now that use
+// some basic work-list structures, and it seems a bit silly
+// to be writing that code intermixed with the actual algorithm.
+//
+// For this file, we break the common work-list functionality
+// out into a base type that we can re-use in specific passes.
+//
+struct WorkListPass
+{
+public:
+ IRModule* module;
+ DiagnosticSink* sink;
+
+protected:
+ // The base type needs to abstract over how the
+ // concrete pass will process each instruction
+ // that gets placed into the work list.
+
+ virtual void processInst(IRInst* inst) = 0;
+
+ // Otherwise, the implementation of the work list
+ // itself is straightforward, and not anything
+ // that hasn't been seen in other files.
+
+ InstWorkList workList;
+ InstHashSet workListSet;
+
+ WorkListPass(IRModule* inModule)
+ : module(inModule)
+ , workList(inModule)
+ , workListSet(inModule)
+ , toBeDeleted(inModule)
+ , toBeDeletedSet(inModule)
{
- public:
- IRModule* module;
- DiagnosticSink* sink;
-
- protected:
-
- // The base type needs to abstract over how the
- // concrete pass will process each instruction
- // that gets placed into the work list.
-
- virtual void processInst(IRInst* inst) = 0;
-
- // Otherwise, the implementation of the work list
- // itself is straightforward, and not anything
- // that hasn't been seen in other files.
-
- InstWorkList workList;
- InstHashSet workListSet;
-
- WorkListPass(IRModule* inModule)
- : module(inModule), workList(inModule), workListSet(inModule), toBeDeleted(inModule), toBeDeletedSet(inModule)
- {}
+ }
- void addToWorkList(IRInst* inst)
- {
- if (workListSet.contains(inst))
- return;
+ void addToWorkList(IRInst* inst)
+ {
+ if (workListSet.contains(inst))
+ return;
- workList.add(inst);
- workListSet.add(inst);
- }
+ workList.add(inst);
+ workListSet.add(inst);
+ }
- void processWorkList()
+ void processWorkList()
+ {
+ while (workList.getCount() != 0)
{
- while (workList.getCount() != 0)
- {
- IRInst* inst = workList.getLast();
+ IRInst* inst = workList.getLast();
- workList.removeLast();
- workListSet.remove(inst);
+ workList.removeLast();
+ workListSet.remove(inst);
- processInst(inst);
+ processInst(inst);
- for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
- {
- addToWorkList(child);
- }
+ for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
+ {
+ addToWorkList(child);
}
}
+ }
- // As long as we are factoring out repeated cruft,
- // it seems reasonable to *also* deal with the
- // frequent need to buffer up instructions to
- // be deleted when a pass is complete.
+ // As long as we are factoring out repeated cruft,
+ // it seems reasonable to *also* deal with the
+ // frequent need to buffer up instructions to
+ // be deleted when a pass is complete.
- InstWorkList toBeDeleted;
- InstHashSet toBeDeletedSet;
+ InstWorkList toBeDeleted;
+ InstHashSet toBeDeletedSet;
- void addToBeDeleted(IRInst* inst)
- {
- if (toBeDeletedSet.contains(inst))
- return;
+ void addToBeDeleted(IRInst* inst)
+ {
+ if (toBeDeletedSet.contains(inst))
+ return;
- toBeDeleted.add(inst);
- toBeDeletedSet.add(inst);
- }
+ toBeDeleted.add(inst);
+ toBeDeletedSet.add(inst);
+ }
- void processDeletions()
+ void processDeletions()
+ {
+ for (auto inst : toBeDeleted)
{
- for (auto inst : toBeDeleted)
- {
- inst->removeAndDeallocate();
- }
- toBeDeleted.clear();
- toBeDeletedSet.clear();
+ inst->removeAndDeallocate();
}
- };
+ toBeDeleted.clear();
+ toBeDeletedSet.clear();
+ }
+};
- // The concrete pass will then be a specialization of
- // the base work-list abstraction.
+// The concrete pass will then be a specialization of
+// the base work-list abstraction.
+//
+struct BindingQueryLoweringContext : public WorkListPass
+{
+ BindingQueryLoweringContext(IRModule* inModule)
+ : WorkListPass(inModule)
+ {
+ }
+
+ // All of the intrinsics we will be processing use
+ // the same result type (`uint`), so it is helpful
+ // to cache a pointer to the IR type at the start
+ // of the pass and re-use it.
//
- struct BindingQueryLoweringContext : public WorkListPass
+ IRType* indexType = nullptr;
+
+ void processModule()
{
- BindingQueryLoweringContext(IRModule* inModule)
- : WorkListPass(inModule)
- {}
-
- // All of the intrinsics we will be processing use
- // the same result type (`uint`), so it is helpful
- // to cache a pointer to the IR type at the start
- // of the pass and re-use it.
+ IRBuilder builder(module);
+ indexType = builder.getUIntType();
+
+ // Processing the module consists of recursively
+ // processing all the instructions in one pass,
+ // and then potentially revisiting instructions
+ // that had new intrinsics added to their bodies.
//
- IRType* indexType = nullptr;
+ addToWorkList(module->getModuleInst());
+ processWorkList();
+ }
- void processModule()
+ void processInst(IRInst* inst)
+ {
+ // For this pass, we really only care about
+ // our binding query instructions.
+ //
+ if (auto query = as<IRBindingQuery>(inst))
{
- IRBuilder builder(module);
- indexType = builder.getUIntType();
-
- // Processing the module consists of recursively
- // processing all the instructions in one pass,
- // and then potentially revisiting instructions
- // that had new intrinsics added to their bodies.
- //
- addToWorkList(module->getModuleInst());
- processWorkList();
+ processQueryInst(query);
}
+ }
- void processInst(IRInst* inst)
+ void processQueryInst(IRBindingQuery* inst)
+ {
+ // Processing one of the query instructions is conceptually
+ // simple: we find a compute a value to replace it with,
+ // and then simply *replace* the instruction.
+ //
+ auto replacementValue = findOrComputeReplacementValueFor(inst);
+ if (!replacementValue)
{
- // For this pass, we really only care about
- // our binding query instructions.
+ // If we cannot find or compute a replacement value,
+ // then we need to treat it as an error, since the
+ // binding query intrinsics don't admit any reasonable
+ // runtime implementation.
//
- if (auto query = as<IRBindingQuery>(inst))
- {
- processQueryInst(query);
- }
+ sink->diagnose(inst, Diagnostics::opaqueReferenceMustResolveToGlobal);
+ return;
}
- void processQueryInst(IRBindingQuery* inst)
- {
- // Processing one of the query instructions is conceptually
- // simple: we find a compute a value to replace it with,
- // and then simply *replace* the instruction.
- //
- auto replacementValue = findOrComputeReplacementValueFor(inst);
- if (!replacementValue)
- {
- // If we cannot find or compute a replacement value,
- // then we need to treat it as an error, since the
- // binding query intrinsics don't admit any reasonable
- // runtime implementation.
- //
- sink->diagnose(
- inst,
- Diagnostics::opaqueReferenceMustResolveToGlobal);
- return;
- }
+ inst->replaceUsesWith(replacementValue);
+ inst->removeAndDeallocate();
+ }
- inst->replaceUsesWith(replacementValue);
- inst->removeAndDeallocate();
- }
+ // We want to cache the results of computing the binding
+ // information for an opaque-type value, in case doing
+ // so required adding or modifying code.
+ //
+ // For that purpose, we introduce a simple data structure
+ // to hold the two pieces of binding information we
+ // care about.
+ //
+ struct OpaqueValueInfo
+ {
+ IRInst* registerIndex = nullptr;
+ IRInst* registerSpace = nullptr;
+ };
- // We want to cache the results of computing the binding
- // information for an opaque-type value, in case doing
- // so required adding or modifying code.
- //
- // For that purpose, we introduce a simple data structure
- // to hold the two pieces of binding information we
- // care about.
- //
- struct OpaqueValueInfo
- {
- IRInst* registerIndex = nullptr;
- IRInst* registerSpace = nullptr;
- };
+ IRInst* findOrComputeReplacementValueFor(IRBindingQuery* query)
+ {
+ // Finding the replacement for a given query instruction
+ // then amounts to computing (or caching) the binding
+ // information for the opaque-type value it queries,
+ // and then projecting out the appropriate field.
+
+ auto opaqueValue = query->getOpaqueValue();
+ auto opaqueValueInfo = findOrComputeOpaqueValueInfo(opaqueValue);
- IRInst* findOrComputeReplacementValueFor(IRBindingQuery* query)
+ switch (query->getOp())
{
- // Finding the replacement for a given query instruction
- // then amounts to computing (or caching) the binding
- // information for the opaque-type value it queries,
- // and then projecting out the appropriate field.
+ default:
+ SLANG_UNEXPECTED("unhandled binding query instruction type");
+ UNREACHABLE_RETURN(query);
- auto opaqueValue = query->getOpaqueValue();
- auto opaqueValueInfo = findOrComputeOpaqueValueInfo(opaqueValue);
+ case kIROp_GetRegisterIndex: return opaqueValueInfo.registerIndex;
- switch (query->getOp())
- {
- default:
- SLANG_UNEXPECTED("unhandled binding query instruction type");
- UNREACHABLE_RETURN(query);
+ case kIROp_GetRegisterSpace: return opaqueValueInfo.registerSpace;
+ }
+ }
- case kIROp_GetRegisterIndex:
- return opaqueValueInfo.registerIndex;
+ // The information will be cached in a dictionary,
+ // keyed on the opaque-type value that the information
+ // was computed for.
+ //
+ Dictionary<IRInst*, OpaqueValueInfo> mapOpaqueValueToInfo;
- case kIROp_GetRegisterSpace:
- return opaqueValueInfo.registerSpace;
- }
- }
+ // Looking up the cached information (if any) is a simple
+ // matter of using the dictionary.
+ //
+ // (We have a distinct operation for lookup vs. the
+ // memo-cached lookup below, because we may want to
+ // query this information while computing an entry,
+ // and we don't want to introduce potential recursion.
+ //
+ OpaqueValueInfo* findOpaqueValueInfo(IRInst* opaqueValue)
+ {
+ return mapOpaqueValueToInfo.tryGetValue(opaqueValue);
+ }
- // The information will be cached in a dictionary,
- // keyed on the opaque-type value that the information
- // was computed for.
- //
- Dictionary<IRInst*, OpaqueValueInfo> mapOpaqueValueToInfo;
+ OpaqueValueInfo findOrComputeOpaqueValueInfo(IRInst* opaqueValue)
+ {
+ if (auto foundInfo = findOpaqueValueInfo(opaqueValue))
+ return *foundInfo;
- // Looking up the cached information (if any) is a simple
- // matter of using the dictionary.
+ // If there is no information registered in the cache, we
+ // compute it on-demand.
//
- // (We have a distinct operation for lookup vs. the
- // memo-cached lookup below, because we may want to
- // query this information while computing an entry,
- // and we don't want to introduce potential recursion.
+ // Note that there is no potential for circularity, so
+ // long as the implementation of `computeOpaqueValueInfo`
+ // does not itself call `findOrComputeValueInfo`.
//
- OpaqueValueInfo* findOpaqueValueInfo(IRInst* opaqueValue)
- {
- return mapOpaqueValueToInfo.tryGetValue(opaqueValue);
- }
+ auto computedInfo = computeOpaqueValueInfo(opaqueValue);
+ mapOpaqueValueToInfo.add(opaqueValue, computedInfo);
+ return computedInfo;
+ }
- OpaqueValueInfo findOrComputeOpaqueValueInfo(IRInst* opaqueValue)
+ // We are now (finally) getting into the meat of what this
+ // pass needs to do. Given an instruction with an opaque
+ // type, we need to try to compute the register and space
+ // it is bound to, or conspire to have that information
+ // passed along.
+ //
+ OpaqueValueInfo computeOpaqueValueInfo(IRInst* opaqueValue)
+ {
+ if (auto globalParam = as<IRGlobalParam>(opaqueValue))
{
- if (auto foundInfo = findOpaqueValueInfo(opaqueValue))
- return *foundInfo;
-
- // If there is no information registered in the cache, we
- // compute it on-demand.
+ // The simple/base case is when we have a global shader
+ // parameter that has layout information attached.
//
- // Note that there is no potential for circularity, so
- // long as the implementation of `computeOpaqueValueInfo`
- // does not itself call `findOrComputeValueInfo`.
+ // Note that this pass needs to run late enough that
+ // shader parameters declared at other scopes will have
+ // been massaged into the appropriate form.
//
- auto computedInfo = computeOpaqueValueInfo(opaqueValue);
- mapOpaqueValueToInfo.add(opaqueValue, computedInfo);
- return computedInfo;
- }
-
- // We are now (finally) getting into the meat of what this
- // pass needs to do. Given an instruction with an opaque
- // type, we need to try to compute the register and space
- // it is bound to, or conspire to have that information
- // passed along.
- //
- OpaqueValueInfo computeOpaqueValueInfo(IRInst* opaqueValue)
- {
- if (auto globalParam = as<IRGlobalParam>(opaqueValue))
+ if (auto layoutDecoration = globalParam->findDecoration<IRLayoutDecoration>())
{
- // The simple/base case is when we have a global shader
- // parameter that has layout information attached.
- //
- // Note that this pass needs to run late enough that
- // shader parameters declared at other scopes will have
- // been massaged into the appropriate form.
- //
- if (auto layoutDecoration = globalParam->findDecoration<IRLayoutDecoration>())
+ if (auto layout = as<IRVarLayout>(layoutDecoration->getLayout()))
{
- if (auto layout = as<IRVarLayout>(layoutDecoration->getLayout()))
+ // We expect any shader parameter of an opaque type
+ // to have a relevant resource kind, but it isn't
+ // too hard to code defensively. We will iterate
+ // over the resource kinds that are present and
+ // take the first one that represents an opaque type.
+ //
+ for (auto offsetAttr : layout->getOffsetAttrs())
{
- // We expect any shader parameter of an opaque type
- // to have a relevant resource kind, but it isn't
- // too hard to code defensively. We will iterate
- // over the resource kinds that are present and
- // take the first one that represents an opaque type.
- //
- for (auto offsetAttr : layout->getOffsetAttrs())
+ switch (offsetAttr->getResourceKind())
{
- switch (offsetAttr->getResourceKind())
- {
- default:
- break;
-
- case LayoutResourceKind::ShaderResource:
- case LayoutResourceKind::UnorderedAccess:
- case LayoutResourceKind::ConstantBuffer:
- case LayoutResourceKind::SamplerState:
- case LayoutResourceKind::DescriptorTableSlot:
+ default: break;
+
+ case LayoutResourceKind::ShaderResource:
+ case LayoutResourceKind::UnorderedAccess:
+ case LayoutResourceKind::ConstantBuffer:
+ case LayoutResourceKind::SamplerState:
+ case LayoutResourceKind::DescriptorTableSlot:
{
IRBuilder builder(module);
OpaqueValueInfo info;
- info.registerIndex = builder.getIntValue(indexType, offsetAttr->getOffset());
- info.registerSpace = builder.getIntValue(indexType, offsetAttr->getSpace());
+ info.registerIndex =
+ builder.getIntValue(indexType, offsetAttr->getOffset());
+ info.registerSpace =
+ builder.getIntValue(indexType, offsetAttr->getSpace());
return info;
}
break;
- }
}
}
}
}
- else if (auto param = as<IRParam>(opaqueValue))
- {
- // The other very interesting case is when the opaque-type
- // value is an `IRParam`, which indicates that it is either
- // a function parameter or a phi node of a basic block.
- //
- // Either way, we always expect a parameter to appear as
- // a child of a block.
- //
- auto block = as<IRBlock>(param->getParent());
- SLANG_ASSERT(block);
+ }
+ else if (auto param = as<IRParam>(opaqueValue))
+ {
+ // The other very interesting case is when the opaque-type
+ // value is an `IRParam`, which indicates that it is either
+ // a function parameter or a phi node of a basic block.
+ //
+ // Either way, we always expect a parameter to appear as
+ // a child of a block.
+ //
+ auto block = as<IRBlock>(param->getParent());
+ SLANG_ASSERT(block);
- // When rewriting call sites, we will need to know the
- // index of `param` within the parameter list.
- //
- Index paramIndex = -1;
+ // When rewriting call sites, we will need to know the
+ // index of `param` within the parameter list.
+ //
+ Index paramIndex = -1;
+ {
+ Count paramCounter = 0;
+ for (auto p : block->getParams())
{
- Count paramCounter = 0;
- for (auto p : block->getParams())
+ Index i = paramCounter++;
+ if (p == param)
{
- Index i = paramCounter++;
- if (p == param)
- {
- paramIndex = i;
- break;
- }
+ paramIndex = i;
+ break;
}
- SLANG_ASSERT(paramIndex >= 0);
}
+ SLANG_ASSERT(paramIndex >= 0);
+ }
- // In either case (function parameter or block parameter),
- // we will insert additional parameters after the original
- // parameter, so that the register index and space can
- // be passed along explicitly.
- //
- IRBuilder builder(module);
+ // In either case (function parameter or block parameter),
+ // we will insert additional parameters after the original
+ // parameter, so that the register index and space can
+ // be passed along explicitly.
+ //
+ IRBuilder builder(module);
- // We create new parameters to pass along the register index/space,
- // and manually insert them where we want them in the parameter list.
- //
- auto registerIndexParam = builder.createParam(builder.getUIntType());
- auto registerSpaceParam = builder.createParam(builder.getUIntType());
- //
- registerSpaceParam->insertAfter(param);
- registerIndexParam->insertAfter(param);
+ // We create new parameters to pass along the register index/space,
+ // and manually insert them where we want them in the parameter list.
+ //
+ auto registerIndexParam = builder.createParam(builder.getUIntType());
+ auto registerSpaceParam = builder.createParam(builder.getUIntType());
+ //
+ registerSpaceParam->insertAfter(param);
+ registerIndexParam->insertAfter(param);
- // We would like for the newly-introduced parameters to have
- // nice human-readable names, if the original parameter did.
- //
- if (auto nameHintDecoration = param->findDecoration<IRNameHintDecoration>())
- {
- String hint;
- hint.append(nameHintDecoration->getName());
- hint.append(".");
- builder.addNameHintDecoration(registerIndexParam, (hint + "index").getUnownedSlice());
- builder.addNameHintDecoration(registerSpaceParam, (hint + "space").getUnownedSlice());
- }
+ // We would like for the newly-introduced parameters to have
+ // nice human-readable names, if the original parameter did.
+ //
+ if (auto nameHintDecoration = param->findDecoration<IRNameHintDecoration>())
+ {
+ String hint;
+ hint.append(nameHintDecoration->getName());
+ hint.append(".");
+ builder.addNameHintDecoration(
+ registerIndexParam,
+ (hint + "index").getUnownedSlice());
+ builder.addNameHintDecoration(
+ registerSpaceParam,
+ (hint + "space").getUnownedSlice());
+ }
- // Similarly, the new parameters should get debugging-related
- // source location information from the original parameter,
- // if it had any.
- //
- registerIndexParam->sourceLoc = param->sourceLoc;
- registerSpaceParam->sourceLoc = param->sourceLoc;
+ // Similarly, the new parameters should get debugging-related
+ // source location information from the original parameter,
+ // if it had any.
+ //
+ registerIndexParam->sourceLoc = param->sourceLoc;
+ registerSpaceParam->sourceLoc = param->sourceLoc;
- // Now we need to scan for the places that the function or block
- // that the parameter belongs to gets referenced. At each such
- // location, we will pass along arguments to match the additional
- // parameters.
+ // Now we need to scan for the places that the function or block
+ // that the parameter belongs to gets referenced. At each such
+ // location, we will pass along arguments to match the additional
+ // parameters.
+ //
+ if (!block->getPrevBlock())
+ {
+ // If this is the first block in the parent function,
+ // then this is a function parameter, and we will
+ // iterate over call sites of the function and rewrite
+ // them to pass along arguments for the new parameters.
//
- if (!block->getPrevBlock())
- {
- // If this is the first block in the parent function,
- // then this is a function parameter, and we will
- // iterate over call sites of the function and rewrite
- // them to pass along arguments for the new parameters.
- //
- auto func = block->getParent();
+ auto func = block->getParent();
- for (auto use = func->firstUse; use; use = use->nextUse)
+ for (auto use = func->firstUse; use; use = use->nextUse)
+ {
+ auto user = use->getUser();
+ if (auto call = as<IRCall>(user))
{
- auto user = use->getUser();
- if (auto call = as<IRCall>(user))
+ if (call->getCallee() == func)
{
- if (call->getCallee() == func)
- {
- rewriteCall(call, paramIndex);
- }
+ rewriteCall(call, paramIndex);
}
}
}
- else
+ }
+ else
+ {
+ // If this is a block parameter, we will iterate over
+ // the instructions that branch to the block, and rewrite
+ // their argument lists, similar to what we do for function calls.
+ //
+ for (auto use = block->firstUse; use; use = use->nextUse)
{
- // If this is a block parameter, we will iterate over
- // the instructions that branch to the block, and rewrite
- // their argument lists, similar to what we do for function calls.
- //
- for (auto use = block->firstUse; use; use = use->nextUse)
+ auto user = use->getUser();
+ if (auto branch = as<IRUnconditionalBranch>(user))
{
- auto user = use->getUser();
- if (auto branch = as<IRUnconditionalBranch>(user))
+ if (branch->getTargetBlock() == block)
{
- if (branch->getTargetBlock() == block)
- {
- rewriteBranch(branch, paramIndex);
- }
+ rewriteBranch(branch, paramIndex);
}
}
}
-
- // The new parameters that we introduced will be used to
- // replace any binding query intrinsics applied to
- // this opaque value.
- //
- OpaqueValueInfo info;
- info.registerIndex = registerIndexParam;
- info.registerSpace = registerSpaceParam;
- return info;
}
- // By default we find that we cannot query binding information
- // for the given instruction.
+ // The new parameters that we introduced will be used to
+ // replace any binding query intrinsics applied to
+ // this opaque value.
+ //
OpaqueValueInfo info;
+ info.registerIndex = registerIndexParam;
+ info.registerSpace = registerSpaceParam;
return info;
}
- // In our IR, there isn't a lot of difference between a `call`
- // and an `unconditionalBranch`; indeed, this is part of what
- // motivates the use of `IRParam`s for both function parameters
- // and phi nodes.
- //
- // However, while both blocks and functions use the same `IRParam`
- // representation, we (currently) do not have a common base
- // between the `call` and `unconditionalBranch` instructions.
+ // By default we find that we cannot query binding information
+ // for the given instruction.
+ OpaqueValueInfo info;
+ return info;
+ }
+
+ // In our IR, there isn't a lot of difference between a `call`
+ // and an `unconditionalBranch`; indeed, this is part of what
+ // motivates the use of `IRParam`s for both function parameters
+ // and phi nodes.
+ //
+ // However, while both blocks and functions use the same `IRParam`
+ // representation, we (currently) do not have a common base
+ // between the `call` and `unconditionalBranch` instructions.
+ //
+ // Rather than have duplicate logic between the two cases, we
+ // simply observe that for our purposes rewriting either a
+ // `call` or an `unconditionalBranch` amounts to doing
+ // special-case work on *one* operand of the original, while
+ // copying over all the other operands as-is.
+ //
+ // Given this observation, we can bottleneck both calls and
+ // branches into a common worker routine by passing down
+ // the instruction to be rewritten and a pointer to the
+ // `IRUse` for the one "interesting" operand.
+
+ void rewriteCall(IRCall* oldCall, Index paramIndex)
+ {
+ rewriteCallOrBranch(oldCall, oldCall->getArgs() + paramIndex);
+ }
+
+ void rewriteBranch(IRUnconditionalBranch* oldBranch, Index paramIndex)
+ {
+ rewriteCallOrBranch(oldBranch, oldBranch->getArgs() + paramIndex);
+ }
+
+ void rewriteCallOrBranch(IRInst* oldCallOrBranch, IRUse* oldOperandToRewrite)
+ {
+ // Our goal here is to generate a new version of
+ // `oldCallOrBranch` that copies over most of the
+ // operands as-is, but introduces our rewrites
+ // around the chosen operand.
+
+ IRBuilder builder(module);
+ builder.setInsertBefore(oldCallOrBranch);
+
+ // We capture the old operand list as a range of
+ // `IRUse`s, and set up a fresh list to hold the
+ // new operands.
//
- // Rather than have duplicate logic between the two cases, we
- // simply observe that for our purposes rewriting either a
- // `call` or an `unconditionalBranch` amounts to doing
- // special-case work on *one* operand of the original, while
- // copying over all the other operands as-is.
+ auto oldOperandsBegin = oldCallOrBranch->getOperands();
+ auto oldOperandsEnd = oldOperandsBegin + oldCallOrBranch->getOperandCount();
//
- // Given this observation, we can bottleneck both calls and
- // branches into a common worker routine by passing down
- // the instruction to be rewritten and a pointer to the
- // `IRUse` for the one "interesting" operand.
+ List<IRInst*> newOperands;
- void rewriteCall(IRCall* oldCall, Index paramIndex)
+ // All of the operands that precede the interesting
+ // one can be copied over from the old list to the
+ // new one as-is.
+ //
+ for (auto u = oldOperandsBegin; u < oldOperandToRewrite; ++u)
{
- rewriteCallOrBranch(
- oldCall,
- oldCall->getArgs() + paramIndex);
+ auto operand = u->get();
+ newOperands.add(operand);
}
- void rewriteBranch(IRUnconditionalBranch* oldBranch, Index paramIndex)
+ // Next we look at the value of the "intersting"
+ // operand, knowing that we need to pass along
+ // not only the original value but also the
+ // binding information.
+ //
+ IRInst* arg = oldOperandToRewrite->get();
+ IRInst* registerIndex = nullptr;
+ IRInst* registerSpace = nullptr;
+
+ // As a simple optimization, if we have *already*
+ // computed and cached binding information for
+ // the argument, we can re-use that information
+ // here and now.
+ //
+ if (auto info = findOpaqueValueInfo(arg))
{
- rewriteCallOrBranch(
- oldBranch,
- oldBranch->getArgs() + paramIndex);
+ registerIndex = info->registerIndex;
+ registerSpace = info->registerSpace;
}
-
- void rewriteCallOrBranch(
- IRInst* oldCallOrBranch,
- IRUse* oldOperandToRewrite)
+ else
{
- // Our goal here is to generate a new version of
- // `oldCallOrBranch` that copies over most of the
- // operands as-is, but introduces our rewrites
- // around the chosen operand.
-
- IRBuilder builder(module);
- builder.setInsertBefore(oldCallOrBranch);
-
- // We capture the old operand list as a range of
- // `IRUse`s, and set up a fresh list to hold the
- // new operands.
- //
- auto oldOperandsBegin = oldCallOrBranch->getOperands();
- auto oldOperandsEnd = oldOperandsBegin + oldCallOrBranch->getOperandCount();
+ // If there is no cached information for
+ // the argument, we choose *not* to make
+ // a recursive call into `findOrComputeOpaqueValueInfo`.
//
- List<IRInst*> newOperands;
-
- // All of the operands that precede the interesting
- // one can be copied over from the old list to the
- // new one as-is.
- //
- for (auto u = oldOperandsBegin; u < oldOperandToRewrite; ++u)
- {
- auto operand = u->get();
- newOperands.add(operand);
- }
-
- // Next we look at the value of the "intersting"
- // operand, knowing that we need to pass along
- // not only the original value but also the
- // binding information.
+ // Instead we will simply emit additional
+ // binding query intrinsics into the body
+ // of the caller (right before the call site),
+ // and add those instructions to our work
+ // list, to be eliminated later.
//
- IRInst* arg = oldOperandToRewrite->get();
- IRInst* registerIndex = nullptr;
- IRInst* registerSpace = nullptr;
-
- // As a simple optimization, if we have *already*
- // computed and cached binding information for
- // the argument, we can re-use that information
- // here and now.
+ registerIndex = builder.emitIntrinsicInst(indexType, kIROp_GetRegisterIndex, 1, &arg);
+ registerSpace = builder.emitIntrinsicInst(indexType, kIROp_GetRegisterSpace, 1, &arg);
//
- if (auto info = findOpaqueValueInfo(arg))
- {
- registerIndex = info->registerIndex;
- registerSpace = info->registerSpace;
- }
- else
- {
- // If there is no cached information for
- // the argument, we choose *not* to make
- // a recursive call into `findOrComputeOpaqueValueInfo`.
- //
- // Instead we will simply emit additional
- // binding query intrinsics into the body
- // of the caller (right before the call site),
- // and add those instructions to our work
- // list, to be eliminated later.
- //
- registerIndex = builder.emitIntrinsicInst(
- indexType,
- kIROp_GetRegisterIndex,
- 1, &arg);
- registerSpace = builder.emitIntrinsicInst(
- indexType,
- kIROp_GetRegisterSpace,
- 1, &arg);
- //
- addToWorkList(registerIndex);
- addToWorkList(registerSpace);
- }
-
- // Whether we have found existing binding information,
- // or emitted new intrinsics, we are now ready
- // to append the argument and its binding information
- // to the new operand list.
- //
- newOperands.add(arg);
- newOperands.add(registerIndex);
- newOperands.add(registerSpace);
+ addToWorkList(registerIndex);
+ addToWorkList(registerSpace);
+ }
- // Any operands of the original instruction that come
- // after the one we rewrite can be copied over as-is.
- //
- // Note: we don't currently have any operands that would
- // appear after the arguments of a `call` or `branch`,
- // but the fact that we encode `IRAttr`s on an instruction
- // as additional (trailing) operands means that this could
- // conceivably happen at some point.
- //
- for (auto u = oldOperandToRewrite + 1; u < oldOperandsEnd; ++u)
- {
- auto operand = u->get();
- newOperands.add(operand);
- }
+ // Whether we have found existing binding information,
+ // or emitted new intrinsics, we are now ready
+ // to append the argument and its binding information
+ // to the new operand list.
+ //
+ newOperands.add(arg);
+ newOperands.add(registerIndex);
+ newOperands.add(registerSpace);
- // Once we've built up the new operand list, we can emit
- // a new instruction that has the same opcode and type,
- // with the new operands, and then use it to replace
- // the existing instruction.
- //
- auto newCallOrBranch = builder.emitIntrinsicInst(
- oldCallOrBranch->getFullType(),
- oldCallOrBranch->getOp(),
- newOperands.getCount(),
- newOperands.getBuffer());
-
- oldCallOrBranch->transferDecorationsTo(newCallOrBranch);
- oldCallOrBranch->replaceUsesWith(newCallOrBranch);
- oldCallOrBranch->removeAndDeallocate();
+ // Any operands of the original instruction that come
+ // after the one we rewrite can be copied over as-is.
+ //
+ // Note: we don't currently have any operands that would
+ // appear after the arguments of a `call` or `branch`,
+ // but the fact that we encode `IRAttr`s on an instruction
+ // as additional (trailing) operands means that this could
+ // conceivably happen at some point.
+ //
+ for (auto u = oldOperandToRewrite + 1; u < oldOperandsEnd; ++u)
+ {
+ auto operand = u->get();
+ newOperands.add(operand);
}
- };
- void lowerBindingQueries(
- IRModule* module,
- DiagnosticSink* sink)
- {
- BindingQueryLoweringContext context(module);
- context.sink = sink;
- context.processModule();
+ // Once we've built up the new operand list, we can emit
+ // a new instruction that has the same opcode and type,
+ // with the new operands, and then use it to replace
+ // the existing instruction.
+ //
+ auto newCallOrBranch = builder.emitIntrinsicInst(
+ oldCallOrBranch->getFullType(),
+ oldCallOrBranch->getOp(),
+ newOperands.getCount(),
+ newOperands.getBuffer());
+
+ oldCallOrBranch->transferDecorationsTo(newCallOrBranch);
+ oldCallOrBranch->replaceUsesWith(newCallOrBranch);
+ oldCallOrBranch->removeAndDeallocate();
}
+};
+
+void lowerBindingQueries(IRModule* module, DiagnosticSink* sink)
+{
+ BindingQueryLoweringContext context(module);
+ context.sink = sink;
+ context.processModule();
}
+} // namespace Slang