Skip to content

Commit 8066e06

Browse files
[PIX] Instrument DebugBreak() calls for PIX (#8349)
Changes: - Add a PIX DXIL pass that replaces `DebugBreak()` calls with UAV atomic writes so PIX can detect hit locations without halting execution. - Wire the new pass into pass registration and the build. - Add unit tests and FileCheck coverage for basic, no DebugBreak(), and multiple DebugBreak() cases. Testing: - Built `dxcompiler`, `ClangHLSLTests`, and `check-all` - `check-all` passed locally Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 769cb9d commit 8066e06

File tree

7 files changed

+299
-0
lines changed

7 files changed

+299
-0
lines changed

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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 = HlslOP->GetU32Const((uint32_t)DXIL::AtomicBinOpCode::Or);
51+
52+
std::map<Function *, CallInst *> FunctionToUAVHandle;
53+
54+
// Collect all DebugBreak calls first, then modify.
55+
// This avoids invalidating iterators during modification.
56+
std::vector<CallInst *> DebugBreakCalls;
57+
58+
Function *DebugBreakFunc =
59+
HlslOP->GetOpFunc(OP::OpCode::DebugBreak, Type::getVoidTy(Ctx));
60+
for (const Use &U : DebugBreakFunc->uses()) {
61+
DebugBreakCalls.push_back(cast<CallInst>(U.getUser()));
62+
}
63+
64+
for (CallInst *CI : DebugBreakCalls) {
65+
if (!PixUAVResource)
66+
PixUAVResource =
67+
PIXPassHelpers::CreateGlobalUAVResource(DM, 0, "PixUAVResource");
68+
69+
Function *F = CI->getParent()->getParent();
70+
71+
CallInst *PixUAVHandle = nullptr;
72+
const auto FunctionToUAVHandleIter = FunctionToUAVHandle.lower_bound(F);
73+
74+
if ((FunctionToUAVHandleIter != FunctionToUAVHandle.end()) &&
75+
(FunctionToUAVHandleIter->first == F)) {
76+
PixUAVHandle = FunctionToUAVHandleIter->second;
77+
} else {
78+
IRBuilder<> Builder(F->getEntryBlock().getFirstInsertionPt());
79+
80+
PixUAVHandle = PIXPassHelpers::CreateHandleForResource(
81+
DM, Builder, PixUAVResource, "PixUAVHandle");
82+
83+
FunctionToUAVHandle.insert(FunctionToUAVHandleIter, {F, PixUAVHandle});
84+
}
85+
86+
IRBuilder<> Builder(CI);
87+
88+
uint32_t InstructionNumber = 0;
89+
if (!pix_dxil::PixDxilInstNum::FromInst(CI, &InstructionNumber)) {
90+
DXASSERT(false, "Failed to extract PIX instruction number metadata from "
91+
"DebugBreak call");
92+
}
93+
94+
// The output UAV is treated as a bit array where each bit corresponds
95+
// to an instruction number.
96+
const uint32_t InstructionNumByteOffset =
97+
(InstructionNumber / 32u) * sizeof(uint32_t);
98+
const uint32_t InstructionNumBitPosition = (InstructionNumber % 32u);
99+
const uint32_t InstructionNumBitMask = 1u << InstructionNumBitPosition;
100+
101+
Constant *UAVByteOffsetArg = HlslOP->GetU32Const(InstructionNumByteOffset);
102+
Constant *BitMaskArg = HlslOP->GetU32Const(InstructionNumBitMask);
103+
104+
// Write a 1 bit at the position corresponding to this DebugBreak's
105+
// instruction number, indicating it was hit.
106+
Builder.CreateCall(
107+
AtomicOpFunc,
108+
{
109+
AtomicBinOpcode, // i32, ; opcode
110+
PixUAVHandle, // %dx.types.Handle, ; resource handle
111+
AtomicOr, // i32, ; binary operation code
112+
UAVByteOffsetArg, // i32, ; coordinate c0: byte offset
113+
UndefArg, // i32, ; coordinate c1 (unused)
114+
UndefArg, // i32, ; coordinate c2 (unused)
115+
BitMaskArg // i32); value
116+
},
117+
"DebugBreakBitSet");
118+
119+
// Remove the original DebugBreak call to prevent GPU halt
120+
CI->eraseFromParent();
121+
}
122+
123+
// Clean up the now-unused declaration. Not strictly required for
124+
// correctness, but keeps the module free of dead references.
125+
if (DebugBreakFunc->use_empty())
126+
DebugBreakFunc->eraseFromParent();
127+
128+
const bool modified = (PixUAVResource != nullptr);
129+
130+
if (modified) {
131+
DM.ReEmitDxilResources();
132+
133+
if (OSOverride != nullptr) {
134+
formatted_raw_ostream FOS(*OSOverride);
135+
FOS << "\nFoundDebugBreak\n";
136+
}
137+
}
138+
139+
return modified;
140+
}
141+
142+
char DxilDebugBreakInstrumentation::ID = 0;
143+
144+
ModulePass *llvm::createDxilDebugBreakInstrumentationPass() {
145+
return new DxilDebugBreakInstrumentation();
146+
}
147+
148+
INITIALIZE_PASS(DxilDebugBreakInstrumentation,
149+
"hlsl-dxil-debugbreak-instrumentation",
150+
"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)