summaryrefslogtreecommitdiff
path: root/source/slang/slang-type-layout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/slang-type-layout.cpp')
-rw-r--r--source/slang/slang-type-layout.cpp247
1 files changed, 190 insertions, 57 deletions
diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp
index 33bdb4ef4..a015bfd78 100644
--- a/source/slang/slang-type-layout.cpp
+++ b/source/slang/slang-type-layout.cpp
@@ -1542,6 +1542,11 @@ bool isCUDATarget(TargetRequest* targetReq)
}
}
+bool areResourceTypesBindlessOnTarget(TargetRequest* targetReq)
+{
+ return isCPUTarget(targetReq) || isCUDATarget(targetReq);
+}
+
static bool isD3D11Target(TargetRequest*)
{
// We aren't officially supporting D3D11 right now
@@ -3675,84 +3680,212 @@ static TypeLayoutResult _createTypeLayout(
}
else if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() )
{
+ RefPtr<ExistentialTypeLayout> typeLayout = new ExistentialTypeLayout();
+ typeLayout->type = type;
+ typeLayout->rules = rules;
+
// When laying out a type that includes interface-type fields,
// we cannot know how much space the concrete type that
// gets stored into the field consumes.
//
- // If we were doing layout for a typical CPU target, then
- // we could just say that each interface-type field consumes
- // some fixed number of pointers (e.g., a data pointer plus a witness
- // table pointer).
+ // For target platforms with flexible memory addressing,
+ // we can reserve a fixed amount of uniform/ordinary storage
+ // to hold a value of "any" type, with the expectation that:
//
- // We will borrow the intuition from that and invent a new
- // resource kind for "existential slots" which conceptually
- // represents the indirections needed to reference the
- // data to be referenced by this field.
+ // * Values which fit entirely in the storage we've reserved
+ // will be stored there directly.
//
-
- RefPtr<TypeLayout> typeLayout = new TypeLayout();
- typeLayout->type = type;
- typeLayout->rules = rules;
-
- LayoutSize fixedExistentialValueSize = 0;
- LayoutSize uniformSlotSize = 0;
- bool targetSupportsPointer =
- isCPUTarget(context.targetReq) || isCUDATarget(context.targetReq);
-
- if (targetSupportsPointer)
+ // * Values that are too big to store directly will be referenced
+ // indirectly, by a pointer stored in the reserved space.
+ //
+ // Note: the latter condition means that the minimum
+ // reservation must be large enough to store a pointer.
+ //
+ // Note: the layout choice here does *not* depend on whether
+ // or not specialization is being used, because we do not
+ // want host code that sets parameters to have to be re-run (and
+ // behave differently) depending on whether specialization is
+ // being used for a particular dispatch.
+ //
+ // For target platforms that do not support flexible memory
+ // addressing, we can follow the same approach in cases
+ // where a value fits in the reserved memory space, and we
+ // will discuss what happens in the other cases in a bit.
+ //
+ // The default reservation will be 16 bytes (and this number
+ // becomes part of our ABI contract), but the `interface`
+ // that is being used to bound the existential can have
+ // an attribute that specifies a different size to use for
+ // its instances.
+ //
+ // Note: changing the "any value size" attribute for an interface
+ // breaks binary compatibility with existing code that uses
+ // or implements that interface).
+ //
+ LayoutSize fixedExistentialValueSize = 16;
+ if (auto anyValueAttr =
+ interfaceDeclRef.getDecl()->findModifier<AnyValueSizeAttribute>())
{
- fixedExistentialValueSize = 16;
- if (auto anyValueAttr =
- interfaceDeclRef.getDecl()->findModifier<AnyValueSizeAttribute>())
- {
- fixedExistentialValueSize = anyValueAttr->size;
- }
- // Append 16 bytes to accommodate RTTI pointer and witness table pointer.
- uniformSlotSize = fixedExistentialValueSize + 16;
- typeLayout->addResourceUsage(LayoutResourceKind::Uniform, uniformSlotSize);
+ fixedExistentialValueSize = anyValueAttr->size;
}
+
+ // The `fixedExistentialValueSize` only accounts for the storage
+ // of a value that conforms to the interface type; you can think
+ // of it like a C `union` where it stores the bits of a value, but
+ // has no way of knowing what the type of the value is.
+ //
+ // For dynamic dispatch we also need to be able to know two key
+ // pieces of information:
+ //
+ // * Some kind of run-time type information (RTTI) that can identify
+ // the actual type stored in the existential, and which can therefore
+ // be used to allocate/copy/release the value stored.
+ //
+ // * A value that "witnesses" the fact that the above type actually
+ // implements the interface, and thus gives us a way to look up
+ // methods, etc. that implement the interface operations for that
+ // type. For a C++-minded programmer, you can think of this like
+ // a virtual function table pointer, stored alongside the object pointer.
+ //
+ // We reserve 16 bytes to accomodate the RTTI and witness table information,
+ // which should be enough space to store a pointer for each on 64-bit
+ // platforms. Note that we don't try to vary this size based on platform-specific
+ // information, because we prefer to keep the encoding of existentials as
+ // simple as we can get away with.
+ //
+ // TODO: This layout logic does *not* accomodate the case where an
+ // existential type is formed from a conjuction of interfaces (e.g.,
+ // a type like `IReadable & IWritable`). In such a case we'd have
+ // to change the layout to accomodate N >= 0 witness tables, either
+ // stored directly in the existential value, or pointed to indirectly
+ // to keep the size independent of N.
+ //
+ LayoutSize uniformSlotSize = fixedExistentialValueSize + 16;
+ typeLayout->addResourceUsage(LayoutResourceKind::Uniform, uniformSlotSize);
+
+ // In addition to the uniform/ordinary storage, we will mark
+ // every interface-type parameter as consuming a few additional
+ // "fictitious" resources that allow applications to keep track
+ // of existential-type parameters in case they want to perform
+ // specialization.
+ //
+ // Each leaf parameter of existential type introduces a potential
+ // specialization parameter into the program, so we add the
+ // parameter to represent that here.
+ //
typeLayout->addResourceUsage(LayoutResourceKind::ExistentialTypeParam, 1);
- typeLayout->addResourceUsage(LayoutResourceKind::ExistentialObjectParam, 1);
- // If there are any concrete types available, the first one will be
- // the value that should be plugged into the slot we just introduced.
+ // A leaf parameter of existential type also introduces a conceptual
+ // "sub-object" that needs to be tracked by an application building
+ // a shader object or parameter block abstraction.
//
- if (context.specializationArgCount)
+ typeLayout->addResourceUsage(LayoutResourceKind::ExistentialObjectParam, 1);
+ //
+ // Note: It might be unclear at this point what the difference is between
+ // `ExistentialTypeParam` and `ExistentialObjectParam` is. The reason for
+ // the confusion is that in this code we are only looking at a single
+ // leaf parameter with a type like `ILight`, which both introduces the
+ // type parameter (for picking a specialized light type), and the object
+ // parameter (for passing in the actual light data).
+ //
+ // In a more general setting we might have `ILight someLights[10]`, and
+ // in that case we would expect to have ten `ExistentialObjectParam`s
+ // (one for each light in the array), but for specialization we would
+ // still only want one `ExistentialTypeParam`.
+ //
+ // Keeping the `LayoutResourceKind`s separate allows us to scale them
+ // differently when a type gets used as part of an array or buffer.
+
+ // At this point we have determined the layout of the existential
+ // type itself, but there are additional steps we need to take
+ // if we are on a platform that doesn't support general-purpose
+ // pointers and addressing *and* we also know of a concrete
+ // type argument that the parameter will be specialized to.
+ //
+ bool targetSupportsPointer =
+ isCPUTarget(context.targetReq) || isCUDATarget(context.targetReq);
+ bool hasConcreteSpecializationArg = context.specializationArgCount != 0;
+ if (!targetSupportsPointer && hasConcreteSpecializationArg)
{
+ // We have a concrete specialization argument, so we
+ // can determine the concrete type that is going to
+ // be stored in this parameter.
+ //
auto& specializationArg = context.specializationArgs[0];
Type* concreteType = as<Type>(specializationArg.val);
SLANG_ASSERT(concreteType);
- // Always use AnyValueRules regardless of the enclosing environment's layout rule
- // for existential values.
+ // Our first job here is to figure out how `concreteType` will
+ // be laid out when stored into this existential.
+ //
+ // We know that *if* the value fits in the "any value" storage,
+ // then that is where it will be stored. We start by computing
+ // how much space the value would take up if stored in
+ // the any-value area.
+ //
auto anyValueRules = context.getRulesFamily()->getAnyValueRules();
+ RefPtr<TypeLayout> concreteTypeAnyValueLayout =
+ createTypeLayout(context.with(anyValueRules), concreteType);
- // TODO: for traditional GPU targets (HLSL/GLSL) we don't force
- // anyValueRule for now, since it requires additional work to load
- // the existential value. We should remove this special case logic
- // and always use anyValueRule once we implement the correct loading
- // code gen logic for these targets.
- if (!targetSupportsPointer)
- anyValueRules = context.rules;
+ // We will look at the resource usage of the concrete type
+ // to determine if it "fits" in the reserved space.
+ //
+ bool fits = true;
+ for(auto usage : concreteTypeAnyValueLayout->resourceInfos)
+ {
+ if(usage.kind == LayoutResourceKind::Uniform)
+ {
+ // If the amount of uniform storage that the concrete type
+ // requires is more than has been reserved, when the
+ // type does not fit.
+ //
+ if(usage.count > fixedExistentialValueSize)
+ {
+ fits = false;
+ break;
+ }
+ }
+ else
+ {
+ // If the concrete type requires any kind of storage
+ // beyond ordinary uniform data, then it also
+ // does not fit.
+ //
+ // TODO: Make sure this is okay with nested existentials.
+ //
+ fits = false;
+ break;
+ }
+ }
- RefPtr<TypeLayout> concreteTypeLayout =
- createTypeLayout(context.with(anyValueRules), concreteType);
- if (!targetSupportsPointer)
+ // If the value does fit, then there is nothing else to be
+ // done; the layout that would have been computed without
+ // knowing the `concreteType` is sufficient.
+ //
+ // If the value does *not* fit, then we need to figure out
+ // where the excess data will go.
+ //
+ if(!fits)
{
- // For targets that supports pointers, oversized existential values
- // should be placed in an overflow region and only a pointer is needed in
- // the place of the fixed sized uniform slot.
- // We only need the "pending layout" mechanism for targets that does not
- // support pointers.
-
- // For legacy targets without pointer support, the 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 data" type that
- // represents the value conceptually pointed to by the interface-type
- // field/variable at runtime.
+ // If we were doing layout for a typical CPU target, then
+ // we could just say that the fixed-size storage contains
+ // a data pointer to a "payload" of the data that wouldn't fit.
+ //
+ // We will borrow intuition from the approach, by saying that
+ // the payload is stored somewhere else, but we will *not*
+ // lock down where precisely "somewhere else" is going to be
+ // at this point.
+ //
+ // Instead, we will store information about the layout of
+ // the data that needs to go somewhere else, and leave it
+ // up to the parent type/context to find a suitable place
+ // for the data.
+ //
+ // Because we know the layout of the data, but not the placement,
+ // it is considered to be a "pending" part of the type layout.
//
- typeLayout->pendingDataTypeLayout = concreteTypeLayout;
+ typeLayout->pendingDataTypeLayout =
+ createTypeLayout(context, concreteType);
}
}
// Interface type occupies a uniform slot for the fixed size storage, with alignment of 4 bytes.