PoCL: SPIR-V vector math fails to link on Windows

Open
#534 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

A pull request for this has already been merged.

  • #2381 by @maleadt — merged

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
42/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
julia
Domain
backend, compilers

Research direction

The issue points to PoCL’s generate_spir_wrapper.py and lib/kernel/host/CMakeLists.txt, where host kernel wrappers are generated. First read the wrapper’s calling-convention rewrites and how CMake selects generation modes for Windows targets. Done means Windows-target wrappers avoid the incompatible coercion, byval, and sret rewrites, with the reported vector math cases linking successfully.

Written by the indexing model from the issue text.

Description

kernels upstream

On Windows, kernels compiled through SPIR-V fail to link when they call a math builtin on a vector wider than the CPU's vector registers. This is the cause of #415, and why the SIMD tests are partially skipped on Windows (#533):

using OpenCL, pocl_jll, SIMD

function kernel(out, v)
    out[] = sin(v)
    return
end

v = Vec{16,Float64}(ntuple(Float64, 16))
out = CLArray{Vec{16,Float64},0}(undef)
@opencl kernel(out, v)
println(OpenCL.@allowscalar out[])
JIT session error: Symbols not found: [ _Z7_cl_sinDv16_d ]
JIT session error: Symbols not found: [ _Z7_cl_sinDv16_d ]
<16 x Float64>[1.304637681984e-311, 1.69759663277e-313, ...]

(That the launch failure then goes unnoticed, and the read returns uninitialized memory, is a separate problem: #535.)

PoCL links SPIR-V kernels against its kernel library through generated wrappers (generate_spir_wrapper.py), which hardcode the x86-64 System V calling convention: vectors wider than the CPU's vector registers are passed byval and returned sret (tables from line 384), and <2 x float> is coerced to double. The Windows kernel library (target triple x86_64-w64-windows-gnu) takes these vectors directly, so the call doesn't match the definition. From the pocl_jll 7.2.2 bitcode:

; kernel-x86_64-linux-gnu-avx512.bc
define <16 x double> @_Z7_cl_sinDv16_d(ptr noundef byval(<16 x double>) align 128 %0)

; kernel-x86_64-w64-mingw32-avx512.bc
define dso_local <16 x double> @_Z7_cl_sinDv16_d(<16 x double> noundef %0)
define spir_func <16 x double> @_Z3sinDv16_d(<16 x double> %a) {
  %1 = alloca <16 x double>, align 128
  store <16 x double> %a, ptr %1, align 128
  %call = tail call <16 x double> @_Z7_cl_sinDv16_d(ptr %1)
  ret <16 x double> %call
}

The 2-wide case (call double @_Z7_cl_sinDv2_f(double) against define <2 x float> @_Z7_cl_sinDv2_f(<2 x float>)) is just as wrong, but happens to work because the types have the same size.

Which widths break depends on the register size of the kernel library variant PoCL picks for the CPU. Testing sin and fma with each variant forced through POCL_KERNELLIB_NAME:

variant failing
avx512 16 x Float64
avx2 8 x Float64, 16 x Float32, 16 x Float64
sse2 16 x Float16, 8 and 16 x Float32, 3 to 16 x Float64

That is exactly the variant's byval table. Everything passes through the OpenCL C path (spirv2clc), which doesn't use these wrappers.

The fix in PoCL is to generate the wrappers without the coercion, byval and sret rewrites when targeting Windows, where the backend lowers vector arguments itself: a Windows mode for the script, selected for *-windows-* triples where lib/kernel/host/CMakeLists.txt generates the wrapper. Generating the wrappers with clang for the actual target triple would avoid duplicating calling-convention rules altogether, but is a larger change.

pocl_jll 7.2.2+0, OpenCL.jl #533, Julia 1.13.1, Windows Server 2022 (VM on an AMD Ryzen 9 9950X).

Dominant language
Julia
Stars
315
Forks
50
Avg merge
1d 1h
Merged PRs (30d)
36

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from JuliaGPU/OpenCL.jl

All issues in JuliaGPU/OpenCL.jl

Similar issues

More Julia issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.