summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
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: