diff --git a/lib/DxilDia/DxcPixLiveVariables.cpp b/lib/DxilDia/DxcPixLiveVariables.cpp index ce8dc87a9..9c1bc38c9 100644 --- a/lib/DxilDia/DxcPixLiveVariables.cpp +++ b/lib/DxilDia/DxcPixLiveVariables.cpp @@ -29,6 +29,58 @@ #include +// 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(FunctionScope)) { + if (auto *ContainingScope = Namespace->getScope()) { + FunctionScope = ContainingScope; + return; + } + } + const llvm::DITypeIdentifierMap EmptyMap; + 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(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(k.FunctionScope); + const uint64_t s = reinterpret_cast(k.InlinedAtScope); + std::hash 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 @@ -61,7 +113,9 @@ struct dxil_debug_info::LiveVariables::Impl { using VariableInfoMap = std::unordered_map>; - using LiveVarsMap = std::unordered_map; + using LiveVarsMap = + std::unordered_map; IMalloc *m_pMalloc; DxcPixDxilDebugInfo *m_pDxilDebugInfo; @@ -115,8 +169,9 @@ void dxil_debug_info::LiveVariables::Impl::Init_DbgDeclare( return; } - auto *S = Variable->getScope(); - if (S == nullptr) { + auto S = UniqueScopeForInlinedFunctions::Create(DbgDeclare->getDebugLoc(), + Variable->getScope()); + if (!S.Valid()) { return; } @@ -204,13 +259,12 @@ HRESULT dxil_debug_info::LiveVariables::GetLiveVariablesAtInstruction( return E_FAIL; } - llvm::DIScope *S = DL->getScope(); - if (S == nullptr) { + auto S = UniqueScopeForInlinedFunctions::Create(DL, DL->getScope()); + if (!S.Valid()) { return E_FAIL; } - const llvm::DITypeIdentifierMap EmptyMap; - while (S != nullptr) { + while (S.Valid()) { auto it = m_pImpl->m_LiveVarsDbgDeclare.find(S); if (it != m_pImpl->m_LiveVarsDbgDeclare.end()) { for (const auto &VarAndInfo : it->second) { @@ -232,7 +286,7 @@ HRESULT dxil_debug_info::LiveVariables::GetLiveVariablesAtInstruction( LiveVars.emplace_back(VarAndInfo.second.get()); } } - S = S->getScope().resolve(EmptyMap); + S.AscendScopeHierarchy(); } for (const auto &VarAndInfo : m_pImpl->m_LiveGlobalVarsDbgDeclare) { if (!LiveVarsName.insert(VarAndInfo.first->getName()).second) { diff --git a/lib/DxilDia/DxilDiaSession.cpp b/lib/DxilDia/DxilDiaSession.cpp index d80698cdc..1c45aa6f5 100644 --- a/lib/DxilDia/DxilDiaSession.cpp +++ b/lib/DxilDia/DxilDiaSession.cpp @@ -20,6 +20,7 @@ #include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" +#include "..\DxilPIXPasses\PixPassHelpers.h" #include "DxilDia.h" #include "DxilDiaEnumTables.h" #include "DxilDiaTable.h" @@ -63,7 +64,9 @@ void dxil_dia::Session::Init(std::shared_ptr context, // Build up a linear list of instructions. The index will be used as the // RVA. - for (llvm::Function &fn : m_module->functions()) { + std::vector allInstrumentableFunctions = + PIXPassHelpers::GetAllInstrumentableFunctions(*m_dxilModule.get()); + for (auto fn : allInstrumentableFunctions) { for (llvm::inst_iterator it = inst_begin(fn), end = inst_end(fn); it != end; ++it) { llvm::Instruction &i = *it; diff --git a/tools/clang/unittests/HLSL/PixDiaTest.cpp b/tools/clang/unittests/HLSL/PixDiaTest.cpp index 3276431d0..3a7f88996 100644 --- a/tools/clang/unittests/HLSL/PixDiaTest.cpp +++ b/tools/clang/unittests/HLSL/PixDiaTest.cpp @@ -177,6 +177,7 @@ public: TEST_METHOD(DxcPixDxilDebugInfo_UnnamedField) TEST_METHOD(DxcPixDxilDebugInfo_SubProgramsInNamespaces) TEST_METHOD(DxcPixDxilDebugInfo_SubPrograms) + TEST_METHOD(DxcPixDxilDebugInfo_TwiceInlinedFunctions) dxc::DxcDllSupport m_dllSupport; VersionSupportInfo m_ver; @@ -2758,4 +2759,165 @@ void main() RunSubProgramsCase(hlsl); } +static DWORD AdvanceUntilFunctionEntered(IDxcPixDxilDebugInfo *dxilDebugger, + DWORD instructionOffset, + wchar_t const *fnName) { + for (;;) { + CComBSTR FunctioName; + if (FAILED( + dxilDebugger->GetFunctionName(instructionOffset, &FunctioName))) { + VERIFY_FAIL(L"Didn't find function"); + return -1; + } + if (FunctioName == fnName) + break; + instructionOffset++; + } + return instructionOffset; +} + +static DWORD GetRegisterNumberForVariable(IDxcPixDxilDebugInfo *dxilDebugger, + DWORD instructionOffset, + wchar_t const *variableName, + wchar_t const *memberName) { + CComPtr DxcPixDxilLiveVariables; + if (SUCCEEDED(dxilDebugger->GetLiveVariablesAt(instructionOffset, + &DxcPixDxilLiveVariables))) { + DWORD count = 42; + VERIFY_SUCCEEDED(DxcPixDxilLiveVariables->GetCount(&count)); + for (DWORD i = 0; i < count; ++i) { + CComPtr DxcPixVariable; + VERIFY_SUCCEEDED( + DxcPixDxilLiveVariables->GetVariableByIndex(i, &DxcPixVariable)); + CComBSTR Name; + VERIFY_SUCCEEDED(DxcPixVariable->GetName(&Name)); + if (Name == variableName) { + CComPtr DxcPixDxilStorage; + VERIFY_SUCCEEDED(DxcPixVariable->GetStorage(&DxcPixDxilStorage)); + CComPtr DxcPixDxilMemberStorage; + VERIFY_SUCCEEDED(DxcPixDxilStorage->AccessField( + memberName, &DxcPixDxilMemberStorage)); + DWORD RegisterNumber = 42; + VERIFY_SUCCEEDED( + DxcPixDxilMemberStorage->GetRegisterNumber(&RegisterNumber)); + return RegisterNumber; + } + } + } + VERIFY_FAIL(L"Couldn't find register number"); + return -1; +} + +TEST_F(PixDiaTest, DxcPixDxilDebugInfo_TwiceInlinedFunctions) { + if (m_ver.SkipDxilVersion(1, 2)) + return; + + const char *hlsl = R"( +struct RayPayload +{ + float4 color; +}; + +RWStructuredBuffer floatRWUAV: register(u0); + +namespace StressScopesABit +{ +#include "included.h" +} + +namespace StressScopesMore +{ +float4 InlinedFunction(in BuiltInTriangleIntersectionAttributes attr, int offset) +{ + float4 ret = floatRWUAV.Load(offset + attr.barycentrics.x + 42); + float4 color2 = StressScopesABit::StressScopesEvenMore::DeeperInlinedFunction(attr, offset) + ret; + float4 color3 = StressScopesABit::StressScopesEvenMore::DeeperInlinedFunction(attr, offset+1); + return color2 + color3; +} +} + +[shader("closesthit")] +void ClosestHitShader0(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + payload.color = StressScopesMore::InlinedFunction(attr, 0); +} + +[shader("closesthit")] +void ClosestHitShader1(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + payload.color = StressScopesMore::InlinedFunction(attr, 1); +} + +[shader("closesthit")] +void ClosestHitShader2(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + float4 generateSomeLocalInstrucitons = floatRWUAV.Load(0); + float4 c0 = StressScopesMore::InlinedFunction(attr, 0); + float4 c1 = StressScopesABit::StressScopesEvenMore::DeeperInlinedFunction(attr, 42); + payload.color = c0 + c1 + generateSomeLocalInstrucitons; +} +)"; + + CComPtr pIncludeHandler = + new DxcIncludeHandlerForInjectedSourcesForPix(this, {{L"included.h", + R"( + +namespace StressScopesEvenMore +{ +float4 DeeperInlinedFunction(in BuiltInTriangleIntersectionAttributes attr, int offset) +{ + float4 ret = float4(0,0,0,0); + for(int i =0; i < offset; ++i) + { + float4 color0 = floatRWUAV.Load(offset + attr.barycentrics.x); + float4 color1 = floatRWUAV.Load(offset + attr.barycentrics.y); + ret += color0 + color1; + } + return ret; +} +} +)"}}); + + auto dxilDebugger = + CompileAndCreateDxcDebug(hlsl, L"lib_6_6", pIncludeHandler); + + DWORD instructionOffset = + AdvanceUntilFunctionEntered(dxilDebugger, 0, L"ClosestHitShader0"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"DeeperInlinedFunction"); + DWORD RegisterNumber0 = GetRegisterNumberForVariable( + dxilDebugger, instructionOffset, L"ret", L"x"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"InlinedFunction"); + DWORD RegisterNumber2 = GetRegisterNumberForVariable( + dxilDebugger, instructionOffset, L"color2", L"x"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"ClosestHitShader1"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"DeeperInlinedFunction"); + DWORD RegisterNumber1 = GetRegisterNumberForVariable( + dxilDebugger, instructionOffset, L"ret", L"x"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"InlinedFunction"); + DWORD RegisterNumber3 = GetRegisterNumberForVariable( + dxilDebugger, instructionOffset, L"color2", L"x"); + VERIFY_ARE_NOT_EQUAL(RegisterNumber0, RegisterNumber1); + VERIFY_ARE_NOT_EQUAL(RegisterNumber2, RegisterNumber3); + + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"ClosestHitShader2"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"InlinedFunction"); + DWORD ColorRegisterNumberWhenCalledFromOuterForInlined = + GetRegisterNumberForVariable(dxilDebugger, instructionOffset, L"ret", + L"x"); + instructionOffset = AdvanceUntilFunctionEntered( + dxilDebugger, instructionOffset, L"DeeperInlinedFunction"); + DWORD ColorRegisterNumberWhenCalledFromOuterForDeeper = + GetRegisterNumberForVariable(dxilDebugger, instructionOffset, L"ret", + L"x"); + VERIFY_ARE_NOT_EQUAL(ColorRegisterNumberWhenCalledFromOuterForInlined, + ColorRegisterNumberWhenCalledFromOuterForDeeper); +} + #endif \ No newline at end of file