| Issue |
207852
|
| Summary |
[HLSL][DirectX] Instcombine's `foldPHIArgLoadIntoPHI` trips up DXILResourceAccess pass
|
| Labels |
backend:DirectX,
HLSL
|
| Assignees |
|
| Reporter |
Icohedron
|
Related to https://github.com/llvm/llvm-project/issues/201901
When an HLSL shader conditionally loads from different resources, InstCombine's `foldPHIArgLoadIntoPHI` transformation converts a PHI of loaded values into a load from a PHI of resource pointers. The DirectX backend's `DXILResourceAccess` pass cannot handle PHI users of `dx.resource.getpointer` results and hits `llvm_unreachable`, crashing the compiler.
Repro: https://godbolt.org/z/rrP3dha6r
Given the shader:
```hlsl
RWStructuredBuffer<float> bufA : register(u0);
RWStructuredBuffer<float> bufB : register(u1);
RWStructuredBuffer<float> output : register(u2);
cbuffer Constants : register(b0) {
uint condition;
};
[numthreads(64, 1, 1)]
void CSMain(uint tid : SV_DispatchThreadID) {
float val;
if (condition == 0) {
val = bufA[tid];
} else {
val = bufB[tid];
}
output[tid] = val;
}
```
Compiling with `clang-dxc -T cs_6_8 -E CSMain` results in the following assertion:
```
clang: warning: argument unused during compilation: '-Qembed_debug' [-Wunused-command-line-argument]
error: Resource access is not guaranteed to map to a unique global resource
Unhandled instruction - pointer escaped?
UNREACHABLE executed at /root/llvm-project/llvm/lib/Target/DirectX/DXILResourceAccess.cpp:790!
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and dumped files.
Stack dump:
0. Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/clang --driver-mode=dxc -Zi -Qembed_debug -Fc /app/output.s -fcolor-diagnostics -fno-crash-diagnostics -T cs_6_8 -E CSMain <source>
1. <eof> parser at end of file
2. Code generation
3. Running pass 'Function Pass Manager' on module '<source>'.
4. Running pass 'DXIL Resource Access' on function '@CSMain'
#0 0x0000000004393158 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4393158)
#1 0x0000000004390074 llvm::sys::RunSignalHandlers() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4390074)
#2 0x0000000004390694 llvm::sys::CleanupOnSignal(unsigned long) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4390694)
#3 0x00000000042cf878 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0
#4 0x00007c4486a45330 (/lib/x86_64-linux-gnu/libc.so.6+0x45330)
#5 0x00007c4486a9eb2c pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x9eb2c)
#6 0x00007c4486a4527e raise (/lib/x86_64-linux-gnu/libc.so.6+0x4527e)
#7 0x00007c4486a288ff abort (/lib/x86_64-linux-gnu/libc.so.6+0x288ff)
#8 0x00000000042d9bea (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x42d9bea)
#9 0x0000000003066757 replaceAccess(llvm::IntrinsicInst*, llvm::dxil::ResourceTypeInfo&) DXILResourceAccess.cpp:0:0
#10 0x0000000003069128 transformResourcePointers(llvm::Function&, llvm::DXILResourceTypeMap&) DXILResourceAccess.cpp:0:0
#11 0x000000000306bd1b (anonymous namespace)::DXILResourceAccessLegacy::runOnFunction(llvm::Function&) DXILResourceAccess.cpp:0:0
#12 0x0000000003c7ab51 llvm::FPPassManager::runOnFunction(llvm::Function&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x3c7ab51)
#13 0x0000000003c7b081 llvm::FPPassManager::runOnModule(llvm::Module&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x3c7b081)
#14 0x0000000003c7ba41 llvm::legacy::PassManagerImpl::run(llvm::Module&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x3c7ba41)
#15 0x00000000047ab988 (anonymous namespace)::EmitAssemblyHelper::TimeCodegenPasses(llvm::function_ref<void ()>) (.isra.0) BackendUtil.cpp:0:0
#16 0x00000000047b8fdb (anonymous namespace)::EmitAssemblyHelper::emitAssembly(clang::BackendAction, std::unique_ptr<llvm::raw_pwrite_stream, std::default_delete<llvm::raw_pwrite_stream>>, clang::BackendConsumer*) BackendUtil.cpp:0:0
#17 0x00000000047b9433 clang::emitBackendOutput(clang::CompilerInstance&, clang::CodeGenOptions&, llvm::StringRef, llvm::Module*, clang::BackendAction, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::unique_ptr<llvm::raw_pwrite_stream, std::default_delete<llvm::raw_pwrite_stream>>, clang::BackendConsumer*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x47b9433)
#18 0x0000000004e80b6c clang::BackendConsumer::HandleTranslationUnit(clang::ASTContext&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4e80b6c)
#19 0x0000000006c8310c clang::ParseAST(clang::Sema&, bool, bool) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x6c8310c)
#20 0x0000000004e80f98 clang::CodeGenAction::ExecuteAction() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4e80f98)
#21 0x00000000051d299d clang::HLSLFrontendAction::ExecuteAction() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x51d299d)
#22 0x00000000051acb08 clang::FrontendAction::Execute() (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x51acb08)
#23 0x0000000005137f6d clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x5137f6d)
#24 0x00000000052af6fd clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x52af6fd)
#25 0x0000000000db49dc cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0xdb49dc)
#26 0x0000000000dab5ea ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) driver.cpp:0:0
#27 0x0000000000dab76d int llvm::function_ref<int (llvm::SmallVectorImpl<char const*>&)>::callback_fn<clang_main(int, char**, llvm::ToolContext const&)::'lambda'(llvm::SmallVectorImpl<char const*>&)>(long, llvm::SmallVectorImpl<char const*>&) driver.cpp:0:0
#28 0x0000000004ef3b59 void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const::'lambda'()>(long) Job.cpp:0:0
#29 0x00000000042cfcb4 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x42cfcb4)
#30 0x0000000004ef4685 clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const (.part.0) Job.cpp:0:0
#31 0x0000000004eafbf2 clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4eafbf2)
#32 0x0000000004eb0c0e clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&, bool) const (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4eb0c0e)
#33 0x0000000004ebcd75 clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0x4ebcd75)
#34 0x0000000000db0eca clang_main(int, char**, llvm::ToolContext const&) (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0xdb0eca)
#35 0x0000000000cccc0a main (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0xcccc0a)
#36 0x00007c4486a2a1ca (/lib/x86_64-linux-gnu/libc.so.6+0x2a1ca)
#37 0x00007c4486a2a28b __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28b)
#38 0x0000000000daaf95 _start (/opt/compiler-explorer/clang-assertions-trunk/bin/clang+0xdaaf95)
clang: error: clang frontend command failed due to signal (use -v to see invocation)
Compiler returned: 134
```
## Root-Cause Analysis
**After inlining + SROA (correct IR):**
Each branch loads from its own resource and the loaded values are merged via a
half-valued PHI:
```llvm
if.then.i:
%handle0 = call @llvm.dx.resource.handlefrombinding(0, 0, ...) ; buf0
%ptr0 = call ptr @llvm.dx.resource.getpointer(%handle0, %offset)
%v0 = load half, ptr %ptr0
br label %merge
if.else.i:
%handle1 = call @llvm.dx.resource.handlefrombinding(0, 1, ...) ; buf1
%ptr1 = call ptr @llvm.dx.resource.getpointer(%handle1, %offset)
%v1 = load half, ptr %ptr1
br label %merge
merge:
%val = phi half [ %v0, %if.then.i ], [ %v1, %if.else.i ] ; ← value PHI ✓
```
**After `InstCombinePass` — `foldPHIArgLoadIntoPHI` (broken IR):**
InstCombine sees that both incoming values of the PHI are loads of the same type
and folds them into a single load from a pointer PHI:
```llvm
if.then.i:
%handle0 = call @llvm.dx.resource.handlefrombinding(0, 0, ...)
%ptr0 = call ptr @llvm.dx.resource.getpointer(%handle0, %offset)
br label %merge
if.else.i:
%handle1 = call @llvm.dx.resource.handlefrombinding(0, 1, ...)
%ptr1 = call ptr @llvm.dx.resource.getpointer(%handle1, %offset)
br label %merge
merge:
%ptr = phi ptr [ %ptr0, %if.then.i ], [ %ptr1, %if.else.i ] ; ← pointer PHI ✗
%val = load half, ptr %ptr
```
### Why it crashes
The `DXILResourceAccess` pass runs in the DirectX backend and converts
pointer-based resource accesses into DXIL buffer load/store intrinsics. Its
`replaceAccess()` function (DXILResourceAccess.cpp:767) walks users of each
`dx.resource.getpointer` call and expects only `GEP`, `Load`, or `Store`
instructions. A `PHINode` user triggers `llvm_unreachable("Unhandled
instruction - pointer escaped?")` at line 790.
The pass does have `legalizeResourceHandles()` which can decompose PHIs of
resource handles into PHIs of indices — but only when all handles refer to the
**same global binding**. When the handles come from different bindings (as in
this case: `buf0` at register u0 vs `buf1` at register u1), it emits a
diagnostic and continues without fixing the issue, leading to the crash in
`replaceAccess()`.
The debugging, analysis, and write-up of this issue was performed with the assistance of Claude Opus 4.8.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs