summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-11-19 01:26:43 -0800
committerGitHub <noreply@github.com>2020-11-19 01:26:43 -0800
commit4459d4428761b0581b221c52eaea595d1b257a9f (patch)
treeff2f3558afb82ee0d1ce0e956b647b0a5e053a9e /source
parentb59451020eee59cd52e4d8231360ebed4fc59adb (diff)
Unify handling of static and dynamic dispatch for interfaces (#1612)
Overview ======== Prior to this change, we had two different code generation strategies for interface/existential types in Slang, that didn't always play nicely together: * The "legacy" static specialization approach could handle plugging in an arbitrary concrete type for an existential type parameter (including types with resources, etc.), but wouldn't work well with things like a `StructuredBuffer<>` of an interface type, and requires somewhat counter-intuitive layout rules to make work. * The new dynamic dispatch approach produces simpler, more easily understood layouts by assuming that values of interface type can fit into a fixed number of bytes. The tradeoff there is that it cannot handle types that include resources (only POD types). The goal of this change is to make it so that the two strategies can co-exist. In particular, in cases where a shader is amenable to both static specialization and dynamic dispatch, the type layouts should agree. In order to make the type layouts agree, we: * Declare that *all* values of existential type reserve storage according to the dynamic-dispatch rules (so 16 bytes for the RTTI and witness-table information, plus whatever bytes are needed to story "any value" of a conforming type). * Then we modify the "legacy" layout rules so that if a value of concrete type can fit in the reserved "any value" space for a given interface, then it is laid out there exactly like the dynamic dispatch rules would do. Otherwise, we fall back to the previous legacy rules (since we don't need to agree with the dynamic-dispatch layout on types that can't be used with dynamic dispatch). Details ======= * Renamed `ExistentialBox` to `BoundInterfaceType` to better clarify how it relates to `BindExistentialsType` * Unconditionally apply the `lowerGenerics` pass during emit, since it is now responsible for aspects of the lowering of existential types when specialization is used. * Made IR type layout take the target into account, so that the layout of resource types can vary by target (e.g., being POD on some targets, and invalid on others) * Cleaned up some issues around using global shader parameters as the "key" for their layout information in the global-scope layout (only comes up when there are global-scope `uniform` parameters) * Made there be a default any-value size (16) instead of making it be an error to leave out. This was the simplest option; we could try to go back to having an error, but we'd need to only issue it if we are sure a type/interface is being used with dynamic dispatch, since static dispatch doesn't have to obey the restrictions. * Changed lowering of existential types to tuples so that bound interfaces where the concrete type won't fit use a "pseudo-pointer" instead of an "any-value" to hold the payload * Changed IR type legalization to handle the "pseudo-pointer" case and apply layout information from an interface type over to the payload part when static specialization was used. * Changed some details of how witness tables were being lowered, so that we didn't have to create "proxy" witness tables for the constraints on associated types (just use the actual requirement entries we generate) * Changed witness tables so that they know the subtype doing the conforming * Added logic so that we don't generate pack/unpack logic and witness table wrapper functions for types that are incompatible with any-value/dynamic dispatch for a given interface. * Changed the core AST-level type layout logic to use the dynamic-dispatch layout in case things fit, and the legacy static specialization case when things don't (while also reserving space for the dynamic-dispatch fields) * Changed a bunch of test cases for static specialization to properly use the new layout (which introduces new buffers in some cases, and moves data around in others). Future Work =========== The experience of trying to reconcile our older way of handling interface-type specialization with our newer model (that supports dynamic dispatch) makes it clear that we really need to make similar changes to our handling of generic type parameters on entry points and at the global scope. A future change should make it so that a global type parameter is lowered with a type layout similar to a value parameter of interface type, including the RTTI and witness-table pieces, and just leaving out the "any value" piece. A similar translation strategy should apply to entry-point generic parameters (mirroring how we lower generic functions for dynamic dispatch already), and value specialization parameters. Co-authored-by: Yong He <yonghe@outlook.com>
Diffstat (limited to 'source')
-rwxr-xr-xsource/slang/slang-compiler.h3
-rw-r--r--source/slang/slang-emit-c-like.cpp4
-rw-r--r--source/slang/slang-emit.cpp22
-rw-r--r--source/slang/slang-ir-bind-existentials.cpp31
-rw-r--r--source/slang/slang-ir-byte-address-legalize.cpp15
-rw-r--r--source/slang/slang-ir-byte-address-legalize.h2
-rw-r--r--source/slang/slang-ir-collect-global-uniforms.cpp33
-rw-r--r--source/slang/slang-ir-generics-lowering-context.cpp174
-rw-r--r--source/slang/slang-ir-generics-lowering-context.h10
-rw-r--r--source/slang/slang-ir-inst-defs.h46
-rw-r--r--source/slang/slang-ir-insts.h48
-rw-r--r--source/slang/slang-ir-layout.cpp47
-rw-r--r--source/slang/slang-ir-layout.h5
-rw-r--r--source/slang/slang-ir-legalize-types.cpp57
-rw-r--r--source/slang/slang-ir-link.cpp3
-rw-r--r--source/slang/slang-ir-lower-existential.cpp67
-rw-r--r--source/slang/slang-ir-lower-generic-function.cpp6
-rw-r--r--source/slang/slang-ir-lower-generics.cpp6
-rw-r--r--source/slang/slang-ir-lower-generics.h6
-rw-r--r--source/slang/slang-ir-specialize.cpp98
-rw-r--r--source/slang/slang-ir-witness-table-wrapper.cpp16
-rw-r--r--source/slang/slang-ir.cpp71
-rw-r--r--source/slang/slang-ir.h25
-rw-r--r--source/slang/slang-legalize-types.cpp35
-rw-r--r--source/slang/slang-lower-to-ir.cpp48
-rw-r--r--source/slang/slang-type-layout.cpp247
-rw-r--r--source/slang/slang-type-layout.h28
27 files changed, 867 insertions, 286 deletions
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 45ebc2909..fdd32ee81 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -1169,6 +1169,9 @@ namespace Slang
/// Are we generating code for a Khronos API (OpenGL or Vulkan)?
bool isKhronosTarget(TargetRequest* targetReq);
+ /// Are resource types "bindless" (implemented as ordinary data) on the given `target`?
+ bool areResourceTypesBindlessOnTarget(TargetRequest* target);
+
// Compute the "effective" profile to use when outputting the given entry point
// for the chosen code-generation target.
//
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp
index e27a5b972..fa3b7c6c4 100644
--- a/source/slang/slang-emit-c-like.cpp
+++ b/source/slang/slang-emit-c-like.cpp
@@ -954,7 +954,7 @@ bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst)
case kIROp_getElementPtr:
case kIROp_Specialize:
case kIROp_lookup_interface_method:
- case kIROp_GetValueFromExistentialBox:
+ case kIROp_GetValueFromBoundInterface:
return true;
}
@@ -2421,7 +2421,7 @@ void CLikeSourceEmitter::defaultEmitInstExpr(IRInst* inst, const EmitOpInfo& inO
break;
case kIROp_GlobalConstant:
- case kIROp_GetValueFromExistentialBox:
+ case kIROp_GetValueFromBoundInterface:
emitOperand(inst->getOperand(0), outerPrec);
break;
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index baa020b6d..574631567 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -307,20 +307,12 @@ Result linkAndOptimizeIR(
eliminateDeadCode(irModule);
- switch (target)
- {
- case CodeGenTarget::CPPSource:
- case CodeGenTarget::CUDASource:
- // For targets that supports dynamic dispatch, we need to lower the
- // generics / interface types to ordinary functions and types using
- // function pointers.
- dumpIRIfEnabled(compileRequest, irModule, "BEFORE-LOWER-GENERICS");
- lowerGenerics(targetRequest, irModule, sink);
- dumpIRIfEnabled(compileRequest, irModule, "LOWER-GENERICS");
- break;
- default:
- break;
- }
+ // For targets that supports dynamic dispatch, we need to lower the
+ // generics / interface types to ordinary functions and types using
+ // function pointers.
+ dumpIRIfEnabled(compileRequest, irModule, "BEFORE-LOWER-GENERICS");
+ lowerGenerics(targetRequest, irModule, sink);
+ dumpIRIfEnabled(compileRequest, irModule, "LOWER-GENERICS");
if (sink->getErrorCount() != 0)
return SLANG_FAIL;
@@ -571,7 +563,7 @@ Result linkAndOptimizeIR(
break;
}
- legalizeByteAddressBufferOps(session, irModule, byteAddressBufferOptions);
+ legalizeByteAddressBufferOps(session, targetRequest, irModule, byteAddressBufferOptions);
}
// For CUDA targets only, we will need to turn operations
diff --git a/source/slang/slang-ir-bind-existentials.cpp b/source/slang/slang-ir-bind-existentials.cpp
index 651143847..2ceda3c06 100644
--- a/source/slang/slang-ir-bind-existentials.cpp
+++ b/source/slang/slang-ir-bind-existentials.cpp
@@ -318,26 +318,45 @@ struct BindExistentialSlots
// new ones we'll be making.
//
List<IRUse*> usesToReplace;
- for(auto use = inst->firstUse; use; use = use->nextUse )
+ for( auto use = inst->firstUse; use; use = use->nextUse )
+ {
+ auto user = use->getUser();
+
+ // Note: We don't want to replace uses that are
+ // just referring to an instruction to identify
+ // it (e.g., a global shader parameter). We enumerate
+ // the relevant cases here and skip them.
+ //
+ if(as<IRDecoration>(user))
+ continue;
+ if(as<IRAttr>(user))
+ continue;
+ if(as<IRLayout>(user))
+ continue;
+
usesToReplace.add(use);
+ }
// Now we can loop over our list of uses and replace each.
//
for(auto use : usesToReplace)
{
- // First we emit a `makeExisential` right before the
- // use site.
+ // We are going to emit a `wrapExistential` (or `makeExistential`)
+ // right before each use site of the value.
//
builder.setInsertBefore(use->getUser());
+
+ // The `inst` used to have an existential/interface type,
+ // but will now have a concrete/bound type, and we need
+ // to wrap it up again to get a value of the original
+ // expected type.
+ //
auto newVal = builder.emitWrapExistential(
fullType,
inst,
slotOperandCount,
slotOperands.getBuffer());
- // Second we make the use site point at the new
- // value instead.
- //
use->set(newVal);
}
}
diff --git a/source/slang/slang-ir-byte-address-legalize.cpp b/source/slang/slang-ir-byte-address-legalize.cpp
index ed54a3ef6..f76236e0d 100644
--- a/source/slang/slang-ir-byte-address-legalize.cpp
+++ b/source/slang/slang-ir-byte-address-legalize.cpp
@@ -24,6 +24,7 @@ struct ByteAddressBufferLegalizationContext
// that control what constructs we legalize, and how.
//
Session* m_session = nullptr;
+ TargetRequest* m_target = nullptr;
ByteAddressBufferLegalizationOptions m_options;
// We will also use a central IR builder when generating new
@@ -260,7 +261,7 @@ struct ByteAddressBufferLegalizationContext
// then we fail to legalize this load.
//
IRIntegerValue fieldOffset = 0;
- SLANG_RETURN_NULL_ON_FAIL(getNaturalOffset(field, &fieldOffset));
+ SLANG_RETURN_NULL_ON_FAIL(getNaturalOffset(m_target, field, &fieldOffset));
// Otherwise, we load the field by recursively calling this function
// on the field type, with an adjusted immediate offset.
@@ -422,7 +423,7 @@ struct ByteAddressBufferLegalizationContext
// the "stride" of the element type.
//
IRSizeAndAlignment elementLayout;
- SLANG_RETURN_NULL_ON_FAIL(getNaturalSizeAndAlignment(elementType, &elementLayout));
+ SLANG_RETURN_NULL_ON_FAIL(getNaturalSizeAndAlignment(m_target, elementType, &elementLayout));
IRIntegerValue elementStride = elementLayout.getStride();
// We will collect all the element values into an array so
@@ -512,7 +513,7 @@ struct ByteAddressBufferLegalizationContext
auto offsetType = offset->getDataType();
IRSizeAndAlignment typeLayout;
- SLANG_RETURN_NULL_ON_FAIL(getNaturalSizeAndAlignment(type, &typeLayout));
+ SLANG_RETURN_NULL_ON_FAIL(getNaturalSizeAndAlignment(m_target, type, &typeLayout));
auto typeStrideVal = typeLayout.getStride();
auto typeStrideInst = m_builder.getIntValue(offsetType, typeStrideVal);
@@ -808,7 +809,7 @@ struct ByteAddressBufferLegalizationContext
auto fieldType = field->getFieldType();
IRIntegerValue fieldOffset;
- SLANG_RETURN_ON_FAIL(getNaturalOffset(field, &fieldOffset));
+ SLANG_RETURN_ON_FAIL(getNaturalOffset(m_target, field, &fieldOffset));
auto fieldVal = m_builder.emitFieldExtract(fieldType, value, field->getKey());
SLANG_RETURN_ON_FAIL(emitLegalStore(fieldType, buffer, baseOffset, immediateOffset + fieldOffset, fieldVal));
@@ -892,7 +893,7 @@ struct ByteAddressBufferLegalizationContext
auto indexType = offset->getDataType();
IRSizeAndAlignment typeLayout;
- SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(type, &typeLayout));
+ SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(m_target, type, &typeLayout));
auto typeStride = m_builder.getIntValue(indexType, typeLayout.getStride());
@@ -920,7 +921,7 @@ struct ByteAddressBufferLegalizationContext
// We iterate over the elements and fetch then store each one.
//
IRSizeAndAlignment elementLayout;
- SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(elementType, &elementLayout));
+ SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(m_target, elementType, &elementLayout));
IRIntegerValue elementStride = elementLayout.getStride();
auto indexType = m_builder.getIntType();
@@ -938,11 +939,13 @@ struct ByteAddressBufferLegalizationContext
void legalizeByteAddressBufferOps(
Session* session,
+ TargetRequest* target,
IRModule* module,
ByteAddressBufferLegalizationOptions const& options)
{
ByteAddressBufferLegalizationContext context;
context.m_session = session;
+ context.m_target = target;
context.m_options = options;
context.processModule(module);
}
diff --git a/source/slang/slang-ir-byte-address-legalize.h b/source/slang/slang-ir-byte-address-legalize.h
index 7b5c8ed3e..c01c0c34d 100644
--- a/source/slang/slang-ir-byte-address-legalize.h
+++ b/source/slang/slang-ir-byte-address-legalize.h
@@ -4,6 +4,7 @@
namespace Slang
{
class Session;
+class TargetRequest;
struct IRModule;
struct ByteAddressBufferLegalizationOptions
@@ -21,6 +22,7 @@ struct ByteAddressBufferLegalizationOptions
///
void legalizeByteAddressBufferOps(
Session* session,
+ TargetRequest* target,
IRModule* module,
ByteAddressBufferLegalizationOptions const& options);
}
diff --git a/source/slang/slang-ir-collect-global-uniforms.cpp b/source/slang/slang-ir-collect-global-uniforms.cpp
index 44f1e4ac0..90b168022 100644
--- a/source/slang/slang-ir-collect-global-uniforms.cpp
+++ b/source/slang/slang-ir-collect-global-uniforms.cpp
@@ -238,6 +238,38 @@ struct CollectGlobalUniformParametersContext
}
for( auto use : uses )
{
+ auto user = use->user;
+
+ // There is an annoying gotcha here, in that we are using
+ // global shader parameters themselves (the `IRGlobalParam`s)
+ // to represent their "keys" in the layout objects that
+ // represent the layout of the global scope.
+ //
+ // We don't want to replace the reference to the global
+ // parameter in one of these layouts with a reference
+ // to a field of our new collected parameter, and instead
+ // want to replace such a reference with the key for that
+ // field.
+ //
+ // TODO: We should probably be assigning keys to global
+ // parameters, and using those keys in the layout instructions
+ // instead of directly using the parameters. The parameters
+ // could then have a decoration to assocaite them with their
+ // key.
+ //
+ // TODO: Alternatively, we could considering doing this
+ // kind of collection work earlier, on a per-module basis,
+ // so that we don't need to perform collection as a back-end step.
+ // (Note that the main sticking point there is explicit layout
+ // markers on global parameters, that stop the entire parameter
+ // range for a module from being contiguous).
+ //
+ if(auto layoutAttr = as<IRStructFieldLayoutAttr>(user))
+ {
+ layoutAttr->setOperand(0, fieldKey);
+ continue;
+ }
+
// For each use site for the global parameter, we will
// insert new code right before the instruction that uses
// the parameter.
@@ -250,7 +282,6 @@ struct CollectGlobalUniformParametersContext
// so that these loads can be merged/moved without concern
// for aliasing.
//
- auto user = use->user;
builder->setInsertBefore(user);
IRInst* value = nullptr;
diff --git a/source/slang/slang-ir-generics-lowering-context.cpp b/source/slang/slang-ir-generics-lowering-context.cpp
index 4c4224295..5ae19391b 100644
--- a/source/slang/slang-ir-generics-lowering-context.cpp
+++ b/source/slang/slang-ir-generics-lowering-context.cpp
@@ -66,7 +66,7 @@ namespace Slang
// For now the only type info we encapsualte is type size.
IRSizeAndAlignment sizeAndAlignment;
- getNaturalSizeAndAlignment((IRType*)typeInst, &sizeAndAlignment);
+ getNaturalSizeAndAlignment(targetReq, (IRType*)typeInst, &sizeAndAlignment);
builder->addRTTITypeSizeDecoration(result, sizeAndAlignment.size);
// Give a name to the rtti object.
@@ -118,12 +118,19 @@ namespace Slang
}
if (anyValueSize == kInvalidAnyValueSize)
{
- sink->diagnose(type->sourceLoc, Diagnostics::dynamicInterfaceLacksAnyValueSizeAttribute, type);
+ // We could conceivably make it an error to have an associated type
+ // without an `[anyValueSize(...)]` attribute, but then we risk
+ // producing error messages even when doing 100% static specialization.
+ //
+ // It is simpler to use a reasonable default size and treat any
+ // type without an explicit attribute as using that size.
+ //
+ anyValueSize = kDefaultAnyValueSize;
}
return builder->getAnyValueType(anyValueSize);
}
- IRType* SharedGenericsLoweringContext::lowerType(IRBuilder* builder, IRInst* paramType, const Dictionary<IRInst*, IRInst*>& typeMapping)
+ IRType* SharedGenericsLoweringContext::lowerType(IRBuilder* builder, IRInst* paramType, const Dictionary<IRInst*, IRInst*>& typeMapping, IRType* concreteType)
{
if (!paramType)
return nullptr;
@@ -137,7 +144,6 @@ namespace Slang
return builder->getRTTIHandleType();
}
- IRIntegerValue anyValueSize = kInvalidAnyValueSize;
switch (paramType->op)
{
case kIROp_WitnessTableType:
@@ -151,19 +157,28 @@ namespace Slang
{
if (isBuiltin(anyValueSizeDecor->getConstraintType()))
return (IRType*)paramType;
- anyValueSize = getInterfaceAnyValueSize(anyValueSizeDecor->getConstraintType(), paramType->sourceLoc);
+ auto anyValueSize = getInterfaceAnyValueSize(anyValueSizeDecor->getConstraintType(), paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
}
- sink->diagnose(paramType, Diagnostics::unconstrainedGenericParameterNotAllowedInDynamicFunction, paramType);
- return builder->getAnyValueType(kInvalidAnyValueSize);
+ // We could conceivably make it an error to have a generic parameter
+ // without an `[anyValueSize(...)]` attribute, but then we risk
+ // producing error messages even when doing 100% static specialization.
+ //
+ // It is simpler to use a reasonable default size and treat any
+ // type without an explicit attribute as using that size.
+ //
+ return builder->getAnyValueType(kDefaultAnyValueSize);
}
case kIROp_ThisType:
+ {
+
if (isBuiltin(cast<IRThisType>(paramType)->getConstraintType()))
return (IRType*)paramType;
- anyValueSize = getInterfaceAnyValueSize(
+ auto anyValueSize = getInterfaceAnyValueSize(
cast<IRThisType>(paramType)->getConstraintType(),
paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
+ }
case kIROp_AssociatedType:
{
return lowerAssociatedType(builder, paramType);
@@ -172,12 +187,97 @@ namespace Slang
{
if (isBuiltin(paramType))
return (IRType*)paramType;
- // An existential type translates into a tuple of (AnyValue, WitnessTable, RTTI*)
- anyValueSize = getInterfaceAnyValueSize(paramType, paramType->sourceLoc);
+
+ // In the dynamic-dispatch case, a value of interface type
+ // is going to be packed into the "any value" part of a tuple.
+ // The size of the "any value" part depends on the interface
+ // type (e.g., it might have an `[anyValueSize(8)]` attribute
+ // indicating that 8 bytes needs to be reserved).
+ //
+ auto anyValueSize = getInterfaceAnyValueSize(paramType, paramType->sourceLoc);
+
+ // If there is a non-null `concreteType` parameter, then this
+ // interface type is one that has been statically bound (via
+ // specialization parameters) to hold a value of that concrete
+ // type.
+ //
+ IRType* pendingType = nullptr;
+ if( concreteType )
+ {
+ // Because static specialization is being used (at least in part),
+ // we do *not* have a guarantee that the `concreteType` is one
+ // that can fit into the `anyValueSize` of the interface.
+ //
+ // We will use the IR layout logic to see if we can compute
+ // a size for the type, which can lead to a few different outcomes:
+ //
+ // * If a size is computed successfully, and it is smaller than or
+ // equal to `anyValueSize`, then the concrete value will fit into
+ // the reserved area, and the layout will match the dynamic case.
+ //
+ // * If a size is computed successfully, and it is larger than
+ // `anyValueSize`, then the concrete value cannot fit into the
+ // reserved area, and it needs to be stored out-of-line.
+ //
+ // * If size cannot be computed, then that implies that the type
+ // includes non-ordinary data (e.g., a `Texture2D` on a D3D11
+ // target), and cannot possible fit into the reserved area
+ // (which consists of only uniform bytes). In this case, the
+ // value must be stored out-of-line.
+ //
+ IRSizeAndAlignment sizeAndAlignment;
+ Result result = getNaturalSizeAndAlignment(targetReq, concreteType, &sizeAndAlignment);
+ if(SLANG_FAILED(result) || (sizeAndAlignment.size > anyValueSize))
+ {
+ // If the value must be stored out-of-line, we construct
+ // a "pseudo pointer" to the concrete type, and the
+ // constructed tuple will contain such a pseudo pointer.
+ //
+ // Semantically, the pseudo pointer behaves a bit like
+ // a pointer to the concrete type, in that it can be
+ // (pseudo-)dereferenced to produce a value of the chosen
+ // type.
+ //
+ // In terms of layout, the pseudo pointer occupies no
+ // space in the parent tuple/type, and will be automatically
+ // moved out-of-line by a later type legalization pass.
+ //
+ pendingType = builder->getPseudoPtrType(concreteType);
+ }
+ }
+
auto anyValueType = builder->getAnyValueType(anyValueSize);
auto witnessTableType = builder->getWitnessTableIDType((IRType*)paramType);
auto rttiType = builder->getRTTIHandleType();
- auto tupleType = builder->getTupleType(rttiType, witnessTableType, anyValueType);
+
+ IRType* tupleType = nullptr;
+ if( !pendingType )
+ {
+ // In the oridnary (dynamic) case, an existential type decomposes
+ // into a tuple of:
+ //
+ // (RTTI, witness table, any-value).
+ //
+ tupleType = builder->getTupleType(rttiType, witnessTableType, anyValueType);
+ }
+ else
+ {
+ // In the case where static specialization mandateds out-of-line storage,
+ // an existential type decomposes into a tuple of:
+ //
+ // (RTTI, witness table, pseudo pointer, any-value)
+ //
+ tupleType = builder->getTupleType(rttiType, witnessTableType, pendingType, anyValueType);
+ //
+ // Note that in each of the cases, the third element of the tuple
+ // is a representation of the value being stored in the existential.
+ //
+ // Also note that each of these representations has the same
+ // size and alignment when only "ordinary" data is considered
+ // (the pseudo-pointer will eventually be legalized away, leaving
+ // behind a tuple with equivalent layout).
+ }
+
return tupleType;
}
case kIROp_lookup_interface_method:
@@ -196,12 +296,20 @@ namespace Slang
interfaceType,
lookupInterface->getRequirementKey());
SLANG_ASSERT(reqVal && reqVal->op == kIROp_AssociatedType);
- return lowerType(builder, reqVal, typeMapping);
+ return lowerType(builder, reqVal, typeMapping, nullptr);
}
- case kIROp_ExistentialBoxType:
+ case kIROp_BoundInterfaceType:
{
- auto existentialBoxType = static_cast<IRExistentialBoxType*>(paramType);
- return lowerType(builder, existentialBoxType->getInterfaceType(), typeMapping);
+ // A bound interface type represents an existential together with
+ // static knowledge that the value stored in the extistential has
+ // a particular concrete type.
+ //
+ // We handle this case by lowering the underlying interface type,
+ // but pass along the concrete type so that it can impact the
+ // layout of the interface type.
+ //
+ auto boundInterfaceType = static_cast<IRBoundInterfaceType*>(paramType);
+ return lowerType(builder, boundInterfaceType->getInterfaceType(), typeMapping, boundInterfaceType->getConcreteType());
}
default:
{
@@ -209,7 +317,7 @@ namespace Slang
List<IRInst*> loweredOperands;
for (UInt i = 0; i < paramType->getOperandCount(); i++)
{
- loweredOperands.add(lowerType(builder, paramType->getOperand(i), typeMapping));
+ loweredOperands.add(lowerType(builder, paramType->getOperand(i), typeMapping, nullptr));
if (loweredOperands.getLast() != paramType->getOperand(i))
translated = true;
}
@@ -237,12 +345,40 @@ namespace Slang
IRIntegerValue SharedGenericsLoweringContext::getInterfaceAnyValueSize(IRInst* type, SourceLoc usageLocation)
{
+ SLANG_UNUSED(usageLocation);
+
if (auto decor = type->findDecoration<IRAnyValueSizeDecoration>())
{
return decor->getSize();
}
- sink->diagnose(type->sourceLoc, Diagnostics::dynamicInterfaceLacksAnyValueSizeAttribute, type);
- sink->diagnose(usageLocation, Diagnostics::seeInterfaceUsage, type);
- return kInvalidAnyValueSize;
+
+ // We could conceivably make it an error to have an interface
+ // without an `[anyValueSize(...)]` attribute, but then we risk
+ // producing error messages even when doing 100% static specialization.
+ //
+ // It is simpler to use a reasonable default size and treat any
+ // type without an explicit attribute as using that size.
+ //
+ return kDefaultAnyValueSize;
+ }
+
+
+ bool SharedGenericsLoweringContext::doesTypeFitInAnyValue(IRType* concreteType, IRInterfaceType* interfaceType)
+ {
+ auto anyValueSize = getInterfaceAnyValueSize(interfaceType, interfaceType->sourceLoc);
+
+ IRSizeAndAlignment sizeAndAlignment;
+ Result result = getNaturalSizeAndAlignment(targetReq, concreteType, &sizeAndAlignment);
+ if(SLANG_FAILED(result) || (sizeAndAlignment.size > anyValueSize))
+ {
+ // The value does not fit, either because it is too large,
+ // or because it includes types that cannot be stored
+ // in uniform/ordinary memory for this target.
+ //
+ return false;
+ }
+
+ return true;
}
+
}
diff --git a/source/slang/slang-ir-generics-lowering-context.h b/source/slang/slang-ir-generics-lowering-context.h
index f9d81e884..78dd068b8 100644
--- a/source/slang/slang-ir-generics-lowering-context.h
+++ b/source/slang/slang-ir-generics-lowering-context.h
@@ -4,11 +4,14 @@
#include "slang-ir.h"
#include "slang-ir-insts.h"
+#include "slang-ir-lower-generics.h"
+
namespace Slang
{
struct IRModule;
constexpr IRIntegerValue kInvalidAnyValueSize = 0xFFFFFFFF;
+ constexpr IRIntegerValue kDefaultAnyValueSize = 16;
struct SharedGenericsLoweringContext
{
@@ -70,11 +73,11 @@ namespace Slang
IRIntegerValue getInterfaceAnyValueSize(IRInst* type, SourceLoc usageLocation);
IRType* lowerAssociatedType(IRBuilder* builder, IRInst* type);
- IRType* lowerType(IRBuilder* builder, IRInst* paramType, const Dictionary<IRInst*, IRInst*>& typeMapping);
+ IRType* lowerType(IRBuilder* builder, IRInst* paramType, const Dictionary<IRInst*, IRInst*>& typeMapping, IRType* concreteType);
IRType* lowerType(IRBuilder* builder, IRInst* paramType)
{
- return lowerType(builder, paramType, Dictionary<IRInst*, IRInst*>());
+ return lowerType(builder, paramType, Dictionary<IRInst*, IRInst*>(), nullptr);
}
// Get a list of all witness tables whose conformance type is `interfaceType`.
@@ -89,6 +92,9 @@ namespace Slang
}
return nullptr;
}
+
+ /// Does the given `concreteType` fit within the any-value size deterined by `interfaceType`?
+ bool doesTypeFitInAnyValue(IRType* concreteType, IRInterfaceType* interfaceType);
};
bool isPolymorphicType(IRInst* typeInst);
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 8256646e0..4642ed3f0 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -49,13 +49,23 @@ INST(Nop, nop, 0, 0)
INST(TaggedUnionType, TaggedUnion, 0, 0)
- // A `BindExistentials<B, T0,w0, T1,w1, ...>` represents
- // taking type `B` and binding each of its existential type
- // parameters, recursively, with the specified arguments,
- // where each `Ti, wi` pair represents the concrete type
- // and witness table to plug in for parameter `i`.
- //
- INST(BindExistentialsType, BindExistentials, 1, 0)
+ /* BindExistentialsTypeBase */
+
+ // A `BindExistentials<B, T0,w0, T1,w1, ...>` represents
+ // taking type `B` and binding each of its existential type
+ // parameters, recursively, with the specified arguments,
+ // where each `Ti, wi` pair represents the concrete type
+ // and witness table to plug in for parameter `i`.
+ //
+ INST(BindExistentialsType, BindExistentials, 1, 0)
+
+ // An `BindInterface<B, T0, w0>` represents the special case
+ // of a `BindExistentials` where the type `B` is known to be
+ // an interface type.
+ //
+ INST(BoundInterfaceType, BoundInterface, 3, 0)
+
+ INST_RANGE(BindExistentialsTypeBase, BindExistentialsType, BoundInterfaceType)
/* Rate */
INST(ConstExprRate, ConstExpr, 0, 0)
@@ -78,12 +88,12 @@ INST(Nop, nop, 0, 0)
INST(PtrType, Ptr, 1, 0)
INST(RefType, Ref, 1, 0)
- // An `ExistentialBox<T>` represents a logical pointer to a value of type `T`.
- // On targets that support pointers this might lower to a pointer, but on
- // current targets it will lower to zero bytes, with a value of type `T`
- // being stored "out of line" somewhere.
- //
- INST(ExistentialBoxType, ExistentialBox, 1, 0)
+ // A `PsuedoPtr<T>` logically represents a pointer to a value of type
+ // `T` on a platform that cannot support pointers. The expectation
+ // is that the "pointer" will be legalized away by storing a value
+ // of type `T` somewhere out-of-line.
+
+ INST(PseudoPtrType, PseudoPtr, 1, 0)
/* OutTypeBase */
INST(OutType, Out, 1, 0)
@@ -607,11 +617,12 @@ INST(MakeExistentialWithRTTI, makeExistentialWithRTTI, 3, 0)
// result of the `wrapExistentials` operation is a value of type `T`, allowing us to
// "smuggle" a value of specialized type into computations that expect an unspecialized type.
//
-INST(WrapExistential, wrapExistential, 2, 0)
+INST(WrapExistential, wrapExistential, 1, 0)
-// A `GetValueFromExistentialBox` takes a `ExistentialBox` value and returns the value wrapped by
-// the existential box.
-INST(GetValueFromExistentialBox, getValueFromExistentialBox, 1, 0)
+// A `GetValueFromBoundInterface` takes a `BindInterface<I, T, w0>` value and returns the
+// value of concrete type `T` value that is being stored.
+//
+INST(GetValueFromBoundInterface, getValueFromBoundInterface, 1, 0)
INST(ExtractExistentialValue, extractExistentialValue, 1, 0)
INST(ExtractExistentialType, extractExistentialType, 1, 0)
@@ -635,6 +646,7 @@ INST(GetEquivalentStructuredBuffer, getEquivalentStructuredBuffer, 1, 0)
INST(StreamOutputTypeLayout, streamOutputTypeLayout, 1, 0)
INST(MatrixTypeLayout, matrixTypeLayout, 1, 0)
INST(TaggedUnionTypeLayout, taggedUnionTypeLayout, 0, 0)
+ INST(ExistentialTypeLayout, existentialTypeLayout, 0, 0)
INST(StructTypeLayout, structTypeLayout, 0, 0)
INST_RANGE(TypeLayout, TypeLayoutBase, StructTypeLayout)
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index f651920d9..baa511a3c 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -1054,6 +1054,30 @@ struct IRTaggedUnionTypeLayout : IRTypeLayout
};
};
+ /// Type layout for an existential/interface type.
+struct IRExistentialTypeLayout : IRTypeLayout
+{
+ typedef IRTypeLayout Super;
+
+ IR_LEAF_ISA(ExistentialTypeLayout)
+
+ struct Builder : Super::Builder
+ {
+ Builder(IRBuilder* irBuilder)
+ : Super::Builder(irBuilder)
+ {}
+
+ IRExistentialTypeLayout* build()
+ {
+ return cast<IRExistentialTypeLayout>(Super::Builder::build());
+ }
+
+ protected:
+ IROp getOp() SLANG_OVERRIDE { return kIROp_ExistentialTypeLayout; }
+ };
+};
+
+
/// Layout information for an entry point
struct IREntryPointLayout : IRLayout
{
@@ -1589,6 +1613,11 @@ struct IRWitnessTable : IRInst
return cast<IRWitnessTableType>(getDataType())->getConformanceType();
}
+ IRType* getConcreteType()
+ {
+ return (IRType*) getOperand(0);
+ }
+
IR_LEAF_ISA(WitnessTable)
};
@@ -1672,9 +1701,9 @@ struct IRWrapExistential : IRInst
IR_LEAF_ISA(WrapExistential)
};
-struct IRGetValueFromExistentialBox : IRInst
+struct IRGetValueFromBoundInterface : IRInst
{
- IR_LEAF_ISA(GetValueFromExistentialBox);
+ IR_LEAF_ISA(GetValueFromBoundInterface);
};
struct IRExtractExistentialValue : IRInst
@@ -1815,6 +1844,7 @@ struct IRBuilder
IRTupleType* getTupleType(UInt count, IRType* const* types);
IRTupleType* getTupleType(IRType* type0, IRType* type1);
IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2);
+ IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2, IRType* type3);
IRBasicBlockType* getBasicBlockType();
IRWitnessTableType* getWitnessTableType(IRType* baseType);
@@ -1830,7 +1860,6 @@ struct IRBuilder
IRInOutType* getInOutType(IRType* valueType);
IRRefType* getRefType(IRType* valueType);
IRPtrTypeBase* getPtrType(IROp op, IRType* valueType);
- IRType* getExistentialBoxType(IRType* concreteType, IRType* interfaceType);
IRArrayTypeBase* getArrayTypeBase(
IROp op,
@@ -1895,12 +1924,20 @@ struct IRBuilder
UInt slotArgCount,
IRUse const* slotArgs);
+ IRType* getBoundInterfaceType(
+ IRType* interfaceType,
+ IRType* concreteType,
+ IRInst* witnessTable);
+
+ IRType* getPseudoPtrType(
+ IRType* concreteType);
+
// Set the data type of an instruction, while preserving
// its rate, if any.
void setDataType(IRInst* inst, IRType* dataType);
/// Extract the value wrapped inside an existential box.
- IRInst* emitGetValueFromExistentialBox(IRType* type, IRInst* existentialBox);
+ IRInst* emitGetValueFromBoundInterface(IRType* type, IRInst* boundInterfaceValue);
/// Given an existential value, extract the underlying "real" value
IRInst* emitExtractExistentialValue(
@@ -2079,7 +2116,8 @@ struct IRBuilder
/// Creates an IRWitnessTable value.
/// @param baseType: The comformant-to type of this witness.
- IRWitnessTable* createWitnessTable(IRType* baseType);
+ /// @param subType: The type that is doing the conforming.
+ IRWitnessTable* createWitnessTable(IRType* baseType, IRType* subType);
IRWitnessTableEntry* createWitnessTableEntry(
IRWitnessTable* witnessTable,
IRInst* requirementKey,
diff --git a/source/slang/slang-ir-layout.cpp b/source/slang/slang-ir-layout.cpp
index 6ea1093f5..41b004372 100644
--- a/source/slang/slang-ir-layout.cpp
+++ b/source/slang/slang-ir-layout.cpp
@@ -49,7 +49,11 @@
namespace Slang
{
-static Result _calcNaturalArraySizeAndAlignment(IRType* elementType, IRInst* elementCountInst, IRSizeAndAlignment* outSizeAndAlignment)
+static Result _calcNaturalArraySizeAndAlignment(
+ TargetRequest* target,
+ IRType* elementType,
+ IRInst* elementCountInst,
+ IRSizeAndAlignment* outSizeAndAlignment)
{
auto elementCountLit = as<IRIntLit>(elementCountInst);
if(!elementCountLit)
@@ -63,7 +67,7 @@ static Result _calcNaturalArraySizeAndAlignment(IRType* elementType, IRInst* ele
}
IRSizeAndAlignment elementTypeLayout;
- SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(elementType, &elementTypeLayout));
+ SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(target, elementType, &elementTypeLayout));
auto elementStride = elementTypeLayout.getStride();
@@ -73,7 +77,10 @@ static Result _calcNaturalArraySizeAndAlignment(IRType* elementType, IRInst* ele
return SLANG_OK;
}
-static Result _calcNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAlignment)
+static Result _calcNaturalSizeAndAlignment(
+ TargetRequest* target,
+ IRType* type,
+ IRSizeAndAlignment* outSizeAndAlignment)
{
switch( type->op )
{
@@ -127,7 +134,7 @@ static Result _calcNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* out
for( auto field : structType->getFields() )
{
IRSizeAndAlignment fieldTypeLayout;
- SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(field->getFieldType(), &fieldTypeLayout));
+ SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(target, field->getFieldType(), &fieldTypeLayout));
structLayout.size = align(structLayout.size, fieldTypeLayout.alignment);
structLayout.alignment = std::max(structLayout.alignment, fieldTypeLayout.alignment);
@@ -166,6 +173,7 @@ static Result _calcNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* out
auto arrayType = cast<IRArrayType>(type);
return _calcNaturalArraySizeAndAlignment(
+ target,
arrayType->getElementType(),
arrayType->getElementCount(),
outSizeAndAlignment);
@@ -177,6 +185,7 @@ static Result _calcNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* out
auto vecType = cast<IRVectorType>(type);
return _calcNaturalArraySizeAndAlignment(
+ target,
vecType->getElementType(),
vecType->getElementCount(),
outSizeAndAlignment);
@@ -184,11 +193,31 @@ static Result _calcNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* out
break;
default:
- return SLANG_FAIL;
+ break;
+ }
+
+ if( areResourceTypesBindlessOnTarget(target) )
+ {
+ // TODO: need this to be based on target, instead of hard-coded
+ int pointerSize = sizeof(void*);
+
+ if(as<IRTextureType>(type) )
+ {
+ *outSizeAndAlignment = IRSizeAndAlignment(pointerSize, pointerSize);
+ return SLANG_OK;
+ }
+ else if(as<IRSamplerStateTypeBase>(type) )
+ {
+ *outSizeAndAlignment = IRSizeAndAlignment(pointerSize, pointerSize);
+ return SLANG_OK;
+ }
+ // TODO: the remaining cases for "bindless" resources on CPU/CUDA targets
}
+
+ return SLANG_FAIL;
}
-Result getNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAlignment)
+Result getNaturalSizeAndAlignment(TargetRequest* target, IRType* type, IRSizeAndAlignment* outSizeAndAlignment)
{
if( auto decor = type->findDecoration<IRNaturalSizeAndAlignmentDecoration>() )
{
@@ -197,7 +226,7 @@ Result getNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAl
}
IRSizeAndAlignment sizeAndAlignment;
- SLANG_RETURN_ON_FAIL(_calcNaturalSizeAndAlignment(type, &sizeAndAlignment));
+ SLANG_RETURN_ON_FAIL(_calcNaturalSizeAndAlignment(target, type, &sizeAndAlignment));
if( auto module = type->getModule() )
{
@@ -221,7 +250,7 @@ Result getNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAl
}
-Result getNaturalOffset(IRStructField* field, IRIntegerValue* outOffset)
+Result getNaturalOffset(TargetRequest* target, IRStructField* field, IRIntegerValue* outOffset)
{
if( auto decor = field->findDecoration<IRNaturalOffsetDecoration>() )
{
@@ -239,7 +268,7 @@ Result getNaturalOffset(IRStructField* field, IRIntegerValue* outOffset)
return SLANG_FAIL;
IRSizeAndAlignment structTypeLayout;
- SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(structType, &structTypeLayout));
+ SLANG_RETURN_ON_FAIL(getNaturalSizeAndAlignment(target, structType, &structTypeLayout));
if( auto decor = field->findDecoration<IRNaturalOffsetDecoration>() )
{
diff --git a/source/slang/slang-ir-layout.h b/source/slang/slang-ir-layout.h
index 64653b5f3..5c736f474 100644
--- a/source/slang/slang-ir-layout.h
+++ b/source/slang/slang-ir-layout.h
@@ -21,6 +21,7 @@
namespace Slang
{
+class TargetRequest;
/// Align `value` to the next multiple of `alignment`, which must be a power of two.
inline IRIntegerValue align(IRIntegerValue value, int alignment)
@@ -56,7 +57,7 @@ struct IRSizeAndAlignment
/// general-purpose memory for the current target. In that case the
/// type is considered to have no natural layout.
///
-Result getNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAlignment);
+Result getNaturalSizeAndAlignment(TargetRequest* target, IRType* type, IRSizeAndAlignment* outSizeAndAlignment);
/// Compute (if necessary) and return the natural offset of `field`
///
@@ -64,7 +65,7 @@ Result getNaturalSizeAndAlignment(IRType* type, IRSizeAndAlignment* outSizeAndAl
/// that can be stored in general-purpose memory. In that case, the
/// field is considered to have no natural offset.
///
-Result getNaturalOffset(IRStructField* field, IRIntegerValue* outOffset);
+Result getNaturalOffset(TargetRequest* target, IRStructField* field, IRIntegerValue* outOffset);
}
diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp
index d72117702..91229b80d 100644
--- a/source/slang/slang-ir-legalize-types.cpp
+++ b/source/slang/slang-ir-legalize-types.cpp
@@ -1210,7 +1210,7 @@ static LegalVal legalizeInst(
case kIROp_Load:
return legalizeLoad(context, args[0]);
- case kIROp_GetValueFromExistentialBox:
+ case kIROp_GetValueFromBoundInterface:
return args[0];
case kIROp_FieldAddress:
@@ -1826,6 +1826,58 @@ static void _addFieldsToWrappedBufferElementTypeLayout(
case LegalElementWrapping::Flavor::tuple:
{
+ auto tupleInfo = elementInfo.getTuple();
+
+ // There is an extremely special case that we need to deal with here,
+ // which is the case where the original element/field had an interface
+ // type, which was subject to static specialization.
+ //
+ // In such a case, the layout will show a simple type layout for
+ // the field/element itself (just uniform data) plus a "pending"
+ // layout for any fields related to the static specialization.
+ //
+ // In contrast, the actual IR type structure will have been turned
+ // into a `struct` type with multiple fields, one of which is a
+ // pseudo-pointer to the "pending" data. That field would require
+ // legalization, sending us down this path.
+ //
+ // The situation here is that we have an `elementTypeLayout` that
+ // is for a single field of interface type, but an `elementInfo`
+ // that corresponds to a struct with 3 or more fields (the tuple
+ // that was introduced to represent the interface type).
+ //
+ // We expect that `elementInfo` will represent a tuple with
+ // only a single element, and that element will reference the third
+ // field of the tuple/struct (the payload).
+ //
+ // What we want to do in this case is instead add the fields
+ // corresponding to the payload type, which are stored as
+ // the pending type layout on `elementTypeLayout`.
+ //
+ if( isSpecial )
+ {
+ if( auto existentialTypeLayout = as<IRExistentialTypeLayout>(elementTypeLayout) )
+ {
+ if( auto pendingTypeLayout = existentialTypeLayout->getPendingDataTypeLayout() )
+ {
+ SLANG_ASSERT(tupleInfo->elements.getCount() == 1);
+
+ for( auto ee : tupleInfo->elements )
+ {
+ _addFieldsToWrappedBufferElementTypeLayout(
+ irBuilder,
+ existentialTypeLayout,
+ newTypeLayout,
+ ee.field,
+ varChain,
+ true);
+ }
+
+ return;
+ }
+ }
+ }
+
// A tuple comes up when we've turned an aggregate
// with one or more interface-type fields into
// distinct fields at the top level.
@@ -1835,7 +1887,6 @@ static void _addFieldsToWrappedBufferElementTypeLayout(
// 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);
@@ -2702,7 +2753,7 @@ struct IRExistentialTypeLegalizationContext : IRTypeLegalizationContext
// boxes, or arrays thereof.
//
auto type = unwrapArray(inType);
- return as<IRExistentialBoxType>(type) != nullptr;
+ return as<IRPseudoPtrType>(type) != nullptr;
}
LegalType createLegalUniformBufferType(
diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp
index 802288c0b..a0c46066c 100644
--- a/source/slang/slang-ir-link.cpp
+++ b/source/slang/slang-ir-link.cpp
@@ -572,7 +572,8 @@ IRWitnessTable* cloneWitnessTableImpl(
if (!clonedTable)
{
auto clonedBaseType = cloneType(context, as<IRType>(originalTable->getConformanceType()));
- clonedTable = builder->createWitnessTable(clonedBaseType);
+ auto clonedSubType = cloneType(context, as<IRType>(originalTable->getConcreteType()));
+ clonedTable = builder->createWitnessTable(clonedBaseType, clonedSubType);
}
cloneSimpleGlobalValueImpl(context, originalTable, originalValues, clonedTable, registerValue);
return clonedTable;
diff --git a/source/slang/slang-ir-lower-existential.cpp b/source/slang/slang-ir-lower-existential.cpp
index a87169ac3..9df0e51db 100644
--- a/source/slang/slang-ir-lower-existential.cpp
+++ b/source/slang/slang-ir-lower-existential.cpp
@@ -83,28 +83,53 @@ namespace Slang
processExtractExistentialElement(inst, 0);
}
- void processGetValueFromExistentialBox(IRGetValueFromExistentialBox* inst)
+ void processGetValueFromBoundInterface(IRGetValueFromBoundInterface* inst)
{
- // Currently we do not translate on HLSL/GLSL targets,
- // since we don't attempt to actually layout the value inside an fixed sized
- // existential box for these targets.
- if (isCPUTarget(sharedContext->targetReq) || isCUDATarget(sharedContext->targetReq))
+ IRBuilder builderStorage;
+ auto builder = &builderStorage;
+ builder->sharedBuilder = &sharedContext->sharedBuilderStorage;
+ builder->setInsertBefore(inst);
+
+ // A value of interface will lower as a tuple, and
+ // the third element of that tuple represents the
+ // concrete value that was put into the existential.
+ //
+ auto element = extractTupleElement(builder, inst->getOperand(0), 2);
+ auto elementType = element->getDataType();
+
+ // There are two cases we expect to see for that
+ // tuple element.
+ //
+ IRInst* replacement = nullptr;
+ if(as<IRPseudoPtrType>(elementType))
+ {
+ // The first case is when legacy static specialization
+ // is applied, and the element is a "pseudo-pointer."
+ //
+ // Semantically, we should emit a (pseudo-)load from the pseudo-pointer
+ // to go from `PseudoPtr<T>` to `T`.
+ //
+ // TODO: Actually introduce and emit a "psedudo-load" instruction
+ // here. For right now we are just using the value directly and
+ // downstream passes seem okay with it, but it isn't really
+ // type-correct to be doing this.
+ //
+ replacement = element;
+ }
+ else
{
- IRBuilder builderStorage;
- auto builder = &builderStorage;
- builder->sharedBuilder = &sharedContext->sharedBuilderStorage;
- builder->setInsertBefore(inst);
-
- auto element = extractTupleElement(builder, inst->getOperand(0), 2);
- // TODO: it is not technically sound to use `getAddress` on a temporary value.
- // We probably need to develop a mechanism to allow a temporary value to be used
- // in the place of a pointer.
- auto elementAddr =
- builder->emitGetAddress(builder->getPtrType(element->getDataType()), element);
- auto reinterpretAddr = builder->emitBitCast(inst->getDataType(), elementAddr);
- inst->replaceUsesWith(reinterpretAddr);
- inst->removeAndDeallocate();
+ // The second case is when the dynamic-dispatch layout is
+ // being used, and the element is an "any-value."
+ //
+ // In this case we need to emit an unpacking operation
+ // to get from `AnyValue` to `T`.
+ //
+ SLANG_ASSERT(as<IRAnyValueType>(elementType));
+ replacement = builder->emitUnpackAnyValue(inst->getFullType(), element);
}
+
+ inst->replaceUsesWith(replacement);
+ inst->removeAndDeallocate();
}
void processInst(IRInst* inst)
@@ -113,9 +138,9 @@ namespace Slang
{
processMakeExistential(makeExistential);
}
- else if (auto getExistentialValue = as<IRGetValueFromExistentialBox>(inst))
+ else if (auto getValueFromBoundInterface = as<IRGetValueFromBoundInterface>(inst))
{
- processGetValueFromExistentialBox(getExistentialValue);
+ processGetValueFromBoundInterface(getValueFromBoundInterface);
}
else if (auto extractExistentialVal = as<IRExtractExistentialValue>(inst))
{
diff --git a/source/slang/slang-ir-lower-generic-function.cpp b/source/slang/slang-ir-lower-generic-function.cpp
index 75c58a2f4..11e1c0f04 100644
--- a/source/slang/slang-ir-lower-generic-function.cpp
+++ b/source/slang/slang-ir-lower-generic-function.cpp
@@ -61,7 +61,7 @@ namespace Slang
if (clonedChild->op == kIROp_Param)
{
auto paramType = clonedChild->getFullType();
- auto loweredParamType = sharedContext->lowerType(&builder, paramType, Dictionary<IRInst*, IRInst*>());
+ auto loweredParamType = sharedContext->lowerType(&builder, paramType);
if (loweredParamType != paramType)
{
clonedChild->setFullType((IRType*)loweredParamType);
@@ -91,7 +91,7 @@ namespace Slang
Dictionary<IRInst*, IRInst*> typeMapping;
for (auto genericParam : genericVal->getParams())
{
- genericParamTypes.add(sharedContext->lowerType(builder, genericParam->getFullType(), Dictionary<IRInst*, IRInst*>()));
+ genericParamTypes.add(sharedContext->lowerType(builder, genericParam->getFullType()));
if (auto anyValueSizeDecor = genericParam->findDecoration<IRTypeConstraintDecoration>())
{
auto anyValueSize = sharedContext->getInterfaceAnyValueSize(anyValueSizeDecor->getConstraintType(), genericParam->sourceLoc);
@@ -118,7 +118,7 @@ namespace Slang
for (UInt i = 0; i < funcType->getOperandCount(); i++)
{
auto paramType = funcType->getOperand(i);
- auto loweredParamType = sharedContext->lowerType(builder, paramType, typeMapping);
+ auto loweredParamType = sharedContext->lowerType(builder, paramType, typeMapping, nullptr);
translated = translated || (loweredParamType != paramType);
newOperands.add(loweredParamType);
}
diff --git a/source/slang/slang-ir-lower-generics.cpp b/source/slang/slang-ir-lower-generics.cpp
index 0253c4df8..5f466c70c 100644
--- a/source/slang/slang-ir-lower-generics.cpp
+++ b/source/slang/slang-ir-lower-generics.cpp
@@ -107,9 +107,9 @@ namespace Slang
}
void lowerGenerics(
- TargetRequest* targetReq,
- IRModule* module,
- DiagnosticSink* sink)
+ TargetRequest* targetReq,
+ IRModule* module,
+ DiagnosticSink* sink)
{
SharedGenericsLoweringContext sharedContext;
sharedContext.targetReq = targetReq;
diff --git a/source/slang/slang-ir-lower-generics.h b/source/slang/slang-ir-lower-generics.h
index ea83b35bf..3b29219bd 100644
--- a/source/slang/slang-ir-lower-generics.h
+++ b/source/slang/slang-ir-lower-generics.h
@@ -12,8 +12,8 @@ namespace Slang
/// Lower generic and interface-based code to ordinary types and functions using
/// dynamic dispatch mechanisms.
void lowerGenerics(
- TargetRequest* targetReq,
- IRModule* module,
- DiagnosticSink* sink);
+ TargetRequest* targetReq,
+ IRModule* module,
+ DiagnosticSink* sink);
}
diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp
index efcdb4498..693494ac1 100644
--- a/source/slang/slang-ir-specialize.cpp
+++ b/source/slang/slang-ir-specialize.cpp
@@ -69,6 +69,16 @@ struct SpecializationContext
//
if(!inst) return true;
+ // An interface requirement entry should always be considered
+ // to be fully specialized, even if it hasn't been visited.
+ //
+ // Note: This logic is here to stop a circularity, where we
+ // can't mark an interface as used until its requirements are
+ // used, etc.
+ //
+ if(inst->op == kIROp_InterfaceRequirementEntry)
+ return true;
+
return fullySpecializedInsts.Contains(inst);
}
@@ -618,33 +628,6 @@ struct SpecializationContext
return nullptr;
}
- void maybeInsertGetExistentialValue(IRInst* inst)
- {
- // If inst has `ExistentialBox` type, we need to make sure
- // all uses are through `GetValueFromExistentialBox`.
- if (auto existentialBoxType = as<IRExistentialBoxType>(inst->getDataType()))
- {
- ShortList<IRUse*> usesToReplace;
- for (auto use = inst->firstUse; use; use = use->nextUse)
- {
- if (use->getUser()->op != kIROp_GetValueFromExistentialBox)
- usesToReplace.add(use);
- }
- for (auto use : usesToReplace)
- {
- auto user = use->getUser();
- IRBuilder builderStorage;
- auto builder = &builderStorage;
- builder->sharedBuilder = &sharedBuilderStorage;
- builder->setInsertBefore(user);
- auto getValueInst = builder->emitGetValueFromExistentialBox(
- builder->getPtrType(existentialBoxType->getValueType()), inst);
- use->set(getValueInst);
- addToWorkList(getValueInst);
- }
- }
- }
-
// All of the machinery for generic specialization
// has been defined above, so we will now walk
// through the flow of the overall specialization pass.
@@ -712,10 +695,6 @@ struct SpecializationContext
workListSet.Remove(inst);
cleanInsts.Add(inst);
- // If inst represents a value of ExistentialBox type, all its uses
- // must be through a `GetValueFromExistentialBox` inst.
- maybeInsertGetExistentialValue(inst);
-
// For each instruction we process, we want to perform
// a few steps.
//
@@ -1131,8 +1110,33 @@ struct SpecializationContext
// (which implicitly determines the concrete type), and
// the witness table `w.
//
- if(as<IRMakeExistential>(inst))
+ if( auto makeExistential = as<IRMakeExistential>(inst) )
+ {
+ // We need to be careful about the type that we'd be specializing
+ // to, since it needs to be visible to both the caller and calee.
+ //
+ // In particular, if the type is the result of a function-local
+ // operation like `extractExistentialType`, then we can't possibly
+ // specialize the callee, since it wouldn't be able to access
+ // the same type (since the type is the result of an instruction in
+ // the body of the caller)
+ //
+ auto concreteVal = makeExistential->getWrappedValue();
+ auto concreteType = concreteVal->getDataType();
+
+ // TODO: We probably need/want a more robust test here.
+ // For now we are just listing the single opcode that is
+ // causing problems.
+ //
+ // TODO: eventually this check would become unnecessary because
+ // we can simply check if the `concreteType` is a compile-time
+ // constant value.
+ //
+ if(concreteType->op == kIROp_ExtractExistentialType)
+ return false;
+
return true;
+ }
// A `wrapExistential(v, T0,w0, T1, w1, ...)` instruction
// is just a generalization of `makeExistential`, so it
@@ -1531,7 +1535,11 @@ struct SpecializationContext
UInt calcExistentialBoxSlotCount(IRType* type)
{
top:
- if( as<IRExistentialBoxType>(type) )
+ if( as<IRBoundInterfaceType>(type) )
+ {
+ return 2;
+ }
+ else if( as<IRInterfaceType>(type) )
{
return 2;
}
@@ -1793,14 +1801,16 @@ struct SpecializationContext
auto index = inst->getIndex();
auto val = wrapInst->getWrappedValue();
+ auto ptrType = cast<IRPtrTypeBase>(val->getDataType());
+ auto arrayType = cast<IRArrayTypeBase>(ptrType->getValueType());
+ auto elementType = arrayType->getElementType();
+
auto resultType = inst->getFullType();
IRBuilder builder;
builder.sharedBuilder = &sharedBuilderStorage;
builder.setInsertBefore(inst);
- auto elementType = cast<IRArrayTypeBase>(val->getDataType())->getElementType();
-
List<IRInst*> slotOperands;
UInt slotOperandCount = wrapInst->getSlotOperandCount();
@@ -1809,7 +1819,8 @@ struct SpecializationContext
slotOperands.add(wrapInst->getSlotOperand(ii));
}
- auto newElementAddr = builder.emitElementAddress(elementType, val, index);
+ auto elementPtrType = builder.getPtrType(ptrType->op, elementType);
+ auto newElementAddr = builder.emitElementAddress(elementPtrType, val, index);
auto newWrapExistentialInst = builder.emitWrapExistential(
resultType, newElementAddr, slotOperandCount, slotOperands.getBuffer());
@@ -1870,24 +1881,15 @@ struct SpecializationContext
if( auto baseInterfaceType = as<IRInterfaceType>(baseType) )
{
- // A `BindExistentials<ISomeInterface, ConcreteType, ...>` can
- // just be simplified to `ExistentialBox<ConcreteType>`.
- //
- // Note: We do *not* simplify straight to `ConcreteType`, because
- // that would mess up the layout for aggregate types that
- // contain interfaces. The logical indirection introduced
- // by `ExistentialBox<...>` will be handled by a later type
- // legalization pass that moved the type "pointed to" by
- // the box out of line from other fields.
-
// We always expect two slot operands, one for the concrete type
// and one for the witness table.
//
SLANG_ASSERT(slotOperandCount == 2);
- if(slotOperandCount <= 1) return;
+ if(slotOperandCount < 2) return;
auto concreteType = (IRType*) type->getExistentialArg(0);
- auto newVal = builder.getExistentialBoxType(concreteType, baseInterfaceType);
+ auto witnessTable = type->getExistentialArg(1);
+ auto newVal = builder.getBoundInterfaceType(baseInterfaceType, concreteType, witnessTable);
addUsersToWorkList(type);
type->replaceUsesWith(newVal);
diff --git a/source/slang/slang-ir-witness-table-wrapper.cpp b/source/slang/slang-ir-witness-table-wrapper.cpp
index 219513fc7..555aae846 100644
--- a/source/slang/slang-ir-witness-table-wrapper.cpp
+++ b/source/slang/slang-ir-witness-table-wrapper.cpp
@@ -176,6 +176,22 @@ namespace Slang
auto interfaceType = cast<IRInterfaceType>(witnessTable->getConformanceType());
if (isBuiltin(interfaceType))
return;
+
+ // We need to consider whether the concrete type that is conforming
+ // in this witness table actually fits within the declared any-value
+ // size for the interface.
+ //
+ // If the type doesn't fit then it would be invalid to use for dynamic
+ // dispatch, and the packing/unpacking operations we emit would fail
+ // to generate valid code.
+ //
+ // Such a type might still be useful for static specialization, so
+ // we can't consider this case a hard error.
+ //
+ auto concreteType = witnessTable->getConcreteType();
+ if(!sharedContext->doesTypeFitInAnyValue(concreteType, interfaceType))
+ return;
+
for (auto child : witnessTable->getChildren())
{
auto entry = as<IRWitnessTableEntry>(child);
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index e735edce9..99c601051 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -2468,6 +2468,12 @@ namespace Slang
return getTupleType(3, operands);
}
+ IRTupleType* IRBuilder::getTupleType(IRType* type0, IRType* type1, IRType* type2, IRType* type3)
+ {
+ IRType* operands[] = { type0, type1, type2, type3 };
+ return getTupleType(SLANG_COUNT_OF(operands), operands);
+ }
+
IRBasicBlockType* IRBuilder::getBasicBlockType()
{
return (IRBasicBlockType*)getType(kIROp_BasicBlockType);
@@ -2512,16 +2518,6 @@ namespace Slang
operands);
}
- IRType* IRBuilder::getExistentialBoxType(IRType* concreteType, IRType* interfaceType)
- {
- // Don't wrap an existential box if concreteType is __Dynamic.
- if (as<IRDynamicType>(concreteType))
- return interfaceType;
-
- IRInst* operands[] = {concreteType, interfaceType};
- return getType(kIROp_ExistentialBoxType, 2, operands);
- }
-
IRArrayTypeBase* IRBuilder::getArrayTypeBase(
IROp op,
IRType* elementType,
@@ -2666,13 +2662,14 @@ namespace Slang
//
if(as<IRInterfaceType>(baseType))
{
- if(slotArgCount >= 1)
+ if(slotArgCount >= 2)
{
// We are being asked to emit `BindExistentials(someInterface, someConcreteType, ...)`
// so we just want to return `ExistentialBox<someConcreteType>`.
//
auto concreteType = (IRType*) slotArgs[0];
- auto ptrType = getExistentialBoxType(concreteType, (IRType*)baseType);
+ auto witnessTable = slotArgs[1];
+ auto ptrType = getBoundInterfaceType((IRType*) baseType, concreteType, witnessTable);
return ptrType;
}
}
@@ -2704,6 +2701,25 @@ namespace Slang
slotArgs.getBuffer());
}
+ IRType* IRBuilder::getBoundInterfaceType(
+ IRType* interfaceType,
+ IRType* concreteType,
+ IRInst* witnessTable)
+ {
+ // Don't wrap an existential box if concreteType is __Dynamic.
+ if (as<IRDynamicType>(concreteType))
+ return interfaceType;
+
+ IRInst* operands[] = {interfaceType, concreteType, witnessTable};
+ return getType(kIROp_BoundInterfaceType, SLANG_COUNT_OF(operands), operands);
+ }
+
+ IRType* IRBuilder::getPseudoPtrType(
+ IRType* concreteType)
+ {
+ IRInst* operands[] = {concreteType};
+ return getType(kIROp_PseudoPtrType, SLANG_COUNT_OF(operands), operands);
+ }
void IRBuilder::setDataType(IRInst* inst, IRType* dataType)
@@ -2725,10 +2741,10 @@ namespace Slang
}
}
- IRInst* IRBuilder::emitGetValueFromExistentialBox(IRType* type, IRInst* existentialBox)
+ IRInst* IRBuilder::emitGetValueFromBoundInterface(IRType* type, IRInst* boundInterfaceValue)
{
auto inst =
- createInst<IRInst>(this, kIROp_GetValueFromExistentialBox, type, 1, &existentialBox);
+ createInst<IRInst>(this, kIROp_GetValueFromBoundInterface, type, 1, &boundInterfaceValue);
addInst(inst);
return inst;
}
@@ -2812,6 +2828,14 @@ namespace Slang
IRInst* witnessTableVal,
IRInst* interfaceMethodVal)
{
+ // TODO: if somebody tries to declare a struct that inherits
+ // an interface conformance from a base type, then we hit
+ // this assert. The problem should be fixed higher up in
+ // the emit logic, but this is a reasonably early place
+ // to catch it.
+ //
+ SLANG_ASSERT(witnessTableVal->op != kIROp_StructKey);
+
auto inst = createInst<IRLookupWitnessMethod>(
this,
kIROp_lookup_interface_method,
@@ -3044,14 +3068,16 @@ namespace Slang
// We are being asked to emit `wrapExistential(value, concreteType, witnessTable, ...) : someInterface`
//
// We also know that a concrete value being wrapped will always be an existential box,
- // so we expect that `value : ExistentialBox<T>` for some `T`.
+ // so we expect that `value : BindInterface<I, C>` for some concrete `C`.
//
- // We want to emit `makeExistential(load(value), witnessTable)`.
+ // We want to emit `makeExistential(getValueFromBoundInterface(value) : C, witnessTable)`.
//
- auto deref = emitLoad(value);
+ auto concreteType = cast<IRType>(slotArgs[0]);
+ auto witnessTable = slotArgs[1];
if (slotArgs[0]->op == kIROp_DynamicType)
- return deref;
- return emitMakeExistential(type, deref, slotArgs[1]);
+ return value;
+ auto deref = emitGetValueFromBoundInterface(concreteType, value);
+ return emitMakeExistential(type, deref, witnessTable);
}
}
@@ -3180,12 +3206,13 @@ namespace Slang
return inst;
}
- IRWitnessTable* IRBuilder::createWitnessTable(IRType* baseType)
+ IRWitnessTable* IRBuilder::createWitnessTable(IRType* baseType, IRType* subType)
{
IRWitnessTable* witnessTable = createInst<IRWitnessTable>(
this,
kIROp_WitnessTable,
- getWitnessTableType(baseType));
+ getWitnessTableType(baseType),
+ subType);
addGlobalValue(this, witnessTable);
return witnessTable;
}
@@ -5513,7 +5540,7 @@ namespace Slang
case kIROp_lookup_interface_method:
case kIROp_GetSequentialID:
case kIROp_getAddr:
- case kIROp_GetValueFromExistentialBox:
+ case kIROp_GetValueFromBoundInterface:
case kIROp_Construct:
case kIROp_makeUInt64:
case kIROp_makeVector:
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index eb95cda1f..c41fa9708 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -1111,10 +1111,9 @@ SIMPLE_IR_PARENT_TYPE(OutTypeBase, PtrTypeBase)
SIMPLE_IR_TYPE(OutType, OutTypeBase)
SIMPLE_IR_TYPE(InOutType, OutTypeBase)
-struct IRExistentialBoxType : public IRPtrTypeBase
+struct IRPseudoPtrType : public IRPtrTypeBase
{
- IR_LEAF_ISA(ExistentialBoxType);
- IRType* getInterfaceType() { return (IRType*)getOperand(1); }
+ IR_LEAF_ISA(PseudoPtrType);
};
/// The base class of RawPointerType and RTTIPointerType.
@@ -1303,9 +1302,9 @@ struct IRWitnessTableIDType : IRWitnessTableTypeBase
IR_LEAF_ISA(WitnessTableIDType);
};
-struct IRBindExistentialsType : IRType
+struct IRBindExistentialsTypeBase : IRType
{
- IR_LEAF_ISA(BindExistentialsType)
+ IR_PARENT_ISA(BindExistentialsTypeBase)
IRType* getBaseType() { return (IRType*) getOperand(0); }
UInt getExistentialArgCount() { return getOperandCount() - 1; }
@@ -1313,6 +1312,22 @@ struct IRBindExistentialsType : IRType
IRInst* getExistentialArg(UInt index) { return getExistentialArgs()[index].get(); }
};
+struct IRBindExistentialsType : IRBindExistentialsTypeBase
+{
+ IR_LEAF_ISA(BindExistentialsType)
+
+};
+
+struct IRBoundInterfaceType : IRBindExistentialsTypeBase
+{
+ IR_LEAF_ISA(BoundInterfaceType)
+
+ IRType* getInterfaceType() { return getBaseType(); }
+ IRType* getConcreteType() { return (IRType*) getExistentialArg(0); }
+ IRInst* getWitnessTable() { return getExistentialArg(1); }
+};
+
+
/// @brief A global value that potentially holds executable code.
///
struct IRGlobalValueWithCode : IRInst
diff --git a/source/slang/slang-legalize-types.cpp b/source/slang/slang-legalize-types.cpp
index a6100f45c..3568e4d0e 100644
--- a/source/slang/slang-legalize-types.cpp
+++ b/source/slang/slang-legalize-types.cpp
@@ -1172,19 +1172,34 @@ LegalType legalizeTypeImpl(
{
return LegalType::simple(type);
}
- else if( auto existentialPtrType = as<IRExistentialBoxType>(type))
+ else if( auto pseudoPtrType = as<IRPseudoPtrType>(type))
{
- // We want to transform an `ExistentialBox<T>` into just
- // a `T`, with an `implicitDeref` to make sure that any
- // pointer-related operations on the box Just Work.
+ // The type `PseudoPtr<T>` represents a type that conceptually
+ // behaves like a pointer to a `T`, but on a target platform
+ // that can't actually handle such a type.
//
- // Note: the logic here doesn't have to deal with moving
- // existential-type fields to the end of their outer
- // type(s) because that is mostly dealt with in the
- // case for struct types below.
+ // This type will be legalized by storing the `T` value somwhere
+ // else (so that it doesn't impact the layout of the parent
+ // `struct` type or other context it is placed in), without
+ // an actual indirection on that `T` value.
//
- auto legalValueType = legalizeType(context, existentialPtrType->getValueType());
- return LegalType::implicitDeref(legalValueType);
+ // (Note that the logic for moving pseudo-pointer fields to
+ // the end of their outer type(s) is not dealt with here because
+ // it is mostly handled in the case for `struct` types below).
+ //
+ auto legalConcreteType = legalizeType(context, pseudoPtrType->getValueType());
+
+ // TODO: If/when we change our generation of pseudo-pointers
+ // so that use-site code emits a "pseudo-load" then we may
+ // need to change the logic here so that we return
+ // `LegalType::implicitDeref(legalConcreteType)` so as
+ // to respect the nominal levels of indirection.
+ //
+ // For now we are just using the value directly at use sites
+ // so that a pseduo-pointer isn't very pointer-like, and
+ // that makes the legalization here quite simple.
+ //
+ return legalConcreteType;
}
else if (auto ptrType = as<IRPtrTypeBase>(type))
{
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index ad7cec773..fefec805c 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -1214,8 +1214,9 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
UNREACHABLE_RETURN(LoweredValInfo());
}
+ auto subType = lowerType(context, val->sub);
auto irWitnessTableBaseType = lowerType(context, supDeclRefType);
- auto irWitnessTable = getBuilder()->createWitnessTable(irWitnessTableBaseType);
+ auto irWitnessTable = getBuilder()->createWitnessTable(irWitnessTableBaseType, subType);
// Now we will iterate over the requirements (members) of the
// interface and try to synthesize an appropriate value for each.
@@ -5223,7 +5224,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
{
// Need to construct a sub-witness-table
auto irWitnessTableBaseType = lowerType(subContext, astReqWitnessTable->baseType);
- irSatisfyingWitnessTable = subBuilder->createWitnessTable(irWitnessTableBaseType);
+ irSatisfyingWitnessTable = subBuilder->createWitnessTable(irWitnessTableBaseType, irWitnessTable->getConcreteType());
auto mangledName = getMangledNameForConformanceWitness(
subContext->astBuilder,
astReqWitnessTable->witnessedType,
@@ -5349,7 +5350,14 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
auto irWitnessTableBaseType = lowerType(subContext, superType);
// Create the IR-level witness table
- auto irWitnessTable = subBuilder->createWitnessTable(irWitnessTableBaseType);
+ auto irWitnessTable = subBuilder->createWitnessTable(irWitnessTableBaseType, nullptr);
+
+ // Register the value now, rather than later, to avoid any possible infinite recursion.
+ setGlobalValue(context, inheritanceDecl, LoweredValInfo::simple(irWitnessTable));
+
+ auto irSubType = lowerType(subContext, subType);
+ irWitnessTable->setOperand(0, irSubType);
+
addLinkageDecoration(context, irWitnessTable, inheritanceDecl, mangledName.getUnownedSlice());
if (parentDecl->findModifier<PublicModifier>())
{
@@ -5357,8 +5365,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
subBuilder->addKeepAliveDecoration(irWitnessTable);
}
- // Register the value now, rather than later, to avoid any possible infinite recursion.
- setGlobalValue(context, inheritanceDecl, LoweredValInfo::simple(irWitnessTable));
// Make sure that all the entries in the witness table have been filled in,
// including any cases where there are sub-witness-tables for conformances
@@ -5973,16 +5979,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo::simple(assocType);
}
- Dictionary<IRType*, IRWitnessTable*> placeholderWitnessTables;
- IRWitnessTable* getPlaceholderWitnessTable(IRType* type)
- {
- if (auto rs = placeholderWitnessTables.TryGetValue(type))
- return *rs;
- auto w = getBuilder()->createWitnessTable(type);
- placeholderWitnessTables[type] = w;
- return w;
- }
-
LoweredValInfo visitInterfaceDecl(InterfaceDecl* decl)
{
// The members of an interface will turn into the keys that will
@@ -6038,14 +6034,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
for (auto assocTypeDecl : decl->getMembersOfType<AssocTypeDecl>())
{
ensureDecl(subContext, assocTypeDecl);
- // The type constraints on an associated type lowers to a dummy
- // witness table, since only the type of the witness table matters.
- for (auto constraintDecl : assocTypeDecl->getMembersOfType<TypeConstraintDecl>())
- {
- auto constraintInterfaceType = lowerType(context, constraintDecl->getSup().type);
- auto placeholderWitnessTable = getPlaceholderWitnessTable(constraintInterfaceType);
- setValue(context, constraintDecl, LoweredValInfo::simple(placeholderWitnessTable));
- }
}
UInt entryIndex = 0;
@@ -6089,10 +6077,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
lowerType(context, constraintDecl->getSup().type);
auto witnessTableType =
getBuilder()->getWitnessTableType(constraintInterfaceType);
- irInterface->setOperand(entryIndex,
- subBuilder->createInterfaceRequirementEntry(constraintKey,
- witnessTableType));
+
+ auto constraintEntry = subBuilder->createInterfaceRequirementEntry(constraintKey,
+ witnessTableType);
+ irInterface->setOperand(entryIndex, constraintEntry);
entryIndex++;
+
+ setValue(context, constraintDecl, LoweredValInfo::simple(constraintEntry));
}
}
else
@@ -8125,6 +8116,11 @@ IRTypeLayout* lowerTypeLayout(
IRMatrixTypeLayout::Builder builder(context->irBuilder, matrixTypeLayout->mode);
return _lowerTypeLayoutCommon(context, &builder, matrixTypeLayout);
}
+ else if( auto existentialTypeLayout = as<ExistentialTypeLayout>(typeLayout) )
+ {
+ IRExistentialTypeLayout::Builder builder(context->irBuilder);
+ return _lowerTypeLayoutCommon(context, &builder, existentialTypeLayout);
+ }
else
{
// If no special case applies we will build a generic `IRTypeLayout`.
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.
diff --git a/source/slang/slang-type-layout.h b/source/slang/slang-type-layout.h
index fc24f46bc..1849b5736 100644
--- a/source/slang/slang-type-layout.h
+++ b/source/slang/slang-type-layout.h
@@ -636,7 +636,35 @@ public:
LayoutSize tagOffset;
};
+ /// Layout information for an interface/existential type
+ ///
+ /// This class is used to represent the layout of an interface type
+ /// such as `IThing`, including both the information about the
+ /// space consumed by the existential itself (including, e.g., the
+ /// RTTI and witness-table infromation), along with any
+ /// "pending" data related to legacy static specialization.
+ ///
+class ExistentialTypeLayout : public TypeLayout
+{
+public:
+};
+
/// Layout information for a type with existential (sub-)field types specialized.
+ ///
+ /// This class is used to represent a type like `SomeStruct` that
+ /// contains nested existential-type fields, and which has been explicitly
+ /// specialized with concrete type information for those fields.
+ ///
+ /// Note that the front-end language should not ever produce types that
+ /// make use of layouts like this. The only way such a type layout can
+ /// arise is if a user explicitly specialized a type with existential-type
+ /// fields using the Slang API, and then used that specialized type as
+ /// a type argument for a shader. This type layout class serves to remember
+ /// the specialization information (and resulting layout) for the type.
+ ///
+ /// TODO: Given our changes to how layout for existential types is being
+ /// handled, the details of this type probably need to be re-thought.
+ ///
class ExistentialSpecializedTypeLayout : public TypeLayout
{
public: