Skip to content

Commit 7211c61

Browse files
Add DebgBreak hits DXIL PIX pass
1 parent 33656f6 commit 7211c61

7 files changed

Lines changed: 303 additions & 0 deletions

File tree

include/dxc/DxilPIXPasses/DxilPIXPasses.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ ModulePass *createDxilShaderAccessTrackingPass();
2828
ModulePass *createDxilPIXAddTidToAmplificationShaderPayloadPass();
2929
ModulePass *createDxilPIXDXRInvocationsLogPass();
3030
ModulePass *createDxilNonUniformResourceIndexInstrumentationPass();
31+
ModulePass *createDxilDebugBreakInstrumentationPass();
3132

3233
void initializeDxilAddPixelHitInstrumentationPass(llvm::PassRegistry &);
3334
void initializeDxilDbgValueToDbgDeclarePass(llvm::PassRegistry &);
@@ -44,5 +45,6 @@ void initializeDxilPIXAddTidToAmplificationShaderPayloadPass(
4445
void initializeDxilPIXDXRInvocationsLogPass(llvm::PassRegistry &);
4546
void initializeDxilNonUniformResourceIndexInstrumentationPass(
4647
llvm::PassRegistry &);
48+
void initializeDxilDebugBreakInstrumentationPass(llvm::PassRegistry &);
4749

4850
} // namespace llvm

lib/DxilPIXPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_llvm_library(LLVMDxilPIXPasses
2121
DxilPIXAddTidToAmplificationShaderPayload.cpp
2222
DxilPIXDXRInvocationsLog.cpp
2323
DxilNonUniformResourceIndexInstrumentation.cpp
24+
DxilDebugBreakInstrumentation.cpp
2425

2526
ADDITIONAL_HEADER_DIRS
2627
${LLVM_MAIN_INCLUDE_DIR}/llvm/IR
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
///////////////////////////////////////////////////////////////////////////////
2+
// //
3+
// DxilDebugBreakInstrumentation.cpp //
4+
// Copyright (C) Microsoft Corporation. All rights reserved. //
5+
// This file is distributed under the University of Illinois Open Source //
6+
// License. See LICENSE.TXT for details. //
7+
// //
8+
// Provides a pass to instrument DebugBreak() calls for PIX. Each //
9+
// DebugBreak call is replaced with a UAV bit-write so PIX can detect //
10+
// which DebugBreak locations were hit without halting the GPU. //
11+
// //
12+
///////////////////////////////////////////////////////////////////////////////
13+
14+
#include "PixPassHelpers.h"
15+
#include "dxc/DXIL/DxilOperations.h"
16+
#include "dxc/DxilPIXPasses/DxilPIXPasses.h"
17+
#include "dxc/DxilPIXPasses/DxilPIXVirtualRegisters.h"
18+
#include "dxc/Support/Global.h"
19+
#include "llvm/IR/Module.h"
20+
#include "llvm/Support/FormattedStream.h"
21+
22+
using namespace llvm;
23+
using namespace hlsl;
24+
25+
class DxilDebugBreakInstrumentation : public ModulePass {
26+
27+
public:
28+
static char ID; // Pass identification, replacement for typeid
29+
explicit DxilDebugBreakInstrumentation() : ModulePass(ID) {}
30+
StringRef getPassName() const override {
31+
return "DXIL DebugBreak Instrumentation";
32+
}
33+
bool runOnModule(Module &M) override;
34+
};
35+
36+
bool DxilDebugBreakInstrumentation::runOnModule(Module &M) {
37+
DxilModule &DM = M.GetOrCreateDxilModule();
38+
LLVMContext &Ctx = M.getContext();
39+
OP *HlslOP = DM.GetOP();
40+
41+
hlsl::DxilResource *PixUAVResource = nullptr;
42+
43+
UndefValue *UndefArg = UndefValue::get(Type::getInt32Ty(Ctx));
44+
45+
// Atomic operation to use for writing to the result UAV resource
46+
Function *AtomicOpFunc =
47+
HlslOP->GetOpFunc(OP::OpCode::AtomicBinOp, Type::getInt32Ty(Ctx));
48+
Constant *AtomicBinOpcode =
49+
HlslOP->GetU32Const((uint32_t)OP::OpCode::AtomicBinOp);
50+
Constant *AtomicOr =
51+
HlslOP->GetU32Const((uint32_t)DXIL::AtomicBinOpCode::Or);
52+
53+
std::map<Function *, CallInst *> FunctionToUAVHandle;
54+
55+
// Collect all DebugBreak calls first, then modify.
56+
// This avoids invalidating iterators during modification.
57+
std::vector<CallInst *> DebugBreakCalls;
58+
59+
Function *DebugBreakFunc =
60+
HlslOP->GetOpFunc(OP::OpCode::DebugBreak, Type::getVoidTy(Ctx));
61+
for (auto UI = DebugBreakFunc->use_begin();
62+
UI != DebugBreakFunc->use_end();) {
63+
auto &Use = *UI++;
64+
DebugBreakCalls.push_back(cast<CallInst>(Use.getUser()));
65+
}
66+
67+
for (CallInst *CI : DebugBreakCalls) {
68+
if (!PixUAVResource) {
69+
PixUAVResource =
70+
PIXPassHelpers::CreateGlobalUAVResource(DM, 0, "PixUAVResource");
71+
}
72+
73+
Function *F = CI->getParent()->getParent();
74+
75+
CallInst *PixUAVHandle = nullptr;
76+
const auto FunctionToUAVHandleIter = FunctionToUAVHandle.lower_bound(F);
77+
78+
if ((FunctionToUAVHandleIter != FunctionToUAVHandle.end()) &&
79+
(FunctionToUAVHandleIter->first == F)) {
80+
PixUAVHandle = FunctionToUAVHandleIter->second;
81+
} else {
82+
IRBuilder<> Builder(F->getEntryBlock().getFirstInsertionPt());
83+
84+
PixUAVHandle = PIXPassHelpers::CreateHandleForResource(
85+
DM, Builder, PixUAVResource, "PixUAVHandle");
86+
87+
FunctionToUAVHandle.insert(FunctionToUAVHandleIter, {F, PixUAVHandle});
88+
}
89+
90+
IRBuilder<> Builder(CI);
91+
92+
uint32_t InstructionNumber = 0;
93+
if (!pix_dxil::PixDxilInstNum::FromInst(CI, &InstructionNumber)) {
94+
DXASSERT_NOMSG(false);
95+
}
96+
97+
// The output UAV is treated as a bit array where each bit corresponds
98+
// to an instruction number.
99+
const uint32_t InstructionNumByteOffset =
100+
(InstructionNumber / 32u) * sizeof(uint32_t);
101+
const uint32_t InstructionNumBitPosition = (InstructionNumber % 32u);
102+
const uint32_t InstructionNumBitMask = 1u << InstructionNumBitPosition;
103+
104+
Constant *UAVByteOffsetArg =
105+
HlslOP->GetU32Const(InstructionNumByteOffset);
106+
Constant *BitMaskArg = HlslOP->GetU32Const(InstructionNumBitMask);
107+
108+
// Write a 1 bit at the position corresponding to this DebugBreak's
109+
// instruction number, indicating it was hit.
110+
Builder.CreateCall(
111+
AtomicOpFunc,
112+
{
113+
AtomicBinOpcode, // i32, ; opcode
114+
PixUAVHandle, // %dx.types.Handle, ; resource handle
115+
AtomicOr, // i32, ; binary operation code
116+
UAVByteOffsetArg, // i32, ; coordinate c0: byte offset
117+
UndefArg, // i32, ; coordinate c1 (unused)
118+
UndefArg, // i32, ; coordinate c2 (unused)
119+
BitMaskArg // i32); value
120+
},
121+
"DebugBreakBitSet");
122+
123+
// Remove the original DebugBreak call to prevent GPU halt
124+
CI->eraseFromParent();
125+
}
126+
127+
// Remove the now-unused dx.op.debugBreak function declaration so the
128+
// DebugBreak operation is fully eliminated from the module.
129+
if (DebugBreakFunc->use_empty())
130+
DebugBreakFunc->eraseFromParent();
131+
132+
const bool modified = (PixUAVResource != nullptr);
133+
134+
if (modified) {
135+
DM.ReEmitDxilResources();
136+
137+
if (OSOverride != nullptr) {
138+
formatted_raw_ostream FOS(*OSOverride);
139+
FOS << "\nFoundDebugBreak\n";
140+
}
141+
}
142+
143+
return modified;
144+
}
145+
146+
char DxilDebugBreakInstrumentation::ID = 0;
147+
148+
ModulePass *llvm::createDxilDebugBreakInstrumentationPass() {
149+
return new DxilDebugBreakInstrumentation();
150+
}
151+
152+
INITIALIZE_PASS(DxilDebugBreakInstrumentation,
153+
"hlsl-dxil-debugbreak-instrumentation",
154+
"HLSL DXIL DebugBreak instrumentation for PIX", false, false)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %dxc -Emain -Tcs_6_10 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debugbreak-instrumentation | %FileCheck %s
2+
3+
// Verify the PIX UAV handle is created for DebugBreak instrumentation:
4+
// CHECK: %PixUAVHandle = call %dx.types.Handle @dx.op.createHandleFromBinding(
5+
6+
// Verify an AtomicBinOp (opcode 78) was emitted to record the DebugBreak hit:
7+
// CHECK: %DebugBreakBitSet = call i32 @dx.op.atomicBinOp.i32(i32 78, %dx.types.Handle
8+
9+
// Verify the original DebugBreak call was removed:
10+
// CHECK-NOT: @dx.op.debugBreak
11+
12+
[numthreads(1, 1, 1)]
13+
void main() {
14+
DebugBreak();
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %dxc -Emain -Tcs_6_10 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debugbreak-instrumentation | %FileCheck %s
2+
3+
// Verify the PIX UAV handle is created:
4+
// CHECK: %PixUAVHandle = call %dx.types.Handle @dx.op.createHandleFromBinding(
5+
6+
// Verify two AtomicBinOp calls were emitted (one per DebugBreak):
7+
// CHECK: DebugBreakBitSet{{.*}} = call i32 @dx.op.atomicBinOp.i32(i32 78, %dx.types.Handle
8+
// CHECK: DebugBreakBitSet{{.*}} = call i32 @dx.op.atomicBinOp.i32(i32 78, %dx.types.Handle
9+
10+
// Verify no DebugBreak calls remain:
11+
// CHECK-NOT: @dx.op.debugBreak
12+
13+
RWByteAddressBuffer buf : register(u0);
14+
15+
[numthreads(1, 1, 1)]
16+
void main(uint3 tid : SV_DispatchThreadID) {
17+
if (tid.x == 0)
18+
DebugBreak();
19+
20+
buf.Store(0, tid.x);
21+
22+
if (tid.x == 1)
23+
DebugBreak();
24+
}

tools/clang/unittests/HLSL/PixTest.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ class PixTest : public ::testing::Test {
152152

153153
TEST_METHOD(DebugInstrumentation_VectorAllocaWrite_Structs)
154154

155+
TEST_METHOD(DebugBreakInstrumentation_Basic)
156+
TEST_METHOD(DebugBreakInstrumentation_NoDebugBreak)
157+
TEST_METHOD(DebugBreakInstrumentation_Multiple)
158+
155159
TEST_METHOD(NonUniformResourceIndex_Resource)
156160
TEST_METHOD(NonUniformResourceIndex_DescriptorHeap)
157161
TEST_METHOD(NonUniformResourceIndex_Raytracing)
@@ -238,6 +242,27 @@ class PixTest : public ::testing::Test {
238242
std::move(pOptimizedModule), {}, Tokenize(outputText.c_str(), "\n")};
239243
}
240244

245+
PassOutput RunDebugBreakPass(IDxcBlob *dxil) {
246+
CComPtr<IDxcOptimizer> pOptimizer;
247+
VERIFY_SUCCEEDED(
248+
m_dllSupport.CreateInstance(CLSID_DxcOptimizer, &pOptimizer));
249+
std::vector<LPCWSTR> Options;
250+
Options.push_back(L"-opt-mod-passes");
251+
Options.push_back(L"-dxil-annotate-with-virtual-regs");
252+
Options.push_back(L"-hlsl-dxil-debugbreak-instrumentation");
253+
Options.push_back(L"-hlsl-dxilemit");
254+
255+
CComPtr<IDxcBlob> pOptimizedModule;
256+
CComPtr<IDxcBlobEncoding> pText;
257+
VERIFY_SUCCEEDED(pOptimizer->RunOptimizer(
258+
dxil, Options.data(), Options.size(), &pOptimizedModule, &pText));
259+
260+
std::string outputText = BlobToUtf8(pText);
261+
262+
return {
263+
std::move(pOptimizedModule), {}, Tokenize(outputText.c_str(), "\n")};
264+
}
265+
241266
CComPtr<IDxcBlob> FindModule(hlsl::DxilFourCC fourCC, IDxcBlob *pSource) {
242267
const UINT32 BC_C0DE = ((INT32)(INT8)'B' | (INT32)(INT8)'C' << 8 |
243268
(INT32)0xDEC0 << 16); // BC0xc0de in big endian
@@ -3362,3 +3387,79 @@ void RaygenInternalName()
33623387
for (auto const &b : RayPayloadElementCoverage)
33633388
VERIFY_IS_TRUE(b);
33643389
}
3390+
3391+
TEST_F(PixTest, DebugBreakInstrumentation_Basic) {
3392+
3393+
const char *source = R"x(
3394+
[numthreads(1, 1, 1)]
3395+
void main() {
3396+
DebugBreak();
3397+
})x";
3398+
3399+
auto compiled = Compile(m_dllSupport, source, L"cs_6_10", {});
3400+
auto output = RunDebugBreakPass(compiled);
3401+
bool foundDebugBreak = false;
3402+
for (auto const &line : output.lines) {
3403+
if (line.find("FoundDebugBreak") != std::string::npos)
3404+
foundDebugBreak = true;
3405+
}
3406+
VERIFY_IS_TRUE(foundDebugBreak);
3407+
}
3408+
3409+
TEST_F(PixTest, DebugBreakInstrumentation_NoDebugBreak) {
3410+
3411+
const char *source = R"x(
3412+
RWByteAddressBuffer buf : register(u0);
3413+
[numthreads(1, 1, 1)]
3414+
void main() {
3415+
buf.Store(0, 1);
3416+
})x";
3417+
3418+
auto compiled = Compile(m_dllSupport, source, L"cs_6_0", {});
3419+
auto output = RunDebugBreakPass(compiled);
3420+
bool foundDebugBreak = false;
3421+
for (auto const &line : output.lines) {
3422+
if (line.find("FoundDebugBreak") != std::string::npos)
3423+
foundDebugBreak = true;
3424+
}
3425+
VERIFY_IS_FALSE(foundDebugBreak);
3426+
}
3427+
3428+
TEST_F(PixTest, DebugBreakInstrumentation_Multiple) {
3429+
3430+
const char *source = R"x(
3431+
RWByteAddressBuffer buf : register(u0);
3432+
[numthreads(1, 1, 1)]
3433+
void main(uint3 tid : SV_DispatchThreadID) {
3434+
if (tid.x == 0)
3435+
DebugBreak();
3436+
buf.Store(0, tid.x);
3437+
if (tid.x == 1)
3438+
DebugBreak();
3439+
})x";
3440+
3441+
auto compiled = Compile(m_dllSupport, source, L"cs_6_10", {});
3442+
auto output = RunDebugBreakPass(compiled);
3443+
bool foundDebugBreak = false;
3444+
for (auto const &line : output.lines) {
3445+
if (line.find("FoundDebugBreak") != std::string::npos)
3446+
foundDebugBreak = true;
3447+
}
3448+
VERIFY_IS_TRUE(foundDebugBreak);
3449+
3450+
// Verify the disassembly contains the expected AtomicBinOp calls
3451+
// and no remaining DebugBreak calls
3452+
auto disassembly = Disassemble(output.blob);
3453+
VERIFY_IS_TRUE(disassembly.find("dx.op.debugBreak") == std::string::npos);
3454+
3455+
// Count the number of DebugBreakBitSet calls to verify both
3456+
// DebugBreak() calls were instrumented
3457+
int debugBreakBitSetCount = 0;
3458+
std::string::size_type pos = 0;
3459+
while ((pos = disassembly.find("DebugBreakBitSet", pos)) !=
3460+
std::string::npos) {
3461+
debugBreakBitSetCount++;
3462+
pos += strlen("DebugBreakBitSet");
3463+
}
3464+
VERIFY_ARE_EQUAL(debugBreakBitSetCount, 2);
3465+
}

utils/hct/hctdb.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7134,6 +7134,12 @@ def add_pass(name, type_name, doc, opts):
71347134
"HLSL DXIL NonUniformResourceIndex instrumentation for PIX",
71357135
[],
71367136
)
7137+
add_pass(
7138+
"hlsl-dxil-debugbreak-instrumentation",
7139+
"DxilDebugBreakInstrumentation",
7140+
"HLSL DXIL DebugBreak instrumentation for PIX",
7141+
[],
7142+
)
71377143

71387144
category_lib = "dxil_gen"
71397145

0 commit comments

Comments
 (0)