Skip to content

Commit e0faf63

Browse files
authored
Add 'IsDebuggerPresent' and detail DebugBreak more (#752)
**HLSL Debugging Intrinsics** - **Broadened Proposal Scope**: The proposal now covers two new debugging intrinsics: - `DebugBreak()`: Triggers a breakpoint when a debugger is attached. - `dx::IsDebuggerPresent()`: Returns whether a graphics debugger is currently attached (DirectX only). - **Motivation Expanded**: Highlights the need for conditional debug checks and selective breakpoints, allowing shader authors to perform expensive debugging operations only when needed. - **Detailed API & Usage Examples**: - Provides updated HLSL signatures and usage snippets for both intrinsics. - Demonstrates conditional execution of validation code based on debugger presence. - **Implementation Details Added**: - Specifies DXIL lowering for both intrinsics (`dx.op.debugBreak`, `dx.op.isDebuggerPresent`). - Clarifies convergence requirements to prevent optimizations that might move breakpoints. - Notes that both intrinsics are only valid in Shader Model 6.10+. - Explains runtime and driver behavior. - **SPIR-V Targeting**: - Outlines use of `NonSemantic.DebugBreak` for `DebugBreak()` in SPIR-V. - Specifies that `dx::IsDebuggerPresent()` has no SPIR-V equivalent. - **Testing Section**: Introduces compiler, validation, and execution testing requirements to ensure correct integration and behavior.
1 parent 7817bea commit e0faf63

1 file changed

Lines changed: 138 additions & 54 deletions

File tree

proposals/0039-debugbreak.md

Lines changed: 138 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
---
2-
title: 0039 - DebugBreak()
2+
title: 0039 - Debugging Intrinsics
33
params:
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.
2935
Conditional breakpoints can be a powerful tool for shader authors to narrow down
3036
and 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)]
5660
void 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

6174
This 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
7585
void 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
95123
declare 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

Comments
 (0)