diff options
Diffstat (limited to 'source')
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: |
