//===- DXILResource.cpp - Representations of DXIL resources ---------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/Analysis/DXILResource.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IntrinsicsDirectX.h" #include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" #include "llvm/InitializePasses.h" #include "llvm/Support/FormatVariadic.h" #include #include #define DEBUG_TYPE "dxil-resource" using namespace llvm; using namespace dxil; static StringRef getResourceClassName(ResourceClass RC) { switch (RC) { case ResourceClass::SRV: return "SRV"; case ResourceClass::UAV: return "UAV"; case ResourceClass::CBuffer: return "CBuffer"; case ResourceClass::Sampler: return "Sampler"; } llvm_unreachable("Unhandled ResourceClass"); } static StringRef getResourceKindName(ResourceKind RK) { switch (RK) { case ResourceKind::Texture1D: return "Texture1D"; case ResourceKind::Texture2D: return "Texture2D"; case ResourceKind::Texture2DMS: return "Texture2DMS"; case ResourceKind::Texture3D: return "Texture3D"; case ResourceKind::TextureCube: return "TextureCube"; case ResourceKind::Texture1DArray: return "Texture1DArray"; case ResourceKind::Texture2DArray: return "Texture2DArray"; case ResourceKind::Texture2DMSArray: return "Texture2DMSArray"; case ResourceKind::TextureCubeArray: return "TextureCubeArray"; case ResourceKind::TypedBuffer: return "Buffer"; case ResourceKind::RawBuffer: return "RawBuffer"; case ResourceKind::StructuredBuffer: return "StructuredBuffer"; case ResourceKind::CBuffer: return "CBuffer"; case ResourceKind::Sampler: return "Sampler"; case ResourceKind::TBuffer: return "TBuffer"; case ResourceKind::RTAccelerationStructure: return "RTAccelerationStructure"; case ResourceKind::FeedbackTexture2D: return "FeedbackTexture2D"; case ResourceKind::FeedbackTexture2DArray: return "FeedbackTexture2DArray"; case ResourceKind::NumEntries: case ResourceKind::Invalid: return ""; } llvm_unreachable("Unhandled ResourceKind"); } static StringRef getElementTypeName(ElementType ET) { switch (ET) { case ElementType::I1: return "i1"; case ElementType::I16: return "i16"; case ElementType::U16: return "u16"; case ElementType::I32: return "i32"; case ElementType::U32: return "u32"; case ElementType::I64: return "i64"; case ElementType::U64: return "u64"; case ElementType::F16: return "f16"; case ElementType::F32: return "f32"; case ElementType::F64: return "f64"; case ElementType::SNormF16: return "snorm_f16"; case ElementType::UNormF16: return "unorm_f16"; case ElementType::SNormF32: return "snorm_f32"; case ElementType::UNormF32: return "unorm_f32"; case ElementType::SNormF64: return "snorm_f64"; case ElementType::UNormF64: return "unorm_f64"; case ElementType::PackedS8x32: return "p32i8"; case ElementType::PackedU8x32: return "p32u8"; case ElementType::Invalid: return ""; } llvm_unreachable("Unhandled ElementType"); } static StringRef getElementTypeNameForTemplate(ElementType ET) { switch (ET) { case ElementType::I1: return "bool"; case ElementType::I16: return "int16_t"; case ElementType::U16: return "uint16_t"; case ElementType::I32: return "int32_t"; case ElementType::U32: return "uint32_t"; case ElementType::I64: return "int64_t"; case ElementType::U64: return "uint32_t"; case ElementType::F16: case ElementType::SNormF16: case ElementType::UNormF16: return "half"; case ElementType::F32: case ElementType::SNormF32: case ElementType::UNormF32: return "float"; case ElementType::F64: case ElementType::SNormF64: case ElementType::UNormF64: return "double"; case ElementType::PackedS8x32: return "int8_t4_packed"; case ElementType::PackedU8x32: return "uint8_t4_packed"; case ElementType::Invalid: return ""; } llvm_unreachable("Unhandled ElementType"); } static StringRef getSamplerTypeName(SamplerType ST) { switch (ST) { case SamplerType::Default: return "Default"; case SamplerType::Comparison: return "Comparison"; case SamplerType::Mono: return "Mono"; } llvm_unreachable("Unhandled SamplerType"); } static StringRef getSamplerFeedbackTypeName(SamplerFeedbackType SFT) { switch (SFT) { case SamplerFeedbackType::MinMip: return "MinMip"; case SamplerFeedbackType::MipRegionUsed: return "MipRegionUsed"; } llvm_unreachable("Unhandled SamplerFeedbackType"); } static dxil::ElementType toDXILElementType(Type *Ty, bool IsSigned) { // TODO: Handle unorm, snorm, and packed. Ty = Ty->getScalarType(); if (Ty->isIntegerTy()) { switch (Ty->getIntegerBitWidth()) { case 16: return IsSigned ? ElementType::I16 : ElementType::U16; case 32: return IsSigned ? ElementType::I32 : ElementType::U32; case 64: return IsSigned ? ElementType::I64 : ElementType::U64; case 1: default: return ElementType::Invalid; } } else if (Ty->isFloatTy()) { return ElementType::F32; } else if (Ty->isDoubleTy()) { return ElementType::F64; } else if (Ty->isHalfTy()) { return ElementType::F16; } return ElementType::Invalid; } ResourceTypeInfo::ResourceTypeInfo(TargetExtType *HandleTy, const dxil::ResourceClass RC_, const dxil::ResourceKind Kind_) : HandleTy(HandleTy) { // If we're provided a resource class and kind, trust them. if (Kind_ != dxil::ResourceKind::Invalid) { RC = RC_; Kind = Kind_; return; } if (auto *Ty = dyn_cast(HandleTy)) { RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV; Kind = Ty->isStructured() ? ResourceKind::StructuredBuffer : ResourceKind::RawBuffer; } else if (auto *Ty = dyn_cast(HandleTy)) { RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV; Kind = ResourceKind::TypedBuffer; } else if (auto *Ty = dyn_cast(HandleTy)) { RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV; Kind = Ty->getDimension(); } else if (auto *Ty = dyn_cast(HandleTy)) { RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV; Kind = Ty->getDimension(); } else if (auto *Ty = dyn_cast(HandleTy)) { RC = ResourceClass::UAV; Kind = Ty->getDimension(); } else if (isa(HandleTy)) { RC = ResourceClass::CBuffer; Kind = ResourceKind::CBuffer; } else if (isa(HandleTy)) { RC = ResourceClass::Sampler; Kind = ResourceKind::Sampler; } else llvm_unreachable("Unknown handle type"); } static void formatTypeName(SmallString<64> &Dest, StringRef Name, bool IsWriteable, bool IsROV, Type *ContainedType = nullptr, bool IsSigned = true) { raw_svector_ostream DestStream(Dest); if (IsWriteable) DestStream << (IsROV ? "RasterizerOrdered" : "RW"); DestStream << Name; if (!ContainedType) return; StringRef ElementName; ElementType ET = toDXILElementType(ContainedType, IsSigned); if (ET != ElementType::Invalid) { ElementName = getElementTypeNameForTemplate(ET); } else { assert(isa(ContainedType) && "invalid element type for raw buffer"); StructType *ST = cast(ContainedType); if (!ST->hasName()) return; ElementName = ST->getStructName(); } DestStream << "<" << ElementName; if (const FixedVectorType *VTy = dyn_cast(ContainedType)) DestStream << VTy->getNumElements(); DestStream << ">"; } static StructType *getOrCreateElementStruct(Type *ElemType, StringRef Name) { StructType *Ty = StructType::getTypeByName(ElemType->getContext(), Name); if (Ty && Ty->getNumElements() == 1 && Ty->getElementType(0) == ElemType) return Ty; return StructType::create(ElemType, Name); } StructType *ResourceTypeInfo::createElementStruct(StringRef CBufferName) { SmallString<64> TypeName; switch (Kind) { case ResourceKind::Texture1D: case ResourceKind::Texture2D: case ResourceKind::Texture3D: case ResourceKind::TextureCube: case ResourceKind::Texture1DArray: case ResourceKind::Texture2DArray: case ResourceKind::TextureCubeArray: { auto *RTy = cast(HandleTy); formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(), RTy->isROV(), RTy->getResourceType(), RTy->isSigned()); return getOrCreateElementStruct(RTy->getResourceType(), TypeName); } case ResourceKind::Texture2DMS: case ResourceKind::Texture2DMSArray: { auto *RTy = cast(HandleTy); formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(), /*IsROV=*/false, RTy->getResourceType(), RTy->isSigned()); return getOrCreateElementStruct(RTy->getResourceType(), TypeName); } case ResourceKind::TypedBuffer: { auto *RTy = cast(HandleTy); formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(), RTy->isROV(), RTy->getResourceType(), RTy->isSigned()); return getOrCreateElementStruct(RTy->getResourceType(), TypeName); } case ResourceKind::RawBuffer: { auto *RTy = cast(HandleTy); formatTypeName(TypeName, "ByteAddressBuffer", RTy->isWriteable(), RTy->isROV()); return getOrCreateElementStruct(Type::getInt32Ty(HandleTy->getContext()), TypeName); } case ResourceKind::StructuredBuffer: { auto *RTy = cast(HandleTy); Type *Ty = RTy->getResourceType(); formatTypeName(TypeName, "StructuredBuffer", RTy->isWriteable(), RTy->isROV(), RTy->getResourceType(), true); return getOrCreateElementStruct(Ty, TypeName); } case ResourceKind::FeedbackTexture2D: case ResourceKind::FeedbackTexture2DArray: { auto *RTy = cast(HandleTy); TypeName = formatv("{0}<{1}>", getResourceKindName(Kind), llvm::to_underlying(RTy->getFeedbackType())); return getOrCreateElementStruct(Type::getInt32Ty(HandleTy->getContext()), TypeName); } case ResourceKind::CBuffer: { auto *RTy = cast(HandleTy); LayoutExtType *LayoutType = cast(RTy->getResourceType()); StructType *Ty = cast(LayoutType->getWrappedType()); SmallString<64> Name = getResourceKindName(Kind); if (!CBufferName.empty()) { Name.append("."); Name.append(CBufferName); } return StructType::create(Ty->elements(), Name); } case ResourceKind::Sampler: { auto *RTy = cast(HandleTy); TypeName = formatv("SamplerState<{0}>", llvm::to_underlying(RTy->getSamplerType())); return getOrCreateElementStruct(Type::getInt32Ty(HandleTy->getContext()), TypeName); } case ResourceKind::TBuffer: case ResourceKind::RTAccelerationStructure: llvm_unreachable("Unhandled resource kind"); case ResourceKind::Invalid: case ResourceKind::NumEntries: llvm_unreachable("Invalid resource kind"); } llvm_unreachable("Unhandled ResourceKind enum"); } bool ResourceTypeInfo::isUAV() const { return RC == ResourceClass::UAV; } bool ResourceTypeInfo::isCBuffer() const { return RC == ResourceClass::CBuffer; } bool ResourceTypeInfo::isSampler() const { return RC == ResourceClass::Sampler; } bool ResourceTypeInfo::isStruct() const { return Kind == ResourceKind::StructuredBuffer; } bool ResourceTypeInfo::isTyped() const { switch (Kind) { case ResourceKind::Texture1D: case ResourceKind::Texture2D: case ResourceKind::Texture2DMS: case ResourceKind::Texture3D: case ResourceKind::TextureCube: case ResourceKind::Texture1DArray: case ResourceKind::Texture2DArray: case ResourceKind::Texture2DMSArray: case ResourceKind::TextureCubeArray: case ResourceKind::TypedBuffer: return true; case ResourceKind::RawBuffer: case ResourceKind::StructuredBuffer: case ResourceKind::FeedbackTexture2D: case ResourceKind::FeedbackTexture2DArray: case ResourceKind::CBuffer: case ResourceKind::Sampler: case ResourceKind::TBuffer: case ResourceKind::RTAccelerationStructure: return false; case ResourceKind::Invalid: case ResourceKind::NumEntries: llvm_unreachable("Invalid resource kind"); } llvm_unreachable("Unhandled ResourceKind enum"); } bool ResourceTypeInfo::isFeedback() const { return Kind == ResourceKind::FeedbackTexture2D || Kind == ResourceKind::FeedbackTexture2DArray; } bool ResourceTypeInfo::isMultiSample() const { return Kind == ResourceKind::Texture2DMS || Kind == ResourceKind::Texture2DMSArray; } static bool isROV(dxil::ResourceKind Kind, TargetExtType *Ty) { switch (Kind) { case ResourceKind::Texture1D: case ResourceKind::Texture2D: case ResourceKind::Texture3D: case ResourceKind::TextureCube: case ResourceKind::Texture1DArray: case ResourceKind::Texture2DArray: case ResourceKind::TextureCubeArray: return cast(Ty)->isROV(); case ResourceKind::TypedBuffer: return cast(Ty)->isROV(); case ResourceKind::RawBuffer: case ResourceKind::StructuredBuffer: return cast(Ty)->isROV(); case ResourceKind::Texture2DMS: case ResourceKind::Texture2DMSArray: case ResourceKind::FeedbackTexture2D: case ResourceKind::FeedbackTexture2DArray: return false; case ResourceKind::CBuffer: case ResourceKind::Sampler: case ResourceKind::TBuffer: case ResourceKind::RTAccelerationStructure: case ResourceKind::Invalid: case ResourceKind::NumEntries: llvm_unreachable("Resource cannot be ROV"); } llvm_unreachable("Unhandled ResourceKind enum"); } ResourceTypeInfo::UAVInfo ResourceTypeInfo::getUAV() const { assert(isUAV() && "Not a UAV"); return {isROV(Kind, HandleTy)}; } uint32_t ResourceTypeInfo::getCBufferSize(const DataLayout &DL) const { assert(isCBuffer() && "Not a CBuffer"); Type *ElTy = cast(HandleTy)->getResourceType(); if (auto *LayoutTy = dyn_cast(ElTy)) return LayoutTy->getSize(); // TODO: What should we do with unannotated arrays? return DL.getTypeAllocSize(ElTy); } dxil::SamplerType ResourceTypeInfo::getSamplerType() const { assert(isSampler() && "Not a Sampler"); return cast(HandleTy)->getSamplerType(); } ResourceTypeInfo::StructInfo ResourceTypeInfo::getStruct(const DataLayout &DL) const { assert(isStruct() && "Not a Struct"); Type *ElTy = cast(HandleTy)->getResourceType(); uint32_t Stride = DL.getTypeAllocSize(ElTy); MaybeAlign Alignment; if (auto *STy = dyn_cast(ElTy)) Alignment = DL.getStructLayout(STy)->getAlignment(); uint32_t AlignLog2 = Alignment ? Log2(*Alignment) : 0; return {Stride, AlignLog2}; } static std::pair getTypedElementType(dxil::ResourceKind Kind, TargetExtType *Ty) { switch (Kind) { case ResourceKind::Texture1D: case ResourceKind::Texture2D: case ResourceKind::Texture3D: case ResourceKind::TextureCube: case ResourceKind::Texture1DArray: case ResourceKind::Texture2DArray: case ResourceKind::TextureCubeArray: { auto *RTy = cast(Ty); return {RTy->getResourceType(), RTy->isSigned()}; } case ResourceKind::Texture2DMS: case ResourceKind::Texture2DMSArray: { auto *RTy = cast(Ty); return {RTy->getResourceType(), RTy->isSigned()}; } case ResourceKind::TypedBuffer: { auto *RTy = cast(Ty); return {RTy->getResourceType(), RTy->isSigned()}; } case ResourceKind::RawBuffer: case ResourceKind::StructuredBuffer: case ResourceKind::FeedbackTexture2D: case ResourceKind::FeedbackTexture2DArray: case ResourceKind::CBuffer: case ResourceKind::Sampler: case ResourceKind::TBuffer: case ResourceKind::RTAccelerationStructure: case ResourceKind::Invalid: case ResourceKind::NumEntries: llvm_unreachable("Resource is not typed"); } llvm_unreachable("Unhandled ResourceKind enum"); } ResourceTypeInfo::TypedInfo ResourceTypeInfo::getTyped() const { assert(isTyped() && "Not typed"); auto [ElTy, IsSigned] = getTypedElementType(Kind, HandleTy); dxil::ElementType ET = toDXILElementType(ElTy, IsSigned); uint32_t Count = 1; if (auto *VTy = dyn_cast(ElTy)) Count = VTy->getNumElements(); return {ET, Count}; } dxil::SamplerFeedbackType ResourceTypeInfo::getFeedbackType() const { assert(isFeedback() && "Not Feedback"); return cast(HandleTy)->getFeedbackType(); } uint32_t ResourceTypeInfo::getMultiSampleCount() const { assert(isMultiSample() && "Not MultiSampled"); return cast(HandleTy)->getSampleCount(); } bool ResourceTypeInfo::operator==(const ResourceTypeInfo &RHS) const { return HandleTy == RHS.HandleTy; } bool ResourceTypeInfo::operator<(const ResourceTypeInfo &RHS) const { // An empty datalayout is sufficient for sorting purposes. DataLayout DummyDL; if (std::tie(RC, Kind) < std::tie(RHS.RC, RHS.Kind)) return true; if (isCBuffer() && RHS.isCBuffer() && getCBufferSize(DummyDL) < RHS.getCBufferSize(DummyDL)) return true; if (isSampler() && RHS.isSampler() && getSamplerType() < RHS.getSamplerType()) return true; if (isUAV() && RHS.isUAV() && getUAV() < RHS.getUAV()) return true; if (isStruct() && RHS.isStruct() && getStruct(DummyDL) < RHS.getStruct(DummyDL)) return true; if (isFeedback() && RHS.isFeedback() && getFeedbackType() < RHS.getFeedbackType()) return true; if (isTyped() && RHS.isTyped() && getTyped() < RHS.getTyped()) return true; if (isMultiSample() && RHS.isMultiSample() && getMultiSampleCount() < RHS.getMultiSampleCount()) return true; return false; } void ResourceTypeInfo::print(raw_ostream &OS, const DataLayout &DL) const { OS << " Class: " << getResourceClassName(RC) << "\n" << " Kind: " << getResourceKindName(Kind) << "\n"; if (isCBuffer()) { OS << " CBuffer size: " << getCBufferSize(DL) << "\n"; } else if (isSampler()) { OS << " Sampler Type: " << getSamplerTypeName(getSamplerType()) << "\n"; } else { if (isUAV()) { UAVInfo UAVFlags = getUAV(); OS << " IsROV: " << UAVFlags.IsROV << "\n"; } if (isMultiSample()) OS << " Sample Count: " << getMultiSampleCount() << "\n"; if (isStruct()) { StructInfo Struct = getStruct(DL); OS << " Buffer Stride: " << Struct.Stride << "\n"; OS << " Alignment: " << Struct.AlignLog2 << "\n"; } else if (isTyped()) { TypedInfo Typed = getTyped(); OS << " Element Type: " << getElementTypeName(Typed.ElementTy) << "\n" << " Element Count: " << Typed.ElementCount << "\n"; } else if (isFeedback()) OS << " Feedback Type: " << getSamplerFeedbackTypeName(getFeedbackType()) << "\n"; } } GlobalVariable *ResourceInfo::createSymbol(Module &M, StructType *Ty) { assert(!Symbol && "Symbol has already been created"); Symbol = new GlobalVariable(M, Ty, /*isConstant=*/true, GlobalValue::ExternalLinkage, /*Initializer=*/nullptr, Name); return Symbol; } MDTuple *ResourceInfo::getAsMetadata(Module &M, dxil::ResourceTypeInfo &RTI) const { LLVMContext &Ctx = M.getContext(); const DataLayout &DL = M.getDataLayout(); SmallVector MDVals; Type *I32Ty = Type::getInt32Ty(Ctx); Type *I1Ty = Type::getInt1Ty(Ctx); auto getIntMD = [&I32Ty](uint32_t V) { return ConstantAsMetadata::get( Constant::getIntegerValue(I32Ty, APInt(32, V))); }; auto getBoolMD = [&I1Ty](uint32_t V) { return ConstantAsMetadata::get( Constant::getIntegerValue(I1Ty, APInt(1, V))); }; MDVals.push_back(getIntMD(Binding.RecordID)); assert(Symbol && "Cannot yet create useful resource metadata without symbol"); MDVals.push_back(ValueAsMetadata::get(Symbol)); MDVals.push_back(MDString::get(Ctx, Name)); MDVals.push_back(getIntMD(Binding.Space)); MDVals.push_back(getIntMD(Binding.LowerBound)); MDVals.push_back(getIntMD(Binding.Size)); if (RTI.isCBuffer()) { MDVals.push_back(getIntMD(RTI.getCBufferSize(DL))); MDVals.push_back(nullptr); } else if (RTI.isSampler()) { MDVals.push_back(getIntMD(llvm::to_underlying(RTI.getSamplerType()))); MDVals.push_back(nullptr); } else { MDVals.push_back(getIntMD(llvm::to_underlying(RTI.getResourceKind()))); if (RTI.isUAV()) { ResourceTypeInfo::UAVInfo UAVFlags = RTI.getUAV(); MDVals.push_back(getBoolMD(GloballyCoherent)); MDVals.push_back(getBoolMD(hasCounter())); MDVals.push_back(getBoolMD(UAVFlags.IsROV)); } else { // All SRVs include sample count in the metadata, but it's only meaningful // for multi-sampled textured. Also, UAVs can be multisampled in SM6.7+, // but this just isn't reflected in the metadata at all. uint32_t SampleCount = RTI.isMultiSample() ? RTI.getMultiSampleCount() : 0; MDVals.push_back(getIntMD(SampleCount)); } // Further properties are attached to a metadata list of tag-value pairs. SmallVector Tags; if (RTI.isStruct()) { Tags.push_back( getIntMD(llvm::to_underlying(ExtPropTags::StructuredBufferStride))); Tags.push_back(getIntMD(RTI.getStruct(DL).Stride)); } else if (RTI.isTyped()) { Tags.push_back(getIntMD(llvm::to_underlying(ExtPropTags::ElementType))); Tags.push_back(getIntMD(llvm::to_underlying(RTI.getTyped().ElementTy))); } else if (RTI.isFeedback()) { Tags.push_back( getIntMD(llvm::to_underlying(ExtPropTags::SamplerFeedbackKind))); Tags.push_back(getIntMD(llvm::to_underlying(RTI.getFeedbackType()))); } MDVals.push_back(Tags.empty() ? nullptr : MDNode::get(Ctx, Tags)); } return MDNode::get(Ctx, MDVals); } std::pair ResourceInfo::getAnnotateProps(Module &M, dxil::ResourceTypeInfo &RTI) const { const DataLayout &DL = M.getDataLayout(); uint32_t ResourceKind = llvm::to_underlying(RTI.getResourceKind()); uint32_t AlignLog2 = RTI.isStruct() ? RTI.getStruct(DL).AlignLog2 : 0; bool IsUAV = RTI.isUAV(); ResourceTypeInfo::UAVInfo UAVFlags = IsUAV ? RTI.getUAV() : ResourceTypeInfo::UAVInfo{}; bool IsROV = IsUAV && UAVFlags.IsROV; bool IsGloballyCoherent = IsUAV && GloballyCoherent; uint8_t SamplerCmpOrHasCounter = 0; if (IsUAV) SamplerCmpOrHasCounter = hasCounter(); else if (RTI.isSampler()) SamplerCmpOrHasCounter = RTI.getSamplerType() == SamplerType::Comparison; // TODO: Document this format. Currently the only reference is the // implementation of dxc's DxilResourceProperties struct. uint32_t Word0 = 0; Word0 |= ResourceKind & 0xFF; Word0 |= (AlignLog2 & 0xF) << 8; Word0 |= (IsUAV & 1) << 12; Word0 |= (IsROV & 1) << 13; Word0 |= (IsGloballyCoherent & 1) << 14; Word0 |= (SamplerCmpOrHasCounter & 1) << 15; uint32_t Word1 = 0; if (RTI.isStruct()) Word1 = RTI.getStruct(DL).Stride; else if (RTI.isCBuffer()) Word1 = RTI.getCBufferSize(DL); else if (RTI.isFeedback()) Word1 = llvm::to_underlying(RTI.getFeedbackType()); else if (RTI.isTyped()) { ResourceTypeInfo::TypedInfo Typed = RTI.getTyped(); uint32_t CompType = llvm::to_underlying(Typed.ElementTy); uint32_t CompCount = Typed.ElementCount; uint32_t SampleCount = RTI.isMultiSample() ? RTI.getMultiSampleCount() : 0; Word1 |= (CompType & 0xFF) << 0; Word1 |= (CompCount & 0xFF) << 8; Word1 |= (SampleCount & 0xFF) << 16; } return {Word0, Word1}; } void ResourceInfo::print(raw_ostream &OS, dxil::ResourceTypeInfo &RTI, const DataLayout &DL) const { if (!Name.empty()) OS << " Name: " << Name << "\n"; if (Symbol) { OS << " Symbol: "; Symbol->printAsOperand(OS); OS << "\n"; } OS << " Binding:\n" << " Record ID: " << Binding.RecordID << "\n" << " Space: " << Binding.Space << "\n" << " Lower Bound: " << Binding.LowerBound << "\n" << " Size: " << Binding.Size << "\n"; OS << " Globally Coherent: " << GloballyCoherent << "\n"; OS << " Counter Direction: "; switch (CounterDirection) { case ResourceCounterDirection::Increment: OS << "Increment\n"; break; case ResourceCounterDirection::Decrement: OS << "Decrement\n"; break; case ResourceCounterDirection::Unknown: OS << "Unknown\n"; break; case ResourceCounterDirection::Invalid: OS << "Invalid\n"; break; } RTI.print(OS, DL); } //===----------------------------------------------------------------------===// bool DXILResourceTypeMap::invalidate(Module &M, const PreservedAnalyses &PA, ModuleAnalysisManager::Invalidator &Inv) { // Passes that introduce resource types must explicitly invalidate this pass. auto PAC = PA.getChecker(); return !PAC.preservedWhenStateless(); } //===----------------------------------------------------------------------===// static bool isUpdateCounterIntrinsic(Function &F) { return F.getIntrinsicID() == Intrinsic::dx_resource_updatecounter; } StringRef dxil::getResourceNameFromBindingCall(CallInst *CI) { Value *Op = nullptr; switch (CI->getCalledFunction()->getIntrinsicID()) { default: llvm_unreachable("unexpected handle creation intrinsic"); case Intrinsic::dx_resource_handlefrombinding: case Intrinsic::dx_resource_handlefromimplicitbinding: Op = CI->getArgOperand(5); break; } auto *GV = dyn_cast(Op); if (!GV) return ""; auto *CA = dyn_cast(GV->getInitializer()); assert(CA && CA->isString() && "expected constant string"); StringRef Name = CA->getAsString(); // strip trailing 0 if (Name.ends_with('\0')) Name = Name.drop_back(1); return Name; } void DXILResourceMap::populateResourceInfos(Module &M, DXILResourceTypeMap &DRTM) { SmallVector> CIToInfos; for (Function &F : M.functions()) { if (!F.isDeclaration()) continue; LLVM_DEBUG(dbgs() << "Function: " << F.getName() << "\n"); Intrinsic::ID ID = F.getIntrinsicID(); switch (ID) { default: continue; case Intrinsic::dx_resource_handlefrombinding: { auto *HandleTy = cast(F.getReturnType()); ResourceTypeInfo &RTI = DRTM[HandleTy]; for (User *U : F.users()) if (CallInst *CI = dyn_cast(U)) { LLVM_DEBUG(dbgs() << " Visiting: " << *U << "\n"); uint32_t Space = cast(CI->getArgOperand(0))->getZExtValue(); uint32_t LowerBound = cast(CI->getArgOperand(1))->getZExtValue(); uint32_t Size = cast(CI->getArgOperand(2))->getZExtValue(); StringRef Name = getResourceNameFromBindingCall(CI); ResourceInfo RI = ResourceInfo{/*RecordID=*/0, Space, LowerBound, Size, HandleTy, Name}; CIToInfos.emplace_back(CI, RI, RTI); } break; } } } llvm::stable_sort(CIToInfos, [](auto &LHS, auto &RHS) { const auto &[LCI, LRI, LRTI] = LHS; const auto &[RCI, RRI, RRTI] = RHS; // Sort by resource class first for grouping purposes, and then by the // binding and type so we can remove duplicates. ResourceClass LRC = LRTI.getResourceClass(); ResourceClass RRC = RRTI.getResourceClass(); return std::tie(LRC, LRI, LRTI) < std::tie(RRC, RRI, RRTI); }); for (auto [CI, RI, RTI] : CIToInfos) { if (Infos.empty() || RI != Infos.back()) Infos.push_back(RI); CallMap[CI] = Infos.size() - 1; } unsigned Size = Infos.size(); // In DXC, Record ID is unique per resource type. Match that. FirstUAV = FirstCBuffer = FirstSampler = Size; uint32_t NextID = 0; for (unsigned I = 0, E = Size; I != E; ++I) { ResourceInfo &RI = Infos[I]; ResourceTypeInfo &RTI = DRTM[RI.getHandleTy()]; if (RTI.isUAV() && FirstUAV == Size) { FirstUAV = I; NextID = 0; } else if (RTI.isCBuffer() && FirstCBuffer == Size) { FirstCBuffer = I; NextID = 0; } else if (RTI.isSampler() && FirstSampler == Size) { FirstSampler = I; NextID = 0; } // We need to make sure the types of resource are ordered even if some are // missing. FirstCBuffer = std::min({FirstCBuffer, FirstSampler}); FirstUAV = std::min({FirstUAV, FirstCBuffer}); // Adjust the resource binding to use the next ID. RI.setBindingID(NextID++); } } void DXILResourceMap::populateCounterDirections(Module &M) { for (Function &F : M.functions()) { if (!isUpdateCounterIntrinsic(F)) continue; LLVM_DEBUG(dbgs() << "Update Counter Function: " << F.getName() << "\n"); for (const User *U : F.users()) { const CallInst *CI = dyn_cast(U); assert(CI && "Users of dx_resource_updateCounter must be call instrs"); // Determine if the use is an increment or decrement Value *CountArg = CI->getArgOperand(1); ConstantInt *CountValue = cast(CountArg); int64_t CountLiteral = CountValue->getSExtValue(); // 0 is an unknown direction and shouldn't result in an insert if (CountLiteral == 0) continue; ResourceCounterDirection Direction = ResourceCounterDirection::Decrement; if (CountLiteral > 0) Direction = ResourceCounterDirection::Increment; // Collect all potential creation points for the handle arg Value *HandleArg = CI->getArgOperand(0); SmallVector RBInfos = findByUse(HandleArg); for (ResourceInfo *RBInfo : RBInfos) { if (RBInfo->CounterDirection == ResourceCounterDirection::Unknown) RBInfo->CounterDirection = Direction; else if (RBInfo->CounterDirection != Direction) { RBInfo->CounterDirection = ResourceCounterDirection::Invalid; HasInvalidDirection = true; } } } } } void DXILResourceMap::populate(Module &M, DXILResourceTypeMap &DRTM) { populateResourceInfos(M, DRTM); populateCounterDirections(M); } void DXILResourceMap::print(raw_ostream &OS, DXILResourceTypeMap &DRTM, const DataLayout &DL) const { for (unsigned I = 0, E = Infos.size(); I != E; ++I) { OS << "Resource " << I << ":\n"; const dxil::ResourceInfo &RI = Infos[I]; RI.print(OS, DRTM[RI.getHandleTy()], DL); OS << "\n"; } for (const auto &[CI, Index] : CallMap) { OS << "Call bound to " << Index << ":"; CI->print(OS); OS << "\n"; } } SmallVector DXILResourceMap::findByUse(const Value *Key) { if (const PHINode *Phi = dyn_cast(Key)) { SmallVector Children; for (const Value *V : Phi->operands()) { Children.append(findByUse(V)); } return Children; } const CallInst *CI = dyn_cast(Key); if (!CI) return {}; switch (CI->getIntrinsicID()) { // Found the create, return the binding case Intrinsic::dx_resource_handlefrombinding: { auto Pos = CallMap.find(CI); assert(Pos != CallMap.end() && "HandleFromBinding must be in resource map"); return {&Infos[Pos->second]}; } default: break; } // Check if any of the parameters are the resource we are following. If so // keep searching. If none of them are return an empty list const Type *UseType = CI->getType(); SmallVector Children; for (const Value *V : CI->args()) { if (V->getType() != UseType) continue; Children.append(findByUse(V)); } return Children; } //===----------------------------------------------------------------------===// void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { struct Binding { ResourceClass RC; uint32_t Space; uint32_t LowerBound; uint32_t UpperBound; Value *Name; Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound, uint32_t UpperBound, Value *Name) : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound), Name(Name) {} }; SmallVector Bindings; // collect all of the llvm.dx.resource.handlefrombinding calls; // make a note if there is llvm.dx.resource.handlefromimplicitbinding for (Function &F : M.functions()) { if (!F.isDeclaration()) continue; switch (F.getIntrinsicID()) { default: continue; case Intrinsic::dx_resource_handlefrombinding: { auto *HandleTy = cast(F.getReturnType()); ResourceTypeInfo &RTI = DRTM[HandleTy]; for (User *U : F.users()) if (CallInst *CI = dyn_cast(U)) { uint32_t Space = cast(CI->getArgOperand(0))->getZExtValue(); uint32_t LowerBound = cast(CI->getArgOperand(1))->getZExtValue(); int32_t Size = cast(CI->getArgOperand(2))->getZExtValue(); Value *Name = CI->getArgOperand(5); // negative size means unbounded resource array; // upper bound register overflow should be detected in Sema assert((Size < 0 || (unsigned)LowerBound + Size - 1 <= UINT32_MAX) && "upper bound register overflow"); uint32_t UpperBound = Size < 0 ? UINT32_MAX : LowerBound + Size - 1; Bindings.emplace_back(RTI.getResourceClass(), Space, LowerBound, UpperBound, Name); } break; } case Intrinsic::dx_resource_handlefromimplicitbinding: { ImplicitBinding = true; break; } } } // sort all the collected bindings llvm::stable_sort(Bindings, [](auto &LHS, auto &RHS) { return std::tie(LHS.RC, LHS.Space, LHS.LowerBound) < std::tie(RHS.RC, RHS.Space, RHS.LowerBound); }); // remove duplicates Binding *NewEnd = llvm::unique(Bindings, [](auto &LHS, auto &RHS) { return std::tie(LHS.RC, LHS.Space, LHS.LowerBound, LHS.UpperBound, LHS.Name) == std::tie(RHS.RC, RHS.Space, RHS.LowerBound, RHS.UpperBound, RHS.Name); }); if (NewEnd != Bindings.end()) Bindings.erase(NewEnd); // Go over the sorted bindings and build up lists of free register ranges // for each binding type and used spaces. Bindings are sorted by resource // class, space, and lower bound register slot. BindingSpaces *BS = &SRVSpaces; for (const Binding &B : Bindings) { if (BS->RC != B.RC) // move to the next resource class spaces BS = &getBindingSpaces(B.RC); RegisterSpace *S = BS->Spaces.empty() ? &BS->Spaces.emplace_back(B.Space) : &BS->Spaces.back(); assert(S->Space <= B.Space && "bindings not sorted correctly?"); if (B.Space != S->Space) // add new space S = &BS->Spaces.emplace_back(B.Space); // the space is full - set flag to report overlapping binding later if (S->FreeRanges.empty()) { OverlappingBinding = true; continue; } // adjust the last free range lower bound, split it in two, or remove it BindingRange &LastFreeRange = S->FreeRanges.back(); assert(LastFreeRange.UpperBound == UINT32_MAX); if (LastFreeRange.LowerBound == B.LowerBound) { if (B.UpperBound < UINT32_MAX) LastFreeRange.LowerBound = B.UpperBound + 1; else S->FreeRanges.pop_back(); } else if (LastFreeRange.LowerBound < B.LowerBound) { LastFreeRange.UpperBound = B.LowerBound - 1; if (B.UpperBound < UINT32_MAX) S->FreeRanges.emplace_back(B.UpperBound + 1, UINT32_MAX); } else { OverlappingBinding = true; if (B.UpperBound < UINT32_MAX) LastFreeRange.LowerBound = std::max(LastFreeRange.LowerBound, B.UpperBound + 1); else S->FreeRanges.pop_back(); } } } // returns std::nulopt if binding could not be found in given space std::optional DXILResourceBindingInfo::findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size) { BindingSpaces &BS = getBindingSpaces(RC); RegisterSpace &RS = BS.getOrInsertSpace(Space); return RS.findAvailableBinding(Size); } DXILResourceBindingInfo::RegisterSpace & DXILResourceBindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) { for (auto *I = Spaces.begin(); I != Spaces.end(); ++I) { if (I->Space == Space) return *I; if (I->Space < Space) continue; return *Spaces.insert(I, Space); } return Spaces.emplace_back(Space); } std::optional DXILResourceBindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) { assert((Size == -1 || Size > 0) && "invalid size"); if (FreeRanges.empty()) return std::nullopt; // unbounded array if (Size == -1) { BindingRange &Last = FreeRanges.back(); if (Last.UpperBound != UINT32_MAX) // this space is already occupied by an unbounded array return std::nullopt; uint32_t RegSlot = Last.LowerBound; FreeRanges.pop_back(); return RegSlot; } // single resource or fixed-size array for (BindingRange &R : FreeRanges) { // compare the size as uint64_t to prevent overflow for range (0, // UINT32_MAX) if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size) continue; uint32_t RegSlot = R.LowerBound; // This might create a range where (LowerBound == UpperBound + 1). When // that happens, the next time this function is called the range will // skipped over by the check above (at this point Size is always > 0). R.LowerBound += Size; return RegSlot; } return std::nullopt; } //===----------------------------------------------------------------------===// AnalysisKey DXILResourceTypeAnalysis::Key; AnalysisKey DXILResourceAnalysis::Key; AnalysisKey DXILResourceBindingAnalysis::Key; DXILResourceMap DXILResourceAnalysis::run(Module &M, ModuleAnalysisManager &AM) { DXILResourceMap Data; DXILResourceTypeMap &DRTM = AM.getResult(M); Data.populate(M, DRTM); return Data; } DXILResourceBindingInfo DXILResourceBindingAnalysis::run(Module &M, ModuleAnalysisManager &AM) { DXILResourceBindingInfo Data; DXILResourceTypeMap &DRTM = AM.getResult(M); Data.populate(M, DRTM); return Data; } PreservedAnalyses DXILResourcePrinterPass::run(Module &M, ModuleAnalysisManager &AM) { DXILResourceMap &DRM = AM.getResult(M); DXILResourceTypeMap &DRTM = AM.getResult(M); DRM.print(OS, DRTM, M.getDataLayout()); return PreservedAnalyses::all(); } void DXILResourceTypeWrapperPass::anchor() {} DXILResourceTypeWrapperPass::DXILResourceTypeWrapperPass() : ImmutablePass(ID) {} INITIALIZE_PASS(DXILResourceTypeWrapperPass, "dxil-resource-type", "DXIL Resource Type Analysis", false, true) char DXILResourceTypeWrapperPass::ID = 0; ModulePass *llvm::createDXILResourceTypeWrapperPassPass() { return new DXILResourceTypeWrapperPass(); } DXILResourceWrapperPass::DXILResourceWrapperPass() : ModulePass(ID) {} DXILResourceWrapperPass::~DXILResourceWrapperPass() = default; void DXILResourceWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequiredTransitive(); AU.setPreservesAll(); } bool DXILResourceWrapperPass::runOnModule(Module &M) { Map.reset(new DXILResourceMap()); DRTM = &getAnalysis().getResourceTypeMap(); Map->populate(M, *DRTM); return false; } void DXILResourceWrapperPass::releaseMemory() { Map.reset(); } void DXILResourceWrapperPass::print(raw_ostream &OS, const Module *M) const { if (!Map) { OS << "No resource map has been built!\n"; return; } Map->print(OS, *DRTM, M->getDataLayout()); } #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) LLVM_DUMP_METHOD void DXILResourceWrapperPass::dump() const { print(dbgs(), nullptr); } #endif INITIALIZE_PASS(DXILResourceWrapperPass, "dxil-resources", "DXIL Resources Analysis", false, true) char DXILResourceWrapperPass::ID = 0; ModulePass *llvm::createDXILResourceWrapperPassPass() { return new DXILResourceWrapperPass(); } DXILResourceBindingWrapperPass::DXILResourceBindingWrapperPass() : ModulePass(ID) {} DXILResourceBindingWrapperPass::~DXILResourceBindingWrapperPass() = default; void DXILResourceBindingWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequiredTransitive(); AU.setPreservesAll(); } bool DXILResourceBindingWrapperPass::runOnModule(Module &M) { BindingInfo.reset(new DXILResourceBindingInfo()); DXILResourceTypeMap &DRTM = getAnalysis().getResourceTypeMap(); BindingInfo->populate(M, DRTM); return false; } void DXILResourceBindingWrapperPass::releaseMemory() { BindingInfo.reset(); } INITIALIZE_PASS(DXILResourceBindingWrapperPass, "dxil-resource-binding", "DXIL Resource Binding Analysis", false, true) char DXILResourceBindingWrapperPass::ID = 0; ModulePass *llvm::createDXILResourceBindingWrapperPassPass() { return new DXILResourceWrapperPass(); }