microsoft / microsoft/DirectXShaderCompiler
DXC emits racey zero-init code when numthreads(1,1,1)
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 3.7k
- Forks
- 900
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 44
Description
Description
With a compute shader with numthreads(1,1,1) where a groupshared variable is initialized to zero, DXC emits different code depending on whether the write is conditional on the group index. One of these results in race conditions on some GPUs.
Steps to Reproduce
See this compiler explorer example: https://godbolt.org/z/GPjP58To9
The interesting bit is the function that initializes the groupshared var to 0:
void tint_zero_workgroup_memory(uint local_idx) {
#ifdef CHECK_LOCAL_INDEX
if (local_idx < 1)
#endif
{
testVar = (0.0f).xxx;
}
GroupMemoryBarrierWithGroupSync();
}
There are 2 compilations, one without CHECK_LOCAL_INDEX defined, and one where it is defined. The diff is also shown at the bottom. The main things to note between the two:
-
When not checking the index, DXC emits the variable with the
zeroinitializertag, and there is no explicit code to zero-init the variable before the call to@dx.op.barrier. -
Whereas with the index check, it emits the same variable with the
undeftag, and there is now explicit code to zero-init the variable:
store float 0.000000e+00, float addrspace(3)* getelementptr inbounds ([3 x float], [3 x float] addrspace(3)* @"\01?testVar@@3V?$vector@M$02@@A.v", i32 0, i32 0), align 4, !dbg !92
store float 0.000000e+00, float addrspace(3)* getelementptr inbounds ([3 x float], [3 x float] addrspace(3)* @"\01?testVar@@3V?$vector@M$02@@A.v", i32 0, i32 1), align 4, !dbg !92
store float 0.000000e+00, float addrspace(3)* getelementptr inbounds ([3 x float], [3 x float] addrspace(3)* @"\01?testVar@@3V?$vector@M$02@@A.v", i32 0, i32 2), align 4, !dbg !92
br label %"\01?tint_zero_workgroup_memory@@YAXI@Z.exit.i", !dbg !94
"\01?tint_zero_workgroup_memory@@YAXI@Z.exit.i":
call void @dx.op.barrier(i32 80, i32 9), !dbg !95
Our compiler (Tint) emits HLSL without a group index check when numthreads(1,1,1), since we reason that at most one thread is executing the code. In practice, we have seen this fail on some GPUs intermittently - in other words, the zero-init is racey. We can fix this, of course, by simply adding the conditional check for the index; but we'd like to know:
- Is this is actually a bug in DXC?
- Or is the problem that when writing HLSL, we cannot assume that the compiler uses
numthreadsto reason about data races, and must always guard access to shared variables? - What does the
zeroinitializertag actually mean? Are GPUs supposed to ensure the memory is zero-initialized before any workgroup threads begin execution? Are certain GPU implementations wrong if they're not handling this properly?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the compiler explorer example linked in the issue and compare the two generated DXC outputs for the CHECK_LOCAL_INDEX variants. Investigate the zeroinitializer and undef handling around the groupshared stores and dx.op.barrier. Done means establishing whether DXC or the HLSL assumptions are incorrect and identifying the required compiler or test change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100