/////////////////////////////////////////////////////////////////////////////// // // // DxilAnnotateWithVirtualRegister.cpp // // Copyright (C) Microsoft Corporation. All rights reserved. // // This file is distributed under the University of Illinois Open Source // // This file is distributed under the University of Illinois Open Source // // License. See LICENSE.TXT for details. // // // // Annotates the llvm instructions with a virtual register number to be used // // during PIX debugging. // // // /////////////////////////////////////////////////////////////////////////////// #include #include "dxc/DXIL/DxilModule.h" #include "dxc/DXIL/DxilOperations.h" #include "dxc/DXIL/DxilUtil.h" #include "dxc/DxilPIXPasses/DxilPIXPasses.h" #include "dxc/DxilPIXPasses/DxilPIXVirtualRegisters.h" #include "dxc/Support/Global.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/Constant.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/ModuleSlotTracker.h" #include "llvm/IR/Type.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include "PixPassHelpers.h" #define DEBUG_TYPE "dxil-annotate-with-virtual-regs" uint32_t CountStructMembers(llvm::Type const *pType) { uint32_t Count = 0; if (auto *VT = llvm::dyn_cast(pType)) { // Vector types can only contain scalars: Count = VT->getVectorNumElements(); } else if (auto *ST = llvm::dyn_cast(pType)) { for (auto &El : ST->elements()) { Count += CountStructMembers(El); } } else if (auto *AT = llvm::dyn_cast(pType)) { Count = CountStructMembers(AT->getArrayElementType()) * AT->getArrayNumElements(); } else { Count = 1; } return Count; } namespace { using namespace pix_dxil; static bool IsInstrumentableFundamentalType(llvm::Type *pAllocaTy) { return pAllocaTy->isFloatingPointTy() || pAllocaTy->isIntegerTy(); } class DxilAnnotateWithVirtualRegister : public llvm::ModulePass { public: static char ID; DxilAnnotateWithVirtualRegister() : llvm::ModulePass(ID) {} bool runOnModule(llvm::Module &M) override; void applyOptions(llvm::PassOptions O) override; private: void AnnotateValues(llvm::Instruction *pI); void AnnotateStore(llvm::Instruction *pI); bool IsAllocaRegisterWrite(llvm::Value *V, llvm::AllocaInst **pAI, llvm::Value **pIdx); void AnnotateAlloca(llvm::AllocaInst *pAlloca); void AnnotateGeneric(llvm::Instruction *pI); void AssignNewDxilRegister(llvm::Instruction *pI); void AssignNewAllocaRegister(llvm::AllocaInst *pAlloca, std::uint32_t C); hlsl::DxilModule *m_DM; std::uint32_t m_uVReg; std::unique_ptr m_MST; int m_StartInstruction = 0; void Init(llvm::Module &M) { m_DM = &M.GetOrCreateDxilModule(); m_uVReg = 0; m_MST.reset(new llvm::ModuleSlotTracker(&M)); auto functions = m_DM->GetExportedFunctions(); for (auto &fn : functions) { m_MST->incorporateFunction(*fn); } } }; void DxilAnnotateWithVirtualRegister::applyOptions(llvm::PassOptions O) { GetPassOptionInt(O, "startInstruction", &m_StartInstruction, 0); } char DxilAnnotateWithVirtualRegister::ID = 0; static llvm::StringRef PrintableSubsetOfMangledFunctionName(llvm::StringRef mangled) { llvm::StringRef printableNameSubset = mangled; if (mangled.size() > 2 && mangled[0] == '\1' && mangled[1] == '?') { printableNameSubset = llvm::StringRef(mangled.data() + 2, mangled.size() - 2); } return printableNameSubset; } bool DxilAnnotateWithVirtualRegister::runOnModule(llvm::Module &M) { Init(M); if (m_DM == nullptr) { return false; } unsigned int Major = 0; unsigned int Minor = 0; m_DM->GetValidatorVersion(Major, Minor); if (hlsl::DXIL::CompareVersions(Major, Minor, 1, 4) < 0) { m_DM->SetValidatorVersion(1, 4); } std::uint32_t InstNum = m_StartInstruction; auto instrumentableFunctions = PIXPassHelpers::GetAllInstrumentableFunctions(*m_DM); for (auto *F : instrumentableFunctions) { for (auto &block : F->getBasicBlockList()) { for (llvm::Instruction &I : block.getInstList()) { AnnotateValues(&I); } } } for (auto *F : instrumentableFunctions) { for (auto &block : F->getBasicBlockList()) { for (llvm::Instruction &I : block.getInstList()) { AnnotateStore(&I); } } } for (auto *F : instrumentableFunctions) { int InstructionRangeStart = InstNum; int InstructionRangeEnd = InstNum; for (auto &block : F->getBasicBlockList()) { for (llvm::Instruction &I : block.getInstList()) { // If the instruction is part of the debug value instrumentation added // by this pass, it doesn't need to be instrumented for the PIX user. uint32_t unused1, unused2; if (auto *Alloca = llvm::dyn_cast(&I)) if (PixAllocaReg::FromInst(Alloca, &unused1, &unused2)) continue; if (!llvm::isa(&I)) { pix_dxil::PixDxilInstNum::AddMD(M.getContext(), &I, InstNum++); InstructionRangeEnd = InstNum; } } } if (OSOverride != nullptr) { auto shaderKind = PIXPassHelpers::GetFunctionShaderKind(*m_DM, F); std::string FunctioNamePlusKind = F->getName().str() + " " + hlsl::ShaderModel::GetKindName(shaderKind); *OSOverride << "InstructionRange: "; llvm::StringRef printableNameSubset = PrintableSubsetOfMangledFunctionName(FunctioNamePlusKind); *OSOverride << InstructionRangeStart << " " << InstructionRangeEnd << " " << printableNameSubset << "\n"; } } if (OSOverride != nullptr) { // Print a set of strings of the exemplary form "InstructionCount: // " if (m_DM->GetShaderModel()->GetKind() == hlsl::ShaderModel::Kind::Library) *OSOverride << "\nIsLibrary\n"; *OSOverride << "\nInstructionCount:" << InstNum << "\n"; } m_DM = nullptr; return m_uVReg > 0; } void DxilAnnotateWithVirtualRegister::AnnotateValues(llvm::Instruction *pI) { if (auto *pAlloca = llvm::dyn_cast(pI)) { AnnotateAlloca(pAlloca); } else if (!pI->getType()->isPointerTy()) { AnnotateGeneric(pI); } else if (!pI->getType()->isVoidTy()) { AnnotateGeneric(pI); } } void DxilAnnotateWithVirtualRegister::AnnotateStore(llvm::Instruction *pI) { auto *pSt = llvm::dyn_cast(pI); if (pSt == nullptr) { return; } llvm::AllocaInst *Alloca; llvm::Value *Index; if (!IsAllocaRegisterWrite(pSt->getPointerOperand(), &Alloca, &Index)) { return; } llvm::MDNode *AllocaReg = Alloca->getMetadata(PixAllocaReg::MDName); if (AllocaReg == nullptr) { return; } PixAllocaRegWrite::AddMD(m_DM->GetCtx(), pSt, AllocaReg, Index); } static uint32_t GetStructOffset(llvm::GetElementPtrInst *pGEP, uint32_t &GEPOperandIndex, llvm::Type *pElementType) { if (IsInstrumentableFundamentalType(pElementType)) { return 0; } else if (auto *pArray = llvm::dyn_cast(pElementType)) { // 1D-array example: // // When referring to the zeroth member of the array in this struct: // struct smallPayload { // uint32_t Array[2]; // }; // getelementptr inbounds% struct.smallPayload, % struct.smallPayload*% p, // i32 0, i32 0, i32 0 The zeros above are: // -The zeroth element in the array pointed to (so, the actual struct) // -The zeroth element in the struct (which is the array) // -The zeroth element in that array auto *pArrayIndex = llvm::dyn_cast(pGEP->getOperand(GEPOperandIndex++)); if (pArrayIndex == nullptr) { return 0; } uint32_t ArrayIndex = pArrayIndex->getLimitedValue(); auto pArrayElementType = pArray->getArrayElementType(); uint32_t MemberIndex = ArrayIndex * CountStructMembers(pArrayElementType); return MemberIndex + GetStructOffset(pGEP, GEPOperandIndex, pArrayElementType); } else if (auto *pStruct = llvm::dyn_cast(pElementType)) { DXASSERT(GEPOperandIndex < pGEP->getNumOperands(), "Unexpectedly read too many GetElementPtrInst operands"); auto *pMemberIndex = llvm::dyn_cast(pGEP->getOperand(GEPOperandIndex++)); if (pMemberIndex == nullptr) { return 0; } uint32_t MemberIndex = pMemberIndex->getLimitedValue(); uint32_t MemberOffset = 0; for (uint32_t i = 0; i < MemberIndex; ++i) { MemberOffset += CountStructMembers(pStruct->getElementType(i)); } return MemberOffset + GetStructOffset(pGEP, GEPOperandIndex, pStruct->getElementType(MemberIndex)); } else { return 0; } } bool DxilAnnotateWithVirtualRegister::IsAllocaRegisterWrite( llvm::Value *V, llvm::AllocaInst **pAI, llvm::Value **pIdx) { llvm::IRBuilder<> B(m_DM->GetCtx()); *pAI = nullptr; *pIdx = nullptr; if (auto *pGEP = llvm::dyn_cast(V)) { uint32_t precedingMemberCount = 0; auto *Alloca = llvm::dyn_cast(pGEP->getPointerOperand()); if (Alloca == nullptr) { // In the case of vector types (floatN, matrixNxM), the pointer operand // will actually point to another element pointer instruction. But this // isn't a recursive thing- we only need to check these two levels. if (auto *pPointerGEP = llvm::dyn_cast( pGEP->getPointerOperand())) { Alloca = llvm::dyn_cast(pPointerGEP->getPointerOperand()); if (Alloca == nullptr) { return false; } // And of course the member we're after might not be at the beginning of // the struct: auto *pStructType = llvm::dyn_cast( pPointerGEP->getPointerOperandType()->getPointerElementType()); auto *pStructMember = llvm::dyn_cast(pPointerGEP->getOperand(2)); uint64_t memberIndex = pStructMember->getLimitedValue(); for (uint64_t i = 0; i < memberIndex; ++i) { precedingMemberCount += CountStructMembers(pStructType->getStructElementType(i)); } } else { return false; } } // Deref pointer type to get struct type: llvm::Type *pStructType = pGEP->getPointerOperandType(); pStructType = pStructType->getContainedType(0); // The 1th operand is an index into the array of the above type. In DXIL // derived from HLSL, we always expect this to be 0 (since llvm structs are // only used for single-valued objects in HLSL, such as the // amplification-to-mesh or TraceRays payloads). uint32_t GEPOperandIndex = 1; auto *pBaseArrayIndex = llvm::dyn_cast(pGEP->getOperand(GEPOperandIndex++)); DXASSERT_LOCALVAR(pBaseArrayIndex, pBaseArrayIndex != nullptr, "null base array index pointer"); DXASSERT_LOCALVAR(pBaseArrayIndex, pBaseArrayIndex->getLimitedValue() == 0, "unexpected >0 array index"); // From here on, the indices always come in groups: first, the type // referenced in the current struct. If that type is an (n-dimensional) // array, then there follow n indices. auto offset = GetStructOffset(pGEP, GEPOperandIndex, pStructType); llvm::Value *IndexValue = B.getInt32(offset + precedingMemberCount); if (IndexValue != nullptr) { *pAI = Alloca; *pIdx = IndexValue; return true; } return false; } if (auto *pAlloca = llvm::dyn_cast(V)) { llvm::Type *pAllocaTy = pAlloca->getType()->getElementType(); if (!IsInstrumentableFundamentalType(pAllocaTy)) { return false; } *pAI = pAlloca; *pIdx = B.getInt32(0); return true; } return false; } void DxilAnnotateWithVirtualRegister::AnnotateAlloca( llvm::AllocaInst *pAlloca) { llvm::Type *pAllocaTy = pAlloca->getType()->getElementType(); if (IsInstrumentableFundamentalType(pAllocaTy)) { AssignNewAllocaRegister(pAlloca, 1); } else if (auto *AT = llvm::dyn_cast(pAllocaTy)) { AssignNewAllocaRegister(pAlloca, AT->getNumElements()); } else if (auto *ST = llvm::dyn_cast(pAllocaTy)) { AssignNewAllocaRegister(pAlloca, CountStructMembers(ST)); } else { DXASSERT_ARGS(false, "Unhandled alloca kind: %d", pAllocaTy->getTypeID()); } } void DxilAnnotateWithVirtualRegister::AnnotateGeneric(llvm::Instruction *pI) { if (auto *GEP = llvm::dyn_cast(pI)) { // https://llvm.org/docs/LangRef.html#getelementptr-instruction DXASSERT(!GEP->getOperand(1)->getType()->isVectorTy(), "struct vectors not supported"); llvm::AllocaInst *StructAlloca = llvm::dyn_cast(GEP->getOperand(0)); if (StructAlloca != nullptr) { // This is the case of a pointer to a struct member. // We treat it as an alias of the actual member in the alloca. std::uint32_t baseStructRegNum = 0; std::uint32_t regSize = 0; if (pix_dxil::PixAllocaReg::FromInst(StructAlloca, &baseStructRegNum, ®Size)) { llvm::ConstantInt *OffsetAsInt = llvm::dyn_cast(GEP->getOperand(2)); if (OffsetAsInt != nullptr) { std::uint32_t OffsetInElementsFromStructureStart = static_cast( OffsetAsInt->getValue().getLimitedValue()); DXASSERT(OffsetInElementsFromStructureStart < regSize, "Structure member offset out of expected range"); std::uint32_t OffsetInValuesFromStructureStart = OffsetInElementsFromStructureStart; if (auto *ST = llvm::dyn_cast( GEP->getPointerOperandType()->getPointerElementType())) { DXASSERT(OffsetInElementsFromStructureStart < ST->getNumElements(), "Offset into struct is bigger than struct"); OffsetInValuesFromStructureStart = 0; for (std::uint32_t Element = 0; Element < OffsetInElementsFromStructureStart; ++Element) { OffsetInValuesFromStructureStart += CountStructMembers(ST->getElementType(Element)); } } PixDxilReg::AddMD(m_DM->GetCtx(), pI, baseStructRegNum + OffsetInValuesFromStructureStart); } } } } else { if (!IsInstrumentableFundamentalType(pI->getType())) { return; } AssignNewDxilRegister(pI); } } void DxilAnnotateWithVirtualRegister::AssignNewDxilRegister( llvm::Instruction *pI) { PixDxilReg::AddMD(m_DM->GetCtx(), pI, m_uVReg); m_uVReg++; } void DxilAnnotateWithVirtualRegister::AssignNewAllocaRegister( llvm::AllocaInst *pAlloca, std::uint32_t C) { PixAllocaReg::AddMD(m_DM->GetCtx(), pAlloca, m_uVReg, C); m_uVReg += C; } } // namespace using namespace llvm; INITIALIZE_PASS(DxilAnnotateWithVirtualRegister, DEBUG_TYPE, "Annotates each instruction in the DXIL module with a virtual " "register number", false, false) ModulePass *llvm::createDxilAnnotateWithVirtualRegisterPass() { return new DxilAnnotateWithVirtualRegister(); }