|
@@ -0,0 +1,633 @@
|
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
|
+// //
|
|
|
+// 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/HLSL/DxilGenerationPass.h"
|
|
|
+#include "dxc/HLSL/DxilOperations.h"
|
|
|
+#include "dxc/HLSL/DxilSignatureElement.h"
|
|
|
+#include "dxc/HLSL/DxilModule.h"
|
|
|
+#include "dxc/Support/Global.h"
|
|
|
+#include "dxc/HLSL/DxilTypeSystem.h"
|
|
|
+#include "dxc/HLSL/DxilConstants.h"
|
|
|
+#include "dxc/HLSL/DxilInstructions.h"
|
|
|
+#include "dxc/HLSL/DxilSpanAllocator.h"
|
|
|
+
|
|
|
+#include "llvm/IR/Instructions.h"
|
|
|
+#include "llvm/IR/IntrinsicInst.h"
|
|
|
+#include "llvm/IR/InstIterator.h"
|
|
|
+#include "llvm/IR/Module.h"
|
|
|
+#include "llvm/IR/PassManager.h"
|
|
|
+#include "llvm/ADT/BitVector.h"
|
|
|
+#include "llvm/Pass.h"
|
|
|
+#include "llvm/Support/FormattedStream.h"
|
|
|
+#include "llvm/Transforms/Utils/Local.h"
|
|
|
+#include <memory>
|
|
|
+#include <map>
|
|
|
+#include <deque>
|
|
|
+
|
|
|
+#include <winerror.h>
|
|
|
+
|
|
|
+using namespace llvm;
|
|
|
+using namespace hlsl;
|
|
|
+
|
|
|
+
|
|
|
+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
|
|
|
+};
|
|
|
+
|
|
|
+enum class RegisterType
|
|
|
+{
|
|
|
+ CBV,
|
|
|
+ SRV,
|
|
|
+ UAV,
|
|
|
+ RTV,
|
|
|
+ DSV,
|
|
|
+ Sampler,
|
|
|
+ SOV,
|
|
|
+ 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;
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+struct DxilResourceAndClass {
|
|
|
+ DxilResourceBase * resource;
|
|
|
+ Value * index;
|
|
|
+ DXIL::ResourceClass resClass;
|
|
|
+};
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+class DxilShaderAccessTracking : public ModulePass {
|
|
|
+public:
|
|
|
+ static char ID; // Pass identification, replacement for typeid
|
|
|
+ explicit DxilShaderAccessTracking() : ModulePass(ID) {}
|
|
|
+ const char *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(DxilResourceAndClass &res, Instruction * instruction, OP * HlslOP, LLVMContext & Ctx, ShaderAccessFlags readWrite);
|
|
|
+
|
|
|
+private:
|
|
|
+ bool m_CheckForDynamicIndexing = false;
|
|
|
+ std::vector<std::pair<RSRegisterIdentifier, ShaderAccessFlags>> m_limitedAccessOutputs;
|
|
|
+ std::map<RegisterTypeAndSpace, SlotRange> m_slotAssignments;
|
|
|
+ CallInst *m_HandleForUAV;
|
|
|
+ std::set<RSRegisterIdentifier> m_DynamicallyIndexedBindPoints;
|
|
|
+};
|
|
|
+
|
|
|
+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 'R': return RegisterType::RTV;
|
|
|
+ case 'D': return RegisterType::DSV;
|
|
|
+ case 'M': return RegisterType::Sampler;
|
|
|
+ case 'O': return RegisterType::SOV;
|
|
|
+ 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::RTV: return 'R';
|
|
|
+ case RegisterType::DSV: return 'D';
|
|
|
+ case RegisterType::Sampler: return 'M';
|
|
|
+ case RegisterType::SOV: return 'O';
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse limited access outputs
|
|
|
+ rt = ParseRegisterType(config);
|
|
|
+ while (rt != RegisterType::Terminator) {
|
|
|
+
|
|
|
+ RSRegisterIdentifier rid;
|
|
|
+ rid.Type = rt;
|
|
|
+
|
|
|
+ rid.Space = DeserializeInt(config);
|
|
|
+ ValidateDelimiter(config, ':');
|
|
|
+
|
|
|
+ rid.Index = DeserializeInt(config);
|
|
|
+ ValidateDelimiter(config, ':');
|
|
|
+
|
|
|
+ unsigned AccessFlags = DeserializeInt(config);
|
|
|
+ ValidateDelimiter(config, ';');
|
|
|
+
|
|
|
+ m_limitedAccessOutputs.emplace_back(rid, static_cast<ShaderAccessFlags>(AccessFlags));
|
|
|
+
|
|
|
+ rt = ParseRegisterType(config);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DxilShaderAccessTracking::EmitAccess(LLVMContext & Ctx, OP *HlslOP, IRBuilder<> & Builder, Value * slot, ShaderAccessFlags access)
|
|
|
+{
|
|
|
+ // Slots are four bytes each:
|
|
|
+ auto ByteIndex = Builder.CreateMul(slot, HlslOP->GetU32Const(4));
|
|
|
+
|
|
|
+ // Insert the UAV increment instruction:
|
|
|
+
|
|
|
+ Function* AtomicOpFunc = HlslOP->GetOpFunc(OP::OpCode::AtomicBinOp, Type::getInt32Ty(Ctx));
|
|
|
+ Constant* AtomicBinOpcode = HlslOP->GetU32Const((unsigned)OP::OpCode::AtomicBinOp);
|
|
|
+ Constant* AtomicOr = HlslOP->GetU32Const((unsigned)DXIL::AtomicBinOpCode::Or);
|
|
|
+
|
|
|
+ Constant* AccessValue = HlslOP->GetU32Const(static_cast<unsigned>(access));
|
|
|
+ UndefValue* UndefArg = UndefValue::get(Type::getInt32Ty(Ctx));
|
|
|
+
|
|
|
+ (void)Builder.CreateCall(AtomicOpFunc, {
|
|
|
+ AtomicBinOpcode,// i32, ; opcode
|
|
|
+ m_HandleForUAV, // %dx.types.Handle, ; resource handle
|
|
|
+ AtomicOr, // i32, ; binary operation code : EXCHANGE, IADD, AND, OR, XOR, IMIN, IMAX, UMIN, UMAX
|
|
|
+ ByteIndex, // i32, ; coordinate c0: byte offset
|
|
|
+ UndefArg, // i32, ; coordinate c1 (unused)
|
|
|
+ UndefArg, // i32, ; coordinate c2 (unused)
|
|
|
+ AccessValue // i32) ; OR value
|
|
|
+ }, "UAVOrResult");
|
|
|
+}
|
|
|
+
|
|
|
+bool DxilShaderAccessTracking::EmitResourceAccess(DxilResourceAndClass &res, Instruction * instruction, OP * HlslOP, LLVMContext & Ctx, ShaderAccessFlags readWrite) {
|
|
|
+
|
|
|
+ RegisterTypeAndSpace typeAndSpace{ RegisterTypeFromResourceClass(res.resClass), res.resource->GetSpaceID() };
|
|
|
+
|
|
|
+ auto slot = m_slotAssignments.find(typeAndSpace);
|
|
|
+ // If the assignment isn't found, we assume it's not accessed
|
|
|
+ if (slot != m_slotAssignments.end()) {
|
|
|
+
|
|
|
+ IRBuilder<> Builder(instruction);
|
|
|
+ Value * slotIndex;
|
|
|
+
|
|
|
+ if (isa<ConstantInt>(res.index)) {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ RSRegisterIdentifier id{ typeAndSpace.Type, typeAndSpace.Space, res.resource->GetID() };
|
|
|
+ m_DynamicallyIndexedBindPoints.emplace(std::move(id));
|
|
|
+
|
|
|
+
|
|
|
+ // 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(res.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 SlotOffset = Builder.CreateAdd(res.index, HlslOP->GetU32Const(slot->second.startSlot), "SlotOffset");
|
|
|
+
|
|
|
+ // This will drive an out-of-bounds access slot down to 0
|
|
|
+ slotIndex = Builder.CreateMul(SlotOffset, IsInBounds, "slotIndex");
|
|
|
+ }
|
|
|
+
|
|
|
+ EmitAccess(Ctx, HlslOP, Builder, slotIndex, readWrite);
|
|
|
+
|
|
|
+ return true; // did modify
|
|
|
+ }
|
|
|
+ return false; // did not modify
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+DxilResourceAndClass GetResourceFromHandle(Value * resHandle, DxilModule &DM) {
|
|
|
+
|
|
|
+ DxilResourceAndClass ret{ nullptr, nullptr, DXIL::ResourceClass::Invalid };
|
|
|
+
|
|
|
+ CallInst *handle = cast<CallInst>(resHandle);
|
|
|
+ DxilInst_CreateHandle createHandle(handle);
|
|
|
+
|
|
|
+
|
|
|
+ // Dynamic rangeId is not supported - skip and let validation report the
|
|
|
+ // error.
|
|
|
+ if (!isa<ConstantInt>(createHandle.get_rangeId()))
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ unsigned rangeId =
|
|
|
+ cast<ConstantInt>(createHandle.get_rangeId())->getLimitedValue();
|
|
|
+
|
|
|
+ auto resClass = static_cast<DXIL::ResourceClass>(createHandle.get_resourceClass_val());
|
|
|
+
|
|
|
+ switch (resClass) {
|
|
|
+ case DXIL::ResourceClass::SRV:
|
|
|
+ ret.resource = &DM.GetSRV(rangeId);
|
|
|
+ break;
|
|
|
+ case DXIL::ResourceClass::UAV:
|
|
|
+ ret.resource = &DM.GetUAV(rangeId);
|
|
|
+ break;
|
|
|
+ case DXIL::ResourceClass::CBuffer:
|
|
|
+ ret.resource = &DM.GetCBuffer(rangeId);
|
|
|
+ break;
|
|
|
+ case DXIL::ResourceClass::Sampler:
|
|
|
+ ret.resource = &DM.GetSampler(rangeId);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ DXASSERT(0, "invalid res class");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret.index = createHandle.get_index();
|
|
|
+ ret.resClass = resClass;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+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 = false;
|
|
|
+
|
|
|
+ auto CreateHandleFn = HlslOP->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(Ctx));
|
|
|
+ auto CreateHandleUses = CreateHandleFn->uses();
|
|
|
+ for (auto FI = CreateHandleUses.begin(); FI != CreateHandleUses.end(); ) {
|
|
|
+ auto & FunctionUse = *FI++;
|
|
|
+ auto FunctionUser = FunctionUse.getUser();
|
|
|
+ auto instruction = cast<Instruction>(FunctionUser);
|
|
|
+ Value * index = instruction->getOperand(3);
|
|
|
+ if (!isa<Constant>(index)) {
|
|
|
+ FoundDynamicIndexing = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (FoundDynamicIndexing) {
|
|
|
+ if (OSOverride != nullptr) {
|
|
|
+ formatted_raw_ostream FOS(*OSOverride);
|
|
|
+ FOS << "FoundDynamicIndexing";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ {
|
|
|
+ if (DM.m_ShaderFlags.GetForceEarlyDepthStencil()) {
|
|
|
+ if (OSOverride != nullptr) {
|
|
|
+ formatted_raw_ostream FOS(*OSOverride);
|
|
|
+ FOS << "ShouldAssumeDsvAccess";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ IRBuilder<> Builder(DM.GetEntryFunction()->getEntryBlock().getFirstInsertionPt());
|
|
|
+
|
|
|
+ unsigned int UAVResourceHandle = static_cast<unsigned int>(DM.GetUAVs().size());
|
|
|
+
|
|
|
+ // Set up a UAV with structure of a single int
|
|
|
+ SmallVector<llvm::Type*, 1> Elements{ Type::getInt32Ty(Ctx) };
|
|
|
+ llvm::StructType *UAVStructTy = llvm::StructType::create(Elements, "class.RWStructuredBuffer");
|
|
|
+ std::unique_ptr<DxilResource> pUAV = llvm::make_unique<DxilResource>();
|
|
|
+ pUAV->SetGlobalName("PIX_CountUAVName");
|
|
|
+ pUAV->SetGlobalSymbol(UndefValue::get(UAVStructTy->getPointerTo()));
|
|
|
+ pUAV->SetID(UAVResourceHandle);
|
|
|
+ pUAV->SetSpaceID((unsigned int)-2); // This is the reserved-for-tools register space
|
|
|
+ pUAV->SetSampleCount(1);
|
|
|
+ pUAV->SetGloballyCoherent(false);
|
|
|
+ pUAV->SetHasCounter(false);
|
|
|
+ pUAV->SetCompType(CompType::getI32());
|
|
|
+ pUAV->SetLowerBound(0);
|
|
|
+ pUAV->SetRangeSize(1);
|
|
|
+ pUAV->SetKind(DXIL::ResourceKind::RawBuffer);
|
|
|
+
|
|
|
+ auto pAnnotation = DM.GetTypeSystem().GetStructAnnotation(UAVStructTy);
|
|
|
+ if (pAnnotation == nullptr) {
|
|
|
+
|
|
|
+ pAnnotation = DM.GetTypeSystem().AddStructAnnotation(UAVStructTy);
|
|
|
+ pAnnotation->GetFieldAnnotation(0).SetCBufferOffset(0);
|
|
|
+ pAnnotation->GetFieldAnnotation(0).SetCompType(hlsl::DXIL::ComponentType::I32);
|
|
|
+ pAnnotation->GetFieldAnnotation(0).SetFieldName("count");
|
|
|
+ }
|
|
|
+
|
|
|
+ ID = DM.AddUAV(std::move(pUAV));
|
|
|
+
|
|
|
+ assert(ID == UAVResourceHandle);
|
|
|
+
|
|
|
+ // Create handle for the newly-added UAV
|
|
|
+ Function* CreateHandleOpFunc = HlslOP->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(Ctx));
|
|
|
+ Constant* CreateHandleOpcodeArg = HlslOP->GetU32Const((unsigned)DXIL::OpCode::CreateHandle);
|
|
|
+ Constant* UAVArg = HlslOP->GetI8Const(static_cast<std::underlying_type<DxilResourceBase::Class>::type>(DXIL::ResourceClass::UAV));
|
|
|
+ Constant* MetaDataArg = HlslOP->GetU32Const(ID); // position of the metadata record in the corresponding metadata list
|
|
|
+ Constant* IndexArg = HlslOP->GetU32Const(0); //
|
|
|
+ Constant* FalseArg = HlslOP->GetI1Const(0); // non-uniform resource index: false
|
|
|
+ m_HandleForUAV = Builder.CreateCall(CreateHandleOpFunc,
|
|
|
+ { CreateHandleOpcodeArg, UAVArg, MetaDataArg, IndexArg, FalseArg }, "PIX_CountUAV_Handle");
|
|
|
+
|
|
|
+ DM.ReEmitDxilResources();
|
|
|
+ }
|
|
|
+
|
|
|
+ struct ResourceAccessFunction
|
|
|
+ {
|
|
|
+ DXIL::OpCode opcode;
|
|
|
+ ShaderAccessFlags readWrite;
|
|
|
+ bool functionUsesSamplerAtIndex2;
|
|
|
+ std::vector<Type*> overloads;
|
|
|
+ };
|
|
|
+
|
|
|
+ std::vector<Type*> voidType = { Type::getVoidTy(Ctx) };
|
|
|
+ std::vector<Type*> i32 = { Type::getInt32Ty(Ctx) };
|
|
|
+ std::vector<Type*> f16f32 = { Type::getHalfTy(Ctx), Type::getFloatTy(Ctx) };
|
|
|
+ std::vector<Type*> f32i32 = { Type::getFloatTy(Ctx), Type::getInt32Ty(Ctx) };
|
|
|
+ std::vector<Type*> f32i32f64 = { Type::getFloatTy(Ctx), Type::getInt32Ty(Ctx), Type::getDoubleTy(Ctx) };
|
|
|
+ std::vector<Type*> f16f32i16i32 = { Type::getHalfTy(Ctx), Type::getFloatTy(Ctx), Type::getInt16Ty(Ctx), Type::getInt32Ty(Ctx) };
|
|
|
+ std::vector<Type*> f16f32f64i16i32i64 = { Type::getHalfTy(Ctx), Type::getFloatTy(Ctx), Type::getDoubleTy(Ctx), Type::getInt16Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt64Ty(Ctx) };
|
|
|
+
|
|
|
+
|
|
|
+ // todo: should "GetDimensions" mean a resource access?
|
|
|
+ ResourceAccessFunction raFunctions[] = {
|
|
|
+ { DXIL::OpCode::CBufferLoadLegacy , ShaderAccessFlags::Read , false, f32i32f64 },
|
|
|
+ { DXIL::OpCode::CBufferLoad , ShaderAccessFlags::Read , false, f16f32f64i16i32i64 },
|
|
|
+ { DXIL::OpCode::Sample , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::SampleBias , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::SampleLevel , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::SampleGrad , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::SampleCmp , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::SampleCmpLevelZero , ShaderAccessFlags::Read , true , f16f32 },
|
|
|
+ { DXIL::OpCode::TextureLoad , ShaderAccessFlags::Read , false, f16f32i16i32 },
|
|
|
+ { DXIL::OpCode::TextureStore , ShaderAccessFlags::Write , false, f16f32i16i32 },
|
|
|
+ { DXIL::OpCode::TextureGather , ShaderAccessFlags::Read , true , f32i32 }, // todo: SM6: f16f32i16i32 },
|
|
|
+ { DXIL::OpCode::TextureGatherCmp , ShaderAccessFlags::Read , false, f32i32 }, // todo: SM6: f16f32i16i32 },
|
|
|
+ { DXIL::OpCode::BufferLoad , ShaderAccessFlags::Read , false, f32i32 },
|
|
|
+ { DXIL::OpCode::RawBufferLoad , ShaderAccessFlags::Read , false, f32i32 },
|
|
|
+ { DXIL::OpCode::BufferStore , ShaderAccessFlags::Write , false, f32i32 },
|
|
|
+ { DXIL::OpCode::BufferUpdateCounter , ShaderAccessFlags::Counter, false, voidType },
|
|
|
+ { DXIL::OpCode::AtomicBinOp , ShaderAccessFlags::Write , false, i32 },
|
|
|
+ { DXIL::OpCode::AtomicCompareExchange , ShaderAccessFlags::Write , false, i32 },
|
|
|
+ };
|
|
|
+
|
|
|
+ for (const auto & raFunction : raFunctions) {
|
|
|
+ for (const auto & Overload : raFunction.overloads) {
|
|
|
+ Function * TheFunction = HlslOP->GetOpFunc(raFunction.opcode, Overload);
|
|
|
+ auto TexLoadFunctionUses = TheFunction->uses();
|
|
|
+ for (auto FI = TexLoadFunctionUses.begin(); FI != TexLoadFunctionUses.end(); ) {
|
|
|
+ auto & FunctionUse = *FI++;
|
|
|
+ auto FunctionUser = FunctionUse.getUser();
|
|
|
+ auto instruction = cast<Instruction>(FunctionUser);
|
|
|
+
|
|
|
+ auto res = GetResourceFromHandle(instruction->getOperand(1), DM);
|
|
|
+
|
|
|
+ // Don't instrument the accesses to the UAV that we just added
|
|
|
+ if (res.resource->GetSpaceID() == (unsigned)-2) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (EmitResourceAccess(res, instruction, HlslOP, Ctx, raFunction.readWrite)) {
|
|
|
+ Modified = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (raFunction.functionUsesSamplerAtIndex2) {
|
|
|
+ auto sampler = GetResourceFromHandle(instruction->getOperand(2), DM);
|
|
|
+ if (EmitResourceAccess(sampler, instruction, HlslOP, Ctx, ShaderAccessFlags::Read)) {
|
|
|
+ Modified = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // StoreOutput for render-targets:
|
|
|
+ for (const auto & Overload : f16f32i16i32) {
|
|
|
+ Function * TheFunction = HlslOP->GetOpFunc(DXIL::OpCode::StoreOutput, Overload);
|
|
|
+ auto FunctionUses = TheFunction->uses();
|
|
|
+ for (auto FI = FunctionUses.begin(); FI != FunctionUses.end(); ) {
|
|
|
+ auto & FunctionUse = *FI++;
|
|
|
+ auto FunctionUser = FunctionUse.getUser();
|
|
|
+ auto instruction = cast<Instruction>(FunctionUser);
|
|
|
+
|
|
|
+ unsigned outputId = cast<ConstantInt>(instruction->getOperand(1))->getLimitedValue();
|
|
|
+
|
|
|
+ const DxilSignatureElement & sig = DM.GetOutputSignature().GetElement(outputId);
|
|
|
+
|
|
|
+ if (sig.GetSemantic()->GetKind() == DXIL::SemanticKind::Target){
|
|
|
+
|
|
|
+ auto slot = m_slotAssignments.find({ RegisterType::RTV, 0 });
|
|
|
+
|
|
|
+ if (slot != m_slotAssignments.end()) {
|
|
|
+ IRBuilder<> Builder(instruction);
|
|
|
+ EmitAccess(
|
|
|
+ Ctx,
|
|
|
+ HlslOP,
|
|
|
+ Builder,
|
|
|
+ HlslOP->GetU32Const(slot->second.startSlot + sig.GetSemanticStartIndex()),
|
|
|
+ ShaderAccessFlags::Write);
|
|
|
+ Modified = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (auto const & limited : m_limitedAccessOutputs) {
|
|
|
+
|
|
|
+ auto slot = m_slotAssignments.find({ limited.first.Type, limited.first.Space });
|
|
|
+
|
|
|
+ if (slot != m_slotAssignments.end()) {
|
|
|
+ IRBuilder<> Builder(instruction);
|
|
|
+ EmitAccess(
|
|
|
+ Ctx,
|
|
|
+ HlslOP,
|
|
|
+ Builder,
|
|
|
+ HlslOP->GetU32Const(slot->second.startSlot),
|
|
|
+ ShaderAccessFlags::Write);
|
|
|
+ Modified = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // We do the limited access outputs (e.g. depth) on the first StoreOutput to the render target,
|
|
|
+ // a moment in the shader which is a good proxy for "this invocation hasn't been discarded".
|
|
|
+ m_limitedAccessOutputs.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // EmitStream for stream out
|
|
|
+ {
|
|
|
+ Function * TheFunction = HlslOP->GetOpFunc(DXIL::OpCode::EmitStream, Type::getVoidTy(Ctx));
|
|
|
+ auto FunctionUses = TheFunction->uses();
|
|
|
+ for (auto FI = FunctionUses.begin(); FI != FunctionUses.end(); ) {
|
|
|
+ auto & FunctionUse = *FI++;
|
|
|
+ auto FunctionUser = FunctionUse.getUser();
|
|
|
+ auto instruction = cast<Instruction>(FunctionUser);
|
|
|
+
|
|
|
+ unsigned outputId = cast<ConstantInt>(instruction->getOperand(DXIL::OperandIndex::kStreamEmitCutIDOpIdx))->getLimitedValue();
|
|
|
+
|
|
|
+ auto slot = m_slotAssignments.find({ RegisterType::SOV, 0 /* register space */ });
|
|
|
+
|
|
|
+ if (slot != m_slotAssignments.end()) {
|
|
|
+ IRBuilder<> Builder(instruction);
|
|
|
+ EmitAccess(
|
|
|
+ Ctx,
|
|
|
+ HlslOP,
|
|
|
+ Builder,
|
|
|
+ HlslOP->GetU32Const(slot->second.startSlot + outputId),
|
|
|
+ ShaderAccessFlags::Write);
|
|
|
+ Modified = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 << ".";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|