Skip to content

POCL backend: sitofp from i1 gives 1.0 instead of -1.0 #829

Description

@giordano

Note: issue drafted by the bot, but posted by me after verifying that the reproducer is accurate.

On the POCL backend, converting a signed integer that LLVM can prove is 0 or -1 to a float yields 1.0 instead of -1.0. LLVM canonicalises such conversions to sitofp i1 %c to double, where a true i1 must convert to -1.0, but the SPIR-V we emit converts it as if it were unsigned. The result is silently wrong: in Oceananigans.jl this turned an immersed boundary bottom_height = (x, y) -> ifelse(x < 2, 0, -1) into +1, so the whole grid was considered immersed.

Reproducer:

using KernelAbstractions

@kernel function k!(a, x, f)
    i = @index(Global)
    @inbounds a[i] = f(x[i])
end

x = [1.0, 3.0]
for (name, f, T) in [("ifelse(x < 2, 0, -1) → Float64", x -> ifelse(x < 2, 0, -1), Float64),
                     ("ifelse(x < 2, 0, -1) → Float32", x -> ifelse(x < 2, 0, -1), Float32),
                     ("x < 2 ? 0 : -1 → Float64",       x -> x < 2 ? 0 : -1,       Float64),
                     ("-Int(x ≥ 2) → Float64",          x -> -Int(x ≥ 2),          Float64),
                     ("ifelse(x < 2, 0.0, -1.0)",       x -> ifelse(x < 2, 0.0, -1.0), Float64)]
    a = zeros(T, 2)
    k!(CPU())(a, x, f; ndrange=2); synchronize(CPU())
    println(rpad(name, 34), a, "   expected ", T[f(xi) for xi in x])
end

With KernelAbstractions at e444fb9, GPUCompiler v2.10.0, SPIRV_LLVM_Backend_jll v23.1.1+2, pocl_standalone_jll v7.2.1+0 and Julia v1.13.1:

ifelse(x < 2, 0, -1) → Float64    [0.0, 1.0]   expected [0.0, -1.0]
ifelse(x < 2, 0, -1) → Float32    Float32[0.0, 1.0]   expected Float32[0.0, -1.0]
x < 2 ? 0 : -1 → Float64          [0.0, 1.0]   expected [0.0, -1.0]
-Int(x ≥ 2) → Float64             [0.0, 1.0]   expected [0.0, -1.0]
ifelse(x < 2, 0.0, -1.0)          [0.0, -1.0]   expected [0.0, -1.0]

Storing the constant -1 into a Float64 array, or converting the elements of an Int array with Float64.(n), works.

Generated code

For f(c::Bool) = Float64(ifelse(c, 0, -1)), the optimised LLVM IR is correct:

define double @julia_f_169(i8 zeroext %"c::Bool") {
  ...
  %1 = sitofp i1 %ifelse_cond to double
  ret double %1
}

but the SPIR-V selects 1 rather than all ones before the signed conversion:

     %8 = OpConstantNull %ulong
%ulong_1 = OpConstant %ulong 1
    %17 = OpLogicalNotEqual %bool %16 %true
    %18 = OpSelect %ulong %17 %ulong_1 %8
    %19 = OpConvertSToF %double %18
          OpReturnValue %19

(obtained with GPUCompiler.compile(:asm, job) for a job built from KernelAbstractions.POCL.compiler_config(KernelAbstractions.POCL.device(); kernel=false)).

So the bug is in the SPIR-V backend rather than in POCL. On LLVM main, SPIRVInstructionSelector::selectIToF handles a bool source through selectBoolToInt(..., IsSigned), which uses all ones for signed conversions, so either the backend shipped in SPIRV_LLVM_Backend_jll v23.1.1 predates a fix or the i1 is widened somewhere else (e.g. during legalisation) with a zero extension. I couldn't check with llc directly because the JLL only ships libspirv. If it's an LLVM bug that is still present upstream, a reduced test case for llc -mtriple=spirv64-unknown-unknown would be

define spir_func double @f(i1 %c) {
  %r = sitofp i1 %c to double
  ret double %r
}

which should select -1 (or produce -1.0 directly) for %c = true.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SPIR-VbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions