https://github.com/shader-slang/slang
Tip revision: e59516fa8c3a16eb7b99a928c5b85b97bf44fd72 authored by Yong He on 01 February 2022, 00:26:03 UTC
Revise entrypoint renaming interface. (#2113)
Revise entrypoint renaming interface. (#2113)
Tip revision: e59516f
slang-ir-generics-lowering-context.cpp
//slang-ir-generics-lowering-context.cpp
#include "slang-ir-generics-lowering-context.h"
#include "slang-ir-layout.h"
namespace Slang
{
bool isPolymorphicType(IRInst* typeInst)
{
if (as<IRParam>(typeInst) && as<IRTypeType>(typeInst->getDataType()))
return true;
switch (typeInst->getOp())
{
case kIROp_ThisType:
case kIROp_AssociatedType:
case kIROp_InterfaceType:
case kIROp_lookup_interface_method:
return true;
case kIROp_Specialize:
{
for (UInt i = 0; i < typeInst->getOperandCount(); i++)
{
if (isPolymorphicType(typeInst->getOperand(i)))
return true;
}
return false;
}
default:
break;
}
if (auto ptrType = as<IRPtrTypeBase>(typeInst))
{
return isPolymorphicType(ptrType->getValueType());
}
return false;
}
bool isTypeValue(IRInst* typeInst)
{
if (typeInst)
{
switch (typeInst->getOp())
{
case kIROp_TypeType:
case kIROp_TypeKind:
return true;
default:
return false;
}
}
return false;
}
IRInst* SharedGenericsLoweringContext::maybeEmitRTTIObject(IRInst* typeInst)
{
IRInst* result = nullptr;
if (mapTypeToRTTIObject.TryGetValue(typeInst, result))
return result;
IRBuilder builderStorage(sharedBuilderStorage);
auto builder = &builderStorage;
builder->setInsertBefore(typeInst->next);
result = builder->emitMakeRTTIObject(typeInst);
// For now the only type info we encapsualte is type size.
IRSizeAndAlignment sizeAndAlignment;
getNaturalSizeAndAlignment(targetReq, (IRType*)typeInst, &sizeAndAlignment);
builder->addRTTITypeSizeDecoration(result, sizeAndAlignment.size);
// Give a name to the rtti object.
if (auto exportDecoration = typeInst->findDecoration<IRExportDecoration>())
{
String rttiObjName = exportDecoration->getMangledName();
builder->addExportDecoration(result, rttiObjName.getUnownedSlice());
}
// Make sure the RTTI object for a public struct type has public visiblity.
if (typeInst->findDecoration<IRPublicDecoration>())
{
builder->addPublicDecoration(result);
builder->addKeepAliveDecoration(result);
}
mapTypeToRTTIObject[typeInst] = result;
return result;
}
IRInst* SharedGenericsLoweringContext::findInterfaceRequirementVal(IRInterfaceType* interfaceType, IRInst* requirementKey)
{
if (auto dict = mapInterfaceRequirementKeyValue.TryGetValue(interfaceType))
return (*dict)[requirementKey].GetValue();
_builldInterfaceRequirementMap(interfaceType);
return findInterfaceRequirementVal(interfaceType, requirementKey);
}
void SharedGenericsLoweringContext::_builldInterfaceRequirementMap(IRInterfaceType* interfaceType)
{
mapInterfaceRequirementKeyValue.Add(interfaceType,
Dictionary<IRInst*, IRInst*>());
auto dict = mapInterfaceRequirementKeyValue.TryGetValue(interfaceType);
for (UInt i = 0; i < interfaceType->getOperandCount(); i++)
{
auto entry = cast<IRInterfaceRequirementEntry>(interfaceType->getOperand(i));
(*dict)[entry->getRequirementKey()] = entry->getRequirementVal();
}
}
IRType* SharedGenericsLoweringContext::lowerAssociatedType(IRBuilder* builder, IRInst* type)
{
if (type->getOp() != kIROp_AssociatedType)
return (IRType*)type;
IRIntegerValue anyValueSize = kInvalidAnyValueSize;
for (UInt i = 0; i < type->getOperandCount(); i++)
{
anyValueSize = Math::Min(
anyValueSize,
getInterfaceAnyValueSize(type->getOperand(i), type->sourceLoc));
}
if (anyValueSize == kInvalidAnyValueSize)
{
// 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* concreteType)
{
if (!paramType)
return nullptr;
IRInst* resultType;
if (typeMapping.TryGetValue(paramType, resultType))
return (IRType*)resultType;
if (isTypeValue(paramType))
{
return builder->getRTTIHandleType();
}
switch (paramType->getOp())
{
case kIROp_WitnessTableType:
case kIROp_WitnessTableIDType:
case kIROp_ExtractExistentialType:
// Do not translate these types.
return (IRType*)paramType;
case kIROp_Param:
{
if (auto anyValueSizeDecor = paramType->findDecoration<IRTypeConstraintDecoration>())
{
if (isBuiltin(anyValueSizeDecor->getConstraintType()))
return (IRType*)paramType;
auto anyValueSize = getInterfaceAnyValueSize(anyValueSizeDecor->getConstraintType(), paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
}
// 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;
auto anyValueSize = getInterfaceAnyValueSize(
cast<IRThisType>(paramType)->getConstraintType(),
paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
}
case kIROp_AssociatedType:
{
return lowerAssociatedType(builder, paramType);
}
case kIROp_InterfaceType:
{
if (isBuiltin(paramType))
return (IRType*)paramType;
// 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();
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:
{
auto lookupInterface = static_cast<IRLookupWitnessMethod*>(paramType);
auto interfaceType = cast<IRInterfaceType>(cast<IRWitnessTableType>(
lookupInterface->getWitnessTable()->getDataType())->getConformanceType());
if (isBuiltin(interfaceType))
return (IRType*)paramType;
// Make sure we are looking up inside the original interface type (prior to lowering).
// Only in the original interface type will an associated type entry have an IRAssociatedType value.
// We need to extract AnyValueSize from this IRAssociatedType.
// In lowered interface type, that entry is lowered into an Ptr(RTTIType) and this info is lost.
mapLoweredInterfaceToOriginal.TryGetValue(interfaceType, interfaceType);
auto reqVal = findInterfaceRequirementVal(
interfaceType,
lookupInterface->getRequirementKey());
SLANG_ASSERT(reqVal && reqVal->getOp() == kIROp_AssociatedType);
return lowerType(builder, reqVal, typeMapping, nullptr);
}
case kIROp_BoundInterfaceType:
{
// 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:
{
bool translated = false;
List<IRInst*> loweredOperands;
for (UInt i = 0; i < paramType->getOperandCount(); i++)
{
loweredOperands.add(lowerType(builder, paramType->getOperand(i), typeMapping, nullptr));
if (loweredOperands.getLast() != paramType->getOperand(i))
translated = true;
}
if (translated)
return builder->getType(paramType->getOp(), loweredOperands.getCount(), loweredOperands.getBuffer());
return (IRType*)paramType;
}
}
}
List<IRWitnessTable*> SharedGenericsLoweringContext::getWitnessTablesFromInterfaceType(IRInst* interfaceType)
{
List<IRWitnessTable*> witnessTables;
for (auto globalInst : module->getGlobalInsts())
{
if (globalInst->getOp() == kIROp_WitnessTable &&
cast<IRWitnessTableType>(globalInst->getDataType())->getConformanceType() ==
interfaceType)
{
witnessTables.add(cast<IRWitnessTable>(globalInst));
}
}
return witnessTables;
}
IRIntegerValue SharedGenericsLoweringContext::getInterfaceAnyValueSize(IRInst* type, SourceLoc usageLocation)
{
SLANG_UNUSED(usageLocation);
if (auto decor = type->findDecoration<IRAnyValueSizeDecoration>())
{
return decor->getSize();
}
// 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;
}
}