11---
2- title : 0039 - DebugBreak()
2+ title : 0039 - Debugging Intrinsics
33params :
44 authors :
55 - llvm-beanz : Chris Bieneman
6+ - joecitizen : Jack Elliott
67 sponsors :
78 - llvm-beanz : Chris Bieneman
89 status : Under Consideration
910---
1011
11-
1212
1313* Issue(s): https://github.com/microsoft/hlsl-specs/issues/33
1414
1515## Introduction
1616
17- This proposal specifies a new HLSL function ` DebugBreak() ` which will lower to a
18- new DXIL operation described in this spec for DirectX and to the SPIRV
19- [ NonSemantic.DebugBreak] ( https://github.khronos.org/SPIRV-Registry/nonsemantic/NonSemantic.DebugBreak.html )
20- for SPIRV targets.
17+ This proposal specifies two new HLSL debugging intrinsics:
18+
19+ 1 . ** ` DebugBreak() ` ** : Triggers a breakpoint when a debugger is attached,
20+ allowing developers to pause execution and inspect shader state.
21+ 2 . ** ` dx::IsDebuggerPresent() ` ** : Returns whether a graphics debugger is currently
22+ attached to the process, enabling conditional debug-only code paths.
23+
24+ ` DebugBreak() ` will lower to new DXIL operations for DirectX and to appropriate
25+ SPIR-V instructions for Vulkan targets. ` dx::IsDebuggerPresent() ` is a DirectX
26+ extension and has no Vulkan/SPIR-V equivalent.
2127
2228## Motivation
2329
@@ -29,99 +35,177 @@ millions of times without issue, but in one instance produces a bad result.
2935Conditional breakpoints can be a powerful tool for shader authors to narrow down
3036and identify these complex rare-occurring problems.
3137
32- This proposal introduces a ` DebugBreak() ` intrinsic, which can be combined with
33- an ` assert() ` macro implementation to provide support for conditional
34- breakpoints in shader code.
38+ Additionally, shader authors need a way to conditionally enable expensive debug
39+ checks only when a debugger is attached, avoiding runtime overhead in production
40+ scenarios.
41+
42+ This proposal introduces two intrinsics that together provide a debugging
43+ toolkit for shader development.
3544
3645## Proposed solution
3746
38- This proposal introduces a new HLSL intrinsic ` DebugBreak() ` , a new header
39- ` assert.h ` which will define the ` assert() ` macro in a C-compatible interface.
47+ This proposal introduces two new HLSL intrinsics for debugging shader code.
4048
41- assert.h will provide the following definitions
42- ``` c
43- #if NDEBUG
44- #define assert(cond) do { } while(false)
45- #else
46- #define assert(cond) do { if (!cond) DebugBreak();} while(false)
47- #endif
48- ```
49+ ### Intrinsics
4950
50- This will enable shader authors to write code such as:
51+ ``` hlsl
52+ void DebugBreak(); // Trigger a breakpoint if debugger attached
53+ bool dx::IsDebuggerPresent(); // Query if a debugger is attached (DirectX only)
54+ ```
5155
52- ``` c++
53- #include < assert.h>
56+ ### Example Usage
5457
58+ ``` hlsl
5559[numthreads(8,1,1)]
5660void main(uint GI : SV_GroupIndex) {
57- assert(GI < 8);
61+ // Conditional expensive debug checks
62+ if (dx::IsDebuggerPresent()) {
63+ // Expensive validation only when debugging
64+ ValidateComplexInvariants();
65+ }
66+
67+ // Manual breakpoint for debugging specific conditions
68+ if (someRareCondition) {
69+ DebugBreak();
70+ }
5871}
5972```
6073
6174This aligns with C/C++ conventions that our users are already familiar with.
6275
6376## Detailed Design
6477
65- This proposal introduces a new HLSL `DebugBreak` intrinsic which has a
66- runtime-defined behavior to facilitate shader debugging workflows. If the
67- runtime does not support or is not configured to enable support for the
68- corresponding DXIL instruction, it must be treated as a no-op by the driver.
69-
7078### HLSL Surface
7179
72- A new `DebugBreak` function is added with the signature :
80+ Two new intrinsic functions are added:
7381
74- ```
82+ #### ` DebugBreak() `
83+
84+ ``` hlsl
7585void DebugBreak();
7686```
7787
78- A new header `assert.h` is added and included with the compiler packaging which
79- implements the `assert` macro:
88+ Triggers a breakpoint if a graphics debugger is attached. If no debugger is
89+ attached or the runtime does not support this operation, it is treated as a
90+ no-op. Execution continues after the breakpoint.
8091
81- ```c
82- #if NDEBUG
83- #define assert(cond) do { } while(false)
84- #else
85- #define assert(cond) do { if (!cond) DebugBreak();} while(false)
86- #endif
92+ #### ` dx::IsDebuggerPresent() `
93+
94+ ``` hlsl
95+ bool dx::IsDebuggerPresent();
8796```
8897
98+ Returns ` true ` if a graphics debugger is currently attached to the process,
99+ ` false ` otherwise. This allows shader authors to conditionally execute expensive
100+ debug validation code only when a debugger is present:
101+
102+ ``` hlsl
103+ if (dx::IsDebuggerPresent()) {
104+ // Expensive bounds checking, validation, etc.
105+ for (uint i = 0; i < arraySize; ++i) {
106+ if (data[i] < 0.0f || data[i] > 1.0f) {
107+ DebugBreak();
108+ }
109+ }
110+ }
111+ ```
112+
113+ The value returned is uniform across all threads in a dispatch/draw and remains
114+ constant for the duration of shader execution.
115+
89116### DXIL Lowering
90117
91- This change introduces a new DXIL operation :
118+ This change introduces two new DXIL operations :
92119
120+ #### ` dx.op.debugBreak `
93121
94- ``` llvm
122+ ``` llvm
95123declare void @dx.op.debugBreak(
96124 immarg i32 ; opcode
97- )
125+ ) convergent
126+ ```
127+
128+ Triggers a debugger breakpoint. Must be treated as ` convergent ` to prevent code
129+ motion. Should not be marked ` readonly ` or ` readnone ` . If no debugger is
130+ attached, this is a no-op.
131+
132+ #### ` dx.op.isDebuggerPresent `
133+
134+ ``` llvm
135+ declare i1 @dx.op.isDebuggerPresent(
136+ immarg i32 ; opcode
137+ ) readonly
98138```
99139
100- This DXIL operation must be treated as ` convergent ` even though it is not to
101- prevent code motion. It should also not be marked ` readonly ` or ` readnone ` even
102- though it technically doesn't read memory.
140+ Returns ` true ` (1) if a debugger is attached, ` false ` (0) otherwise. Marked
141+ ` readonly ` as it only queries state.
103142
104- This instruction will only be valid in a new shader model.
143+ ### Convergence Requirements
105144
106- Because it is valid to treat this operation as a no-op, it is a required
107- supported feature and does not require a capabilities bit.
145+ ` debugBreak ` operations must be treated as ` convergent ` to prevent
146+ code motion that could change their observable behavior:
147+ - ` debugBreak ` : Must break at the exact location specified by the programmer
108148
109- ### SPIRV Lowering
149+ These operations should not be hoisted, sunk, or duplicated by optimizers.
110150
111- This change will utilize the existing ` NonSemantic.DebugBreak ` instruction.
112- While this instruction is not widely supported by Vulkan debuggers, it is
113- supported by NVIDIA's NSight and can be safely ignored by Vulkan runtimes.
151+ ### Shader Model Requirements
114152
115- The SPIRV usage will utilize the following instructions:
153+ These instructions will only be valid in Shader Model 6.10 or later.
154+
155+ Because ` DebugBreak() ` can be treated as a no-op when no debugger is present,
156+ it is a required supported feature and does not require a capability bit.
157+
158+ ### Runtime Behavior for DebugBreak
159+
160+ It is valid for the runtime to change the behavior of debug break on a
161+ per-pipeline basis.
162+
163+ Behavioral changes may include:
164+
165+ - Breaking regardless of a debugger being attached
166+ - Disabling debug break instructions entirely
167+
168+ It is expected that the driver compiler will alter behavior during lowering
169+ based on information provided by the runtime at pipeline creation.
170+
171+ ### SPIR-V Lowering
172+
173+ #### DebugBreak
174+
175+ Uses the existing ` NonSemantic.DebugBreak ` instruction:
116176
117177```
118178%1 = OpExtInstImport "NonSemantic.DebugBreak"
119179%2 = OpExtInst %void %1 DebugBreak
120180```
121181
182+ While this instruction is not widely supported by Vulkan debuggers, it is
183+ supported by NVIDIA's NSight and can be safely ignored by Vulkan runtimes.
184+
185+ No SPIR-V lowering is defined for ` dx::IsDebuggerPresent() ` .
186+
187+ ## Testing
188+
189+ ### Compiler Testing
190+
191+ - Verify correct DXIL generation for both intrinsics
192+ - Verify correct SPIR-V generation for ` DebugBreak() ` where applicable
193+
194+ ### Validation Testing
195+
196+ - Confirm validation accepts the new operations in SM 6.10+
197+ - Confirm validation rejects operations in earlier shader models
198+ - Verify convergence requirements are properly validated
199+
200+ ### Execution Testing
201+
202+ - Test ` DebugBreak() ` triggers breakpoint when debugger attached
203+ - Test ` DebugBreak() ` is no-op when no debugger present
204+ - Test ` dx::IsDebuggerPresent() ` returns correct value based on debugger state
205+
122206## Open Questions
123207
124208* Consider introducing the ` convergent ` attribute to DXIL.
125209 * This should be "cheap" and would potentially address pre-existing bugs.
126- * This would preserve the requirement that this operation not be moved during
127- optimization in the final DXIL.
210+ * This would preserve the requirement that these operations not be moved
211+ during optimization in the final DXIL.
0 commit comments