Skip to content

Fix abs() of the minimum signed integer - #11

Closed
maleadt wants to merge 32 commits into
mainfrom
tb/integer-abs
Closed

maleadt wants to merge 32 commits into
mainfrom
tb/integer-abs

Conversation

@maleadt

@maleadt maleadt commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Fork copy of pocl#2423 (fixes pocl#2422), for the Julia JLLs.

abs() of INT_MIN and LONG_MIN relied on signed overflow (__builtin_abs for int, -a for long), so LLVM assumed the result's top bit is clear: abs(x) >> 31 folded to 0, and convert_float(abs(x)) gave -2147483648.0. Julia's abs reaches PoCL as SPIR-V s_abs, so e.g. unsigned(abs(typemin(Int32))) was wrong on KernelAbstractions.jl's POCL back end, as was Julia 1.13's integer to BFloat16 conversion of typemin, which uses abs. abs() now negates in the unsigned result type. kernel/test_abs and kernel/test_abs_diff are registered with CTest, and test_abs checks the top bit of abs() of the minimum value.

The 7.2 backport, as shipped in the Julia JLLs, is on tb/integer-abs-7.2 (on top of tb/spirv-bfloat16-7.2).

Tested with LLVM 22.1.8: the new check fails without the fix and passes with it on both branches; ctest passes on main (349/349) and 7.2 (358/358). With the 7.2 build, unsigned(abs(x)) for typemin of Int8 to Int64 (stored, shifted, converted to Float32) is correct through KernelAbstractions.jl on Julia 1.12 and 1.13, and so is BFloat16(typemin(Int32)) / BFloat16(typemin(Int64)) on 1.13 with JuliaGPU/GPUCompiler.jl#1076.

ping-ee and others added 30 commits October 19, 2026 12:39
compile_and_link_program rejected any program whose device reported
available == CL_FALSE. With POCL_OFFLINE_COMPILE a device only builds
programs and is deliberately unavailable (e.g. almaif sets Available
to CL_FALSE in offline mode), so clBuildProgram failed with
CL_DEVICE_NOT_AVAILABLE before the build started.

Skip the check in offline mode, matching the existing handling in
devices.c and clCreateContext.c.
…rams

pocl_driver_build_poclbinary compiles the hidden gvar initialization kernel
through pocl_driver_build_gvar_init_kernel without a callback, which also
allocated the storage. The first launch then found the storage allocated and
never ran the initializer, so querying CL_PROGRAM_BINARIES before running a
kernel left all program-scope variables uninitialized. Only allocate the
storage when the initializer actually runs, and do so under the program lock,
publishing the storage after initialization, so that launches racing on
different threads or queues can't see a half-initialized (or doubly
allocated) buffer either.

The CPU drivers' free_program also freed the storage without clearing the
pointer, so after clBuildProgram on an already built program the initializer
was skipped and kernels used freed memory.
…d root

With the device list [sub1, sub2, other], where sub1 and sub2 partition one
root device, each device must run the binary given for it and every
binary_status entry must be written. The same devices listed as
[other, sub1, sub2] are the control. Runs with POCL_DEVICES="pthread basic"
and skips where there is no partitionable device plus a second device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he caller's index

The device list is reduced to distinct root devices before the binaries are
read, and the loop then indexed the caller's binaries[], lengths[] and
binary_status[] by the reduced position. When sub-devices of one root come
before a second root device, [sub1, sub2, other] reduces to [other, root]:
`other` was given the sub-devices' binary and ran it, and binary_status[2]
was never written.

Each root now reads the binary of the first caller entry that reduces to it,
and its status is written to every caller entry that reduces to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ries

The test leaked every program it built for its binaries, and the binaries
themselves, which the asan CI job reports as leaks.
For each device with a compiler, builds and compiles a kernel with
-cl-fp32-correctly-rounded-divide-sqrt. A device whose
CL_DEVICE_SINGLE_FP_CONFIG has CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT must
return CL_SUCCESS; one without it must return CL_BUILD_PROGRAM_FAILURE
from clBuildProgram and CL_COMPILE_PROGRAM_FAILURE from clCompileProgram,
with the reason in the build log, so a driver that cannot compile the
kernel does not pass by accident.

Labelled for CUDA, Vulkan and Level Zero. The CUDA and Vulkan devices
never set the bit and take the failure path; the x86-64 CPU devices set
it and take the success path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API spec says clBuildProgram and clCompileProgram fail for a device
whose CL_DEVICE_SINGLE_FP_CONFIG lacks CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT
when -cl-fp32-correctly-rounded-divide-sqrt is given.
compile_and_link_program has had that check since f391158, but
process_options set the flag it reads only inside the branch for
cl_program_link_options, which does not contain this option. So the
check never ran, and the option was accepted on every device, including
CUDA, Vulkan and RISC-V CPUs without F, none of which set the bit.

Set the flag where the option is accepted as a compile option, by exact
token match, and only when compiling, so clLinkProgram accepts it as
before. A build for several devices still fails as a whole when one of
them lacks the bit.

Level Zero devices do not set the bit, so the option is now refused
there, as the spec requires: PoCL does not pass it to the device
compiler, and their sqrt is not correctly rounded under it (the CTS
sqrt_cr test fails by 0.66 ulp on an Intel UHD when the bit is mapped
from ZE_DEVICE_FP_FLAG_ROUNDED_DIVIDE_SQRT). Mapping it waits until the
build requests correct rounding.

Fixes pocl#2404

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AArch64 CPU device reported the minimum single_fp_config, without
CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT, which x86-64 and RISC-V with F
set. Now that -cl-fp32-correctly-rounded-divide-sqrt is checked against
the bit (pocl#2404), builds with the option would start failing on ARM64.

The device is correctly rounded: float division compiles to FDIV and
the kernel library's sqrt (Sleef_sqrtf_u05, which is __builtin_sqrtf)
to FSQRT, in scalar and vector code, and both instructions are
correctly rounded. Measured through PoCL on a Neoverse N1 with LLVM 19,
in a conformance and a default build: fp32 x/y on 16.8M pairs (random,
subnormal and edge cases), and 1/x and sqrt on all 2^32 inputs, in
scalar and float4 kernels, gave no result that was not correctly
rounded. With the bit set, the CTS divide_cr and sqrt_cr tests, which
are skipped without it, pass at 0 ulp for every vector size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A device that offers cl_khr_subgroups must let an application reach the
extension's entry point through clGetExtensionFunctionAddressForPlatform.
Check that it does, and that it answers as the core function does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orPlatform

cl_khr_subgroups has one entry point, clGetKernelSubGroupInfoKHR, which an
application gets by name from clGetExtensionFunctionAddressForPlatform.
PoCL's list there had no entry for it, and PoCL exports no symbol of that
name, so without an ICD loader (ENABLE_ICD=OFF) a device that advertises
cl_khr_subgroups gave no way to reach it: OpenCL-CTS test_subgroups fails
its *_ext sub-tests with "clGetKernelSubGroupInfoKHR function not
available". Through a loader the name resolves (ocl-icd answers it from
its own table; the Khronos loader's cl_khr_icd2 path asks
clIcdGetFunctionAddressForPlatformKHR, which has had it since pocl#2337),
which hid the gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The analysis caches uniformity by Value pointer and opts out of
invalidation. Later transformations can delete a value and allocate
another at the same address, which then inherits the cached uniformity.
An incorrect "uniform" result makes WorkitemLoops skip saving the value
separately for each work-item, so later uses can read the last
work-item's value. Whether this happens depends on the allocator, so a
kernel can compile correctly or not depending on the process's history.

Store a WeakVH in each cache entry, and reject entries whose handle no
longer matches the queried value.
VariableUniformityAnalysis: don't reuse the uniformity of deleted values
cl_half_to_float shifted the uint16_t sign, promoted to int, left by
31, which is undefined for a negative half and flagged by UBSan. Widen
it to uint32_t first. KhronosGroup/OpenCL-Headers has the same shift,
so note the local change for whoever syncs the header next.
The _Float16 half printf formatter makes GCC emit calls to
__extendhfdf2 and __extendhfxf2, which x86 libgcc_s only exports from
GCC 12 on. A libpocl built with GCC 12 then fails to load in a process
that already loaded an older libgcc_s, e.g. under `nsys profile`.

Decode the half bits with pocl_half_to_float instead. The conversion is
exact, so the output is unchanged, and half printf now also works
without host _Float16 support, where it printed an error and the raw
bits.

Extend the halfn printf test with subnormals, the largest finite half,
-0, -inf and a negative NaN.
pocl_cmd_max_grid_dim_width multiplied the z local size with itself instead
of with the number of groups, and ignored the global offset. A grid that is
wide only in z, or that is small but offset far from the origin, therefore
got the work-group function specialized for small grids, whose range
assumptions on the group counts and global offsets then let the optimizer
fold e.g. get_num_groups(2) > 65535 or get_global_offset(0) > 65535 to false.
Bumps the codeql-dependencies group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.39.0 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@6f2ac81...9b32540)

Updates `github/codeql-action/analyze` from 4.39.0 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@6f2ac81...9b32540)

Updates `github/codeql-action/upload-sarif` from 4.39.0 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@6f2ac81...9b32540)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-dependencies
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-dependencies
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…ql-dependencies-52fc152648

build(deps): bump the codeql-dependencies group with 3 updates
Check -cl-fp32-correctly-rounded-divide-sqrt against each device
…able-device

Allow building for unavailable devices in offline compile mode
Fix program-scope variable initialization on queried and rebuilt programs
find_index_for_root_device for caller_index, original_device_list and
original_num_devices for the caller's list, loop counters declared in the
loops, and the comment on set_binary_status as suggested. No change in
behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Return clGetKernelSubGroupInfoKHR from clGetExtensionFunctionAddressForPlatform
…ices of one root

A program keeps one binary per root device, so two sub-devices of one
root given different binaries cannot both be kept: kernels on the second
would run the first one's binary. clCreateProgramWithBinary must fail with
CL_INVALID_BINARY and say so in both entries of binary_status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program keeps one binary per root device, and sub-devices use their
root's slot. When two entries of the caller's list reduce to the same root
and carry different binaries, the second could not be kept and kernels on
its device would run the first's. Fail with CL_INVALID_BINARY and set it in
both entries of binary_status. Identical binaries, the usual case for a
program built once for both sub-devices, keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In LLVM 24, the llvm::PassBuilder constructor was updated to take
llvm::LLVMContext &Context as its first parameter:
llvm/llvm-project@910cdcd
(PR llvm/llvm-project#233150)

Guard the PassBuilder constructor calls in pocl_llvm_wg.cc with
#if LLVM_MAJOR >= 24 to pass Context when building with LLVM 24 while
preserving backward compatibility with older LLVM versions.
In LLVM 24, llvm::BasicBlock::getFirstNonPHIOrDbgOrLifetime() was
renamed to llvm::BasicBlock::getFirstNonPHIOrLifetime():
llvm/llvm-project@06d62fc
(PR llvm/llvm-project#232604)

Guard the call in SubCFGFormation.cc with #if LLVM_MAJOR >= 24 to
support LLVM 24 while preserving backward compatibility with older
LLVM versions.
Pass LLVMContext to PassBuilder constructor for LLVM 24
clCreateProgramWithBinary: read binaries and write binary_status by the caller's index
clMemAllocINTEL and clSVMAlloc detect a failed shadow clCreateBuffer
only through errcode. That path is reachable when no device in the
context supports cl_ext_buffer_device_address (clCreateBuffer then
fails with CL_INVALID_DEVICE), and, once clCreateBuffer reports an
overlapping device address as an error (pocl#2376), on an overlap
too. Both were broken:

clMemAllocINTEL returned NULL without writing errcode_ret. Go through
the common ERROR exit instead.

clSVMAlloc called pocl_raw_ptr_set_erase, which frees the item, and then
POCL_MEM_FREE on the same item. Drop the second free.
pvelesko and others added 2 commits November 3, 2026 11:50
pocl_svm_memcpy_common forwards to clEnqueueCopyBuffer,
clEnqueueWriteBuffer or clEnqueueReadBuffer and then writes
(*event)->command_type whenever event is non-NULL. When the inner call
fails it does not create an event, so *event is whatever the caller
left there, typically NULL, and clEnqueueSVMMemcpy or
clEnqueueMemcpyINTEL crashes instead of returning the error (for
example CL_INVALID_CONTEXT for a wait-list event from another context,
or CL_INVALID_MEM_OBJECT for a pointer whose shadow buffer is missing).
Only set the command type on success.

Adds a check to runtime/test_svm.
abs() returns ugentype, so abs(INT_MIN) is (uint)2147483648. The int
overload used __builtin_abs, which is undefined for INT_MIN and becomes
llvm.abs with int_min_is_poison set, and the long overload negated the
signed value with an nsw sub. LLVM then assumed that the result's top
bit is clear, e.g. folding abs(x) >> 31 to 0 or converting abs(x) to
float as a signed value. Vector overloads were split down to these
scalar ones, and abs_diff() of operands with different signs uses
abs().

Negate in the unsigned type instead, for all types at once, which LLVM
turns into llvm.abs without the poison flag.

Register the existing kernel/test_abs and kernel/test_abs_diff, which
were never run, and check the top bit of abs() of the minimum value.
maleadt added a commit to maleadt/Yggdrasil that referenced this pull request Nov 3, 2026
abs() of INT_MIN and LONG_MIN relied on signed overflow, so LLVM assumed the result's top bit is clear, e.g. folding abs(x) >> 31 to 0 (pocl/pocl#2422). Julia's abs reaches PoCL as SPIR-V s_abs. Patch 0048 for the next track, 0051 for the v7.2 track (pocl/pocl#2423, JuliaGPU/pocl#11).
@maleadt maleadt closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

abs() of INT_MIN and LONG_MIN is wrong when the result is used

7 participants