| /////////////////////////////////////////////////////////////////////////////// |
| // // |
| // 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) |