summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/emit.cpp24
-rw-r--r--source/slang/ir-entry-point-uniforms.cpp2
-rw-r--r--source/slang/ir-legalize-types.cpp696
-rw-r--r--source/slang/ir-specialize-resources.cpp4
-rw-r--r--source/slang/legalize-types.cpp79
-rw-r--r--source/slang/legalize-types.h151
-rw-r--r--source/slang/options.cpp1
-rw-r--r--source/slang/parameter-binding.cpp97
-rw-r--r--source/slang/type-layout.cpp1009
-rw-r--r--source/slang/type-layout.h141
10 files changed, 1721 insertions, 483 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index f17b13762..5a2a93a53 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -6758,25 +6758,19 @@ StructTypeLayout* getScopeStructLayout(
ScopeLayout* scopeLayout)
{
auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;
- if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
+
+ if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
{
- return structTypeLayout;
+ scopeTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
}
- else if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
- {
- auto elementTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
- auto elementTypeStructLayout = as<StructTypeLayout>(elementTypeLayout);
- // We expect all constant buffers to contain `struct` types for now
- SLANG_RELEASE_ASSERT(elementTypeStructLayout);
-
- return elementTypeStructLayout;
- }
- else
+ if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
{
- SLANG_UNEXPECTED("uhandled global-scope binding layout");
- return nullptr;
+ return structTypeLayout;
}
+
+ SLANG_UNEXPECTED("uhandled global-scope binding layout");
+ return nullptr;
}
/// Given a layout computed for a program, get the layout to use when lookup up variables.
@@ -7005,6 +6999,7 @@ String emitEntryPoint(
legalizeExistentialTypeLayout(
irModule,
sink);
+ eliminateDeadCode(compileRequest, irModule);
#if 0
dumpIRIfEnabled(compileRequest, irModule, "EXISTENTIALS LEGALIZED");
@@ -7025,6 +7020,7 @@ String emitEntryPoint(
legalizeResourceTypes(
irModule,
sink);
+ eliminateDeadCode(compileRequest, irModule);
// Debugging output of legalization
#if 0
diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp
index 7bbf58810..501af56cf 100644
--- a/source/slang/ir-entry-point-uniforms.cpp
+++ b/source/slang/ir-entry-point-uniforms.cpp
@@ -68,7 +68,7 @@ namespace Slang
// return t.Sample(s, uv);
// }
//
-// In this case the output of the transformation shold be:
+// In this case the output of the transformation should be:
//
// struct Params
// {
diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp
index bc26f9afc..7b6c4d79d 100644
--- a/source/slang/ir-legalize-types.cpp
+++ b/source/slang/ir-legalize-types.cpp
@@ -74,6 +74,20 @@ LegalVal LegalVal::getImplicitDeref()
return as<ImplicitDerefVal>(obj)->val;
}
+LegalVal LegalVal::wrappedBuffer(
+ LegalVal const& baseVal,
+ LegalElementWrapping const& elementInfo)
+{
+ RefPtr<WrappedBufferPseudoVal> obj = new WrappedBufferPseudoVal();
+ obj->base = baseVal;
+ obj->elementInfo = elementInfo;
+
+ LegalVal result;
+ result.flavor = LegalVal::Flavor::wrappedBuffer;
+ result.obj = obj;
+ return result;
+}
+
//
IRTypeLegalizationContext::IRTypeLegalizationContext(
@@ -109,10 +123,55 @@ static LegalVal declareVars(
IROp op,
LegalType type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo);
+ /// Unwrap a value with flavor `wrappedBuffer`
+ ///
+ /// The original `legalPtrOperand` has a wrapped-buffer type
+ /// which encodes the way that, e.g., a `ConstantBuffer<Foo>`
+ /// where `Foo` includes interface types, got legalized
+ /// into a buffer that stores a `Foo` value plus addition
+ /// fields for the concrete types that got plugged in.
+ ///
+ /// The `elementInfo` is the layout information for the
+ /// modified ("wrapped") buffer type, and specifies how
+ /// the logical element type was expanded into actual fields.
+ ///
+ /// This function returns a new value that undoes all of
+ /// the wrapping and produces a new `LegalVal` that matches
+ /// the nominal type of the original buffer.
+ ///
+static LegalVal unwrapBufferValue(
+ IRTypeLegalizationContext* context,
+ LegalVal legalPtrOperand,
+ LegalElementWrapping const& elementInfo);
+
+ /// Perform any actions required to materialize `val` into a usable value.
+ ///
+ /// Certain case of `LegalVal` (currently just the `wrappedBuffer` case) are
+ /// suitable for use to represent a variable, but cannot be used directly
+ /// in computations, because their structured needs to be "unwrapped."
+ ///
+ /// This function unwraps any `val` that needs it, which may involve
+ /// emitting additional IR instructions, and returns the unmodified
+ /// `val` otherwise.
+ ///
+static LegalVal maybeMaterializeWrappedValue(
+ IRTypeLegalizationContext* context,
+ LegalVal val)
+{
+ if(val.flavor != LegalVal::Flavor::wrappedBuffer)
+ return val;
+
+ auto wrappedBufferVal = val.getWrappedBuffer();
+ return unwrapBufferValue(
+ context,
+ wrappedBufferVal->base,
+ wrappedBufferVal->elementInfo);
+}
+
// Take a value that is being used as an operand,
// and turn it into the equivalent legalized value.
static LegalVal legalizeOperand(
@@ -120,8 +179,10 @@ static LegalVal legalizeOperand(
IRInst* irValue)
{
LegalVal legalVal;
- if (context->mapValToLegalVal.TryGetValue(irValue, legalVal))
- return legalVal;
+ if( context->mapValToLegalVal.TryGetValue(irValue, legalVal) )
+ {
+ return maybeMaterializeWrappedValue(context, legalVal);
+ }
// For now, assume that anything not covered
// by the mapping is legal as-is.
@@ -455,8 +516,8 @@ static LegalVal legalizeFieldExtract(
(IRStructKey*) fieldKey);
}
- /// Take a value of some buffer/pointer type and wrap it according to provided info.
-static LegalVal wrapBufferValue(
+ /// Take a value of some buffer/pointer type and unwrap it according to provided info.
+static LegalVal unwrapBufferValue(
IRTypeLegalizationContext* context,
LegalVal legalPtrOperand,
LegalElementWrapping const& elementInfo)
@@ -495,7 +556,7 @@ static LegalVal wrapBufferValue(
auto simpleElementInfo = elementInfo.getSimple();
auto valPtr = builder->emitFieldAddress(
- simpleElementInfo->type,
+ builder->getPtrType(simpleElementInfo->type),
legalPtrOperand.getSimple(),
simpleElementInfo->key);
@@ -510,7 +571,7 @@ static LegalVal wrapBufferValue(
// wrap them up in an `implicitDeref` value.
//
auto derefField = elementInfo.getImplicitDeref();
- auto baseVal = wrapBufferValue(context, legalPtrOperand, derefField->field);
+ auto baseVal = unwrapBufferValue(context, legalPtrOperand, derefField->field);
return LegalVal::implicitDeref(baseVal);
}
@@ -524,8 +585,8 @@ static LegalVal wrapBufferValue(
auto pairField = elementInfo.getPair();
auto pairInfo = pairField->pairInfo;
- auto ordinaryVal = wrapBufferValue(context, legalPtrOperand, pairField->ordinary);
- auto specialVal = wrapBufferValue(context, legalPtrOperand, pairField->special);
+ auto ordinaryVal = unwrapBufferValue(context, legalPtrOperand, pairField->ordinary);
+ auto specialVal = unwrapBufferValue(context, legalPtrOperand, pairField->special);
return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
}
@@ -541,14 +602,14 @@ static LegalVal wrapBufferValue(
RefPtr<TuplePseudoVal> obj = new TuplePseudoVal();
for( auto ee : tupleField->elements )
{
- auto elementVal = wrapBufferValue(
+ auto elementVal = unwrapBufferValue(
context,
legalPtrOperand,
ee.field);
TuplePseudoVal::Element element;
element.key = ee.key;
- element.val = wrapBufferValue(
+ element.val = unwrapBufferValue(
context,
legalPtrOperand,
ee.field);
@@ -1244,16 +1305,10 @@ static LegalVal legalizeLocalVar(
context->insertBeforeLocalVar = irLocalVar;
- LegalVarChain* varChain = nullptr;
- LegalVarChain varChainStorage;
- if (varLayout)
- {
- varChainStorage.next = nullptr;
- varChainStorage.varLayout = varLayout;
- varChain = &varChainStorage;
- }
+ LegalVarChainLink varChain(LegalVarChain(), varLayout);
UnownedStringSlice nameHint = findNameHint(irLocalVar);
+ context->builder->setInsertBefore(irLocalVar);
LegalVal newVal = declareVars(context, kIROp_Var, legalValueType, typeLayout, varChain, nameHint, nullptr);
// Remove the old local var.
@@ -1286,7 +1341,9 @@ static LegalVal legalizeParam(
context->insertBeforeParam = originalParam;
UnownedStringSlice nameHint = findNameHint(originalParam);
- auto newVal = declareVars(context, kIROp_Param, legalParamType, nullptr, nullptr, nameHint, nullptr);
+
+ context->builder->setInsertBefore(originalParam);
+ auto newVal = declareVars(context, kIROp_Param, legalParamType, nullptr, LegalVarChain(), nameHint, nullptr);
originalParam->removeFromParent();
context->replacedInstructions.Add(originalParam);
@@ -1314,6 +1371,12 @@ static LegalVal legalizeInst(
IRTypeLegalizationContext* context,
IRInst* inst)
{
+ // Any additional instructions we need to emit
+ // in the process of legalizing `inst` should
+ // by default be insertied right before `inst`.
+ //
+ context->builder->setInsertBefore(inst);
+
// Special-case certain operations
switch (inst->op)
{
@@ -1343,9 +1406,15 @@ static LegalVal legalizeInst(
break;
}
- // Need to legalize all the operands.
+ // We will iterate over all the operands, extract the legalized
+ // value of each, and collect them in an array for subsequent use.
+ //
auto argCount = inst->getOperandCount();
List<LegalVal> legalArgs;
+ //
+ // Along the way we will also note whether there were any operands
+ // with non-simple legalized values.
+ //
bool anyComplex = false;
for (UInt aa = 0; aa < argCount; ++aa)
{
@@ -1357,14 +1426,19 @@ static LegalVal legalizeInst(
anyComplex = true;
}
- // Also legalize the type of the instruction
+ // We must also legalize the type of the instruction, since that
+ // is implicitly one of its operands.
+ //
LegalType legalType = legalizeType(context, inst->getFullType());
+ // If there was nothing interesting that occured for the operands
+ // then we can re-use this instruction as-is.
+ //
if (!anyComplex && legalType.flavor == LegalType::Flavor::simple)
{
- // Nothing interesting happened to the operands,
- // so we seem to be okay, right?
-
+ // While the operands are all "simple," they might not necessarily
+ // be equal to the operands we started with.
+ //
for (UInt aa = 0; aa < argCount; ++aa)
{
auto legalArg = legalArgs[aa];
@@ -1510,7 +1584,7 @@ static LegalVal declareSimpleVar(
IROp op,
IRType* type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo)
{
@@ -1518,11 +1592,7 @@ static LegalVal declareSimpleVar(
RefPtr<VarLayout> varLayout = createVarLayout(varChain, typeLayout);
- DeclRef<VarDeclBase> varDeclRef;
- if (varChain)
- {
- varDeclRef = varChain->varLayout->varDecl;
- }
+ DeclRef<VarDeclBase> varDeclRef = varChain.getLeafVarDeclRef();
IRBuilder* builder = context->builder;
@@ -1611,12 +1681,526 @@ static LegalVal declareSimpleVar(
return legalVarVal;
}
+ /// Add layout information for the fields of a wrapped buffer type.
+ ///
+ /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
+ /// where `Foo` might have interface-type fields that have been
+ /// specialized to a concrete type. E.g.:
+ ///
+ /// struct Car { IDriver driver; int mph; };
+ /// ConstantBuffer<Car> machOne;
+ ///
+ /// In a case where the `machOne.driver` field has been specialized
+ /// to the type `SpeedRacer`, we need to generate a legalized
+ /// buffer layout something like:
+ ///
+ /// struct Car_0 { int mph; }
+ /// struct Wrapped { Car_0 car; SpeedRacer card_d; }
+ /// ConstantBuffer<Wrapped> machOne;
+ ///
+ /// The layout information for the existing `machOne` clearly
+ /// can't apply because we have a new element type with new fields.
+ ///
+ /// This function is used to recursively fill in the layout for
+ /// the fields of the `Wrapped` type, using information recorded
+ /// when the legal wrapped buffer type was created.
+ ///
+static void _addFieldsToWrappedBufferElementTypeLayout(
+ TypeLayout* elementTypeLayout, // layout of the original field type
+ StructTypeLayout* newTypeLayout, // layout we are filling in
+ LegalElementWrapping const& elementInfo, // information on how the original type got wrapped
+ LegalVarChain const& varChain, // chain of variables that is leading to this field
+ bool isSpecial) // should we assume a leaf field is a special (interface) type?
+{
+ // The way we handle things depends primary on the
+ // `elementInfo`, because that tells us how things
+ // were wrapped up when the type was legalized.
+
+ switch( elementInfo.flavor )
+ {
+ case LegalElementWrapping::Flavor::none:
+ // A leaf `none` value meant there was nothing
+ // to encode for a particular field (probably
+ // had a `void` or empty structure type).
+ break;
+
+ case LegalElementWrapping::Flavor::simple:
+ {
+ auto simpleInfo = elementInfo.getSimple();
+
+ // A `simple` wrapping means we hit a leaf
+ // field that can be encoded directly.
+ // What we do here depends on whether we've
+ // reached an ordinary field of the original
+ // data type, or if we've reached a leaf
+ // field of interface type.
+ //
+ // We've been tracking a `varChain` that
+ // remembers all the parent `struct` fields
+ // we've navigated through to get here, and
+ // that information has been tracking two
+ // different pieces of layout:
+ //
+ // * The "primary" layout represents the storage
+ // of the buffer element type as we usually
+ // think of its (e.g., the bytes starting at offset zero).
+ //
+ // * The "pending" layout tells us where all the
+ // fields representing concrete types plugged in
+ // for interface-type slots got placed.
+ //
+ // We have tunneled down info to tell us which case
+ // we should use (`isSpecial`).
+ //
+ // Most of the logic is the same between the two
+ // cases. We will be computing layout information
+ // for a field of the new/wrapped buffer element type.
+ //
+ RefPtr<VarLayout> newFieldLayout;
+ if(isSpecial)
+ {
+ // In the special case, that field will be laid out
+ // based on the "pending" var chain, and the type
+ // of the pending data for the element.
+ //
+ newFieldLayout = createSimpleVarLayout(varChain.pendingChain, elementTypeLayout->pendingDataTypeLayout);
+ }
+ else
+ {
+ // The ordinary case just uses the primary layout
+ // information and the primary/nominal type of
+ // the field.
+ //
+ newFieldLayout = createSimpleVarLayout(varChain.primaryChain, elementTypeLayout);
+ }
+
+ // Either way, we add the new field to the struct type
+ // layout we are building, and also update the mapping
+ // information so that we can find the field layout
+ // based on the IR key for the struct field.
+ //
+ newTypeLayout->fields.Add(newFieldLayout);
+ newTypeLayout->mapKeyToLayout.Add(simpleInfo->key, newFieldLayout);
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::implicitDeref:
+ {
+ // This is the case where a field in the element type
+ // has been legalized from `SomePtrLikeType<T>` to
+ // `T`, so there is a different in levels of indirection.
+ //
+ // We need to recurse and see how the type `T`
+ // got laid out to know what field(s) it might comprise.
+ //
+ auto implicitDerefInfo = elementInfo.getImplicitDeref();
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ implicitDerefInfo->field,
+ varChain,
+ isSpecial);
+ return;
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::pair:
+ {
+ // The pair case is the first main workhorse where
+ // if we had a type that mixed ordinary and interface-type
+ // fields, it would get split into an ordinary part
+ // and a "special" part, each of which might comprise
+ // zero or more fields.
+ //
+ // Here we recurse on both the ordinary and special
+ // sides, and the only interesting tidbit is that
+ // we pass along appropriate values for the `isSpecial`
+ // flag so that we act appropriately upon running
+ // into a leaf field.
+ //
+ auto pairElementInfo = elementInfo.getPair();
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ pairElementInfo->ordinary,
+ varChain,
+ false);
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ pairElementInfo->special,
+ varChain,
+ true);
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::tuple:
+ {
+ // A tuple comes up when we've turned an aggregate
+ // with one or more interface-type fields into
+ // distinct fields at the top level.
+ //
+ // For the most part we just recurse on each field,
+ // but note that we set the `isSpecial` flag on
+ // the recursive calls, since we never use tuples
+ // to store anything that isn't special.
+
+ auto tupleInfo = elementInfo.getTuple();
+ for( auto ee : tupleInfo->elements )
+ {
+ auto oldFieldLayout = getFieldLayout(elementTypeLayout, ee.key);
+ SLANG_ASSERT(oldFieldLayout);
+
+ LegalVarChainLink fieldChain(varChain, oldFieldLayout);
+
+ _addFieldsToWrappedBufferElementTypeLayout(
+ oldFieldLayout->typeLayout,
+ newTypeLayout,
+ ee.field,
+ fieldChain,
+ true);
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unhandled element wrapping flavor");
+ break;
+ }
+}
+
+ /// Add offset information for `kind` to `resultVarLayout`,
+ /// if it doesn't already exist, and adjust the offset so
+ /// that it will represent an offset relative to the
+ /// "primary" data for the surrounding type, rather than
+ /// being relative to the "pending" data.
+ ///
+static void _addOffsetVarLayoutEntry(
+ VarLayout* resultVarLayout,
+ LegalVarChain const& varChain,
+ LayoutResourceKind kind)
+{
+ // If the target already has an offset for this kind, bail out.
+ //
+ if(resultVarLayout->FindResourceInfo(kind))
+ return;
+
+ // Add the `ResourceInfo` that will represent the offset for
+ // this resource kind (it will be initialized to zero by default)
+ //
+ auto resultResInfo = resultVarLayout->findOrAddResourceInfo(kind);
+
+ // Add in any contributions from the "pending" var chain, since
+ // that chain of offsets will accumulate to get the leaf offset
+ // within the pending data, which in this case we assume amounts
+ // to an *absolute* offset.
+ //
+ for(auto vv = varChain.pendingChain; vv; vv = vv->next )
+ {
+ if( auto chainResInfo = vv->varLayout->FindResourceInfo(kind) )
+ {
+ resultResInfo->index += chainResInfo->index;
+ resultResInfo->space += chainResInfo->space;
+ }
+ }
+
+ // Subtract any contributions from the primary var chain, since
+ // we want the resulting offset to be relative to the same
+ // base as that chain.
+ //
+ for(auto vv = varChain.primaryChain; vv; vv = vv->next )
+ {
+ if( auto chainResInfo = vv->varLayout->FindResourceInfo(kind) )
+ {
+ resultResInfo->index -= chainResInfo->index;
+ resultResInfo->space -= chainResInfo->space;
+ }
+ }
+}
+
+ /// Create a variable layout for an field with "pending" type.
+ ///
+ /// The given `typeLayout` should represent the type of a field
+ /// that is being stored in "pending" data, but that now needs
+ /// to be made relative to the "primary" data, because we are
+ /// legalizing the pending data out of the code.
+ ///
+static RefPtr<VarLayout> _createOffsetVarLayout(
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout)
+{
+ RefPtr<VarLayout> resultVarLayout = new VarLayout();
+
+ // For every resource kind the type consumes, we will
+ // compute an adjusted offset for the variable that
+ // encodes the (absolute) offset of the pending data
+ // in `varChain` relative to its primary data.
+ //
+ for( auto resInfo : typeLayout->resourceInfos )
+ {
+ _addOffsetVarLayoutEntry(resultVarLayout, varChain, resInfo.kind);
+ }
+
+ return resultVarLayout;
+}
+
+ /// Place offset information from `srcResInfo` onto `dstLayout`,
+ /// offset by whatever is in `offsetVarLayout`
+static void addOffsetResInfo(
+ VarLayout* dstLayout,
+ VarLayout::ResourceInfo const& srcResInfo,
+ VarLayout* offsetVarLayout)
+{
+ auto kind = srcResInfo.kind;
+ auto dstResInfo = dstLayout->findOrAddResourceInfo(kind);
+
+ dstResInfo->index = srcResInfo.index;
+ dstResInfo->space = srcResInfo.space;
+
+ if( auto offsetResInfo = offsetVarLayout->findOrAddResourceInfo(kind) )
+ {
+ dstResInfo->index += offsetResInfo->index;
+ dstResInfo->space += offsetResInfo->space;
+ }
+}
+
+ /// Create layout information for a wrapped buffer type.
+ ///
+ /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
+ /// where `Foo` might have interface-type fields that have been
+ /// specialized to a concrete type.
+ ///
+ /// Consider:
+ ///
+ /// struct Car { IDriver driver; int mph; };
+ /// ConstantBuffer<Car> machOne;
+ ///
+ /// In a case where the `machOne.driver` field has been specialized
+ /// to the type `SpeedRacer`, we need to generate a legalized
+ /// buffer layout something like:
+ ///
+ /// struct Car_0 { int mph; }
+ /// struct Wrapped { Car_0 car; SpeedRacer card_d; }
+ /// ConstantBuffer<Wrapped> machOne;
+ ///
+ /// The layout information for the existing `machOne` clearly
+ /// can't apply because we have a new element type with new fields.
+ ///
+ /// This function is used to create a layout for a legalized
+ /// buffer type that requires wrapping, based on the original
+ /// type layout information and the variable layout information
+ /// of the surrounding context (e.g., the global shader parameter
+ /// that has this type).
+ ///
+static RefPtr<TypeLayout> _createWrappedBufferTypeLayout(
+ TypeLayout* oldTypeLayout,
+ WrappedBufferPseudoType* wrappedBufferTypeInfo,
+ LegalVarChain const& outerVarChain)
+{
+ // We shouldn't get invoked unless there was a parameter group type,
+ // so we will sanity check for that just to be sure.
+ //
+ auto oldParameterGroupTypeLayout = as<ParameterGroupTypeLayout>(oldTypeLayout);
+ SLANG_ASSERT(oldParameterGroupTypeLayout);
+ if(!oldParameterGroupTypeLayout)
+ return oldTypeLayout;
+
+ // The original type must have been split between the direct/primary
+ // data and some amount of "pending" data to deal with interface-type
+ // data in the element type of the parameter group.
+ //
+ // The legalization step will have already flattened the data inside of
+ // the group to a single `struct` type, which places the primary data first,
+ // and then any pending data into additional fields.
+ //
+ // Our job is to compute a type layout that we can apply to that new
+ // element type, and to a parameter group surrounding it, that will
+ // re-create the original intention of the split layout (both primary
+ // and pending data) for a type that now only has the "primary" data.
+ //
+ RefPtr<ParameterGroupTypeLayout> newTypeLayout = new ParameterGroupTypeLayout();
+ newTypeLayout->type = oldTypeLayout->type;
+ newTypeLayout->rules = oldTypeLayout->rules;
+ newTypeLayout->uniformAlignment = oldTypeLayout->uniformAlignment;
+ for(auto resInfo : oldTypeLayout->resourceInfos)
+ newTypeLayout->addResourceUsage(resInfo);
+
+ // Any fields in the "pending" data will have offset information
+ // that is relative to the pending data for their parent, and so on.
+ // We need to compute layout information that only includes primary
+ // data, so any offset information that is relative to the pending data
+ // needs to instead be relative to the primary data. That amounts to
+ // computing the absolute offset of each pending field, and then
+ // subtracting off the absolute offset of the primary data.
+ //
+ // We will compute the offset that needs to be added up front,
+ // and store it in the form of a `VarLayout`. The offsets we need
+ // can be computed from the `outerVarChain`, and we only need to
+ // store offset information for resource kinds actually consumed
+ // by the pending data type for the buffer as a whole (e.g., we
+ // don't need to apply offsetting to uniform bytes, because
+ // those don't show up in the resource usage of a constant buffer
+ // itself, and so the offsets already *are* relative to the start
+ // of the buffer).
+ //
+ auto offsetVarLayout = _createOffsetVarLayout(outerVarChain, oldTypeLayout->pendingDataTypeLayout);
+ LegalVarChainLink offsetVarChain(LegalVarChain(), offsetVarLayout);
+
+ // We will start our construction of the pieces of the output
+ // type layout by looking at the "container" type/variable.
+ //
+ // A parameter block or constant buffer in Slang needs to
+ // distinguish between the resource usage of the thing in
+ // the block/buffer, vs. the resource usage of the block/buffer
+ // itself. Consider:
+ //
+ // struct Material { float4 color; Texture2D tex; }
+ // ConstantBuffer<Material> gMat;
+ //
+ // When compiling for Vulkan, the `gMat` constant buffer needs
+ // a `binding`, and the `tex` field does too, so the overall
+ // resource usage of `gMat` is two bindings, but we need a
+ // way to encode which of those bindings goes to `gMat.tex`
+ // and which to the constant buffer for `gMat` itself.
+ //
+ {
+ // We will start by extracting the "primary" part of the old
+ // container type/var layout, and constructing new objects
+ // that will represent the layout for our wrapped buffer.
+ //
+ auto oldPrimaryContainerVarLayout = oldParameterGroupTypeLayout->containerVarLayout;
+ auto oldPrimaryContainerTypeLayout = oldPrimaryContainerVarLayout->typeLayout;
+
+ RefPtr<TypeLayout> newContainerTypeLayout = new TypeLayout();
+ newContainerTypeLayout->type = oldPrimaryContainerTypeLayout->type;
+
+ RefPtr<VarLayout> newContainerVarLayout = new VarLayout();
+ newContainerVarLayout->typeLayout = newContainerTypeLayout;
+
+ newTypeLayout->containerVarLayout = newContainerVarLayout;
+
+ // Whatever got allocated for the primary container should get copied
+ // over to the new layout (e.g., if we allocated a constant buffer
+ // for `gMat` then we need to retain that information).
+ //
+ newContainerTypeLayout->addResourceUsageFrom(oldPrimaryContainerTypeLayout);
+ for( auto resInfo : oldPrimaryContainerVarLayout->resourceInfos )
+ {
+ auto newResInfo = newContainerVarLayout->findOrAddResourceInfo(resInfo.kind);
+ newResInfo->index = resInfo.index;
+ newResInfo->space = resInfo.space;
+ }
+
+ // It is possible that a constant buffer and/or space didn't get
+ // allocated for the "primary" data, but ended up being required for
+ // the "pending" data (this would happen if, e.g., a constant buffer
+ // didn't appear to have any uniform data in it, but then once we
+ // plugged in concrete types for interface fields it did...), so
+ // we need to account for that case and copy over the relevant
+ // resource usage from the pending data, if there is any.
+ //
+ if( auto oldPendingContainerVarLayout = oldPrimaryContainerVarLayout->pendingVarLayout )
+ {
+ // Whatever resources were allocated for the pending data type,
+ // our new combined container type needs to account for them
+ // (e.g., if we didn't have a constant buffer in the primary
+ // data, but one got allocated in the pending data, we need
+ // to end up with type layout information that includes a
+ // constnat buffer).
+ //
+ auto oldPendingContainerTypeLayout = oldPendingContainerVarLayout->typeLayout;
+ newContainerTypeLayout->addResourceUsageFrom(oldPendingContainerTypeLayout);
+
+ // We also need to add offset information based on the "pending"
+ // var layout, but we need to deal with the fact that this information
+ // is currently stored relative to the pending var layout for the surrounding
+ // context (passed in as `outerVarChain.pendingChain`), but we need it to be
+ // relative to the primary layout for the surrounding context (`outerVarChain.primaryChain`).
+ // This is where the `offsetVarLayout` we computed above comes
+ // in handy, because it represents the value(s) we need to
+ // add to each of the per-resource-kind offsets.
+ //
+ for( auto resInfo : oldPendingContainerVarLayout->resourceInfos )
+ {
+ addOffsetResInfo(newContainerVarLayout, resInfo, offsetVarLayout);
+ }
+ }
+ }
+
+ // Now that we've dealt with the container variable, we can turn
+ // our attention to the element type. This is the part that
+ // actually got legalized and required us to create a "wrapped"
+ // buffer type in the first place, so we know that it will
+ // have both primary and "pending" parts.
+ //
+ // Let's start by extracting the fields we care about from
+ // the original element type/var layout, and constructing
+ // the objects we'll use to represent the type/var layout for
+ // the new element type.
+ //
+ auto oldElementVarLayout = oldParameterGroupTypeLayout->elementVarLayout;
+ auto oldElementTypeLayout = oldElementVarLayout->typeLayout;
+
+ // Now matter what, the element type of a wrapped buffer
+ // will always have a structure type.
+ //
+ RefPtr<StructTypeLayout> newElementTypeLayout = new StructTypeLayout();
+ newElementTypeLayout->type = oldElementTypeLayout->type;
+
+ // The `wrappedBufferTypeInfo` that was passed in tells
+ // us how the fields of the original type got turned into
+ // zero or more fields in the new element type, so we
+ // need to follow its recursive structure to build
+ // layout information for each of the new fields.
+ //
+ // We will track a "chain" of parent variables that
+ // determines how we got to each leaf field, and is
+ // used to add up the offsets that will be stored
+ // in the new `VarLayout`s that get created.
+ // We know we need to add in some offsets (usually
+ // negative) to any fields that were pending data,
+ // so we will account for that in the initial
+ // chain of outer variables that we pass in.
+ //
+ LegalVarChain varChainForElementType;
+ varChainForElementType.primaryChain = nullptr;
+ varChainForElementType.pendingChain = offsetVarChain.primaryChain;
+
+ _addFieldsToWrappedBufferElementTypeLayout(
+ oldElementTypeLayout,
+ newElementTypeLayout,
+ wrappedBufferTypeInfo->elementInfo,
+ varChainForElementType,
+ true);
+
+ // A parameter group type layout holds a `VarLayout` for the element type,
+ // which encodes the offset of the element type with respect to the
+ // start of the parameter group as a whole (e.g., to handle the case
+ // where a constant buffer needs a `binding`, and so does its
+ // element type, so the offset to the first `binding` for the element
+ // type is one, not zero.
+ //
+ LegalVarChainLink elementVarChain(LegalVarChain(), oldParameterGroupTypeLayout->elementVarLayout);
+ auto newElementVarLayout = createVarLayout(elementVarChain, newElementTypeLayout);
+ newTypeLayout->elementVarLayout = newElementVarLayout;
+
+ // For legacy/API reasons, we also need to compute a version of the
+ // element type where the offset stored in the `elementVarLayout`
+ // gets "baked in" to the fields of the element type.
+ //
+ newTypeLayout->offsetElementTypeLayout = applyOffsetToTypeLayout(
+ newElementTypeLayout,
+ newElementVarLayout);
+
+ return newTypeLayout;
+}
+
static LegalVal declareVars(
IRTypeLegalizationContext* context,
IROp op,
LegalType type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo)
{
@@ -1663,27 +2247,18 @@ static LegalVal declareVars(
for (auto ee : tupleType->elements)
{
- // Fields are currently required to have linkage, since we use
- // their mangled name to look up field layout information.
- //
- auto fieldLinkage = ee.key->findDecoration<IRLinkageDecoration>();
- SLANG_ASSERT(fieldLinkage);
-
- auto fieldLayout = getFieldLayout(typeLayout, fieldLinkage->getMangledName());
+ auto fieldLayout = getFieldLayout(typeLayout, ee.key);
RefPtr<TypeLayout> fieldTypeLayout = fieldLayout ? fieldLayout->typeLayout : nullptr;
+ // If we have a type layout coming in, we really expect to have a layout for each field.
+ SLANG_ASSERT(fieldLayout || !typeLayout);
+
// If we are processing layout information, then
// we need to create a new link in the chain
// of variables that will determine offsets
// for the eventual leaf fields...
- LegalVarChain newVarChainStorage;
- LegalVarChain* newVarChain = varChain;
- if (fieldLayout)
- {
- newVarChainStorage.next = varChain;
- newVarChainStorage.varLayout = fieldLayout;
- newVarChain = &newVarChainStorage;
- }
+ //
+ LegalVarChainLink newVarChain(varChain, fieldLayout);
UnownedStringSlice fieldNameHint;
String joinedNameHintStorage;
@@ -1723,21 +2298,18 @@ static LegalVal declareVars(
{
auto wrappedBuffer = type.getWrappedBuffer();
+ auto wrappedTypeLayout = _createWrappedBufferTypeLayout(typeLayout, wrappedBuffer, varChain);
+
auto innerVal = declareSimpleVar(
context,
op,
wrappedBuffer->simpleType,
- typeLayout,
+ wrappedTypeLayout,
varChain,
nameHint,
globalNameInfo);
- auto wrappedVal = wrapBufferValue(
- context,
- innerVal,
- wrappedBuffer->elementInfo);
-
- return wrappedVal;
+ return LegalVal::wrappedBuffer(innerVal, wrappedBuffer->elementInfo);
}
default:
@@ -1776,7 +2348,8 @@ static LegalVal legalizeGlobalVar(
globalNameInfo.counter = 0;
UnownedStringSlice nameHint = findNameHint(irGlobalVar);
- LegalVal newVal = declareVars(context, kIROp_GlobalVar, legalValueType, nullptr, nullptr, nameHint, &globalNameInfo);
+ context->builder->setInsertBefore(irGlobalVar);
+ LegalVal newVal = declareVars(context, kIROp_GlobalVar, legalValueType, nullptr, LegalVarChain(), nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
registerLegalizedValue(context, irGlobalVar, newVal);
@@ -1819,7 +2392,8 @@ static LegalVal legalizeGlobalConstant(
// TODO: need to handle initializer here!
UnownedStringSlice nameHint = findNameHint(irGlobalConstant);
- LegalVal newVal = declareVars(context, kIROp_GlobalConstant, legalValueType, nullptr, nullptr, nameHint, &globalNameInfo);
+ context->builder->setInsertBefore(irGlobalConstant);
+ LegalVal newVal = declareVars(context, kIROp_GlobalConstant, legalValueType, nullptr, LegalVarChain(), nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
registerLegalizedValue(context, irGlobalConstant, newVal);
@@ -1858,14 +2432,7 @@ static LegalVal legalizeGlobalParam(
{
context->insertBeforeGlobal = irGlobalParam->getNextInst();
- LegalVarChain* varChain = nullptr;
- LegalVarChain varChainStorage;
- if (varLayout)
- {
- varChainStorage.next = nullptr;
- varChainStorage.varLayout = varLayout;
- varChain = &varChainStorage;
- }
+ LegalVarChainLink varChain(LegalVarChain(), varLayout);
IRGlobalNameInfo globalNameInfo;
globalNameInfo.globalVar = irGlobalParam;
@@ -1874,6 +2441,7 @@ static LegalVal legalizeGlobalParam(
// TODO: need to handle initializer here!
UnownedStringSlice nameHint = findNameHint(irGlobalParam);
+ context->builder->setInsertBefore(irGlobalParam);
LegalVal newVal = declareVars(context, kIROp_GlobalParam, legalValueType, typeLayout, varChain, nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
diff --git a/source/slang/ir-specialize-resources.cpp b/source/slang/ir-specialize-resources.cpp
index e974ffdb7..d25284927 100644
--- a/source/slang/ir-specialize-resources.cpp
+++ b/source/slang/ir-specialize-resources.cpp
@@ -424,9 +424,9 @@ struct ResourceParameterSpecializationContext
// An easy case is when P is a parameter that doesn't need
// specialization. In that case:
//
- // * The existing argument A shold be used as an argument in
+ // * The existing argument A should be used as an argument in
// the specialized call.
- // * A clone P' of the existing parameter P shold be used as a
+ // * A clone P' of the existing parameter P should be used as a
// parameter of the specialized callee.
// * No additional instructions are needed in the body of
// the callee; the cloned parameter P' should stand in for P.
diff --git a/source/slang/legalize-types.cpp b/source/slang/legalize-types.cpp
index 6e459ecf1..995b797e4 100644
--- a/source/slang/legalize-types.cpp
+++ b/source/slang/legalize-types.cpp
@@ -591,6 +591,18 @@ LegalType createLegalUniformBufferTypeForResources(
{
switch (legalElementType.flavor)
{
+ case LegalType::Flavor::simple:
+ {
+ // Seeing a simple type here means that it must be a
+ // "special" type (a resource type or array thereof)
+ // because otherwise the catch-all behavior in
+ // `createLegalUniformBufferType()` would have handled it.
+ //
+ // This case is the same as what we do for tuple elements below.
+ //
+ return LegalType::implicitDeref(legalElementType);
+ }
+
case LegalType::Flavor::pair:
{
auto pairType = legalElementType.getPair();
@@ -1080,6 +1092,10 @@ LegalType legalizeTypeImpl(
TypeLegalizationContext* context,
IRType* type)
{
+ if(!type)
+ return LegalType::simple(nullptr);
+
+ context->builder->setInsertBefore(type);
if (auto uniformBufferType = as<IRUniformParameterGroupType>(type))
{
@@ -1097,9 +1113,27 @@ LegalType legalizeTypeImpl(
// we'll want to completely eliminate the uniform/ordinary
// part.
+ auto originalElementType = uniformBufferType->getElementType();
+
// Legalize the element type to see what we are working with.
auto legalElementType = legalizeType(context,
- uniformBufferType->getElementType());
+ originalElementType);
+
+ // As a bit of a corner case, if the user requested something
+ // like `ConstantBuffer<Texture2D>` the element type would
+ // legalize to a "simple" type, and that would be interpreted
+ // as an *ordinary* type, but we really need to notice the
+ // case when the element type is simple, but *special*.
+ //
+ if( context->isSpecialType(originalElementType) )
+ {
+ // Anything that has a special element type needs to
+ // be handled by the pass-specific logic in the context.
+ //
+ return context->createLegalUniformBufferType(
+ uniformBufferType->op,
+ legalElementType);
+ }
// Note that even when legalElementType.flavor == Simple
// we still need to create a new uniform buffer type
@@ -1270,7 +1304,7 @@ RefPtr<TypeLayout> getDerefTypeLayout(
RefPtr<VarLayout> getFieldLayout(
TypeLayout* typeLayout,
- String const& mangledFieldName)
+ IRInst* fieldKey)
{
if (!typeLayout)
return nullptr;
@@ -1294,9 +1328,26 @@ RefPtr<VarLayout> getFieldLayout(
if (auto structTypeLayout = as<StructTypeLayout>(typeLayout))
{
+ // First, let's see if the field had a layout registered
+ // directly using its IR key.
+ //
+ RefPtr<VarLayout> fieldLayout;
+ if(structTypeLayout->mapKeyToLayout.TryGetValue(fieldKey, fieldLayout))
+ return fieldLayout;
+
+ // Otherwise, fall back to doing lookup using the linkage
+ // attached to the key, and its mangled name.
+ //
+ auto fieldLinkage = fieldKey->findDecoration<IRLinkageDecoration>();
+ if(!fieldLinkage)
+ return nullptr;
+ auto mangledFieldName = fieldLinkage->getMangledName();
+
+ // In this case we fall back to a linear search over the fields.
+ //
for(auto ff : structTypeLayout->fields)
{
- if(mangledFieldName == getMangledName(ff->varDecl.getDecl()) )
+ if(mangledFieldName == getMangledName(ff->varDecl.getDecl()).getUnownedSlice() )
{
return ff;
}
@@ -1306,9 +1357,9 @@ RefPtr<VarLayout> getFieldLayout(
return nullptr;
}
-RefPtr<VarLayout> createVarLayout(
- LegalVarChain* varChain,
- TypeLayout* typeLayout)
+RefPtr<VarLayout> createSimpleVarLayout(
+ SimpleLegalVarChain* varChain,
+ TypeLayout* typeLayout)
{
if (!typeLayout)
return nullptr;
@@ -1372,7 +1423,23 @@ RefPtr<VarLayout> createVarLayout(
}
}
+ return varLayout;
+}
+
+RefPtr<VarLayout> createVarLayout(
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout)
+{
+ if(!typeLayout)
+ return nullptr;
+
+ auto varLayout = createSimpleVarLayout(varChain.primaryChain, typeLayout);
+
+ if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout)
+ {
+ varLayout->pendingVarLayout = createSimpleVarLayout(varChain.pendingChain, typeLayout);
+ }
return varLayout;
}
diff --git a/source/slang/legalize-types.h b/source/slang/legalize-types.h
index 831d3538d..b35ed9d3c 100644
--- a/source/slang/legalize-types.h
+++ b/source/slang/legalize-types.h
@@ -328,20 +328,139 @@ RefPtr<TypeLayout> getDerefTypeLayout(
RefPtr<VarLayout> getFieldLayout(
TypeLayout* typeLayout,
- String const& mangledFieldName);
+ IRInst* fieldKey);
-// Represents the "chain" of declarations that
-// were followed to get to a variable that we
-// are now declaring as a leaf variable.
+ /// Represents a "chain" of variables leading to some leaf field.
+ ///
+ /// Consider code like:
+ ///
+ /// struct Branch { int leaf; }
+ /// struct Tree { Branch left; Branch right; }
+ /// cbuffer Forest
+ /// {
+ /// int maxTreeHeight;
+ /// Tree tree;
+ /// }
+ ///
+ /// If we ask "what is the offset of `leaf`" the simple answer is zero,
+ /// but sometimes we are talking about `Forest.tree.right.leaf` which
+ /// will have a very different offset. In Slang parameters can consume
+ /// various (and multiple) resource kinds, so a single offset can't
+ /// be tunneled down through most recursive procedures.
+ ///
+ /// Instead we use a "chain" that works up through the stack, and
+ /// records the path from leaf field like `leaf` up to whatever
+ /// variable is the root for the curent operation.
+ ///
+ /// Operations like computing an offset can then be encoded by
+ /// starting with zero and then walking up the chain and adding in
+ /// offsets as encountered.
+ ///
+struct SimpleLegalVarChain
+{
+ // The next link up the chain, or null if this is the end.
+ SimpleLegalVarChain* next = nullptr;
+
+ // The layout for the variable at this link in thain.
+ VarLayout* varLayout = nullptr;
+};
+
+ /// A "chain" of variable declarations that can handle both primary and "pending" data.
+ ///
+ /// In the presence of interface-type fields, a single variable may
+ /// have data that sits in two distinct allocations, and may have
+ /// `VarLayout`s that represent offseting into each of those
+ /// allocations.
+ ///
+ /// A `LegalVarChain` tracks two distinct `SimpleVarChain`s: one for
+ /// the primary/ordinary data allocation, and one for any pending
+ /// data.
+ ///
+ /// It is okay if the primary/pending chains have different numbers
+ /// of links in them.
+ ///
+ /// Offsets for particular resource kinds in the primary or pending
+ /// data allocation can be queried on the appropriate sub-chain.
+ ///
struct LegalVarChain
{
- LegalVarChain* next;
- VarLayout* varLayout;
+ // The chain of variables that represents the primary allocation.
+ SimpleLegalVarChain* primaryChain = nullptr;
+
+ // The chain of variables that represents the pending allocation.
+ SimpleLegalVarChain* pendingChain = nullptr;
+
+ // If the primary chain is non-empty, gets the variable at the leaf.
+ DeclRef<VarDeclBase> getLeafVarDeclRef() const
+ {
+ if(!primaryChain)
+ return DeclRef<VarDeclBase>();
+
+ return primaryChain->varLayout->varDecl;
+ }
+};
+
+ /// RAII type for adding a link to a `LegalVarChain` as needed.
+ ///
+ /// This type handles the bookkeeping for creating a `LegalVarChain`
+ /// that links in one more variable. It will add a link to each of
+ /// the primary and pending sub-chains if and only if there is non-null
+ /// layout information for the primary/pending case.
+ ///
+ /// Typical usage in a recursive function is:
+ ///
+ /// void someRecursiveFunc(LegalVarChain const& outerChain, ...)
+ /// {
+ /// if(auto subVar = needToRecurse(...))
+ /// {
+ /// LegalVarChainLink subChain(outerChain, subVar);
+ /// someRecursiveFunc(subChain, ...);
+ /// }
+ /// ...
+ /// }
+ ///
+struct LegalVarChainLink : LegalVarChain
+{
+ /// Default constructor: yields an empty chain.
+ LegalVarChainLink()
+ {
+ }
+
+ /// Copy constructor: yields a copy of the `parent` chain.
+ LegalVarChainLink(LegalVarChain const& parent)
+ : LegalVarChain(parent)
+ {}
+
+ /// Construct a chain that extends `parent` with `varLayout`, if it is non-null.
+ LegalVarChainLink(LegalVarChain const& parent, VarLayout* varLayout)
+ : LegalVarChain(parent)
+ {
+ if( varLayout )
+ {
+ primaryLink.next = parent.primaryChain;
+ primaryLink.varLayout = varLayout;
+ primaryChain = &primaryLink;
+
+ if( auto pendingVarLayout = varLayout->pendingVarLayout )
+ {
+ pendingLink.next = parent.pendingChain;
+ pendingLink.varLayout = pendingVarLayout;
+ pendingChain = &pendingLink;
+ }
+ }
+ }
+
+ SimpleLegalVarChain primaryLink;
+ SimpleLegalVarChain pendingLink;
};
RefPtr<VarLayout> createVarLayout(
- LegalVarChain* varChain,
- TypeLayout* typeLayout);
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout);
+
+RefPtr<VarLayout> createSimpleVarLayout(
+ SimpleLegalVarChain* varChain,
+ TypeLayout* typeLayout);
//
// The result of legalizing an IR value will be
@@ -409,6 +528,16 @@ struct LegalVal
SLANG_ASSERT(flavor == Flavor::pair);
return obj.as<PairPseudoVal>();
}
+
+ static LegalVal wrappedBuffer(
+ LegalVal const& baseVal,
+ LegalElementWrapping const& elementInfo);
+
+ RefPtr<WrappedBufferPseudoVal> getWrappedBuffer() const
+ {
+ SLANG_ASSERT(flavor == Flavor::wrappedBuffer);
+ return obj.as<WrappedBufferPseudoVal>();
+ }
};
struct TuplePseudoVal : LegalValImpl
@@ -437,6 +566,12 @@ struct ImplicitDerefVal : LegalValImpl
LegalVal val;
};
+struct WrappedBufferPseudoVal : LegalValImpl
+{
+ LegalVal base;
+ LegalElementWrapping elementInfo;
+};
+
//
/// Context that drives type legalization
diff --git a/source/slang/options.cpp b/source/slang/options.cpp
index a7334f13b..dc37873a4 100644
--- a/source/slang/options.cpp
+++ b/source/slang/options.cpp
@@ -468,6 +468,7 @@ struct OptionsParser
else if(argStr == "-validate-ir" )
{
requestImpl->getFrontEndReq()->shouldValidateIR = true;
+ requestImpl->getBackEndReq()->shouldValidateIR = true;
}
else if(argStr == "-skip-codegen" )
{
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp
index e5057828f..caab5ce6e 100644
--- a/source/slang/parameter-binding.cpp
+++ b/source/slang/parameter-binding.cpp
@@ -1278,6 +1278,27 @@ static void completeBindingsForParameter(
applyBindingInfoToParameter(varLayout, bindingInfos);
}
+ /// Allocate binding location for any "pending" data in a shader parameter.
+ ///
+ /// When a parameter contains interface-type fields (recursively), we might
+ /// not have included them in the base layout for the parameter, and instead
+ /// need to allocate space for them after all other shader parameters have
+ /// been laid out.
+ ///
+ /// This function should be called on the `pendingVarLayout` field of an
+ /// existing `VarLayout` to ensure that its pending data has been properly
+ /// assigned storage. It handles the case where the `pendingVarLayout`
+ /// field is null.
+ ///
+static void _allocateBindingsForPendingData(
+ ParameterBindingContext* context,
+ RefPtr<VarLayout> pendingVarLayout)
+{
+ if(!pendingVarLayout) return;
+
+ completeBindingsForParameter(context, pendingVarLayout);
+}
+
struct SimpleSemanticInfo
{
String name;
@@ -1806,6 +1827,13 @@ struct ScopeLayoutBuilder
RefPtr<StructTypeLayout> m_structLayout;
UniformLayoutInfo m_structLayoutInfo;
+ // We need to compute a layout for any "pending" data inside
+ // of the parameters being added to the scope, to facilitate
+ // later allocating space for all the pending parameters after
+ // the primary shader parameters.
+ //
+ StructTypeLayoutBuilder m_pendingDataTypeLayoutBuilder;
+
void beginLayout(
ParameterBindingContext* context)
{
@@ -1870,9 +1898,24 @@ struct ScopeLayoutBuilder
// `struct` layout logic in `type-layout.cpp`. If this gets any
// more complicated we should see if there is a way to share it.
//
- for( auto pendingItem : firstVarLayout->typeLayout->pendingItems )
+ if( auto fieldPendingDataTypeLayout = firstVarLayout->typeLayout->pendingDataTypeLayout )
{
- m_structLayout->pendingItems.Add(pendingItem);
+ m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules);
+ auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(firstVarLayout->varDecl, fieldPendingDataTypeLayout);
+
+ m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout();
+
+ if( parameterInfo )
+ {
+ for( auto& varLayout : parameterInfo->varLayouts )
+ {
+ varLayout->pendingVarLayout = fieldPendingDataVarLayout;
+ }
+ }
+ else
+ {
+ firstVarLayout->pendingVarLayout = fieldPendingDataVarLayout;
+ }
}
}
@@ -1896,6 +1939,7 @@ struct ScopeLayoutBuilder
// Finish computing the layout for the ordindary data (if any).
//
m_rules->EndStructLayout(&m_structLayoutInfo);
+ m_pendingDataTypeLayoutBuilder.endLayout();
// Copy the final layout information computed for ordinary data
// over to the struct type layout for the scope.
@@ -1917,6 +1961,14 @@ struct ScopeLayoutBuilder
// record into a suitable object that represents the scope
RefPtr<VarLayout> scopeVarLayout = new VarLayout();
scopeVarLayout->typeLayout = scopeTypeLayout;
+
+ if( auto pendingTypeLayout = scopeTypeLayout->pendingDataTypeLayout )
+ {
+ RefPtr<VarLayout> pendingVarLayout = new VarLayout();
+ pendingVarLayout->typeLayout = pendingTypeLayout;
+ scopeVarLayout->pendingVarLayout = pendingVarLayout;
+ }
+
return scopeVarLayout;
}
};
@@ -2451,6 +2503,47 @@ RefPtr<ProgramLayout> generateParameterBindings(
cbInfo->space = globalConstantBufferBinding.space;
cbInfo->index = globalConstantBufferBinding.index;
}
+
+ // After we have laid out all the ordinary parameters,
+ // we need to go through the global scope plus each entry point,
+ // and "flush" out any pending data that was associated with
+ // those scopes as part of dealing with interface-type parameters.
+ //
+ _allocateBindingsForPendingData(&context, globalScopeVarLayout->pendingVarLayout);
+ for( auto entryPoint : sharedContext.programLayout->entryPoints )
+ {
+ _allocateBindingsForPendingData(&context, entryPoint->parametersLayout->pendingVarLayout);
+ }
+
+
+ // HACK: we want global parameters to not have to deal with offsetting
+ // by the `VarLayout` stored in `globalScopeVarLayout`, so we will scan
+ // through and for any global parameter that used "pending" data, we will manually
+ // offset all of its resource infos to account for where the global pending data
+ // got placed.
+ //
+ // TODO: A more appropriate solution would be to pass the `globalScopeVarLayout`
+ // down into the pass that puts layout information onto global parameters in
+ // the IR, and apply the offsetting there.
+ //
+ for( auto& parameterInfo : sharedContext.parameters )
+ {
+ for( auto varLayout : parameterInfo->varLayouts )
+ {
+ auto pendingVarLayout = varLayout->pendingVarLayout;
+ if(!pendingVarLayout) continue;
+
+ for( auto& resInfo : pendingVarLayout->resourceInfos )
+ {
+ if( auto globalResInfo = globalScopeVarLayout->pendingVarLayout->FindResourceInfo(resInfo.kind) )
+ {
+ resInfo.index += globalResInfo->index;
+ resInfo.space += globalResInfo->space;
+ }
+ }
+ }
+ }
+
programLayout->parametersLayout = globalScopeVarLayout;
{
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp
index 77588036d..ce767fc8b 100644
--- a/source/slang/type-layout.cpp
+++ b/source/slang/type-layout.cpp
@@ -878,30 +878,6 @@ bool IsResourceKind(LayoutResourceKind kind)
}
- /// A custom tuple to capture the outputs of type layout
-struct TypeLayoutResult
-{
- /// The actual heap-allocated layout object with all the details
- RefPtr<TypeLayout> layout;
-
- /// A simplified representation of layout information.
- ///
- /// This information is suitable for the case where a type only
- /// consumes a single resource.
- ///
- SimpleLayoutInfo info;
-
- /// Default constructor.
- TypeLayoutResult()
- {}
-
- /// Construct a result from the given layout object and simple layout info.
- TypeLayoutResult(RefPtr<TypeLayout> inLayout, SimpleLayoutInfo const& inInfo)
- : layout(inLayout)
- , info(inInfo)
- {}
-};
-
/// Create a type layout for a type that has simple layout needs.
///
/// This handles any type that can express its layout in `SimpleLayoutInfo`,
@@ -1090,18 +1066,17 @@ static bool shouldAllocateRegisterSpaceForParameterBlock(
}
// Given an existing type layout `oldTypeLayout`, apply offsets
-// to any contained fields based on the resource infos in `offsetTypeLayout`
-// *and* the usage implied by `offsetLayout`
+// to any contained fields based on the resource infos in `offsetVarLayout`.
RefPtr<TypeLayout> applyOffsetToTypeLayout(
RefPtr<TypeLayout> oldTypeLayout,
- RefPtr<TypeLayout> offsetTypeLayout)
+ RefPtr<VarLayout> offsetVarLayout)
{
// There is no need to apply offsets if the old type and the offset
// don't share any resource infos in common.
bool anyHit = false;
for (auto oldResInfo : oldTypeLayout->resourceInfos)
{
- if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind))
+ if (auto offsetResInfo = offsetVarLayout->FindResourceInfo(oldResInfo.kind))
{
anyHit = true;
break;
@@ -1138,12 +1113,9 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout(
auto newResInfo = newField->findOrAddResourceInfo(oldResInfo.kind);
newResInfo->index = oldResInfo.index;
newResInfo->space = oldResInfo.space;
- if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind))
+ if (auto offsetResInfo = offsetVarLayout->FindResourceInfo(oldResInfo.kind))
{
- // We should not be trying to offset things by an infinite amount,
- // since that would leave all the indices undefined.
- SLANG_RELEASE_ASSERT(offsetResInfo->count.isFinite());
- newResInfo->index += offsetResInfo->count.getFiniteValue();
+ newResInfo->index += offsetResInfo->index;
}
}
@@ -1180,273 +1152,466 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout(
return newTypeLayout;
}
- /// Take a type layout that might include pending items and fold them into the layout.
-static RefPtr<TypeLayout> flushPendingItems(
- TypeLayoutContext const& context,
- RefPtr<TypeLayout> layout)
+static bool _usesResourceKind(RefPtr<TypeLayout> typeLayout, LayoutResourceKind kind)
{
- SLANG_UNUSED(context);
+ auto resInfo = typeLayout->FindResourceInfo(kind);
+ return resInfo && resInfo->count != 0;
+}
- // If there are no pending items on the layout,
- // then there is nothing to be done.
- //
- if(layout->pendingItems.Count() == 0)
- return layout;
+static bool _usesOrdinaryData(RefPtr<TypeLayout> typeLayout)
+{
+ return _usesResourceKind(typeLayout, LayoutResourceKind::Uniform);
+}
- // We need to compute a new type layout that reflects
- // the resource usage of the provided `layout`, plus
- // any resource usage for the pending items.
- //
- // TODO: To be correct we should construct a new `TypeLayout`
- // of the same class, but that would take more work, so
- // we'll re-use the one we already have... kind of gross...
- //
- for( auto pendingItem : layout->pendingItems )
+ /// Add resource usage from `srcTypeLayout` to `dstTypeLayout` unless it would be "masked."
+ ///
+ /// This function is appropriate for applying resource usage from an element type
+ /// to the resource usage of a container like a `ConstantBuffer<X>` or
+ /// `ParameterBlock<X>`.
+ ///
+static void _addUnmaskedResourceUsage(
+ TypeLayout* dstTypeLayout,
+ TypeLayout* srcTypeLayout,
+ bool haveFullRegisterSpaceOrSet)
+{
+ for( auto resInfo : srcTypeLayout->resourceInfos )
{
- auto itemTypeLayout = pendingItem.getTypeLayout();
-
- // Any resources used by a pending item should be
- // billed against the flushed layout we are computing.
- //
- // TODO: We need to make this handlde ordinary/uniform
- // data carefully, so that it respects alignment and
- // other layout rules for the target.
- //
- // TODO: We should only be adding in resource usage
- // that can be "hidden" by the type of parameter block
- // being built (e.g., only a `ParameterBlock` that allocates
- // full `set`s/`space`s can hide the `register`s/`binding`s
- // used by resource fields).
- //
- // TODO: we need to write something back to the item,
- // which should have a `VarLayout` or something like
- // that attached to it!
- //
- for( auto resInfo : itemTypeLayout->resourceInfos )
+ switch( resInfo.kind )
{
- layout->addResourceUsage(resInfo);
+ case LayoutResourceKind::Uniform:
+ // Ordinary/uniform resource usage will always be masked.
+ break;
+
+ case LayoutResourceKind::RegisterSpace:
+ case LayoutResourceKind::ExistentialTypeParam:
+ // A parameter group will always pay for full registers
+ // spaces consumed by its element type.
+ //
+ // The same is true for existential type parameters,
+ // since these need to be exposed up through the API.
+ //
+ dstTypeLayout->addResourceUsage(resInfo);
+ break;
+
+ default:
+ // For all other resource kinds, a parameter group
+ // will be able to mask them if and only if it
+ // has a full space/set allocated to it.
+ //
+ // Otherwise, the resource usage of the group must
+ // include the resource usage of the element.
+ //
+ if( !haveFullRegisterSpaceOrSet )
+ {
+ dstTypeLayout->addResourceUsage(resInfo);
+ }
+ break;
}
}
- layout->pendingItems.Clear();
-
- return layout;
}
-static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
+static RefPtr<TypeLayout> _createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType,
- SimpleLayoutInfo parameterGroupInfo,
RefPtr<TypeLayout> rawElementTypeLayout)
{
- // If there are any "pending" items that need to be laid out in
- // the element type of the parameter group, then we want to flush
- // them here.
+ // We are being asked to create a layout for a parameter group,
+ // which is curently either a `ParameterBlock<T>` or a `ConstantBuffer<T>`
//
- // TODO: We might need to make this only flush *parts* of the pending
- // items, based on what the parameter group can absorb, and leave
- // other parts still pending in the type layout we return...
- //
- rawElementTypeLayout = flushPendingItems(
- context.with(context.getRulesFamily()->getConstantBufferRules()),
- rawElementTypeLayout);
-
auto parameterGroupRules = context.rules;
-
RefPtr<ParameterGroupTypeLayout> typeLayout = new ParameterGroupTypeLayout();
typeLayout->type = parameterGroupType;
typeLayout->rules = parameterGroupRules;
+ // Computing the layout is made tricky by several factors.
+ //
+ // A parameter group has to draw a distinction between the element type,
+ // and the resources it consumes, and the "container," which main
+ // consume other resources. The type of resource consumed by
+ // the two can overlap.
+ //
+ // Consider:
+ //
+ // struct MyMaterial { float2 uvScale; Texture2D albedoMap; }
+ // ParameterBlock<MyMaterial> gMaterial;
+ //
+ // In this example, `gMaterial` will need both a constant buffer
+ // binding (to hold the data for `uvScale`) and a texture binding
+ // (for `albedoMap`). On Vulkan, those two things require the *same*
+ // `LayoutResourceKind` (representing a GLSL `binding`). We will
+ // thus track the resource usage of the "container" type and
+ // element type separately, and then combine these to form
+ // the overall layout for the parameter group.
+
RefPtr<TypeLayout> containerTypeLayout = new TypeLayout();
containerTypeLayout->type = parameterGroupType;
containerTypeLayout->rules = parameterGroupRules;
- // The layout of the constant buffer if it gets stored
- // in another constant buffer is just what we computed
- // originally (which should be a single binding "slot"
- // and hence no uniform data).
- //
- SLANG_RELEASE_ASSERT(parameterGroupInfo.kind != LayoutResourceKind::Uniform);
- typeLayout->uniformAlignment = 1;
- containerTypeLayout->uniformAlignment = 1;
-
- // TODO(tfoley): There is a subtle question here of whether
- // a constant buffer declaration that then contains zero
- // bytes of uniform data should actually allocate a CB
- // binding slot. For now I'm going to try to ignore it,
- // but handling this robustly could let other code
- // simply handle the "global scope" as a giant outer
- // CB declaration...
-
- // Make sure that we allocate resource usage for the
- // parameter block itself.
- if( parameterGroupInfo.size != 0 )
- {
- containerTypeLayout->addResourceUsage(
- parameterGroupInfo.kind,
- parameterGroupInfo.size);
- }
-
- // There are several different cases that need to be handled here,
- // depending on whether we have a `ParameterBlock`, a `ConstantBuffer`,
- // or some other kind of parameter group. Furthermore, in the
- // `ParameterBlock` case, we need to deal with different layout
- // rules depending on whether a block should map to a register `space`
- // in HLSL or not.
-
- // Check if we are working with a parameter block...
+ // Because the container and element types will each be situated
+ // at some offset relative to the initial register/binding for
+ // the group as a whole, we allocate a `VarLayout` for both
+ // the container and the element type, to store that offset
+ // information (think of `TypeLayout`s as holding size information,
+ // while `VarLayout`s hold offset information).
+
+ RefPtr<VarLayout> containerVarLayout = new VarLayout();
+ containerVarLayout->typeLayout = containerTypeLayout;
+ typeLayout->containerVarLayout = containerVarLayout;
+
+ RefPtr<VarLayout> elementVarLayout = new VarLayout();
+ elementVarLayout->typeLayout = rawElementTypeLayout;
+ typeLayout->elementVarLayout = elementVarLayout;
+
+ // It is possible to have a `ConstantBuffer<T>` that doesn't
+ // actually need a constant buffer register/binding allocated to it,
+ // because the type `T` doesn't actually contain any ordinary/uniform
+ // data that needs to go into the constant buffer. For example:
+ //
+ // struct MyMaterial { Texture2D t; SamplerState s; };
+ // ConstantBuffer<MyMaterial> gMaterial;
+ //
+ // In this example, the `gMaterial` parameter doesn't actually need
+ // a constant buffer allocated for it. This isn't something that
+ // comes up often for `ConstantBuffer`, but can happen a lot for
+ // `ParameterBlock`.
+ //
+ // To determine if we actually need a constant-buffer binding,
+ // we will inspect the element type and see if it contains
+ // any ordinary/uniform data.
+ //
+ bool wantConstantBuffer = _usesOrdinaryData(rawElementTypeLayout);
+ if( wantConstantBuffer )
+ {
+ // If there is any ordinary data, then we'll need to
+ // allocate a constant buffer regiser/binding into
+ // the overall layout, to account for it.
+ //
+ auto cbUsage = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
+ containerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
+ }
+
+ // Similarly to how we only need a constant buffer to be allocated
+ // if the contents of the group actually had ordinary/uniform data,
+ // we also only want to allocate a `space` or `set` if that is really
+ // required.
+ //
+ //
+ bool canUseSpaceOrSet = false;
+ //
+ // We will only allocate a `space` or `set` if the type is `ParameterBlock<T>`
+ // and not just `ConstantBuffer<T>`.
+ //
+ // Note: `parameterGroupType` is allowed to be null here, if we are allocating
+ // an anonymous constant buffer for global or entry-point parameters, but that
+ // is fine because the case will just return null in that case anyway.
+ //
auto parameterBlockType = as<ParameterBlockType>(parameterGroupType);
-
- // Check if we have a parameter block *and* it should be
- // allocated into its own register space(s)
- bool ownRegisterSpace = false;
- if (parameterBlockType)
+ if( parameterBlockType )
{
- // Should we allocate this block its own register space?
+ // We also can't allocate a `space` or `set` unless the compilation
+ // target actually supports them.
+ //
if( shouldAllocateRegisterSpaceForParameterBlock(context) )
{
- ownRegisterSpace = true;
+ canUseSpaceOrSet = true;
}
+ }
- // If we need a register space, then maybe allocate one.
- if( ownRegisterSpace )
- {
- // The basic logic here is that if the parameter block only
- // contains other parameter blocks (which themselves have
- // their own register spaces), then we don't need to
- // allocate *yet another* register space for the block.
-
- bool needsARegisterSpace = false;
- for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
- {
- if(elementResourceInfo.kind != LayoutResourceKind::RegisterSpace)
- {
- needsARegisterSpace = true;
- break;
- }
- }
-
- // If we determine that a register space is needed, then add one here.
- if( needsARegisterSpace )
- {
- typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
- }
- }
+ // Just knowing that we *can* use a `space` or `set` doesn't tell
+ // us if we would *like* to.
+ //
+ // The basic rule here is that if the element type of the parameter
+ // block contains anything that isn't itself consuming a full
+ // register `space` or `set`, then we'll want an umbrella `space`/`set`
+ // for all such data.
+ //
+ bool wantSpaceOrSet = false;
+ if( canUseSpaceOrSet )
+ {
+ // Note that if we are allocating a constant buffer to hold
+ // some ordinary/uniform data then we definitely want a space/set,
+ // but we don't need to special-case that because the loop
+ // here will also detect the `LayoutResourceKind::Uniform` usage.
- // Next, we check if the parameter block has any uniform data, since
- // that means we need to allocate a constant-buffer binding for it.
- bool anyUniformData = false;
- if(auto elementUniformInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
{
- if( elementUniformInfo->count != 0 )
+ if(elementResourceInfo.kind != LayoutResourceKind::RegisterSpace)
{
- // We have a non-zero number of bytes of uniform data here.
- anyUniformData = true;
+ wantSpaceOrSet = true;
+ break;
}
}
- if( anyUniformData )
- {
- // We need to ensure that the block itself consumes at least one "register" for its
- // constant buffer part.
- auto cbUsage = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
- containerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
- }
}
- // The layout for the element type was computed without any knowledge
- // of what resources the parent type was going to consume; we now
- // need to go through and offset that any starting locations (e.g.,
- // in nested `StructTypeLayout`s) based on what we allocated to
- // the parent.
+ // If after all that we determine that we want a register space/set,
+ // then we allocate one as part of the overall resource usage for
+ // the parameter group type.
//
- // Note: at the moment, constant buffers apply their own offsetting
- // logic elsewhere, so we need to only do this logic for parameter blocks
- RefPtr<TypeLayout> offsetTypeLayout = applyOffsetToTypeLayout(rawElementTypeLayout, containerTypeLayout);
- typeLayout->offsetElementTypeLayout = offsetTypeLayout;
-
+ if( wantSpaceOrSet )
+ {
+ containerTypeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
+ }
- RefPtr<VarLayout> containerVarLayout = new VarLayout();
- containerVarLayout->typeLayout = containerTypeLayout;
+ // Now that we've computed basic resource requirements for the container
+ // part of things (i.e., does it require a constant buffer or not?),
+ // let's go ahead and assign the container variable a relative offset
+ // of zero for each of the kinds of resources that it consumes.
+ //
for( auto typeResInfo : containerTypeLayout->resourceInfos )
{
containerVarLayout->findOrAddResourceInfo(typeResInfo.kind);
}
- typeLayout->containerVarLayout = containerVarLayout;
- // We will construct a dummy variable layout to represent the offsettting
- // that needs to be applied to the element type to put it after the
- // container.
- RefPtr<VarLayout> elementVarLayout = new VarLayout();
- elementVarLayout->typeLayout = rawElementTypeLayout;
+ // Because the container's resource allocation is logically coming
+ // first in the overall group, the element needs to have a layout
+ // such that it comes *after* the container in the relative order.
+ //
for( auto elementTypeResInfo : rawElementTypeLayout->resourceInfos )
{
auto kind = elementTypeResInfo.kind;
auto elementVarResInfo = elementVarLayout->findOrAddResourceInfo(kind);
+
+ // If the container part of things is using the same resource kind
+ // as the element type, then the element needs to start at an offset
+ // after the container.
+ //
if( auto containerTypeResInfo = containerTypeLayout->FindResourceInfo(kind) )
{
SLANG_RELEASE_ASSERT(containerTypeResInfo->count.isFinite());
elementVarResInfo->index += containerTypeResInfo->count.getFiniteValue();
}
}
- typeLayout->elementVarLayout = elementVarLayout;
- if (ownRegisterSpace)
+ // The existing Slang reflection API was created before we really
+ // understood the wrinkle that the "container" and elements parts
+ // of a parameter group could collide on some resource kinds,
+ // so the API doesn't currently expose the nice `VarLayout`s we've
+ // just computed.
+ //
+ // Instead, the API allows the user to query the element type layout
+ // for the group, and the user just assumes that the offsetting
+ // is magically applied there. To go back to the earlier example:
+ //
+ // struct MyMaterial { Texture2D t; SamplerState s; };
+ // ConstantBuffer<MyMaterial> gMaterial;
+ //
+ // A user of the existing reflection API expects to be able to
+ // query the `binding` of `gMaterial` and get back zero, then
+ // query the `binding` of the `t` field of the element type
+ // and get *one*. It is clear that in the abstract, the
+ // `MyMaterial::t` field should have an offset of zero (as
+ // the first field in a `struct`), so to meet the user's
+ // expectations, some cleverness is needed.
+ //
+ // We will use a subroutine `applyOffsetToTypeLayout`
+ // that tries to recursively walk an existing `TypeLayout`
+ // and apply an offset to its fields. This is currently
+ // quite ad hoc, but that doesn't matter much as it
+ // handles `struct` types which are the 99% case for
+ // parameter blocks.
+ //
+ typeLayout->offsetElementTypeLayout = applyOffsetToTypeLayout(rawElementTypeLayout, elementVarLayout);
+
+ // Next, resource usage from the container and element
+ // types may need to "bleed through" to the overall
+ // parameter group type.
+ //
+ // If the parameter group is a `ConstantBuffer<Foo>` then
+ // any ordinary/uniform bytes consumed by `Foo` are masked,
+ // but any other resources it consumes (e.g. `binding`s) need
+ // to bleed through and be accounted for in the overall
+ // layout of the type.
+ //
+ // If we have a `ParameterBlock<Foo>` then any ordinary/uniform
+ // bytes are masked. Furthermore, *if* a whole `space`/`set`
+ // was allocated to the block, then any `register`s or
+ // `binding`s consumed by `Foo` (and by the "container" constant
+ // buffer if we allocated one) are also masked. Any whole
+ // spaces/sets consumed by `Foo` need to bleed through.
+ //
+ // We can start with the easier case of the container type,
+ // since it will either be empty or consume a single constant
+ // buffer. Its resource usage will only bleed through if we
+ // didn't allocate a full `space` or `set`.
+ //
+ _addUnmaskedResourceUsage(typeLayout, containerTypeLayout, wantSpaceOrSet);
+
+ // next we turn to the element type, where the cases are slightly
+ // more involved (technically we could use this same logic for
+ // the container, as it is more general, but it was simpler to
+ // just special-case the container).
+ //
+
+ _addUnmaskedResourceUsage(typeLayout, rawElementTypeLayout, wantSpaceOrSet);
+
+ // At this point we have handled all the complexities that
+ // arise for a parameter group that doesn't include interface-type
+ // fields, or that doesn't include specialization for those fields.
+ //
+ // The remaining complexity all arises if we have interface-type
+ // data in the parameter group, and we are specializing it to
+ // concrete types, that will have their own layout requirements.
+ // In those cases there will be "pending data" on the element
+ // type layout that need to get placed somwhere, but wasn't
+ // included in the layout computed so far.
+ //
+ // All of this is extra work we only have to do if there is
+ // "pending" data in the element type layout.
+ //
+ if( auto pendingElementTypeLayout = rawElementTypeLayout->pendingDataTypeLayout )
{
- // A parameter block type that gets its own register space will only
- // include resource usage from the element type when it itself consumes
- // whole register spaces.
- if (auto elementResInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace))
+ auto rules = rawElementTypeLayout->rules;
+
+ // One really annoying complication we need to deal with here
+ // its that it is possible that the original parameter group
+ // declaration didn't need a constant buffer or `space`/`set`
+ // to be allocated, but once we consider the "pending" data
+ // we need to have a constant buffer and/or space.
+ //
+ // We will compute whether the pending data create a demand
+ // for a constant buffer and/or a space/set, so that we know
+ // if we are in the tricky case.
+ //
+ bool pendingDataWantsConstantBuffer = _usesOrdinaryData(pendingElementTypeLayout);
+ bool pendingDataWantsSpaceOrSet = false;
+ if( canUseSpaceOrSet )
{
- typeLayout->addResourceUsage(*elementResInfo);
+ for( auto resInfo : pendingElementTypeLayout->resourceInfos )
+ {
+ if( resInfo.kind != LayoutResourceKind::RegisterSpace )
+ {
+ pendingDataWantsSpaceOrSet = true;
+ break;
+ }
+ }
}
- }
- else
- {
- // If the parameter block is *not* getting its own regsiter space, then
- // it needs to include the resource usage from the "container" type, plus
- // any relevant resource usage for the element type.
- // We start by accumulating any resource usage from the container.
- for (auto containerResourceInfo : containerTypeLayout->resourceInfos)
+ // We will use a few different variables to track resource
+ // usage for the pending data, with roles similar to the
+ // umbrella type layout, container layout, and element layout
+ // that already came up for the main part of the parameter group type.
+
+
+ RefPtr<TypeLayout> pendingContainerTypeLayout = new TypeLayout();
+ pendingContainerTypeLayout->type = parameterGroupType;
+ pendingContainerTypeLayout->rules = parameterGroupRules;
+
+ containerTypeLayout->pendingDataTypeLayout = pendingContainerTypeLayout;
+
+ RefPtr<VarLayout> pendingContainerVarLayout = new VarLayout();
+ pendingContainerVarLayout->typeLayout = pendingContainerTypeLayout;
+
+ containerVarLayout->pendingVarLayout = pendingContainerVarLayout;
+
+
+ RefPtr<VarLayout> pendingElementVarLayout = new VarLayout();
+ pendingElementVarLayout->typeLayout = pendingElementTypeLayout;
+
+ elementVarLayout->pendingVarLayout = pendingElementVarLayout;
+
+ // If we need a space/set for the pending data, and don't already
+ // have one, then we will allocate it now, as part of the
+ // "full" data type.
+ //
+ if( pendingDataWantsSpaceOrSet && !wantSpaceOrSet )
{
- typeLayout->addResourceUsage(containerResourceInfo);
+ pendingContainerTypeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
+
+ // From here on, we know we have access to a register space,
+ // and we can mask any registers/bindings appropriately.
+ //
+ wantSpaceOrSet = true;
}
- // Now we will (possibly) accumulate the resources used by the element
- // type into the resources used by the parameter group. The reason
- // this is "possibly" is because, e.g., a `ConstantBuffer<Foo>` should
- // not report itself as consuming `sizeof(Foo)` bytes of uniform data,
- // or else it would mess up layout for any type that contains the
- // constant buffer.
- for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
+ // If we need a constant buffer for laying out ordinary
+ // data, and didn't have one allocated before, we will create
+ // one.
+ //
+ if( pendingDataWantsConstantBuffer && !wantConstantBuffer )
{
- switch( elementResourceInfo.kind )
- {
- case LayoutResourceKind::Uniform:
- // Uniform resource usages will always be hidden.
- break;
+ auto cbUsage = rules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
+ pendingContainerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
- default:
- // All other register types will not be hidden,
- // since we aren't in the case where the parameter group
- // gets its own register space.
- typeLayout->addResourceUsage(elementResourceInfo);
- break;
+ wantConstantBuffer = true;
+ }
+
+ for( auto resInfo : pendingContainerTypeLayout->resourceInfos )
+ {
+ pendingContainerVarLayout->findOrAddResourceInfo(resInfo.kind);
+ }
+
+ // Now that we've added in the resource usage for any CB or set/space
+ // we needed to allocate just for the pending data, we can safely
+ // lay out the pending data itself.
+ //
+ // The ordinary/uniform part of things wil always be "masked" and
+ // needs to come after any uniform data from the original element type.
+ //
+ // To kick things off we will initialize state for `struct` type layout,
+ // so that we can lay out the pending data as if it were the second
+ // field in a structure type, after the original data.
+ //
+ UniformLayoutInfo uniformLayout = rules->BeginStructLayout();
+ if( auto resInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ uniformLayout.alignment = rawElementTypeLayout->uniformAlignment;
+ uniformLayout.size = resInfo->count;
+ }
+
+ // Now we can scan through the resources used by the pending data.
+ //
+ for( auto resInfo : pendingElementTypeLayout->resourceInfos )
+ {
+ if( resInfo.kind == LayoutResourceKind::Uniform )
+ {
+ // For the ordinary/uniform resource kind, we will add the resource
+ // usage as a structure field, and then write the resulting offset
+ // into the variable layout for the pending data.
+ //
+ auto offset = rules->AddStructField(
+ &uniformLayout,
+ UniformLayoutInfo(
+ resInfo.count,
+ pendingElementTypeLayout->uniformAlignment));
+ pendingElementVarLayout->findOrAddResourceInfo(resInfo.kind)->index = offset.getFiniteValue();
+ }
+ else
+ {
+ // For all other resource kinds, we will set the offset in
+ // the variable layout based on the total resources of that
+ // kind seen so far (including the "container" if any),
+ // and then bump the count for total resource usage.
+ //
+ auto elementVarResInfo = pendingElementVarLayout->findOrAddResourceInfo(resInfo.kind);
+ if( auto containerTypeInfo = pendingContainerTypeLayout->FindResourceInfo(resInfo.kind) )
+ {
+ elementVarResInfo->index = containerTypeInfo->count.getFiniteValue();
+ }
}
}
- }
+ rules->EndStructLayout(&uniformLayout);
- return typeLayout;
-}
+ // Okay, now we have a `VarLayout` for the element data, and an overall `TypeLayout`
+ // for all the data that this parameter group needs allocated for pending
+ // data.
+ //
+ // The next major step is to compute the version of that combined resource usage
+ // that will "bleed through" and thus needs to be allocated at the next level
+ // up the hierarchy.
+ //
+ RefPtr<TypeLayout> unmaskedPendingDataTypeLayout = new TypeLayout();
+ _addUnmaskedResourceUsage(unmaskedPendingDataTypeLayout, pendingContainerTypeLayout, wantSpaceOrSet);
+ _addUnmaskedResourceUsage(unmaskedPendingDataTypeLayout, pendingElementTypeLayout, wantSpaceOrSet);
-static bool usesResourceKind(RefPtr<TypeLayout> typeLayout, LayoutResourceKind kind)
-{
- auto resInfo = typeLayout->FindResourceInfo(kind);
- return resInfo && resInfo->count != 0;
-}
+ // TODO: we should probably optimize for the case where there is no unmasked
+ // usage that needs to be reported out, since it should be a common case.
-static bool usesOrdinaryData(RefPtr<TypeLayout> typeLayout)
-{
- return usesResourceKind(typeLayout, LayoutResourceKind::Uniform);
+ // Now we need to update the type layout to what we've done.
+ //
+ typeLayout->pendingDataTypeLayout = unmaskedPendingDataTypeLayout;
+ }
+
+ return typeLayout;
}
/// Do we need to wrap the given element type in a constant buffer layout?
@@ -1454,15 +1619,15 @@ static bool needsConstantBuffer(RefPtr<TypeLayout> elementTypeLayout)
{
// We need a constant buffer if the element type has ordinary/uniform data.
//
- if(usesOrdinaryData(elementTypeLayout))
+ if(_usesOrdinaryData(elementTypeLayout))
return true;
- // We also need a constant buffer if there are any "pending"
- // items that need ordinary/uniform data allocated to them.
+ // We also need a constant buffer if there is any "pending"
+ // data that need ordinary/uniform data allocated to them.
//
- for( auto pendingItem : elementTypeLayout->pendingItems )
+ if(auto pendingDataTypeLayout = elementTypeLayout->pendingDataTypeLayout)
{
- if(usesOrdinaryData(pendingItem.getTypeLayout()))
+ if(_usesOrdinaryData(pendingDataTypeLayout))
return true;
}
@@ -1487,50 +1652,29 @@ RefPtr<TypeLayout> createConstantBufferTypeLayoutIfNeeded(
.with(parameterGroupRules)
.with(context.targetReq->getDefaultMatrixLayoutMode()),
nullptr,
- parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
elementTypeLayout);
}
-static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
+static RefPtr<TypeLayout> _createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType,
RefPtr<Type> elementType,
LayoutRulesImpl* elementTypeRules)
{
- auto parameterGroupRules = context.rules;
-
- // First compute resource usage of the block itself.
- // For now we assume that the layout of the block can
- // always be described in a `SimpleLayoutInfo` (only
- // a single resource kind consumed).
- SimpleLayoutInfo info;
- if (parameterGroupType)
- {
- info = getParameterGroupLayoutInfo(
- parameterGroupType,
- parameterGroupRules);
- }
- else
- {
- // If there is no concrete type, then it seems like we are
- // being asked to compute layout for the global scope
- info = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
- }
-
- // Now compute a layout for the elements of the parameter block.
- // Note that we need to be careful and deal with the case where
- // the elements of the block use the same resource kind consumed
- // by the block itself.
-
+ // We will first compute a layout for the element type of
+ // the parameter group.
+ //
auto elementTypeLayout = createTypeLayout(
context.with(elementTypeRules),
elementType);
+ // Now we delegate to a routine that does the meat of
+ // the complicated layout logic.
+ //
return _createParameterGroupTypeLayout(
context,
parameterGroupType,
- info,
elementTypeLayout);
}
@@ -1569,7 +1713,7 @@ LayoutRulesImpl* getParameterBufferElementTypeLayoutRules(
}
}
-RefPtr<ParameterGroupTypeLayout> createParameterGroupTypeLayout(
+RefPtr<TypeLayout> createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType)
{
@@ -1962,6 +2106,197 @@ static RefPtr<TypeLayout> maybeAdjustLayoutForArrayElementType(
}
}
+ /// Convert a `TypeLayout` to a `TypeLayoutResult`
+ ///
+ /// A `TypeLayout` holds all the data needed to make a `TypeLayoutResult` in practice,
+ /// but sometimes it is more convenient to have the data split out.
+ ///
+TypeLayoutResult makeTypeLayoutResult(RefPtr<TypeLayout> typeLayout)
+{
+ TypeLayoutResult result;
+ result.layout = typeLayout;
+
+ // If the type only consumes a single kind of non-uniform resource,
+ // we can fill in the `info` field directly.
+ //
+ if( typeLayout->resourceInfos.Count() == 1 )
+ {
+ auto resInfo = typeLayout->resourceInfos[0];
+ if( resInfo.kind != LayoutResourceKind::Uniform )
+ {
+ result.info.kind = resInfo.kind;
+ result.info.size = resInfo.count;
+ return result;
+ }
+ }
+
+ // Otherwise, we will fill out the info based on the uniform
+ // resources consumed, if any.
+ //
+ if( auto resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ result.info.kind = LayoutResourceKind::Uniform;
+ result.info.alignment = typeLayout->uniformAlignment;
+ result.info.size = resInfo->count;
+ }
+
+ // If there was no ordinary/uniform resource usage, then we
+ // will leave the `info` field in its default state (which
+ // shows no resources consumed).
+ //
+ // The type layout might have more detailed information, but
+ // at this point it must contain either zero, or more than one
+ // `ResourceInfo`, so there is nothing unambiguous we can
+ // store into `info`.
+
+ return result;
+}
+
+//
+// StructTypeLayoutBuilder
+//
+
+void StructTypeLayoutBuilder::beginLayout(
+ Type* type,
+ LayoutRulesImpl* rules)
+{
+ m_rules = rules;
+
+ m_typeLayout = new StructTypeLayout();
+ m_typeLayout->type = type;
+ m_typeLayout->rules = m_rules;
+
+ m_info = m_rules->BeginStructLayout();
+}
+
+void StructTypeLayoutBuilder::beginLayoutIfNeeded(
+ Type* type,
+ LayoutRulesImpl* rules)
+{
+ if( !m_typeLayout )
+ {
+ beginLayout(type, rules);
+ }
+}
+
+RefPtr<VarLayout> StructTypeLayoutBuilder::addField(
+ DeclRef<VarDeclBase> field,
+ TypeLayoutResult fieldResult)
+{
+ SLANG_ASSERT(m_typeLayout);
+
+ RefPtr<TypeLayout> fieldTypeLayout = fieldResult.layout;
+ UniformLayoutInfo fieldInfo = fieldResult.info.getUniformLayout();
+
+ // Note: we don't add any zero-size fields
+ // when computing structure layout, just
+ // to avoid having a resource type impact
+ // the final layout.
+ //
+ // This means that the code to generate final
+ // declarations needs to *also* eliminate zero-size
+ // fields to be safe...
+ //
+ LayoutSize uniformOffset = m_info.size;
+ if(fieldInfo.size != 0)
+ {
+ uniformOffset = m_rules->AddStructField(&m_info, fieldInfo);
+ }
+
+
+ // We need to create variable layouts
+ // for each field of the structure.
+ RefPtr<VarLayout> fieldLayout = new VarLayout();
+ fieldLayout->varDecl = field;
+ fieldLayout->typeLayout = fieldTypeLayout;
+ m_typeLayout->fields.Add(fieldLayout);
+ m_typeLayout->mapVarToLayout.Add(field.getDecl(), fieldLayout);
+
+ // Set up uniform offset information, if there is any uniform data in the field
+ if( fieldTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
+ }
+
+ // Add offset information for any other resource kinds
+ for( auto fieldTypeResourceInfo : fieldTypeLayout->resourceInfos )
+ {
+ // Uniforms were dealt with above
+ if(fieldTypeResourceInfo.kind == LayoutResourceKind::Uniform)
+ continue;
+
+ // We should not have already processed this resource type
+ SLANG_RELEASE_ASSERT(!fieldLayout->FindResourceInfo(fieldTypeResourceInfo.kind));
+
+ // The field will need offset information for this kind
+ auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind);
+
+ // It is possible for a `struct` field to use an unbounded array
+ // type, and in the D3D case that would consume an unbounded number
+ // of registers. What is more, a single `struct` could have multiple
+ // such fields, or ordinary resource fields after an unbounded field.
+ //
+ // We handle this case by allocating a distinct register space for
+ // any field that consumes an unbounded amount of registers.
+ //
+ if( fieldTypeResourceInfo.count.isInfinite() )
+ {
+ // We need to add one register space to own the storage for this field.
+ //
+ auto structTypeSpaceResourceInfo = m_typeLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace);
+ auto spaceOffset = structTypeSpaceResourceInfo->count;
+ structTypeSpaceResourceInfo->count += 1;
+
+ // The field itself will record itself as having a zero offset into
+ // the chosen space.
+ //
+ fieldResourceInfo->space = spaceOffset.getFiniteValue();
+ fieldResourceInfo->index = 0;
+ }
+ else
+ {
+ // In the case where the field consumes a finite number of slots, we
+ // can simply set its offset/index to the number of such slots consumed
+ // so far, and then increment the number of slots consumed by the
+ // `struct` type itself.
+ //
+ auto structTypeResourceInfo = m_typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind);
+ fieldResourceInfo->index = structTypeResourceInfo->count.getFiniteValue();
+ structTypeResourceInfo->count += fieldTypeResourceInfo.count;
+ }
+ }
+
+ return fieldLayout;
+}
+
+RefPtr<VarLayout> StructTypeLayoutBuilder::addField(
+ DeclRef<VarDeclBase> field,
+ RefPtr<TypeLayout> fieldTypeLayout)
+{
+ TypeLayoutResult fieldResult = makeTypeLayoutResult(fieldTypeLayout);
+ return addField(field, fieldResult);
+}
+
+void StructTypeLayoutBuilder::endLayout()
+{
+ if(!m_typeLayout) return;
+
+ m_rules->EndStructLayout(&m_info);
+
+ m_typeLayout->uniformAlignment = m_info.alignment;
+ m_typeLayout->addResourceUsage(LayoutResourceKind::Uniform, m_info.size);
+}
+
+RefPtr<StructTypeLayout> StructTypeLayoutBuilder::getTypeLayout()
+{
+ return m_typeLayout;
+}
+
+TypeLayoutResult StructTypeLayoutBuilder::getTypeLayoutResult()
+{
+ return TypeLayoutResult(m_typeLayout, m_info);
+}
+
static TypeLayoutResult _createTypeLayout(
TypeLayoutContext const& context,
Type* type)
@@ -2365,20 +2700,11 @@ static TypeLayoutResult _createTypeLayout(
if (auto structDeclRef = declRef.as<StructDecl>())
{
- RefPtr<StructTypeLayout> typeLayout = new StructTypeLayout();
- typeLayout->type = type;
- typeLayout->rules = rules;
-
- // The layout of a `struct` type is computed in the somewhat
- // obvious fashion by keeping a running counter of the resource
- // usage for each kind of resource, and then for a field that
- // uses a given resource, assigning it the current offset and
- // then bumping the offset by the field size. In the case of
- // uniform data we also need to deal with alignment and other
- // detailed layout rules.
-
- UniformLayoutInfo info = rules->BeginStructLayout();
+ StructTypeLayoutBuilder typeLayoutBuilder;
+ StructTypeLayoutBuilder pendingDataTypeLayoutBuilder;
+ typeLayoutBuilder.beginLayout(type, rules);
+ auto typeLayout = typeLayoutBuilder.getTypeLayout();
for (auto field : GetFields(structDeclRef))
{
// Static fields shouldn't take part in layout.
@@ -2409,105 +2735,37 @@ static TypeLayoutResult _createTypeLayout(
fieldLayoutContext,
GetType(field).Ptr(),
field.getDecl());
- RefPtr<TypeLayout> fieldTypeLayout = fieldResult.layout;
- UniformLayoutInfo fieldInfo = fieldResult.info.getUniformLayout();
-
- // Note: we don't add any zero-size fields
- // when computing structure layout, just
- // to avoid having a resource type impact
- // the final layout.
- //
- // This means that the code to generate final
- // declarations needs to *also* eliminate zero-size
- // fields to be safe...
- LayoutSize uniformOffset = info.size;
- if(fieldInfo.size != 0)
- {
- uniformOffset = rules->AddStructField(&info, fieldInfo);
- }
-
- // We need to create variable layouts
- // for each field of the structure.
- RefPtr<VarLayout> fieldLayout = new VarLayout();
- fieldLayout->varDecl = field;
- fieldLayout->typeLayout = fieldTypeLayout;
- typeLayout->fields.Add(fieldLayout);
- typeLayout->mapVarToLayout.Add(field.getDecl(), fieldLayout);
-
- // Set up uniform offset information, if there is any uniform data in the field
- if( fieldTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
- {
- fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
- }
-
- // Add offset information for any other resource kinds
- for( auto fieldTypeResourceInfo : fieldTypeLayout->resourceInfos )
- {
- // Uniforms were dealt with above
- if(fieldTypeResourceInfo.kind == LayoutResourceKind::Uniform)
- continue;
-
- // We should not have already processed this resource type
- SLANG_RELEASE_ASSERT(!fieldLayout->FindResourceInfo(fieldTypeResourceInfo.kind));
-
- // The field will need offset information for this kind
- auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind);
-
- // It is possible for a `struct` field to use an unbounded array
- // type, and in the D3D case that would consume an unbounded number
- // of registers. What is more, a single `struct` could have multiple
- // such fields, or ordinary resource fields after an unbounded field.
- //
- // We handle this case by allocating a distinct register space for
- // any field that consumes an unbounded amount of registers.
- //
- if( fieldTypeResourceInfo.count.isInfinite() )
- {
- // We need to add one register space to own the storage for this field.
- //
- auto structTypeSpaceResourceInfo = typeLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace);
- auto spaceOffset = structTypeSpaceResourceInfo->count;
- structTypeSpaceResourceInfo->count += 1;
+ auto fieldTypeLayout = fieldResult.layout;
- // The field itself will record itself as having a zero offset into
- // the chosen space.
- //
- fieldResourceInfo->space = spaceOffset.getFiniteValue();
- fieldResourceInfo->index = 0;
- }
- else
- {
- // In the case where the field consumes a finite number of slots, we
- // can simply set its offset/index to the number of such slots consumed
- // so far, and then increment the number of slots consumed by the
- // `struct` type itself.
- //
- auto structTypeResourceInfo = typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind);
- fieldResourceInfo->index = structTypeResourceInfo->count.getFiniteValue();
- structTypeResourceInfo->count += fieldTypeResourceInfo.count;
- }
- }
+ auto fieldVarLayout = typeLayoutBuilder.addField(field, fieldResult);
- // Fields of a structure type may have existential/interface type,
- // or nested existential/interface-type fields. When doing layout
- // for a specialized program, these will show up as "pending" types
- // that need to be laid out at the end of the surrounding block/container.
- //
- // Any pending types on fields of a structure become pending types
- // on the structure itself.
+ // If any of the fields of the `struct` type had existential/interface
+ // type, then we need to compute a second `StructTypeLayout` that
+ // represents the layout and resource using for the "pending data"
+ // that this type needs to have stored somewhere, but which can't
+ // be laid out in the layout of the type itself.
//
- for( auto pendingItem : fieldTypeLayout->pendingItems )
+ if(auto fieldPendingDataTypeLayout = fieldTypeLayout->pendingDataTypeLayout)
{
- typeLayout->pendingItems.Add(pendingItem);
+ // We only create this secondary layout on-demand, so that
+ // we don't end up with a bunch of empty structure type layouts
+ // created for no reason.
+ //
+ pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(type, rules);
+ auto fieldPendingVarLayout = pendingDataTypeLayoutBuilder.addField(field, fieldPendingDataTypeLayout);
+ fieldVarLayout->pendingVarLayout = fieldPendingVarLayout;
}
}
- rules->EndStructLayout(&info);
+ typeLayoutBuilder.endLayout();
+ pendingDataTypeLayoutBuilder.endLayout();
- typeLayout->uniformAlignment = info.alignment;
- typeLayout->addResourceUsage(LayoutResourceKind::Uniform, info.size);
+ if( auto pendingDataTypeLayout = pendingDataTypeLayoutBuilder.getTypeLayout() )
+ {
+ typeLayout->pendingDataTypeLayout = pendingDataTypeLayout;
+ }
- return TypeLayoutResult(typeLayout, info);
+ return typeLayoutBuilder.getTypeLayoutResult();
}
else if (auto globalGenParam = declRef.as<GlobalGenericParamDecl>())
{
@@ -2582,12 +2840,10 @@ static TypeLayoutResult _createTypeLayout(
// Layout for this specialized interface type then results
// in a type layout that tracks both the resource usage of the
// interface type itself (just the type + value slots introduced
- // above), plus a "pending" type that represents the value
+ // above), plus a "pending data" type that represents the value
// conceptually pointed to by the interface-type field/variable at runtime.
//
- TypeLayout::PendingItem pendingItem;
- pendingItem.typeLayout = concreteTypeLayout;
- typeLayout->pendingItems.Add(pendingItem);
+ typeLayout->pendingDataTypeLayout = concreteTypeLayout;
}
return TypeLayoutResult(typeLayout, SimpleLayoutInfo());
@@ -2854,6 +3110,13 @@ RefPtr<TypeLayout> createTypeLayout(
return _createTypeLayout(context, type).layout;
}
+void TypeLayout::addResourceUsageFrom(TypeLayout* otherTypeLayout)
+{
+ for(auto resInfo : otherTypeLayout->resourceInfos)
+ addResourceUsage(resInfo);
+}
+
+
RefPtr<TypeLayout> TypeLayout::unwrapArray()
{
TypeLayout* typeLayout = this;
diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h
index cdeadbb16..5ac9229b4 100644
--- a/source/slang/type-layout.h
+++ b/source/slang/type-layout.h
@@ -328,23 +328,20 @@ public:
// the space storing it in the above array
UInt uniformAlignment = 1;
- /// An item that is conceptually owned by this type, but is pending layout.
+
+ /// The layout for data that is conceptually owned by this type, but which is pending layout.
///
/// When a type contains interface/existential fields (recursively), the
/// actual data referenced by these fields needs to get allocated somewhere,
/// but it cannot go inline at the point where the interface/existential
- /// type appears, or else
+ /// type appears, or else the layout of a composite object would change
+ /// when the concrete type(s) we plug in change.
///
- struct PendingItem
- {
- RefPtr<TypeLayout> typeLayout;
-
- RefPtr<TypeLayout> getTypeLayout() const { return typeLayout; }
- RefPtr<Type> getType() const { return typeLayout->type; }
- };
-
- /// The pending items owned by this type, but which are pending layout.
- List<PendingItem> pendingItems;
+ /// We solve this problem by tracking this data that is "pending" layout,
+ /// and then "flushing" the pending data at appropriate places during
+ /// the layout process.
+ ///
+ RefPtr<TypeLayout> pendingDataTypeLayout;
ResourceInfo* FindResourceInfo(LayoutResourceKind kind)
{
@@ -383,6 +380,8 @@ public:
addResourceUsage(info);
}
+ void addResourceUsageFrom(TypeLayout* otherTypeLayout);
+
/// "Unwrap" any layers of array-ness from this type layout.
///
/// If this is an `ArrayTypeLayout`, returns the result of unwrapping the element type layout.
@@ -475,6 +474,8 @@ public:
return AddResourceInfo(kind);
}
+
+ RefPtr<VarLayout> pendingVarLayout;
};
// type layout for a variable that has a constant-buffer type
@@ -501,6 +502,16 @@ public:
// so that any fields (if the element type is a `struct`)
// will be offset by the resource usage of the container.
RefPtr<TypeLayout> offsetElementTypeLayout;
+
+ // If the element type layout had any "pending" data, then
+ // as much of that data as possible will be flushed to
+ // fit into the overall layout of the parameter group.
+ //
+ // This field stores the offset information for where
+ // the pending data got stored relative to the start of
+ // the group.
+ //
+// RefPtr<VarLayout> flushedDataVarLayout;
};
// type layout for a variable that has a constant-buffer type
@@ -585,6 +596,12 @@ public:
// in the array above, rather than to the actual pointer,
// so that we
Dictionary<Decl*, RefPtr<VarLayout>> mapVarToLayout;
+
+ // As an accellerator for type layouts created at the
+ // IR layer, we include a second map that use IR "key"
+ // instructions to map to fields.
+ //
+ Dictionary<IRInst*, RefPtr<VarLayout>> mapKeyToLayout;
};
class GenericParamTypeLayout : public TypeLayout
@@ -924,6 +941,98 @@ struct TypeLayoutContext
}
};
+//
+
+ /// A custom tuple to capture the outputs of type layout
+struct TypeLayoutResult
+{
+ /// The actual heap-allocated layout object with all the details
+ RefPtr<TypeLayout> layout;
+
+ /// A simplified representation of layout information.
+ ///
+ /// This information is suitable for the case where a type only
+ /// consumes a single resource.
+ ///
+ SimpleLayoutInfo info;
+
+ /// Default constructor.
+ TypeLayoutResult()
+ {}
+
+ /// Construct a result from the given layout object and simple layout info.
+ TypeLayoutResult(RefPtr<TypeLayout> inLayout, SimpleLayoutInfo const& inInfo)
+ : layout(inLayout)
+ , info(inInfo)
+ {}
+};
+
+ /// Helper type for building `struct` type layouts
+struct StructTypeLayoutBuilder
+{
+public:
+ /// Begin the layout process for `type`, using `rules`
+ void beginLayout(
+ Type* type,
+ LayoutRulesImpl* rules);
+
+ /// Begin the layout process for `type`, using `rules`, if it hasn't already been begun.
+ ///
+ /// This functions allows for a `StructTypeLayoutBuilder` to be use lazily,
+ /// only allocating a type layout object if it is actaully needed.
+ ///
+ void beginLayoutIfNeeded(
+ Type* type,
+ LayoutRulesImpl* rules);
+
+ /// Add a field to the struct type layout.
+ ///
+ /// One of the `beginLayout*()` functions must have been called previously.
+ ///
+ RefPtr<VarLayout> addField(
+ DeclRef<VarDeclBase> field,
+ TypeLayoutResult fieldResult);
+
+ /// Add a field to the struct type layout.
+ ///
+ /// One of the `beginLayout*()` functions must have been called previously.
+ ///
+ RefPtr<VarLayout> addField(
+ DeclRef<VarDeclBase> field,
+ RefPtr<TypeLayout> fieldTypeLayout);
+
+ /// Complete layout.
+ ///
+ /// If layout was begun, ensures that the result of `getTypeLayout()` is usable.
+ /// If layout was never begin, does nothing.
+ ///
+ void endLayout();
+
+ /// Get the type layout.
+ ///
+ /// This can be called any time after `beginLayout*()`.
+ /// In particular, it can be called before `endLayout`.
+ ///
+ RefPtr<StructTypeLayout> getTypeLayout();
+
+ /// The the type layout result.
+ ///
+ /// This is primarily useful for implementation code in `_createTypeLayout`.
+ ///
+ TypeLayoutResult getTypeLayoutResult();
+
+private:
+ /// The layout rules being used, if layout has begun.
+ LayoutRulesImpl* m_rules = nullptr;
+
+ /// The type layout being computed, if layout has begun.
+ RefPtr<StructTypeLayout> m_typeLayout;
+
+ /// Uniform offset/alignment statte used when computing offset for uniform fields.
+ UniformLayoutInfo m_info;
+};
+
+//
// Get an appropriate set of layout rules (packaged up
// as a `TypeLayoutContext`) to perform type layout
@@ -963,7 +1072,7 @@ RefPtr<TypeLayout> createTypeLayout(
//
/// Create a layout for a parameter-group type (a `ConstantBuffer` or `ParameterBlock`).
-RefPtr<ParameterGroupTypeLayout> createParameterGroupTypeLayout(
+RefPtr<TypeLayout> createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType);
@@ -993,6 +1102,12 @@ createStructuredBufferTypeLayout(
int findGenericParam(List<RefPtr<GenericParamLayout>> & genericParameters, GlobalGenericParamDecl * decl);
//
+// Given an existing type layout `oldTypeLayout`, apply offsets
+// to any contained fields based on the resource infos in `offsetVarLayout`.
+RefPtr<TypeLayout> applyOffsetToTypeLayout(
+ RefPtr<TypeLayout> oldTypeLayout,
+ RefPtr<VarLayout> offsetVarLayout);
+
}
#endif