Skip to content

Commit 7e0d771

Browse files
PIX: Report correct bitfield values in PIX shader debugger (microsoft#7557)
The key change here is the & in DxcDxilPixStorage.cpp. The generated DXIL packs the bitfields into their 32- or 64-bit-typed Values as expected, but this code, when trying to figure out which Value a bitfield lives in, was looking up the unpacked bit offset, so only fields within the zeroth underlying Value were being reported correctly. With this change, PIX reports correct bitfield values wherever they live, including within deeply nested structs. Unfortunately, the tests had to be in C++ because file-check obv. doesn't run the APIs that PIX uses to read debug data. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent a9d33d3 commit 7e0d771

File tree

2 files changed

+138
-35
lines changed

2 files changed

+138
-35
lines changed

lib/DxilDia/DxcPixDxilStorage.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,11 @@ dxil_debug_info::DxcPixDxilScalarStorage::Index(DWORD Index,
185185
STDMETHODIMP dxil_debug_info::DxcPixDxilScalarStorage::GetRegisterNumber(
186186
DWORD *pRegisterNumber) {
187187
const auto &ValueLocationMap = m_pVarInfo->m_ValueLocationMap;
188-
auto RegIt = ValueLocationMap.find(m_OffsetFromStorageStartInBits);
188+
// Bitfields will have been packed into their containing integer type:
189+
DWORD size;
190+
m_pOriginalType->GetSizeInBits(&size);
191+
auto RegIt =
192+
ValueLocationMap.find(m_OffsetFromStorageStartInBits & ~(size - 1));
189193

190194
if (RegIt == ValueLocationMap.end()) {
191195
return E_FAIL;

tools/clang/unittests/HLSL/PixDiaTest.cpp

Lines changed: 133 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifdef _WIN32
1414

1515
#include <array>
16+
#include <set>
1617

1718
#include "dxc/DxilContainer/DxilContainer.h"
1819
#include "dxc/Support/WinIncludes.h"
@@ -186,6 +187,7 @@ class PixDiaTest {
186187
TEST_METHOD(DxcPixDxilDebugInfo_BitFields_Derived)
187188
TEST_METHOD(DxcPixDxilDebugInfo_BitFields_Bool)
188189
TEST_METHOD(DxcPixDxilDebugInfo_BitFields_Overlap)
190+
TEST_METHOD(DxcPixDxilDebugInfo_BitFields_uint64)
189191
TEST_METHOD(DxcPixDxilDebugInfo_Min16SizesAndOffsets_Enabled)
190192
TEST_METHOD(DxcPixDxilDebugInfo_Min16SizesAndOffsets_Disabled)
191193
TEST_METHOD(DxcPixDxilDebugInfo_Min16VectorOffsets_Enabled)
@@ -658,11 +660,11 @@ class PixDiaTest {
658660
const char *hlsl, const wchar_t *profile,
659661
const char *lineAtWhichToExamineVariables,
660662
std::vector<VariableComponentInfo> const &ExpectedVariables);
661-
void RunSizeAndOffsetTestCase(const char *hlsl,
662-
std::array<DWORD, 4> const &memberOffsets,
663-
std::array<DWORD, 4> const &memberSizes,
664-
std::vector<const wchar_t *> extraArgs = {
665-
L"-Od"});
663+
CComPtr<IDxcPixDxilStorage>
664+
RunSizeAndOffsetTestCase(const char *hlsl,
665+
std::array<DWORD, 4> const &memberOffsets,
666+
std::array<DWORD, 4> const &memberSizes,
667+
std::vector<const wchar_t *> extraArgs = {L"-Od"});
666668
void RunVectorSizeAndOffsetTestCase(const char *hlsl,
667669
std::array<DWORD, 4> const &memberOffsets,
668670
std::vector<const wchar_t *> extraArgs = {
@@ -2948,12 +2950,11 @@ void main()
29482950
VERIFY_ARE_EQUAL(32u, secondFieldOffset);
29492951
}
29502952

2951-
void PixDiaTest::RunSizeAndOffsetTestCase(
2952-
const char *hlsl, std::array<DWORD, 4> const &memberOffsets,
2953-
std::array<DWORD, 4> const &memberSizes,
2954-
std::vector<const wchar_t *> extraArgs) {
2955-
if (m_ver.SkipDxilVersion(1, 5))
2956-
return;
2953+
CComPtr<IDxcPixDxilStorage>
2954+
PixDiaTest::RunSizeAndOffsetTestCase(const char *hlsl,
2955+
std::array<DWORD, 4> const &memberOffsets,
2956+
std::array<DWORD, 4> const &memberSizes,
2957+
std::vector<const wchar_t *> extraArgs) {
29572958
auto debugInfo =
29582959
CompileAndCreateDxcDebug(hlsl, L"cs_6_5", nullptr, extraArgs).debugInfo;
29592960
auto live = GetLiveVariablesAt(hlsl, "STOP_HERE", debugInfo);
@@ -2974,9 +2975,46 @@ void PixDiaTest::RunSizeAndOffsetTestCase(
29742975
VERIFY_SUCCEEDED(field->GetFieldSizeInBits(&sizeInBits));
29752976
VERIFY_ARE_EQUAL(memberSizes[i], sizeInBits);
29762977
}
2978+
// Check that first and second and third are reported as residing in the same
2979+
// register (cuz they do!), and that the third does not
2980+
2981+
CComPtr<IDxcPixDxilStorage> bfStorage;
2982+
VERIFY_SUCCEEDED(bf->GetStorage(&bfStorage));
2983+
return bfStorage;
2984+
}
2985+
2986+
void RunBitfieldAdjacencyTest(
2987+
IDxcPixDxilStorage *bfStorage,
2988+
std::vector<std::vector<wchar_t const *>> const &adjacentRuns) {
2989+
std::vector<std::set<DWORD>> registersByRun;
2990+
registersByRun.resize(adjacentRuns.size());
2991+
for (size_t run = 0; run < adjacentRuns.size(); ++run) {
2992+
for (auto const &field : adjacentRuns[run]) {
2993+
CComPtr<IDxcPixDxilStorage> fieldStorage;
2994+
VERIFY_SUCCEEDED(bfStorage->AccessField(field, &fieldStorage));
2995+
DWORD reg;
2996+
VERIFY_SUCCEEDED(fieldStorage->GetRegisterNumber(&reg));
2997+
registersByRun[run].insert(reg);
2998+
}
2999+
}
3000+
for (size_t run = 0; run < registersByRun.size(); ++run) {
3001+
{
3002+
// Every field in this run should have the same register number, so this
3003+
// set should be of size 1:
3004+
VERIFY_ARE_EQUAL(1, registersByRun[run].size());
3005+
// Every adjacent run should have different register numbers:
3006+
if (run != 0) {
3007+
VERIFY_ARE_NOT_EQUAL(*registersByRun[run - 1].begin(),
3008+
*registersByRun[run].begin());
3009+
}
3010+
}
3011+
}
29773012
}
29783013

29793014
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_BitFields_Simple) {
3015+
if (m_ver.SkipDxilVersion(1, 5))
3016+
return;
3017+
29803018
const char *hlsl = R"(
29813019
struct Bitfields
29823020
{
@@ -3000,10 +3038,16 @@ void main()
30003038
}
30013039
30023040
)";
3003-
RunSizeAndOffsetTestCase(hlsl, {0, 17, 32, 64}, {17, 15, 3, 32});
3041+
auto bfStorage =
3042+
RunSizeAndOffsetTestCase(hlsl, {0, 17, 32, 64}, {17, 15, 3, 32});
3043+
RunBitfieldAdjacencyTest(bfStorage,
3044+
{{L"first", L"second"}, {L"third"}, {L"fourth"}});
30043045
}
30053046

30063047
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_BitFields_Derived) {
3048+
if (m_ver.SkipDxilVersion(1, 5))
3049+
return;
3050+
30073051
const char *hlsl = R"(
30083052
struct Bitfields
30093053
{
@@ -3027,10 +3071,16 @@ void main()
30273071
}
30283072
30293073
)";
3030-
RunSizeAndOffsetTestCase(hlsl, {0, 17, 32, 64}, {17, 15, 3, 32});
3074+
auto bfStorage =
3075+
RunSizeAndOffsetTestCase(hlsl, {0, 17, 32, 64}, {17, 15, 3, 32});
3076+
RunBitfieldAdjacencyTest(bfStorage,
3077+
{{L"first", L"second"}, {L"third"}, {L"fourth"}});
30313078
}
30323079

30333080
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_BitFields_Bool) {
3081+
if (m_ver.SkipDxilVersion(1, 5))
3082+
return;
3083+
30343084
const char *hlsl = R"(
30353085
struct Bitfields
30363086
{
@@ -3054,17 +3104,58 @@ void main()
30543104
}
30553105
30563106
)";
3057-
RunSizeAndOffsetTestCase(hlsl, {0, 1, 2, 32}, {1, 1, 3, 32});
3107+
auto bfStorage = RunSizeAndOffsetTestCase(hlsl, {0, 1, 2, 32}, {1, 1, 3, 32});
3108+
RunBitfieldAdjacencyTest(bfStorage,
3109+
{{L"first", L"second", L"third"}, {L"fourth"}});
30583110
}
30593111

30603112
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_BitFields_Overlap) {
3113+
if (m_ver.SkipDxilVersion(1, 5))
3114+
return;
3115+
3116+
const char *hlsl = R"(
3117+
struct Bitfields
3118+
{
3119+
uint32_t first : 20;
3120+
uint32_t second : 20; // should end up in second DWORD
3121+
uint32_t third : 3; // should shader second DWORD
3122+
uint32_t fourth; // should be in third DWORD
3123+
};
3124+
3125+
RWStructuredBuffer<int> UAV: register(u0);
3126+
3127+
[numthreads(1, 1, 1)]
3128+
void main()
3129+
{
3130+
Bitfields bf;
3131+
bf.first = UAV[0];
3132+
bf.second = UAV[1];
3133+
bf.third = UAV[2];
3134+
bf.fourth = UAV[3];
3135+
UAV[16] = bf.first + bf.second + bf.third + bf.fourth; //STOP_HERE
3136+
}
3137+
3138+
)";
3139+
auto bfStorage =
3140+
RunSizeAndOffsetTestCase(hlsl, {0, 32, 52, 64}, {20, 20, 3, 32});
3141+
// (PIX #58022343): fields that overlap their storage type are not yet
3142+
// reflected properly in terms of their packed offsets as maintained via
3143+
// these PixDxc interfaces based on the dbg.declare data
3144+
// RunBitfieldAdjacencyTest(bfStorage,
3145+
// {{L"first"}, {L"second", L"third"}, {L"fourth"}});
3146+
}
3147+
3148+
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_BitFields_uint64) {
3149+
if (m_ver.SkipDxilVersion(1, 5))
3150+
return;
3151+
30613152
const char *hlsl = R"(
30623153
struct Bitfields
30633154
{
3064-
unsigned int first : 20;
3065-
unsigned int second : 20; // should end up in second DWORD
3066-
unsigned int third : 3; // should shader second DWORD
3067-
unsigned int fourth; // should be in third DWORD
3155+
uint64_t first : 20;
3156+
uint64_t second : 20; // should end up in first uint64 also
3157+
uint64_t third : 24; // in first
3158+
uint64_t fourth; // should be in second
30683159
};
30693160
30703161
RWStructuredBuffer<int> UAV: register(u0);
@@ -3081,7 +3172,10 @@ void main()
30813172
}
30823173
30833174
)";
3084-
RunSizeAndOffsetTestCase(hlsl, {0, 32, 52, 64}, {20, 20, 3, 32});
3175+
auto bfStorage =
3176+
RunSizeAndOffsetTestCase(hlsl, {0, 20, 40, 64}, {20, 20, 24, 64});
3177+
RunBitfieldAdjacencyTest(bfStorage,
3178+
{{L"first", L"second", L"third"}, {L"fourth"}});
30853179
}
30863180

30873181
TEST_F(PixDiaTest, DxcPixDxilDebugInfo_Alignment_ConstInt) {
@@ -3502,9 +3596,10 @@ void ClosestHitShader3(inout RayPayload payload, in BuiltInTriangleIntersectionA
35023596

35033597
// Case: same function called from two places in same top-level function.
35043598
// In this case, we expect the storage for the variable to be in the same
3505-
// place for both "instances" of the function: as a thread proceeds through
3506-
// the caller, it will write new values into the variable's storage during
3507-
// the second or subsequent invocations of the inlined function.
3599+
// place for both "instances" of the function: as a thread proceeds
3600+
// through the caller, it will write new values into the variable's
3601+
// storage during the second or subsequent invocations of the inlined
3602+
// function.
35083603
DWORD instructionOffset =
35093604
AdvanceUntilFunctionEntered(dxilDebugger, 0, L"ClosestHitShader3");
35103605
instructionOffset = AdvanceUntilFunctionEntered(
@@ -3550,9 +3645,10 @@ TEST_F(PixDiaTest, DxcPixDxilDebugInfo_VariableScopes_ForScopes) {
35503645

35513646
// Case: same function called from two places in same top-level function.
35523647
// In this case, we expect the storage for the variable to be in the same
3553-
// place for both "instances" of the function: as a thread proceeds through
3554-
// the caller, it will write new values into the variable's storage during
3555-
// the second or subsequent invocations of the inlined function.
3648+
// place for both "instances" of the function: as a thread proceeds
3649+
// through the caller, it will write new values into the variable's
3650+
// storage during the second or subsequent invocations of the inlined
3651+
// function.
35563652
DWORD instructionOffset =
35573653
AdvanceUntilFunctionEntered(dxilDebugger, 0, L"CSMain");
35583654

@@ -3597,9 +3693,10 @@ TEST_F(PixDiaTest, DxcPixDxilDebugInfo_VariableScopes_ScopeBraces) {
35973693

35983694
// Case: same function called from two places in same top-level function.
35993695
// In this case, we expect the storage for the variable to be in the same
3600-
// place for both "instances" of the function: as a thread proceeds through
3601-
// the caller, it will write new values into the variable's storage during
3602-
// the second or subsequent invocations of the inlined function.
3696+
// place for both "instances" of the function: as a thread proceeds
3697+
// through the caller, it will write new values into the variable's
3698+
// storage during the second or subsequent invocations of the inlined
3699+
// function.
36033700
DWORD instructionOffset =
36043701
AdvanceUntilFunctionEntered(dxilDebugger, 0, L"CSMain");
36053702

@@ -3644,9 +3741,10 @@ TEST_F(PixDiaTest, DxcPixDxilDebugInfo_VariableScopes_Function) {
36443741

36453742
// Case: same function called from two places in same top-level function.
36463743
// In this case, we expect the storage for the variable to be in the same
3647-
// place for both "instances" of the function: as a thread proceeds through
3648-
// the caller, it will write new values into the variable's storage during
3649-
// the second or subsequent invocations of the inlined function.
3744+
// place for both "instances" of the function: as a thread proceeds
3745+
// through the caller, it will write new values into the variable's
3746+
// storage during the second or subsequent invocations of the inlined
3747+
// function.
36503748
DWORD instructionOffset =
36513749
AdvanceUntilFunctionEntered(dxilDebugger, 0, L"CSMain");
36523750

@@ -3692,9 +3790,10 @@ void CSMain()
36923790

36933791
// Case: same function called from two places in same top-level function.
36943792
// In this case, we expect the storage for the variable to be in the same
3695-
// place for both "instances" of the function: as a thread proceeds through
3696-
// the caller, it will write new values into the variable's storage during
3697-
// the second or subsequent invocations of the inlined function.
3793+
// place for both "instances" of the function: as a thread proceeds
3794+
// through the caller, it will write new values into the variable's
3795+
// storage during the second or subsequent invocations of the inlined
3796+
// function.
36983797
DWORD instructionOffset =
36993798
AdvanceUntilFunctionEntered(dxilDebugger, 0, L"CSMain");
37003799

0 commit comments

Comments
 (0)