blob: 36b390124628507954e108a40f742edb18e17170 [file] [log] [blame]
//===- 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 <climits>
#include <cstdint>
#include <optional>
#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 "TypedBuffer";
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 "<invalid>";
}
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 "<invalid>";
}
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<RawBufferExtType>(HandleTy)) {
RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV;
Kind = Ty->isStructured() ? ResourceKind::StructuredBuffer
: ResourceKind::RawBuffer;
} else if (auto *Ty = dyn_cast<TypedBufferExtType>(HandleTy)) {
RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV;
Kind = ResourceKind::TypedBuffer;
} else if (auto *Ty = dyn_cast<TextureExtType>(HandleTy)) {
RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV;
Kind = Ty->getDimension();
} else if (auto *Ty = dyn_cast<MSTextureExtType>(HandleTy)) {
RC = Ty->isWriteable() ? ResourceClass::UAV : ResourceClass::SRV;
Kind = Ty->getDimension();
} else if (auto *Ty = dyn_cast<FeedbackTextureExtType>(HandleTy)) {
RC = ResourceClass::UAV;
Kind = Ty->getDimension();
} else if (isa<CBufferExtType>(HandleTy)) {
RC = ResourceClass::CBuffer;
Kind = ResourceKind::CBuffer;
} else if (isa<SamplerExtType>(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) {
Dest = isWriteable ? (isROV ? "RasterizerOrdered" : "RW") : "";
Dest += Name;
}
StructType *ResourceTypeInfo::createElementStruct() {
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<TextureExtType>(HandleTy);
formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(),
RTy->isROV());
return StructType::create(RTy->getResourceType(), TypeName);
}
case ResourceKind::Texture2DMS:
case ResourceKind::Texture2DMSArray: {
auto *RTy = cast<MSTextureExtType>(HandleTy);
formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(),
/*IsROV=*/false);
return StructType::create(RTy->getResourceType(), TypeName);
}
case ResourceKind::TypedBuffer: {
auto *RTy = cast<TypedBufferExtType>(HandleTy);
formatTypeName(TypeName, getResourceKindName(Kind), RTy->isWriteable(),
RTy->isROV());
return StructType::create(RTy->getResourceType(), TypeName);
}
case ResourceKind::RawBuffer: {
auto *RTy = cast<RawBufferExtType>(HandleTy);
formatTypeName(TypeName, "ByteAddressBuffer", RTy->isWriteable(),
RTy->isROV());
return StructType::create(Type::getInt32Ty(HandleTy->getContext()),
TypeName);
}
case ResourceKind::StructuredBuffer: {
auto *RTy = cast<RawBufferExtType>(HandleTy);
formatTypeName(TypeName, "StructuredBuffer", RTy->isWriteable(),
RTy->isROV());
return StructType::create(RTy->getResourceType(), TypeName);
}
case ResourceKind::FeedbackTexture2D:
case ResourceKind::FeedbackTexture2DArray: {
auto *RTy = cast<FeedbackTextureExtType>(HandleTy);
TypeName = formatv("{0}<{1}>", getResourceKindName(Kind),
llvm::to_underlying(RTy->getFeedbackType()));
return StructType::create(Type::getInt32Ty(HandleTy->getContext()),
TypeName);
}
case ResourceKind::CBuffer:
return StructType::create(HandleTy->getContext(), "cbuffer");
case ResourceKind::Sampler: {
auto *RTy = cast<SamplerExtType>(HandleTy);
TypeName = formatv("SamplerState<{0}>",
llvm::to_underlying(RTy->getSamplerType()));
return StructType::create(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<TextureExtType>(Ty)->isROV();
case ResourceKind::TypedBuffer:
return cast<TypedBufferExtType>(Ty)->isROV();
case ResourceKind::RawBuffer:
case ResourceKind::StructuredBuffer:
return cast<RawBufferExtType>(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<CBufferExtType>(HandleTy)->getResourceType();
if (auto *LayoutTy = dyn_cast<LayoutExtType>(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<SamplerExtType>(HandleTy)->getSamplerType();
}
ResourceTypeInfo::StructInfo
ResourceTypeInfo::getStruct(const DataLayout &DL) const {
assert(isStruct() && "Not a Struct");
Type *ElTy = cast<RawBufferExtType>(HandleTy)->getResourceType();
uint32_t Stride = DL.getTypeAllocSize(ElTy);
MaybeAlign Alignment;
if (auto *STy = dyn_cast<StructType>(ElTy))
Alignment = DL.getStructLayout(STy)->getAlignment();
uint32_t AlignLog2 = Alignment ? Log2(*Alignment) : 0;
return {Stride, AlignLog2};
}
static std::pair<Type *, bool> 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<TextureExtType>(Ty);
return {RTy->getResourceType(), RTy->isSigned()};
}
case ResourceKind::Texture2DMS:
case ResourceKind::Texture2DMSArray: {
auto *RTy = cast<MSTextureExtType>(Ty);
return {RTy->getResourceType(), RTy->isSigned()};
}
case ResourceKind::TypedBuffer: {
auto *RTy = cast<TypedBufferExtType>(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<FixedVectorType>(ElTy))
Count = VTy->getNumElements();
return {ET, Count};
}
dxil::SamplerFeedbackType ResourceTypeInfo::getFeedbackType() const {
assert(isFeedback() && "Not Feedback");
return cast<FeedbackTextureExtType>(HandleTy)->getFeedbackType();
}
uint32_t ResourceTypeInfo::getMultiSampleCount() const {
assert(isMultiSample() && "Not MultiSampled");
return cast<MSTextureExtType>(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,
StringRef Name) {
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<Metadata *, 11> 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, Symbol->getName()));
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<Metadata *> 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<uint32_t, uint32_t>
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 (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<DXILResourceTypeAnalysis>();
return !PAC.preservedWhenStateless();
}
//===----------------------------------------------------------------------===//
static bool isUpdateCounterIntrinsic(Function &F) {
return F.getIntrinsicID() == Intrinsic::dx_resource_updatecounter;
}
void DXILResourceMap::populateResourceInfos(Module &M,
DXILResourceTypeMap &DRTM) {
SmallVector<std::tuple<CallInst *, ResourceInfo, ResourceTypeInfo>> 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<TargetExtType>(F.getReturnType());
ResourceTypeInfo &RTI = DRTM[HandleTy];
for (User *U : F.users())
if (CallInst *CI = dyn_cast<CallInst>(U)) {
LLVM_DEBUG(dbgs() << " Visiting: " << *U << "\n");
uint32_t Space =
cast<ConstantInt>(CI->getArgOperand(0))->getZExtValue();
uint32_t LowerBound =
cast<ConstantInt>(CI->getArgOperand(1))->getZExtValue();
uint32_t Size =
cast<ConstantInt>(CI->getArgOperand(2))->getZExtValue();
ResourceInfo RI =
ResourceInfo{/*RecordID=*/0, Space, LowerBound, Size, HandleTy};
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<CallInst>(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<ConstantInt>(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<ResourceInfo *> 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<dxil::ResourceInfo *> DXILResourceMap::findByUse(const Value *Key) {
if (const PHINode *Phi = dyn_cast<PHINode>(Key)) {
SmallVector<dxil::ResourceInfo *> Children;
for (const Value *V : Phi->operands()) {
Children.append(findByUse(V));
}
return Children;
}
const CallInst *CI = dyn_cast<CallInst>(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<dxil::ResourceInfo *> 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;
Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound,
uint32_t UpperBound)
: RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound) {
}
};
SmallVector<Binding> 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<TargetExtType>(F.getReturnType());
ResourceTypeInfo &RTI = DRTM[HandleTy];
for (User *U : F.users())
if (CallInst *CI = dyn_cast<CallInst>(U)) {
uint32_t Space =
cast<ConstantInt>(CI->getArgOperand(0))->getZExtValue();
uint32_t LowerBound =
cast<ConstantInt>(CI->getArgOperand(1))->getZExtValue();
int32_t Size =
cast<ConstantInt>(CI->getArgOperand(2))->getZExtValue();
// 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);
}
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) ==
std::tie(RHS.RC, RHS.Space, RHS.LowerBound, RHS.UpperBound);
});
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 {
// FIXME: This only detects overlapping bindings that are not an exact
// match (llvm/llvm-project#110723)
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<uint32_t>
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<uint32_t>
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<DXILResourceTypeAnalysis>(M);
Data.populate(M, DRTM);
return Data;
}
DXILResourceBindingInfo
DXILResourceBindingAnalysis::run(Module &M, ModuleAnalysisManager &AM) {
DXILResourceBindingInfo Data;
DXILResourceTypeMap &DRTM = AM.getResult<DXILResourceTypeAnalysis>(M);
Data.populate(M, DRTM);
return Data;
}
PreservedAnalyses DXILResourcePrinterPass::run(Module &M,
ModuleAnalysisManager &AM) {
DXILResourceMap &DRM = AM.getResult<DXILResourceAnalysis>(M);
DXILResourceTypeMap &DRTM = AM.getResult<DXILResourceTypeAnalysis>(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<DXILResourceTypeWrapperPass>();
AU.setPreservesAll();
}
bool DXILResourceWrapperPass::runOnModule(Module &M) {
Map.reset(new DXILResourceMap());
DRTM = &getAnalysis<DXILResourceTypeWrapperPass>().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<DXILResourceTypeWrapperPass>();
AU.setPreservesAll();
}
bool DXILResourceBindingWrapperPass::runOnModule(Module &M) {
BindingInfo.reset(new DXILResourceBindingInfo());
DXILResourceTypeMap &DRTM =
getAnalysis<DXILResourceTypeWrapperPass>().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();
}