diff options
Diffstat (limited to 'source/slang/type-layout.cpp')
| -rw-r--r-- | source/slang/type-layout.cpp | 318 |
1 files changed, 252 insertions, 66 deletions
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp index e3b89a831..7fc6320cc 100644 --- a/source/slang/type-layout.cpp +++ b/source/slang/type-layout.cpp @@ -16,6 +16,15 @@ size_t RoundToAlignment(size_t offset, size_t alignment) return offset + (alignment - remainder); } +LayoutSize RoundToAlignment(LayoutSize offset, size_t alignment) +{ + // An infinite size is assumed to be maximally aligned. + if(offset.isInfinite()) + return LayoutSize::infinite(); + + return RoundToAlignment(offset.getFiniteValue(), alignment); +} + static size_t RoundUpToPowerOfTwo( size_t value ) { // TODO(tfoley): I know this isn't a fast approach @@ -66,9 +75,10 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl } } - SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, size_t elementCount) override + SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { - size_t stride = elementInfo.size; + SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); + auto stride = elementInfo.size.getFiniteValue(); SimpleArrayLayoutInfo arrayInfo; arrayInfo.kind = elementInfo.kind; @@ -109,7 +119,7 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl return structInfo; } - size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override + LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override { // Skip zero-size fields if(fieldInfo.size == 0) @@ -117,7 +127,7 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl ioStructInfo->alignment = std::max(ioStructInfo->alignment, fieldInfo.alignment); ioStructInfo->size = RoundToAlignment(ioStructInfo->size, fieldInfo.alignment); - size_t fieldOffset = ioStructInfo->size; + LayoutSize fieldOffset = ioStructInfo->size; ioStructInfo->size += fieldInfo.size; return fieldOffset; } @@ -136,7 +146,7 @@ struct DefaultConstantBufferLayoutRulesImpl : DefaultLayoutRulesImpl // be a multiple of 16 bytes. // // HLSL agrees. - SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, size_t elementCount) override + SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { if(elementInfo.kind == LayoutResourceKind::Uniform) { @@ -171,7 +181,9 @@ static SimpleLayoutInfo getGLSLVectorLayout( SimpleLayoutInfo elementInfo, size_t elementCount) { SLANG_RELEASE_ASSERT(elementInfo.kind == LayoutResourceKind::Uniform); - auto size = elementInfo.size * elementCount; + SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); + + auto size = elementInfo.size.getFiniteValue() * elementCount; SimpleLayoutInfo vectorInfo( LayoutResourceKind::Uniform, size, @@ -193,7 +205,7 @@ struct Std140LayoutRulesImpl : GLSLConstantBufferLayoutRulesImpl struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl { // Can't let a `struct` field straddle a register (16-byte) boundary - size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override + LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override { // Skip zero-size fields if(fieldInfo.size == 0) @@ -202,8 +214,8 @@ struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl ioStructInfo->alignment = std::max(ioStructInfo->alignment, fieldInfo.alignment); ioStructInfo->size = RoundToAlignment(ioStructInfo->size, fieldInfo.alignment); - size_t fieldOffset = ioStructInfo->size; - size_t fieldSize = fieldInfo.size; + LayoutSize fieldOffset = ioStructInfo->size; + LayoutSize fieldSize = fieldInfo.size; // Would this field cross a 16-byte boundary? auto registerSize = 16; @@ -690,19 +702,30 @@ TypeLayoutContext getInitialLayoutContextForTarget(TargetRequest* targetReq) } -static int GetElementCount(RefPtr<IntVal> val) +static LayoutSize GetElementCount(RefPtr<IntVal> val) { + // Lack of a size indicates an unbounded array. + if(!val) + return LayoutSize::infinite(); + if (auto constantVal = val.As<ConstantIntVal>()) { - return (int) constantVal->value; + return LayoutSize(LayoutSize::RawValue(constantVal->value)); } else if( auto varRefVal = val.As<GenericParamIntVal>() ) { - // TODO(tfoley): do something sensible in this case + // TODO: We want to treat the case where the number of + // elements in an array depends on a generic parameter + // much like the case where the number of elements is + // unbounded, *but* we can't just blindly do that because + // an API might disallow unbounded arrays in various + // cases where a generic bound might work (because + // any concrete specialization will have a finite bound...) + // return 0; } SLANG_UNEXPECTED("unhandled integer literal kind"); - return 0; + UNREACHABLE_RETURN(LayoutSize(0)); } bool IsResourceKind(LayoutResourceKind kind) @@ -721,7 +744,7 @@ bool IsResourceKind(LayoutResourceKind kind) SimpleLayoutInfo GetSimpleLayoutImpl( SimpleLayoutInfo info, - RefPtr<Type> type, + RefPtr<Type> type, LayoutRulesImpl* rules, RefPtr<TypeLayout>* outTypeLayout) { @@ -944,7 +967,10 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout( newResInfo->space = oldResInfo.space; if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind)) { - newResInfo->index += offsetResInfo->count; + // 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(); } } @@ -1017,7 +1043,7 @@ createParameterGroupTypeLayout( // Make sure that we allocate resource usage for the // parameter block itself. - if( parameterGroupInfo.size ) + if( parameterGroupInfo.size != 0 ) { containerTypeLayout->addResourceUsage( parameterGroupInfo.kind, @@ -1121,7 +1147,8 @@ createParameterGroupTypeLayout( auto elementVarResInfo = elementVarLayout->findOrAddResourceInfo(kind); if( auto containerTypeResInfo = containerTypeLayout->FindResourceInfo(kind) ) { - elementVarResInfo->index += containerTypeResInfo->count; + SLANG_RELEASE_ASSERT(containerTypeResInfo->count.isFinite()); + elementVarResInfo->index += containerTypeResInfo->count.getFiniteValue(); } } typeLayout->elementVarLayout = elementVarLayout; @@ -1460,9 +1487,11 @@ bool doesResourceRequireAdjustmentForArrayOfStructs(LayoutResourceKind kind) // // and then we expect `foo_b` to get `register(t8)`, rather // than `register(t1)`. -static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( +// +static RefPtr<TypeLayout> maybeAdjustLayoutForArrayElementType( RefPtr<TypeLayout> originalTypeLayout, - UInt elementCount) + LayoutSize elementCount, + UInt& ioAdditionalSpacesNeeded) { // We will start by looking for cases that we can reject out // of hand. @@ -1494,7 +1523,8 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( auto originalInnerElementTypeLayout = originalArrayTypeLayout->elementTypeLayout; auto adjustedInnerElementTypeLayout = maybeAdjustLayoutForArrayElementType( originalInnerElementTypeLayout, - elementCount); + elementCount, + ioAdditionalSpacesNeeded); // If nothing needed to be changed on the inner element type, // then we are done. @@ -1516,7 +1546,8 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( auto originalInnerElementTypeLayout = originalParameterGroupTypeLayout->elementVarLayout->typeLayout; auto adjustedInnerElementTypeLayout = maybeAdjustLayoutForArrayElementType( originalInnerElementTypeLayout, - elementCount); + elementCount, + ioAdditionalSpacesNeeded); // If nothing needed to be changed on the inner element type, // then we are done. @@ -1537,20 +1568,37 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( if(fieldCount == 0) return originalTypeLayout; - // TODO: we could try to special-case a `struct` type with a single - // field that needs no adjustment, just to avoid some extra allocation. - RefPtr<StructTypeLayout> adjustedStructTypeLayout = new StructTypeLayout(); copyTypeLayoutFields(adjustedStructTypeLayout, originalStructTypeLayout); + // If the array type adjustment forces us to give a whole space to + // one or more fields, then we'll need to carefully compute the space + // index for each field as we go. + // + LayoutSize nextSpaceIndex = 0; + Dictionary<RefPtr<VarLayout>, RefPtr<VarLayout>> mapOriginalFieldToAdjusted; for( auto originalField : originalStructTypeLayout->fields ) { - // Compute the adjusted type for the field auto originalFieldTypeLayout = originalField->typeLayout; + + LayoutSize originalFieldSpaceCount = 0; + if(auto resInfo = originalFieldTypeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) + originalFieldSpaceCount = resInfo->count; + + // Compute the adjusted type for the field + UInt fieldAdditionalSpaces = 0; auto adjustedFieldTypeLayout = maybeAdjustLayoutForArrayElementType( originalFieldTypeLayout, - elementCount); + elementCount, + fieldAdditionalSpaces); + + LayoutSize adjustedFieldSpaceCount = originalFieldSpaceCount + fieldAdditionalSpaces; + + LayoutSize spaceOffsetForField = nextSpaceIndex; + nextSpaceIndex += adjustedFieldSpaceCount; + + ioAdditionalSpacesNeeded += fieldAdditionalSpaces; // Create an adjusted field variable, that is mostly // a clone of the original field (just with our @@ -1559,16 +1607,31 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( copyVarLayoutFields(adjustedField, originalField); adjustedField->typeLayout = adjustedFieldTypeLayout; - // Finally we get down to the real meat of the change, - // which is that the field offsets for any resource-type - // fields need to be "adjusted" which amounts to just - // multiplying them by the element count of the array. - - for( auto& resInfo : adjustedField->resourceInfos ) + // We will now walk through the resource usage for + // the adjusted field, and try to figure out what + // to do with it all. + // + for(auto& resInfo : adjustedField->resourceInfos ) { if( doesResourceRequireAdjustmentForArrayOfStructs(resInfo.kind) ) { - resInfo.index *= elementCount; + if(elementCount.isFinite()) + { + // If the array size is finite, then the field's index/offset + // is just going to be strided by the array size since we + // are effectively doing AoS to SoA conversion. + // + resInfo.index *= elementCount.getFiniteValue(); + } + else + { + // If we are making an unbounded array, then a `struct` + // field with resource type will turn into its own space, + // and it will start at regsiter zero in that space. + // + resInfo.index = 0; + resInfo.space = spaceOffsetForField.getFiniteValue(); + } } } @@ -1592,8 +1655,16 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( } else { - // If the leaf type layout isn't some kind of aggregate, - // then we can just bail out here. + // In the leaf case, we must have a field that used up some resource + // that requires adjustment. Because there is no sub-structure to work + // with, we can just return the type layout as-is, but we also want + // to make a note that this value should consume an additional register + // space *if* the element count is unbounded. + if( elementCount.isInfinite() ) + { + ioAdditionalSpacesNeeded++; + } + return originalTypeLayout; } } @@ -1822,12 +1893,27 @@ SimpleLayoutInfo GetLayoutImpl( arrayType->baseType.Ptr(), outTypeLayout ? &elementTypeLayout : nullptr); - // For layout purposes, we treat an unsized array as an array of zero elements. + // To a first approximation, an array will usually be laid out + // by taking the element's type layout and laying out `elementCount` + // copies of it. There are of course many details that make + // this simplistic version of things not quite work. + // + // An important complication to deal with is the possibility of + // having "unbounded" arrays, which don't specify a size.' + // The layout rules for these vary heavily by resource kind and API. // - // TODO: Longer term we are going to need to be careful to include some indication - // that a type has logically "infinite" size in some resource kind. In particular - // this affects how we would allocate space for parameter binding purposes. - auto elementCount = arrayType->ArrayLength ? GetElementCount(arrayType->ArrayLength) : 0; + + auto elementCount = GetElementCount(arrayType->ArrayLength); + + // + // We can compute the uniform storage layout of an array using + // the rules for the target API. + // + // TODO: ensure that this does something reasonable with the unbounded + // case, or else issue an error message that the target doesn't + // support unbounded types. + // + auto arrayUniformInfo = rules->GetArrayLayout( elementInfo, elementCount).getUniformLayout(); @@ -1837,54 +1923,117 @@ SimpleLayoutInfo GetLayoutImpl( RefPtr<ArrayTypeLayout> typeLayout = new ArrayTypeLayout(); *outTypeLayout = typeLayout; - // If we construct an array over an aggregate type that contains - // resource fields, we may need to adjust the layout we create - // for the element type to - RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType( - elementTypeLayout, - elementCount); - + // Some parts of the array type layout object are easy to fill in: typeLayout->type = type; - typeLayout->originalElementTypeLayout = elementTypeLayout; - typeLayout->elementTypeLayout = adjustedElementTypeLayout; typeLayout->rules = rules; - + typeLayout->originalElementTypeLayout = elementTypeLayout; typeLayout->uniformAlignment = arrayUniformInfo.alignment; typeLayout->uniformStride = arrayUniformInfo.elementStride; typeLayout->addResourceUsage(LayoutResourceKind::Uniform, arrayUniformInfo.size); - // translate element-type resources into array-type resources + // + // The tricky part in constructing an array type layout comes when + // the element type is (or nests) a structure with resource-type + // fields, because in that case we need to perform AoS-to-SoA + // conversion as part of computing the final type layout, and + // we also need to pre-compute an "adjusted" element type + // layout that accounts for the striding that happens with + // resource-type contents. + // + // This complication is only made worse when we have to deal with + // unbounded-size arrays over such element types, since those + // resource-type fields will each end up consuming a full space + // in the resulting layout. + // + // The `maybeAdjustLayoutForArrayElementType` computes an "adjusted" + // type layout for the element type which takes the array stride into + // acount. If it returns the same type layout that was passed in, + // then that means no adjustement took place. + // + // The `additionalSpacesNeededForAdjustedElementType` variable counts + // the number of additional register spaces that were consumed, + // in the case of an unbounded array. + // + UInt additionalSpacesNeededForAdjustedElementType = 0; + RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType( + elementTypeLayout, + elementCount, + additionalSpacesNeededForAdjustedElementType); + + typeLayout->elementTypeLayout = adjustedElementTypeLayout; + + // We will now iterate over the resources consumed by the element + // type to compute how they contribute to the resource usage + // of the overall array type. + // for( auto elementResourceInfo : elementTypeLayout->resourceInfos ) { // The uniform case was already handled above if( elementResourceInfo.kind == LayoutResourceKind::Uniform ) continue; + LayoutSize arrayResourceCount = 0; + // In almost all cases, the resources consumed by an array // will be its element count times the resources consumed - // by its element type. The one exception to this is - // arrays of resources in Vulkan GLSL, where an entire array + // by its element type. + // + // The first exception to this is arrays of resources when + // compiling to GLSL for Vulkan, where an entire array // only consumes a single descriptor-table slot. // - // Note: We extend this logic to arbitrary arrays-of-structs, - // under the assumption that downstream legalization will - // turn those into scalarized structs-of-arrays and this - // logic will work out. - UInt arrayResourceCount = 0; if (elementResourceInfo.kind == LayoutResourceKind::DescriptorTableSlot) { arrayResourceCount = elementResourceInfo.count; } + // + // The next big exception is when we are forming an unbounded-size + // array and the element type got "adjusted," because that means + // the array type will need to allocate full spaces for any resource-type + // fields in the element type. + // + // Note: we carefully carve things out so that the case of a simple + // array of resources does *not* lead to the element type being adjusted, + // so that this logic doesn't trigger and we instead handle it with + // the default logic below. + // + else if( + elementCount.isInfinite() + && adjustedElementTypeLayout != elementTypeLayout + && doesResourceRequireAdjustmentForArrayOfStructs(elementResourceInfo.kind) ) + { + // We want to ignore resource types consumed by the element type + // that need adjustement if the array size is infinite, since + // we will be allocating whole spaces for that part of the + // element's resource usage. + } else { arrayResourceCount = elementResourceInfo.count * elementCount; } - + + // Now that we've computed how the resource usage of the element type + // should contribute to the resource usage of the array, we can + // add in that resource usage. + // typeLayout->addResourceUsage( elementResourceInfo.kind, arrayResourceCount); } + + // The loop above to compute the resource usage of the array from its + // element type ignored any resource-type fields in an unbounded-size + // array if they would have been allocated as full register spaces. + // Those same fields were counted in `additionalSpacesNeededForAdjustedElementType`, + // and need to be added into the total resource usage for the array + // if we skipped them as part of the loop (which happens when + // we detect that the element type layout had been "adjusted"). + // + if( adjustedElementTypeLayout != elementTypeLayout ) + { + typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, additionalSpacesNeededForAdjustedElementType); + } } return arrayUniformInfo; } @@ -1903,6 +2052,14 @@ SimpleLayoutInfo GetLayoutImpl( *outTypeLayout = typeLayout; } + // 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(); for (auto field : GetFields(structDeclRef)) @@ -1922,7 +2079,7 @@ SimpleLayoutInfo GetLayoutImpl( // This means that the code to generate final // declarations needs to *also* eliminate zero-size // fields to be safe... - size_t uniformOffset = info.size; + LayoutSize uniformOffset = info.size; if(fieldInfo.size != 0) { uniformOffset = rules->AddStructField(&info, fieldInfo); @@ -1942,7 +2099,7 @@ SimpleLayoutInfo GetLayoutImpl( // 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; + fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); } // Add offset information for any other resource kinds @@ -1958,10 +2115,39 @@ SimpleLayoutInfo GetLayoutImpl( // The field will need offset information for this kind auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind); - // Check how many slots of the given kind have already been added to the type - auto structTypeResourceInfo = typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind); - fieldResourceInfo->index = structTypeResourceInfo->count; - structTypeResourceInfo->count += fieldTypeResourceInfo.count; + // 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; + + // 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; + } } } } @@ -1989,7 +2175,7 @@ SimpleLayoutInfo GetLayoutImpl( genParamTypeLayout->type = type; genParamTypeLayout->paramIndex = findGenericParam(context.targetReq->layout->globalGenericParams, genParamTypeLayout->getGlobalGenericParamDecl()); genParamTypeLayout->rules = rules; - genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count++; + genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count += 1; *outTypeLayout = genParamTypeLayout; } return info; |
