KhronosGroup / KhronosGroup/SPIRV-LLVM-Translator
Invalid OpenCL version and non-cpp source language lead to crash while generating device-dependent OpenCL binaries from a valid SPIR-V input
- Dominant language
- LLVM
- Stars
- 625
- Forks
- 279
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 34
Description
At the moment, generation of device-dependent OpenCL program binary from a valid SPIR-V input crashes in two slightly different, but related cases:
* when Translator generates a wrong OpenCL version in LLVM IR output during SPIR-V -> LLVM IR translation, and
* when SPIR-V input defines OpenCL_C source language rather than OpenCL_CPP.
1. Let's consider a simple reproducer, referred further as `ocl-ver-crash.ll`:
```
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
target triple = "spir64-unknown-unknown"
define weak_odr dso_local spir_kernel void @foo() {
entry:
%myval.i = alloca float, align 4
%myval.ascast.i = addrspacecast ptr %myval.i to ptr addrspace(4)
store float 0.000000e+00, ptr %myval.i, align 4
%call.i.i.i = call spir_func float @_Z21__spirv_AtomicFMaxEXT(ptr addrspace(4) %myval.ascast.i, i32 1, i32 896, float 1.230000e+02)
ret void
}
declare dso_local spir_func float @_Z21__spirv_AtomicFMaxEXT(ptr addrspace(4), i32, i32, float)
```
After creating SPIR-V by `llvm-as ocl-ver-crash.ll -o - | llvm-spirv --spirv-ext=+SPV_EXT_shader_atomic_float_min_max -o ocl-ver-crash.spv` and running AOT by `opencl-aot ocl-ver-crash.spv --device=cpu --cmd=build` we observe a crash of the build followed by this confusing diagnostics:
```
Failed to build device program
CompilerException Failed to lookup symbol foo
JIT session error: Symbols not found: [ _Z10atomic_maxPU3AS4Vff ]
Failed to materialize symbols: { (main, { foo }) }
```
Let's look into LLVM IR that is produced from the problematic SPIRV (`llvm-spirv ocl-ver-crash.spv -r -o - | llvm-dis -o ocl-ver-crash-to-run.ll`):
```
; ModuleID = ''
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v24:32:32-v32:32:32-v48:64:64-v64:64:64-v96:128:128-v128:128:128-v192:256:256-v256:256:256-v512:512:512-v1024:1024:1024"
target triple = "spir64-unknown-unknown"
; Function Attrs: nounwind
define spir_kernel void @foo() #0 !kernel_arg_addr_space !3 !kernel_arg_access_qual !3 !kernel_arg_type !3 !kernel_arg_type_qual !3 !kernel_arg_base_type !3 !spirv.ParameterDecorations !3 {
entry:
%myval.i = alloca float, align 4
%myval.ascast.i = addrspacecast ptr %myval.i to ptr addrspace(4)
store float 0.000000e+00, ptr %myval.i, align 4
%call.i.i.i = call spir_func float @_Z10atomic_maxPU3AS4Vff(ptr addrspace(4) %myval.ascast.i, float 1.230000e+02) #0
ret void
}
; Function Attrs: nounwind
declare spir_func float @_Z10atomic_maxPU3AS4Vff(ptr addrspace(4), float) #0
attributes #0 = { nounwind }
!spirv.MemoryModel = !{!0}
!opencl.enable.FP_CONTRACT = !{}
!spirv.Source = !{!1}
!opencl.spir.version = !{!2}
!opencl.ocl.version = !{!1}
!opencl.used.extensions = !{!3}
!opencl.used.optional.core.features = !{!3}
!spirv.Generator = !{!4}
!0 = !{i32 2, i32 2}
!1 = !{i32 0, i32 0}
!2 = !{i32 1, i32 2}
!3 = !{}
!4 = !{i16 6, i16 14}
```
There is a correct declaration and call to `@_Z10atomic_maxPU3AS4Vff`, however, generation of the device-dependent OpenCL program binary fails due to the wrong OpenCL version:
```
!opencl.ocl.version = !{!1}
!1 = !{i32 0, i32 0}
```
**As a side note, this invalid version would not be a problem for AOT/JIT when using the atomic buildin with global address space, but for generic address space evidently there are requirements to the version.**
We can see from `ocl-ver-crash.spv ` contents why Translator creates a wrong OpenCL version:
```
...
OpSource Unknown 0
...
```
Such an input along with a logic of SPIRVToLLVM::transSourceLanguage():
https://github.com/KhronosGroup/SPIRV-LLVM-Translator/blob/e1f7ebee5ed8b9021236d1d5e2292bcb45df6988/lib/SPIRV/SPIRVReader.cpp#L4823
lead to 0.0 OpenCL version.
This is the first part of the issue. It has a clear path forward how to fix it, by not allowing producing invalid OpenCL version in SPIRVToLLVM::transSourceLanguage().
2. Let's set source language version (1.0) inside SPIRV file as the following and create a new SPIRV input `ocl-ver-crash-ver1.0.spv`:
```
OpSource OpenCL_C 100000
```
Translator generates a valid OpenCL 1.0 version this time, and refers to `OpenCL_C` source language (that is `3` in `!1 = !{i32 3, i32 100000}`):
```
...
%call.i.i.i = call spir_func float @_Z10atomic_maxPU3AS4Vff(ptr addrspace(4) %myval.ascast.i, float 1.230000e+02) #0
...
declare spir_func float @_Z10atomic_maxPU3AS4Vff(ptr addrspace(4), float) #0
...
!spirv.Source = !{!1}
!1 = !{i32 3, i32 100000}
!opencl.ocl.version = !{!3}
!3 = !{i32 1, i32 0}
```
Running `opencl-aot ocl-ver-crash-ver1.0.spv --device=cpu --cmd=build` we get the same diagnostic, as before, no changes to better:
```
CompilerException Failed to lookup symbol foo
JIT session error: Symbols not found: [ _Z10atomic_maxPU3AS4Vff ]
Failed to materialize symbols: { (main, { foo }) }
```
Let's get back to SPIRV input and set OpSource using CPP rather than C source language, and create a new SPIRV input `ocl-ver-crash-ver1.0CPP.spv`:
```
OpSource OpenCL_CPP 100000
```
Output LLVM IR after `llvm-spirv ocl-ver-crash-ver1.0CPP.spv -r -o - | llvm-dis -o ocl-ver-crash-ver1.0CPP.ll` is:
```
!spirv.Source = !{!1}
!opencl.ocl.version = !{!3}
!1 = !{i32 4, i32 100000}
!3 = !{i32 1, i32 0}
```
The only change is a reference to CPP language (value `4`):
```
git diff ocl-ver-crash-ver1.0.ll ocl-ver-crash-ver1.0CPP.ll
diff --git a/ocl-ver-crash-ver1.0.ll b/ocl-ver-crash-ver1.0CPP.ll
index 458526c..c00c6e2 100644
--- a/ocl-ver-crash-ver1.0.ll
+++ b/ocl-ver-crash-ver1.0CPP.ll
!0 = !{i32 2, i32 2}
-!1 = !{i32 3, i32 100000}
+!1 = !{i32 4, i32 100000}
!2 = !{i32 1, i32 2}
```
Let's try the new version of SPIRV input (`ocl-ver-crash-ver1.0CPP.spv`). Now `opencl-aot ocl-ver-crash-ver1.0CPP.spv --device=cpu --cmd=build` executes successfully, the crash has gone.
This chain of input SPIRV transformation shows a dependency of the issue on C/C++ source language, independent from OpenCL version issue, suggests C on mangling vs. C++ mangling, and it also may (or may not) be related to https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/2513.
_Note, that there is no user-defined mangling neither in the input LLVM IR, where builtin is referred to as `@_Z21__spirv_AtomicFMaxEXT`, not in generated SPIR-V code, where it's transformed to `OpAtomicFMaxEXT`. This points out to the second issue in Translator that needs further investigation and maybe further discussion._
FYI @MrSidims @svenvh @asudarsa
Contributor guide
Research direction
Start with SPIRVToLLVM::transSourceLanguage() in lib/SPIRV/SPIRVReader.cpp and run the supplied llvm-as, llvm-spirv, and opencl-aot reproducers. Verify handling of Unknown and OpenCL_C source languages, including the generic-address-space atomic case. Done means valid OpenCL version metadata is produced and the C/C++ inputs no longer crash during device-dependent binary generation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100