blob: c41d581d63304d9b64dced60f45d528994647cd8 [file] [log] [blame] [edit]
///////////////////////////////////////////////////////////////////////////////
// //
// DxcPixLiveVariables.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. //
// //
// Defines the mapping between instructions and the set of live variables //
// for it. //
// //
///////////////////////////////////////////////////////////////////////////////
#include "dxc/Support/WinIncludes.h"
#include "DxcPixDxilDebugInfo.h"
#include "DxcPixLiveVariables.h"
#include "DxcPixLiveVariables_FragmentIterator.h"
#include "DxilDiaSession.h"
#include "dxc/DXIL/DxilUtil.h"
#include "dxc/Support/Global.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include <unordered_map>
// If a function is inlined, then the scope of variables within that function
// will be that scope. Since many such callers may inline the function, we
// actually need a "scope" that's unique to each "instance" of that function.
// The caller's scope would be unique, but would have the undesirable
// side-effect of smushing all of the function's variables together with the
// caller's variables, at least from the point of view of PIX. The pair of
// scopes (the function's and the caller's) is both unique to that particular
// inlining, and distinct from the caller's scope by itself.
class UniqueScopeForInlinedFunctions {
llvm::DIScope *FunctionScope;
llvm::DIScope *InlinedAtScope;
public:
bool Valid() const { return FunctionScope != nullptr; }
void AscendScopeHierarchy() {
// DINamespace has a getScope member (that hides DIScope's)
// that returns a DIScope directly, but if that namespace
// is at file-level scope, it will return nullptr.
if (auto Namespace = llvm::dyn_cast<llvm::DINamespace>(FunctionScope)) {
if (auto *ContainingScope = Namespace->getScope()) {
if (FunctionScope == InlinedAtScope)
InlinedAtScope = FunctionScope = ContainingScope;
else
FunctionScope = ContainingScope;
return;
}
}
const llvm::DITypeIdentifierMap EmptyMap;
if (FunctionScope == InlinedAtScope)
InlinedAtScope = FunctionScope =
FunctionScope->getScope().resolve(EmptyMap);
else
FunctionScope = FunctionScope->getScope().resolve(EmptyMap);
}
static UniqueScopeForInlinedFunctions Create(llvm::DebugLoc const &DbgLoc,
llvm::DIScope *FunctionScope) {
UniqueScopeForInlinedFunctions ret;
ret.FunctionScope = FunctionScope;
ret.InlinedAtScope =
llvm::dyn_cast_or_null<llvm::DIScope>(DbgLoc.getInlinedAtScope());
return ret;
}
bool operator==(const UniqueScopeForInlinedFunctions &o) const {
return FunctionScope == o.FunctionScope &&
InlinedAtScope == o.InlinedAtScope;
}
std::size_t operator()(const UniqueScopeForInlinedFunctions &k) const {
using std::hash;
using std::size_t;
const uint64_t f = reinterpret_cast<uint64_t>(k.FunctionScope);
const uint64_t s = reinterpret_cast<uint64_t>(k.InlinedAtScope);
std::hash<uint64_t> h;
return (h(f) ^ h(s));
}
};
// ValidateDbgDeclare ensures that all of the bits in
// [FragmentSizeInBits, FragmentOffsetInBits) are currently
// not assigned to a dxil alloca register -- i.e., it
// tries to find overlapping alloca registers -- which should
// never happen -- to report the issue.
void ValidateDbgDeclare(dxil_debug_info::VariableInfo *VarInfo,
unsigned FragmentSizeInBits,
unsigned FragmentOffsetInBits) {
// #DSLTodo: When operating on libraries, each exported
// function is instrumented separately, resulting in
// overlapping variables. This is not a problem for, e.g.
// raytracing debugging because WinPIX only ever (so far)
// invokes debugging on one export at a time.
// With the advent of DSL, this will have to change...
#if 0 // ndef NDEBUG
for (unsigned i = 0; i < FragmentSizeInBits; ++i)
{
const unsigned BitNum = FragmentOffsetInBits + i;
VarInfo->m_DbgDeclareValidation.resize(
std::max<unsigned>(VarInfo->m_DbgDeclareValidation.size(),
BitNum + 1));
assert(!VarInfo->m_DbgDeclareValidation[BitNum]);
VarInfo->m_DbgDeclareValidation[BitNum] = true;
}
#endif // !NDEBUG
}
struct dxil_debug_info::LiveVariables::Impl {
using VariableInfoMap =
std::unordered_map<llvm::DIVariable *, std::unique_ptr<VariableInfo>>;
using LiveVarsMap =
std::unordered_map<UniqueScopeForInlinedFunctions, VariableInfoMap,
UniqueScopeForInlinedFunctions>;
IMalloc *m_pMalloc;
DxcPixDxilDebugInfo *m_pDxilDebugInfo;
llvm::Module *m_pModule;
LiveVarsMap m_LiveVarsDbgDeclare;
VariableInfoMap m_LiveGlobalVarsDbgDeclare;
void Init(IMalloc *pMalloc, DxcPixDxilDebugInfo *pDxilDebugInfo,
llvm::Module *pModule);
void Init_DbgDeclare(llvm::DbgDeclareInst *DbgDeclare);
VariableInfo *AssignValueToOffset(VariableInfoMap *VarInfoMap,
llvm::DIVariable *Var, llvm::Value *Address,
unsigned FragmentIndex,
unsigned FragmentOffsetInBits);
bool IsVariableLive(const VariableInfoMap::value_type &VarAndInfo,
const llvm::DIScope *S, const llvm::DebugLoc &DL);
};
void dxil_debug_info::LiveVariables::Impl::Init(
IMalloc *pMalloc, DxcPixDxilDebugInfo *pDxilDebugInfo,
llvm::Module *pModule) {
m_pMalloc = pMalloc;
m_pDxilDebugInfo = pDxilDebugInfo;
m_pModule = pModule;
llvm::Function *DbgDeclareFn =
llvm::Intrinsic::getDeclaration(m_pModule, llvm::Intrinsic::dbg_declare);
for (llvm::User *U : DbgDeclareFn->users()) {
if (auto *DbgDeclare = llvm::dyn_cast<llvm::DbgDeclareInst>(U)) {
Init_DbgDeclare(DbgDeclare);
}
}
}
void dxil_debug_info::LiveVariables::Impl::Init_DbgDeclare(
llvm::DbgDeclareInst *DbgDeclare) {
llvm::Value *Address = DbgDeclare->getAddress();
auto *Variable = DbgDeclare->getVariable();
auto *Expression = DbgDeclare->getExpression();
if (Address == nullptr || Variable == nullptr || Expression == nullptr) {
return;
}
auto *AddressAsAlloca = llvm::dyn_cast<llvm::AllocaInst>(Address);
if (AddressAsAlloca == nullptr) {
return;
}
auto S = UniqueScopeForInlinedFunctions::Create(DbgDeclare->getDebugLoc(),
Variable->getScope());
if (!S.Valid()) {
return;
}
auto Iter = CreateMemberIterator(DbgDeclare, m_pModule->getDataLayout(),
AddressAsAlloca, Expression);
if (!Iter) {
// MemberIterator creation failure, this skip this var.
return;
}
VariableInfoMap *LiveVarInfoMap;
if (Variable->getName().startswith("global.")) {
LiveVarInfoMap = &m_LiveGlobalVarsDbgDeclare;
} else {
LiveVarInfoMap = &m_LiveVarsDbgDeclare[S];
}
unsigned FragmentIndex;
while (Iter->Next(&FragmentIndex)) {
const unsigned FragmentSizeInBits = Iter->SizeInBits(FragmentIndex);
const unsigned FragmentOffsetInBits = Iter->OffsetInBits(FragmentIndex);
VariableInfo *VarInfo = AssignValueToOffset(
LiveVarInfoMap, Variable, Address, FragmentIndex, FragmentOffsetInBits);
// SROA can split structs so that multiple allocas back the same variable.
// In this case the expression will be empty
if (Expression->getNumElements() != 0) {
ValidateDbgDeclare(VarInfo, FragmentSizeInBits, FragmentOffsetInBits);
}
}
}
dxil_debug_info::VariableInfo *
dxil_debug_info::LiveVariables::Impl::AssignValueToOffset(
VariableInfoMap *VarInfoMap, llvm::DIVariable *Variable,
llvm::Value *Address, unsigned FragmentIndex,
unsigned FragmentOffsetInBits) {
// FragmentIndex is the index within the alloca'd value
// FragmentOffsetInBits is the offset within the HLSL variable
// that maps to Address[FragmentIndex]
auto it = VarInfoMap->find(Variable);
if (it == VarInfoMap->end()) {
auto InsertIt =
VarInfoMap->emplace(Variable, std::make_unique<VariableInfo>(Variable));
assert(InsertIt.second);
it = InsertIt.first;
}
auto *VarInfo = it->second.get();
auto &FragmentLocation = VarInfo->m_ValueLocationMap[FragmentOffsetInBits];
FragmentLocation.m_V = Address;
FragmentLocation.m_FragmentIndex = FragmentIndex;
return VarInfo;
}
dxil_debug_info::LiveVariables::LiveVariables() = default;
dxil_debug_info::LiveVariables::~LiveVariables() = default;
HRESULT
dxil_debug_info::LiveVariables::Init(DxcPixDxilDebugInfo *pDxilDebugInfo) {
Clear();
m_pImpl->Init(pDxilDebugInfo->GetMallocNoRef(), pDxilDebugInfo,
pDxilDebugInfo->GetModuleRef());
return S_OK;
}
void dxil_debug_info::LiveVariables::Clear() {
m_pImpl.reset(new dxil_debug_info::LiveVariables::Impl());
}
HRESULT dxil_debug_info::LiveVariables::GetLiveVariablesAtInstruction(
llvm::Instruction *IP, IDxcPixDxilLiveVariables **ppResult) const {
DXASSERT(IP != nullptr, "else IP should not be nullptr");
DXASSERT(ppResult != nullptr, "else Result should not be nullptr");
std::vector<const VariableInfo *> LiveVars;
std::set<std::string> LiveVarsName;
const llvm::DebugLoc &DL = IP->getDebugLoc();
if (!DL) {
return E_FAIL;
}
auto S = UniqueScopeForInlinedFunctions::Create(DL, DL->getScope());
if (!S.Valid()) {
return E_FAIL;
}
while (S.Valid()) {
auto it = m_pImpl->m_LiveVarsDbgDeclare.find(S);
if (it != m_pImpl->m_LiveVarsDbgDeclare.end()) {
for (const auto &VarAndInfo : it->second) {
auto *Var = VarAndInfo.first;
llvm::StringRef VarName = Var->getName();
if (Var->getLine() > DL.getLine()) {
// Defined later in the HLSL source.
continue;
}
if (VarName.empty()) {
// No name?...
continue;
}
if (!LiveVarsName.insert(VarAndInfo.first->getName()).second) {
// There's a variable with the same name; use the
// previous one instead.
return false;
}
LiveVars.emplace_back(VarAndInfo.second.get());
}
}
S.AscendScopeHierarchy();
}
for (const auto &VarAndInfo : m_pImpl->m_LiveGlobalVarsDbgDeclare) {
// Only consider references to the global variable that are in the same
// function as the instruction.
if (hlsl::dxilutil::DemangleFunctionName(
IP->getParent()->getParent()->getName()) ==
VarAndInfo.first->getScope()->getName()) {
if (!LiveVarsName.insert(VarAndInfo.first->getName()).second) {
// There shouldn't ever be a global variable with the same
// name, but it doesn't hurt to check
continue;
}
LiveVars.emplace_back(VarAndInfo.second.get());
}
}
return CreateDxilLiveVariables(m_pImpl->m_pDxilDebugInfo, std::move(LiveVars),
ppResult);
}