blob: ffc3463db39d9eca84d016deacea3b1525120066 [file] [log] [blame] [edit]
///////////////////////////////////////////////////////////////////////////////
// //
// DxilShaderAccessTracking.cpp //
// Copyright (C) Microsoft Corporation. All rights reserved. //
// This file is distributed under the University of Illinois Open Source //
// License. See LICENSE.TXT for details. //
// //
// Provides a pass to add instrumentation to determine pixel hit count and //
// cost. Used by PIX. //
// //
///////////////////////////////////////////////////////////////////////////////
#include "dxc/DXIL/DxilOperations.h"
#include "dxc/DXIL/DxilConstants.h"
#include "dxc/DXIL/DxilFunctionProps.h"
#include "dxc/DXIL/DxilInstructions.h"
#include "dxc/DXIL/DxilModule.h"
#include "dxc/DXIL/DxilResourceBinding.h"
#include "dxc/DXIL/DxilResourceProperties.h"
#include "dxc/DxilPIXPasses/DxilPIXPasses.h"
#include "dxc/DxilPIXPasses/DxilPIXVirtualRegisters.h"
#include "dxc/HLSL/DxilGenerationPass.h"
#include "dxc/HLSL/DxilSpanAllocator.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Support/FormattedStream.h"
#include "llvm/Transforms/Utils/Local.h"
#include <deque>
#include "PixPassHelpers.h"
#ifdef _WIN32
#include <winerror.h>
#endif
using namespace llvm;
using namespace hlsl;
using namespace hlsl::DXIL::OperandIndex;
void ThrowIf(bool a) {
if (a) {
throw ::hlsl::Exception(E_INVALIDARG);
}
}
//---------------------------------------------------------------------------------------------------------------------------------
// These types are taken from PIX's ShaderAccessHelpers.h
enum class ShaderAccessFlags : uint32_t {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
// "Counter" access is only applicable to UAVs; it means the counter buffer
// attached to the UAV was accessed, but not necessarily the UAV resource.
Counter = 1 << 2,
Sampler = 1 << 3,
// Descriptor-only read (if any), but not the resource contents (if any).
// Used for GetDimensions, samplers, and secondary texture for sampler
// feedback.
// TODO: Make this a unique value if supported in PIX, then enable
// GetDimensions
DescriptorRead = 1 << 0,
};
// Bits in encoded dword:
// 33222222222211111111110000000000
// 10987654321098765432109876543210
// kkkkisssrrrrrrrrrrrrrrrrrrrrrrrr
//
// k: four bits ShaderKind
// i: one bit InstructionOrdinalndicator
// r: 24 bits if i = 0 (resource index) else (instruction ordinal)
constexpr uint32_t InstructionOrdinalndicator = 0x0800'0000;
// (end shared types)
//---------------------------------------------------------------------------------------------------------------------------------
static uint32_t EncodeShaderModel(DXIL::ShaderKind kind) {
DXASSERT_NOMSG(static_cast<int>(DXIL::ShaderKind::Invalid) <= 16);
return static_cast<uint32_t>(kind) << 28;
}
enum class ResourceAccessStyle {
None,
Sampler,
UAVRead,
UAVWrite,
CBVRead,
SRVRead,
EndOfEnum
};
static uint32_t EncodeAccess(ResourceAccessStyle access) {
DXASSERT_NOMSG(static_cast<int>(ResourceAccessStyle::EndOfEnum) <= 8);
uint32_t encoded = static_cast<uint32_t>(access);
return encoded << 24;
}
constexpr uint32_t DWORDsPerResource = 3;
constexpr uint32_t BytesPerDWORD = 4;
static uint32_t OffsetFromAccess(ShaderAccessFlags access) {
switch (access) {
case ShaderAccessFlags::Read:
return 0;
case ShaderAccessFlags::Write:
return 1;
case ShaderAccessFlags::Counter:
return 2;
default:
throw ::hlsl::Exception(E_INVALIDARG);
}
}
// This enum doesn't have to match PIX's version, because the values are
// received from PIX encoded in ASCII. However, for ease of comparing this code
// with PIX, and to be less confusing to future maintainers, this enum does
// indeed match the same-named enum in PIX.
enum class RegisterType {
CBV,
SRV,
UAV,
RTV, // not used.
DSV, // not used.
Sampler,
SOV, // not used.
Invalid,
Terminator
};
RegisterType RegisterTypeFromResourceClass(DXIL::ResourceClass c) {
switch (c) {
case DXIL::ResourceClass::SRV:
return RegisterType::SRV;
break;
case DXIL::ResourceClass::UAV:
return RegisterType::UAV;
break;
case DXIL::ResourceClass::CBuffer:
return RegisterType::CBV;
break;
case DXIL::ResourceClass::Sampler:
return RegisterType::Sampler;
break;
case DXIL::ResourceClass::Invalid:
return RegisterType::Invalid;
break;
default:
ThrowIf(true);
return RegisterType::Invalid;
}
}
struct RegisterTypeAndSpace {
bool operator<(const RegisterTypeAndSpace &o) const {
return static_cast<int>(Type) < static_cast<int>(o.Type) ||
(static_cast<int>(Type) == static_cast<int>(o.Type) &&
Space < o.Space);
}
RegisterType Type;
unsigned Space;
};
// Identifies a bind point as defined by the root signature
struct RSRegisterIdentifier {
RegisterType Type;
unsigned Space;
unsigned Index;
bool operator<(const RSRegisterIdentifier &o) const {
return static_cast<unsigned>(Type) < static_cast<unsigned>(o.Type) &&
Space < o.Space && Index < o.Index;
}
};
struct SlotRange {
unsigned startSlot;
unsigned numSlots;
// Number of slots needed if no descriptors from unbounded ranges are included
unsigned numInvariableSlots;
};
enum class AccessStyle {
None,
FromRootSig,
ResourceFromDescriptorHeap,
SamplerFromDescriptorHeap
};
struct DxilResourceAndClass {
AccessStyle accessStyle;
RegisterType registerType;
int RegisterSpace;
unsigned RegisterID;
Value *index;
Value *indexDynamicOffset;
Value *dynamicallyBoundIndex;
};
//---------------------------------------------------------------------------------------------------------------------------------
class DxilShaderAccessTracking : public ModulePass {
public:
static char ID; // Pass identification, replacement for typeid
explicit DxilShaderAccessTracking() : ModulePass(ID) {}
StringRef getPassName() const override {
return "DXIL shader access tracking";
}
bool runOnModule(Module &M) override;
void applyOptions(PassOptions O) override;
private:
void EmitAccess(LLVMContext &Ctx, OP *HlslOP, IRBuilder<> &, Value *slot,
ShaderAccessFlags access);
bool EmitResourceAccess(DxilModule &DM, DxilResourceAndClass &res,
Instruction *instruction, OP *HlslOP,
LLVMContext &Ctx, ShaderAccessFlags readWrite);
DxilResourceAndClass GetResourceFromHandle(Value *resHandle, DxilModule &DM);
DxilResourceAndClass
DetermineAccessForHandleForLib(CallInst *handleCreation,
DxilResourceAndClass &initializedRnC,
DxilModule &DM);
private:
struct DynamicResourceBinding {
int HeapIndex;
bool HeapIsSampler; // else resource
std::string Name;
};
std::vector<DynamicResourceBinding> m_dynamicResourceBindings;
bool m_CheckForDynamicIndexing = false;
int m_DynamicResourceDataOffset = -1;
int m_DynamicSamplerDataOffset = -1;
int m_OutputBufferSize = -1;
std::map<RegisterTypeAndSpace, SlotRange> m_slotAssignments;
std::map<llvm::Function *, CallInst *> m_FunctionToUAVHandle;
std::map<llvm::Function *, std::map<ResourceAccessStyle, Constant *>>
m_FunctionToEncodedAccess;
std::set<RSRegisterIdentifier> m_DynamicallyIndexedBindPoints;
std::vector<std::unique_ptr<GetElementPtrInst>>
m_GEPOperandAsInstructionDestroyers;
};
static unsigned DeserializeInt(std::deque<char> &q) {
unsigned i = 0;
while (!q.empty() && isdigit(q.front())) {
i *= 10;
i += q.front() - '0';
q.pop_front();
}
return i;
}
static char DequeFront(std::deque<char> &q) {
ThrowIf(q.empty());
auto c = q.front();
q.pop_front();
return c;
}
static RegisterType ParseRegisterType(std::deque<char> &q) {
switch (DequeFront(q)) {
case 'C':
return RegisterType::CBV;
case 'S':
return RegisterType::SRV;
case 'U':
return RegisterType::UAV;
case 'M':
return RegisterType::Sampler;
case 'I':
return RegisterType::Invalid;
default:
return RegisterType::Terminator;
}
}
static char EncodeRegisterType(RegisterType r) {
switch (r) {
case RegisterType::CBV:
return 'C';
case RegisterType::SRV:
return 'S';
case RegisterType::UAV:
return 'U';
case RegisterType::Sampler:
return 'M';
case RegisterType::Invalid:
return 'I';
}
return '.';
}
static void ValidateDelimiter(std::deque<char> &q, char d) {
ThrowIf(q.front() != d);
q.pop_front();
}
void DxilShaderAccessTracking::applyOptions(PassOptions O) {
int checkForDynamic;
GetPassOptionInt(O, "checkForDynamicIndexing", &checkForDynamic, 0);
m_CheckForDynamicIndexing = checkForDynamic != 0;
StringRef configOption;
if (GetPassOption(O, "config", &configOption)) {
std::deque<char> config;
config.assign(configOption.begin(), configOption.end());
// Parse slot assignments. Compare with PIX's ShaderAccessHelpers.cpp
// (TrackingConfiguration::SerializedRepresentation)
RegisterType rt = ParseRegisterType(config);
while (rt != RegisterType::Terminator) {
RegisterTypeAndSpace rst;
rst.Type = rt;
rst.Space = DeserializeInt(config);
ValidateDelimiter(config, ':');
SlotRange sr;
sr.startSlot = DeserializeInt(config);
ValidateDelimiter(config, ':');
sr.numSlots = DeserializeInt(config);
ValidateDelimiter(config, 'i');
sr.numInvariableSlots = DeserializeInt(config);
ValidateDelimiter(config, ';');
m_slotAssignments[rst] = sr;
rt = ParseRegisterType(config);
}
m_DynamicResourceDataOffset = DeserializeInt(config);
ValidateDelimiter(config, ';');
m_DynamicSamplerDataOffset = DeserializeInt(config);
ValidateDelimiter(config, ';');
m_OutputBufferSize = DeserializeInt(config);
}
}
void DxilShaderAccessTracking::EmitAccess(LLVMContext &Ctx, OP *HlslOP,
IRBuilder<> &Builder,
Value *ByteIndex,
ShaderAccessFlags access) {
unsigned OffsetForAccessType =
static_cast<unsigned>(OffsetFromAccess(access) * BytesPerDWORD);
auto OffsetByteIndex = Builder.CreateAdd(
ByteIndex, HlslOP->GetU32Const(OffsetForAccessType), "OffsetByteIndex");
UndefValue *UndefIntArg = UndefValue::get(Type::getInt32Ty(Ctx));
Constant *LiteralOne = HlslOP->GetU32Const(1);
Constant *ElementMask = HlslOP->GetI8Const(1);
Function *StoreFunc =
HlslOP->GetOpFunc(OP::OpCode::BufferStore, Type::getInt32Ty(Ctx));
Constant *StoreOpcode =
HlslOP->GetU32Const((unsigned)OP::OpCode::BufferStore);
(void)Builder.CreateCall(
StoreFunc,
{
StoreOpcode, // i32, ; opcode
m_FunctionToUAVHandle.at(
Builder.GetInsertBlock()
->getParent()), // %dx.types.Handle, ; resource handle
OffsetByteIndex, // i32, ; coordinate c0: byte offset
UndefIntArg, // i32, ; coordinate c1 (unused)
LiteralOne, // i32, ; value v0
UndefIntArg, // i32, ; value v1
UndefIntArg, // i32, ; value v2
UndefIntArg, // i32, ; value v3
ElementMask // i8 ; just the first value is used
});
}
static ResourceAccessStyle
AccessStyleFromAccessAndType(AccessStyle accessStyle, RegisterType registerType,
ShaderAccessFlags readWrite) {
switch (accessStyle) {
case AccessStyle::ResourceFromDescriptorHeap:
switch (registerType) {
case RegisterType::CBV:
return ResourceAccessStyle::CBVRead;
case RegisterType::SRV:
return ResourceAccessStyle::SRVRead;
case RegisterType::UAV:
return readWrite == ShaderAccessFlags::Read
? ResourceAccessStyle::UAVRead
: ResourceAccessStyle::UAVWrite;
default:
return ResourceAccessStyle::None;
}
case AccessStyle::SamplerFromDescriptorHeap:
return ResourceAccessStyle::Sampler;
default:
return ResourceAccessStyle::None;
}
}
bool DxilShaderAccessTracking::EmitResourceAccess(DxilModule &DM,
DxilResourceAndClass &res,
Instruction *instruction,
OP *HlslOP, LLVMContext &Ctx,
ShaderAccessFlags readWrite) {
IRBuilder<> Builder(instruction);
if (res.accessStyle == AccessStyle::FromRootSig) {
RegisterTypeAndSpace typeAndSpace{
res.registerType,
static_cast<unsigned>(res.RegisterSpace) // reserved spaces are -ve, but
// user spaces can only be +ve
};
auto slot = m_slotAssignments.find(typeAndSpace);
// If the assignment isn't found, we assume it's not accessed
if (slot != m_slotAssignments.end()) {
Value *slotIndex;
if (isa<ConstantInt>(res.index) && res.indexDynamicOffset == nullptr) {
unsigned index = cast<ConstantInt>(res.index)->getLimitedValue();
if (index > slot->second.numSlots) {
// out-of-range accesses are written to slot zero:
slotIndex = HlslOP->GetU32Const(0);
} else {
slotIndex = HlslOP->GetU32Const((slot->second.startSlot + index) *
DWORDsPerResource * BytesPerDWORD);
}
} else {
RSRegisterIdentifier id{typeAndSpace.Type, typeAndSpace.Space,
res.RegisterID};
m_DynamicallyIndexedBindPoints.emplace(std::move(id));
Value *index = res.index;
if (res.indexDynamicOffset != nullptr) {
index = Builder.CreateAdd(res.index, res.indexDynamicOffset,
"IndexPlusGEPIndex");
}
// CompareWithSlotLimit will contain 1 if the access is out-of-bounds
// (both over- and and under-flow via the unsigned >= with slot count)
auto CompareWithSlotLimit = Builder.CreateICmpUGE(
index, HlslOP->GetU32Const(slot->second.numSlots),
"CompareWithSlotLimit");
auto CompareWithSlotLimitAsUint = Builder.CreateCast(
Instruction::CastOps::ZExt, CompareWithSlotLimit,
Type::getInt32Ty(Ctx), "CompareWithSlotLimitAsUint");
// IsInBounds will therefore contain 0 if the access is out-of-bounds,
// and 1 otherwise.
auto IsInBounds = Builder.CreateSub(
HlslOP->GetU32Const(1), CompareWithSlotLimitAsUint, "IsInBounds");
auto SlotDwordOffset = Builder.CreateAdd(
index, HlslOP->GetU32Const(slot->second.startSlot),
"SlotDwordOffset");
auto SlotByteOffset = Builder.CreateMul(
SlotDwordOffset,
HlslOP->GetU32Const(DWORDsPerResource * BytesPerDWORD),
"SlotByteOffset");
// This will drive an out-of-bounds access slot down to 0
slotIndex = Builder.CreateMul(SlotByteOffset, IsInBounds, "slotIndex");
}
EmitAccess(Ctx, HlslOP, Builder, slotIndex, readWrite);
return true; // did modify
}
} else if (m_DynamicResourceDataOffset != -1) {
if (res.accessStyle == AccessStyle::ResourceFromDescriptorHeap ||
res.accessStyle == AccessStyle::SamplerFromDescriptorHeap) {
Constant *BaseOfRecordsForType;
int LimitForType;
if (res.accessStyle == AccessStyle::ResourceFromDescriptorHeap) {
LimitForType = m_DynamicSamplerDataOffset - m_DynamicResourceDataOffset;
BaseOfRecordsForType = HlslOP->GetU32Const(m_DynamicResourceDataOffset);
} else {
LimitForType = m_OutputBufferSize - m_DynamicSamplerDataOffset;
BaseOfRecordsForType = HlslOP->GetU32Const(m_DynamicSamplerDataOffset);
}
// Branchless limit: compare offset to size of data reserved for that
// type, resulting in a value of 0 or 1. Extend that 0/1 to an integer,
// and multiply the offset by that value. Result: expected offset, or 0 if
// too large.
// Add 1 to the index in order to skip over the zeroth entry: that's
// reserved for "out of bounds" writes.
auto *IndexToWrite =
Builder.CreateAdd(res.dynamicallyBoundIndex, HlslOP->GetU32Const(1));
// Each record is two dwords:
// the first dword is for write access, the second for read.
Constant *SizeofRecord =
HlslOP->GetU32Const(2 * static_cast<unsigned int>(sizeof(uint32_t)));
auto *BaseOfRecord = Builder.CreateMul(IndexToWrite, SizeofRecord);
Value *OffsetToWrite;
if (readWrite == ShaderAccessFlags::Write) {
OffsetToWrite = BaseOfRecord;
} else {
OffsetToWrite = Builder.CreateAdd(
BaseOfRecord,
HlslOP->GetU32Const(static_cast<unsigned int>(sizeof(uint32_t))));
}
// Generate the 0 (out of bounds) or 1 (in-bounds) multiplier:
Constant *BufferLimit = HlslOP->GetU32Const(LimitForType);
auto *LimitBoolean = Builder.CreateICmpULT(OffsetToWrite, BufferLimit);
auto *ZeroIfOutOfBounds = Builder.CreateCast(
Instruction::CastOps::ZExt, LimitBoolean, Type::getInt32Ty(Ctx));
// Limit the offset to the out-of-bounds record if the above generated 0,
// or leave it as-is if the above generated 1:
auto *LimitedOffset = Builder.CreateMul(OffsetToWrite, ZeroIfOutOfBounds);
// Offset into the range of records for this type of access (resource or
// sampler)
auto *Offset = Builder.CreateAdd(BaseOfRecordsForType, LimitedOffset);
ResourceAccessStyle accessStyle = AccessStyleFromAccessAndType(
res.accessStyle, res.registerType, readWrite);
Constant *EncodedFlags =
m_FunctionToEncodedAccess.at(Builder.GetInsertBlock()->getParent())
.at(accessStyle);
// Now: if we're out-of-bounds, we'll actually write the offending
// instruction number instead, again using the mul-by-one-or-zero trick
auto *OneIfOutOfBounds =
Builder.CreateSub(HlslOP->GetU32Const(1), ZeroIfOutOfBounds);
auto *MultipliedEncodedFlags =
Builder.CreateMul(ZeroIfOutOfBounds, EncodedFlags);
uint32_t InstructionNumber = 0;
(void)pix_dxil::PixDxilInstNum::FromInst(instruction, &InstructionNumber);
auto const *shaderModel = DM.GetShaderModel();
auto shaderKind = shaderModel->GetKind();
uint32_t EncodedInstructionNumber = InstructionNumber |
InstructionOrdinalndicator |
EncodeShaderModel(shaderKind);
auto *MultipliedOutOfBoundsValue = Builder.CreateMul(
OneIfOutOfBounds, HlslOP->GetU32Const(EncodedInstructionNumber));
auto *CombinedFlagOrInstructionValue =
Builder.CreateAdd(MultipliedEncodedFlags, MultipliedOutOfBoundsValue);
Constant *ElementMask = HlslOP->GetI8Const(1);
Function *StoreFunc =
HlslOP->GetOpFunc(OP::OpCode::BufferStore, Type::getInt32Ty(Ctx));
Constant *StoreOpcode =
HlslOP->GetU32Const((unsigned)OP::OpCode::BufferStore);
UndefValue *UndefArg = UndefValue::get(Type::getInt32Ty(Ctx));
(void)Builder.CreateCall(
StoreFunc,
{
StoreOpcode, // i32, ; opcode
m_FunctionToUAVHandle.at(
Builder.GetInsertBlock()
->getParent()), // %dx.types.Handle, ; resource handle
Offset, // i32, ; coordinate c0: byte offset
UndefArg, // i32, ; coordinate c1 (unused)
CombinedFlagOrInstructionValue, // i32, ; value v0
UndefArg, // i32, ; value v1
UndefArg, // i32, ; value v2
UndefArg, // i32, ; value v3
ElementMask // i8 ; just the first value is used
});
return true; // did modify
}
}
return false; // did not modify
}
DxilResourceAndClass DxilShaderAccessTracking::DetermineAccessForHandleForLib(
CallInst *handleCreation, DxilResourceAndClass &initializedRnC,
DxilModule &DM) {
DxilResourceAndClass ret = initializedRnC;
DxilInst_CreateHandleForLib createHandleForLib(handleCreation);
auto *res = createHandleForLib.get_Resource();
auto *loadInstruction = llvm::cast<llvm::LoadInst>(res);
auto *ptr = loadInstruction->getOperand(0);
GlobalVariable *global = nullptr;
Value *GEPIndex = nullptr;
GetElementPtrInst *GEP = nullptr;
if (llvm::isa<GetElementPtrInst>(ptr)) {
GEP = llvm::cast<GetElementPtrInst>(ptr);
} else if (llvm::isa<ConstantExpr>(ptr)) {
auto *constant = llvm::cast<ConstantExpr>(ptr);
if (constant->getOpcode() == Instruction::GetElementPtr) {
m_GEPOperandAsInstructionDestroyers.emplace_back(
llvm::cast<GetElementPtrInst>(constant->getAsInstruction()));
GEP = m_GEPOperandAsInstructionDestroyers.back().get();
}
} else if (llvm::isa<GlobalVariable>(ptr)) {
GlobalVariable *Global = llvm::cast_or_null<GlobalVariable>(ptr);
// If the load instruction points straight at a global, it's not an indexed
// load, so we can ignore it.
global = Global;
}
if (GEP != nullptr) {
// GEPs can have complex nested pointer dereferences, so make sure
// it's the kind we expect for an indexed global lookup:
auto *FirstGEPIndex = GEP->getOperand(1);
if (llvm::isa<ConstantInt>(FirstGEPIndex) &&
llvm::cast<ConstantInt>(FirstGEPIndex)->getLimitedValue() == 0) {
global = llvm::cast_or_null<GlobalVariable>(GEP->getPointerOperand());
GEPIndex = GEP->getOperand(2);
}
}
if (global != nullptr) {
hlsl::DxilResourceBinding binding{};
ret.registerType = RegisterType::Invalid;
auto const &CBuffers = DM.GetCBuffers();
for (auto &CBuffer : CBuffers) {
if (global == CBuffer->GetGlobalSymbol()) {
binding =
hlsl::resource_helper::loadBindingFromResourceBase(CBuffer.get());
ret.registerType = RegisterType::CBV;
break;
}
}
if (ret.registerType == RegisterType::Invalid) {
auto const &SRVs = DM.GetSRVs();
for (auto &SRV : SRVs) {
if (global == SRV->GetGlobalSymbol()) {
binding =
hlsl::resource_helper::loadBindingFromResourceBase(SRV.get());
ret.registerType = RegisterType::SRV;
break;
}
}
}
if (ret.registerType == RegisterType::Invalid) {
auto const &UAVs = DM.GetUAVs();
for (auto &UAV : UAVs) {
if (global == UAV->GetGlobalSymbol()) {
binding =
hlsl::resource_helper::loadBindingFromResourceBase(UAV.get());
ret.registerType = RegisterType::UAV;
break;
}
}
}
if (ret.registerType != RegisterType::Invalid) {
ret.accessStyle = AccessStyle::FromRootSig;
ret.RegisterID = binding.rangeLowerBound;
ret.RegisterSpace = binding.spaceID;
ret.index = DM.GetOP()->GetU32Const(binding.rangeLowerBound);
// The GEP index is of course relative to the base address of the
// resource, so we make a note of it so we can add it to the base
// register index later.
ret.indexDynamicOffset = GEPIndex;
}
}
return ret;
}
DxilResourceAndClass
DxilShaderAccessTracking::GetResourceFromHandle(Value *resHandle,
DxilModule &DM) {
DxilResourceAndClass ret{AccessStyle::None,
RegisterType::Terminator,
0,
0,
nullptr,
nullptr,
nullptr};
Constant *C = dyn_cast<Constant>(resHandle);
if (C && C->isZeroValue()) {
return ret;
}
if (!isa<CallInst>(resHandle)) {
return ret; // todo
}
CallInst *handle = cast<CallInst>(resHandle);
unsigned rangeId = -1;
if (hlsl::OP::IsDxilOpFuncCallInst(handle, hlsl::OP::OpCode::CreateHandle)) {
DxilInst_CreateHandle createHandle(handle);
// Dynamic rangeId is not supported - skip and let validation report the
// error.
if (isa<ConstantInt>(createHandle.get_rangeId())) {
rangeId =
cast<ConstantInt>(createHandle.get_rangeId())->getLimitedValue();
auto resClass = static_cast<DXIL::ResourceClass>(
createHandle.get_resourceClass_val());
DxilResourceBase *resource = nullptr;
RegisterType registerType = RegisterType::Invalid;
switch (resClass) {
case DXIL::ResourceClass::SRV:
resource = &DM.GetSRV(rangeId);
registerType = RegisterType::SRV;
break;
case DXIL::ResourceClass::UAV:
resource = &DM.GetUAV(rangeId);
registerType = RegisterType::UAV;
break;
case DXIL::ResourceClass::CBuffer:
resource = &DM.GetCBuffer(rangeId);
registerType = RegisterType::CBV;
break;
case DXIL::ResourceClass::Sampler:
resource = &DM.GetSampler(rangeId);
registerType = RegisterType::Sampler;
break;
}
if (resource != nullptr) {
ret.index = createHandle.get_index();
ret.registerType = registerType;
ret.accessStyle = AccessStyle::FromRootSig;
ret.RegisterID = resource->GetID();
ret.RegisterSpace = resource->GetSpaceID();
}
}
} else if (hlsl::OP::IsDxilOpFuncCallInst(handle,
hlsl::OP::OpCode::AnnotateHandle)) {
DxilInst_AnnotateHandle annotateHandle(handle);
auto properties = hlsl::resource_helper::loadPropsFromAnnotateHandle(
annotateHandle, *DM.GetShaderModel());
auto *handleCreation = dyn_cast<CallInst>(annotateHandle.get_res());
if (handleCreation != nullptr) {
if (hlsl::OP::IsDxilOpFuncCallInst(
handleCreation, hlsl::OP::OpCode::CreateHandleFromBinding)) {
DxilInst_CreateHandleFromBinding createHandleFromBinding(
handleCreation);
Constant *B = cast<Constant>(createHandleFromBinding.get_bind());
auto binding = hlsl::resource_helper::loadBindingFromConstant(*B);
ret.accessStyle = AccessStyle::FromRootSig;
ret.index = createHandleFromBinding.get_index();
ret.registerType = RegisterTypeFromResourceClass(
static_cast<hlsl::DXIL::ResourceClass>(binding.resourceClass));
ret.RegisterSpace = binding.spaceID;
} else if (hlsl::OP::IsDxilOpFuncCallInst(
handleCreation, hlsl::OP::OpCode::CreateHandleFromHeap)) {
DxilInst_CreateHandleFromHeap createHandleFromHeap(handleCreation);
ret.accessStyle = createHandleFromHeap.get_samplerHeap_val()
? AccessStyle::SamplerFromDescriptorHeap
: AccessStyle::ResourceFromDescriptorHeap;
ret.dynamicallyBoundIndex = createHandleFromHeap.get_index();
ret.registerType =
RegisterTypeFromResourceClass(properties.getResourceClass());
DynamicResourceBinding drb{};
drb.HeapIsSampler = createHandleFromHeap.get_samplerHeap_val();
drb.HeapIndex = -1;
drb.Name = "ShaderNameTodo";
if (auto *constInt =
dyn_cast<ConstantInt>(createHandleFromHeap.get_index())) {
drb.HeapIndex = constInt->getLimitedValue();
}
m_dynamicResourceBindings.emplace_back(std::move(drb));
return ret;
} else if (hlsl::OP::IsDxilOpFuncCallInst(
handleCreation, hlsl::OP::OpCode::CreateHandleForLib)) {
ret = DetermineAccessForHandleForLib(handleCreation, ret, DM);
} else {
DXASSERT_NOMSG(false);
}
}
} else if (hlsl::OP::IsDxilOpFuncCallInst(
handle, hlsl::OP::OpCode::CreateHandleForLib)) {
ret = DetermineAccessForHandleForLib(handle, ret, DM);
}
return ret;
}
static bool CheckForDynamicIndexing(OP *HlslOP, LLVMContext &Ctx,
DxilModule &DM) {
bool FoundDynamicIndexing = false;
for (llvm::Function &F : DM.GetModule()->functions()) {
if (F.isDeclaration() && !F.use_empty() && OP::IsDxilOpFunc(&F)) {
if (F.hasName()) {
if (F.getName().find("createHandleForLib") != StringRef::npos) {
auto FunctionUses = F.uses();
for (auto FI = FunctionUses.begin(); FI != FunctionUses.end();) {
auto &FunctionUse = *FI++;
auto FunctionUser = FunctionUse.getUser();
auto instruction = cast<Instruction>(FunctionUser);
Value *resourceLoad =
instruction->getOperand(kCreateHandleForLibResOpIdx);
if (auto *load = cast<LoadInst>(resourceLoad)) {
auto *resOrGep = load->getOperand(0);
if (isa<GetElementPtrInst>(resOrGep)) {
FoundDynamicIndexing = true;
break;
}
}
}
}
}
}
if (FoundDynamicIndexing) {
break;
}
}
if (!FoundDynamicIndexing) {
auto CreateHandleFn =
HlslOP->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(Ctx));
for (auto FI = CreateHandleFn->user_begin();
FI != CreateHandleFn->user_end();) {
auto *FunctionUser = *FI++;
auto instruction = cast<Instruction>(FunctionUser);
Value *index = instruction->getOperand(kCreateHandleResIndexOpIdx);
if (!isa<Constant>(index)) {
FoundDynamicIndexing = true;
break;
}
}
}
if (!FoundDynamicIndexing) {
auto CreateHandleFromBindingFn = HlslOP->GetOpFunc(
DXIL::OpCode::CreateHandleFromBinding, Type::getVoidTy(Ctx));
for (auto FI = CreateHandleFromBindingFn->user_begin();
FI != CreateHandleFromBindingFn->user_end();) {
auto *FunctionUser = *FI++;
auto instruction = cast<Instruction>(FunctionUser);
Value *index =
instruction->getOperand(kCreateHandleFromBindingResIndexOpIdx);
if (!isa<Constant>(index)) {
FoundDynamicIndexing = true;
break;
}
}
}
if (!FoundDynamicIndexing) {
auto CreateHandleFromHeapFn = HlslOP->GetOpFunc(
DXIL::OpCode::CreateHandleFromHeap, Type::getVoidTy(Ctx));
for (auto FI = CreateHandleFromHeapFn->user_begin();
FI != CreateHandleFromHeapFn->user_end();) {
auto *FunctionUser = *FI++;
auto instruction = cast<Instruction>(FunctionUser);
Value *index =
instruction->getOperand(kCreateHandleFromHeapHeapIndexOpIdx);
if (!isa<Constant>(index)) {
FoundDynamicIndexing = true;
break;
}
}
}
return FoundDynamicIndexing;
}
bool DxilShaderAccessTracking::runOnModule(Module &M) {
// This pass adds instrumentation for shader access to resources
DxilModule &DM = M.GetOrCreateDxilModule();
LLVMContext &Ctx = M.getContext();
OP *HlslOP = DM.GetOP();
bool Modified = false;
if (m_CheckForDynamicIndexing) {
bool FoundDynamicIndexing = CheckForDynamicIndexing(HlslOP, Ctx, DM);
if (FoundDynamicIndexing) {
if (OSOverride != nullptr) {
formatted_raw_ostream FOS(*OSOverride);
FOS << "FoundDynamicIndexing";
}
}
} else {
auto instrumentableFunctions =
PIXPassHelpers::GetAllInstrumentableFunctions(DM);
if (DM.m_ShaderFlags.GetForceEarlyDepthStencil()) {
if (OSOverride != nullptr) {
formatted_raw_ostream FOS(*OSOverride);
FOS << "ShouldAssumeDsvAccess";
}
}
for (auto *F : instrumentableFunctions) {
DXIL::ShaderKind shaderKind = DXIL::ShaderKind::Invalid;
if (!DM.HasDxilFunctionProps(F)) {
auto ShaderModel = DM.GetShaderModel();
shaderKind = ShaderModel->GetKind();
if (shaderKind == DXIL::ShaderKind::Library) {
continue;
}
} else {
hlsl::DxilFunctionProps const &props = DM.GetDxilFunctionProps(F);
shaderKind = props.shaderKind;
}
IRBuilder<> Builder(F->getEntryBlock().getFirstInsertionPt());
m_FunctionToUAVHandle[F] =
PIXPassHelpers::CreateUAV(DM, Builder, 0u, "PIX_CountUAV_Handle");
OP *HlslOP = DM.GetOP();
for (int accessStyle = static_cast<int>(ResourceAccessStyle::None);
accessStyle < static_cast<int>(ResourceAccessStyle::EndOfEnum);
++accessStyle) {
ResourceAccessStyle style =
static_cast<ResourceAccessStyle>(accessStyle);
m_FunctionToEncodedAccess[F][style] = HlslOP->GetU32Const(
EncodeShaderModel(shaderKind) | EncodeAccess(style));
}
}
DM.ReEmitDxilResources();
for (llvm::Function &F : M.functions()) {
if (!F.isDeclaration() || F.isIntrinsic() || !OP::IsDxilOpFunc(&F))
continue;
// Gather handle parameter indices, if any
FunctionType *fnTy =
cast<FunctionType>(F.getType()->getPointerElementType());
SmallVector<unsigned, 4> handleParams;
for (unsigned iParam = 1; iParam < fnTy->getFunctionNumParams();
++iParam) {
if (fnTy->getParamType(iParam) == HlslOP->GetHandleType())
handleParams.push_back(iParam);
}
if (handleParams.empty())
continue;
auto FunctionUses = F.uses();
for (auto FI = FunctionUses.begin(); FI != FunctionUses.end();) {
auto &FunctionUse = *FI++;
auto FunctionUser = FunctionUse.getUser();
auto Call = cast<CallInst>(FunctionUser);
auto *CallerParent = Call->getParent();
if (llvm::isa<llvm::BasicBlock>(CallerParent)) {
auto opCode = OP::GetDxilOpFuncCallInst(Call);
// Base Read/Write on function attribute - should match for all normal
// resource operations
ShaderAccessFlags readWrite = ShaderAccessFlags::Write;
if (OP::GetMemAccessAttr(opCode) ==
llvm::Attribute::AttrKind::ReadOnly)
readWrite = ShaderAccessFlags::Read;
// Special cases
switch (opCode) {
case DXIL::OpCode::GetDimensions:
// readWrite = ShaderAccessFlags::DescriptorRead; // TODO: Support
// GetDimensions
continue;
case DXIL::OpCode::BufferUpdateCounter:
readWrite = ShaderAccessFlags::Counter;
break;
case DXIL::OpCode::TraceRay:
// Read of AccelerationStructure; doesn't match function attribute
// readWrite = ShaderAccessFlags::Read; // TODO: Support
continue;
case DXIL::OpCode::RayQuery_TraceRayInline: {
// Read of AccelerationStructure; doesn't match function attribute
auto res = GetResourceFromHandle(Call->getArgOperand(2), DM);
if (res.accessStyle == AccessStyle::None) {
continue;
}
if (EmitResourceAccess(DM, res, Call, HlslOP, Ctx,
ShaderAccessFlags::Read)) {
Modified = true;
}
}
continue;
default:
break;
}
for (unsigned iParam : handleParams) {
auto res = GetResourceFromHandle(Call->getArgOperand(iParam), DM);
if (res.accessStyle == AccessStyle::None) {
continue;
}
// Don't instrument the accesses to the UAV that we just added
if (res.RegisterSpace == -2) {
break;
}
if (EmitResourceAccess(DM, res, Call, HlslOP, Ctx, readWrite)) {
Modified = true;
}
// Remaining resources are DescriptorRead.
readWrite = ShaderAccessFlags::DescriptorRead;
}
}
}
}
if (OSOverride != nullptr) {
formatted_raw_ostream FOS(*OSOverride);
FOS << "DynamicallyIndexedBindPoints=";
for (auto const &bp : m_DynamicallyIndexedBindPoints) {
FOS << EncodeRegisterType(bp.Type) << bp.Space << ':' << bp.Index
<< ';';
}
FOS << ".";
// todo: this will reflect dynamic resource names when the metadata
// exists
FOS << "DynamicallyBoundResources=";
for (auto const &drb : m_dynamicResourceBindings) {
FOS << (drb.HeapIsSampler ? 'S' : 'R') << drb.HeapIndex << ';';
}
FOS << ".";
}
}
// Done with these guys:
m_GEPOperandAsInstructionDestroyers.clear();
return Modified;
}
char DxilShaderAccessTracking::ID = 0;
ModulePass *llvm::createDxilShaderAccessTrackingPass() {
return new DxilShaderAccessTracking();
}
INITIALIZE_PASS(DxilShaderAccessTracking,
"hlsl-dxil-pix-shader-access-instrumentation",
"HLSL DXIL shader access tracking for PIX", false, false)