Skip to content

Enable SPIR-V on 32-bit x86 CPU hosts - #3

Open
maleadt wants to merge 8 commits into
mainfrom
tb/cpu-spirv-32bit
Open

maleadt wants to merge 8 commits into
mainfrom
tb/cpu-spirv-32bit

Conversation

@maleadt

@maleadt maleadt commented Oct 26, 2026

Copy link
Copy Markdown
Member

SPIR-V on the CPU device has been disabled on 32-bit hosts since pocl#1883, as a stopgap for pocl#1880 ("until someone finishes it"). The crash in that issue, a double free when a 32-bit device rejected the 64-bit spirv/printf module, has since been fixed. What remained were two real gaps, both fixed here for i386 System V hosts (e.g. i686 Linux):

  • The SPIR builtin wrapper. 32-bit hosts used the checked-in spir_wrapper32.ll from 2019, which only has scalar builtins: no vectors, no atomics with a scope, no work_group_barrier(flags, scope), and 38 wrappers with broken pointer parameters. generate_spir_wrapper.py now takes --address-bits 32, which types and mangles size_t parameters as 32-bit and selects the i386 ABI: clang passes and returns every OpenCL vector directly there, except that <8 x i8>, <4 x i16> and <2 x i32> arguments are coerced to i64. That holds for every distro kernel library variant (i386 through AVX-512), so the wrapper is generated once per variant. Comparing every wrapper declaration with the kernel library definitions gives 0 mismatches on all 13 i686 variants, and the same missing builtins as the x86-64 wrappers. The output for 64-bit targets is unchanged.

  • Struct layouts. Before linking a SPIR module with the kernel library, PoCL replaces its datalayout with the host's. SPIR struct types aren't padded explicitly, because the SPIR datalayout already aligns long and double to 8 bytes, as OpenCL C requires. i386 aligns them to 4, so the kernels silently accessed fields, struct arguments and arrays of structs at the wrong offsets, while sizeof/offsetof (folded by the SPIR compiler) still reported the OpenCL layout. A new step before the datalayout swap replaces every struct type whose layout differs, wherever it describes memory, by a packed struct with explicit padding that keeps the SPIR offsets and size. That covers GEPs (with renumbered field indices), allocas, globals and their initializers, and the byval/sret/byref/elementtype attributes. Aggregate SSA values have no layout and keep their type; aggregate loads and stores are split into per-element accesses at the SPIR offsets. Clang does the equivalent for OpenCL C on i386 (explicitly padded struct types). Where the layouts agree, which is the case on all 64-bit hosts, the step changes nothing: on x86-64 every SPIR-V-built program.bc of the test suite is byte-identical with and without it.

ARM32 (AAPCS lowers vectors differently and needs its own wrapper ABI table), RISC-V32 and 32-bit Windows stay disabled.

Tests:

  • regression/test_spirv_struct_layout: struct buffers, by-value struct arguments, nested structs, tail padding, private arrays of structs, constant initializers, and aggregate loads, stores and SSA values (from a hand-written .ll). It has 38 wrong fields on i686 without the layout fix.
  • regression/test_spirv_builtins: the builtins whose wrappers depend on the address bits or the calling convention, plus scoped atomics, work_group_barrier and sub-groups.
  • Both use 32-bit and 64-bit SPIR-V built from the same sources, with the commands in the sources.
  • The regression tests with 64-bit SPIR-V inputs now skip on 32-bit devices, the compiler unit tests with .spirv inputs are only registered on 64-bit hosts, and spirv/printf (the test from test spirv/printf segfaults on 32-bit architectures pocl/pocl#1880) and example1 get 32-bit twins.
  • A new GitHub-hosted CI job builds and tests PoCL in an i386 Debian testing container with LLVM 21. It builds with -msse2 because of Build fails on i686: error: inlining failed in call to ‘always_inline’ ‘_mm_getcsr’: target specific option mismatch pocl/pocl#2097, and excludes the tests that fail on i686 regardless of SPIR-V (listed below). LLVM 21 is needed because before it, JITLink can't handle R_386_GOT32X. With Debian trixie's LLVM 19, the CPU JIT fails on i386 with "Unsupported i386 relocation:43", which breaks many OpenCL C tests too. That is independent of this PR and probably calls for gating the JIT on LLVM 21 for i386.

Validation:

  • Native i686 build (LLVM 22, -DKERNELLIB_HOST_CPU_VARIANTS=distro): 346/357 pass. The 11 failures are identical with -DENABLE_SPIRV=OFF, so they're unrelated:
    • kernel/test_printf (%ld of LONG_MIN);
    • regression/test_signal_handlers* (the SIGFPE avoidance is x86-64 only);
    • runtime/test_image_normalized_conv;
    • segfaults in runtime/test_event_free and examples/vecbuiltin*.
  • The CI recipe in the Debian testing i386 container (LLVM 21): 344/344 pass with the exclusions. Without them, the only other failures are the first three items above and regression/test_issue_2005 (allocations beyond the 32-bit address space).
  • x86-64 (LLVM 23): 374/374 pass.
  • A struct-heavy kernel runs equally fast on i686 from OpenCL C source (Clang's padded structs) and from SPIR-V (the packed replacements).

Add an --address-bits option. With 32, size_t-typed builtin parameters and
returns (vloadN/vstoreN, vload_half, async copies, prefetch,
get_image_array_size) are mangled and typed as uint, and the module uses
the spir triple. Selecting the 32-bit prefix through sys.argv had been dead
since the switch to argparse.

For cpu_x86, 32 address bits select the i386 System V ABI. Clang passes and
returns every OpenCL vector directly there, except that <8 x i8>, <4 x i16>
and <2 x i32> arguments are coerced to i64. This holds for every i686
kernel library variant, so the register size doesn't matter. The output for
64-bit targets is unchanged.
Use the generated wrapper with --address-bits 32 instead of the checked-in
spir_wrapper32.ll, which only has scalar builtins. Like on RISC-V, generate
it once per kernel library variant: the i386 ABI doesn't depend on the
vector registers, and the variants without SSE2 never reach the generation
in the SLEEF vector-size loop.
Before linking a SPIR module with the kernel library, its datalayout is
replaced by the host's. Struct types from SPIR(-V) aren't padded explicitly,
because the SPIR datalayout already aligns long and double to 8 bytes as
OpenCL C requires. Where the host aligns them less (i386), field offsets,
struct sizes and array strides silently changed, while sizeof and offsetof,
folded by the SPIR compiler, still reported the SPIR layout.

Before the swap, replace every struct type whose layout differs, wherever it
describes memory, by a packed struct with explicit padding: GEPs (renumbering
the field indices), allocas, globals and their initializers, and the
byval/sret/byref/elementtype attributes. Aggregate SSA values have no
layout and keep their type; aggregate loads and stores are split into
per-element accesses at the SPIR offsets. This is what Clang does for
OpenCL C on i386, and a no-op where the layouts agree (all 64-bit hosts).

The test checks struct buffers, by-value struct arguments, nested structs,
tail padding, private arrays of structs, constant initializers and aggregate
loads and stores, from 32-bit and 64-bit SPIR-V.
A 32-bit device rightly rejects Physical64 modules, so the regression tests
with 64-bit SPIR-V inputs now skip there, using the poclu helper for the
device's address bits, and the compiler unit tests with .spirv inputs are
only registered on 64-bit hosts. spirv/printf, the test that led to SPIR-V
being disabled on 32-bit hosts (pocl#1880), and example1 get 32-bit SPIR-V
twins built from the same sources.
Call the builtins whose SPIR wrappers depend on the address bits (size_t
parameters of vloadN/vstoreN, vload_half/vstore_half, async copies and
prefetch) or on the calling convention (64-bit vectors that i386 coerces to
integers, wide vectors that x86-64 passes in memory, vec3), plus scoped
atomics, work_group_barrier and sub-groups, from 32-bit and 64-bit SPIR-V
built from the same sources. Double precision and sub-groups are in
separate modules so that devices without them still run the rest.
SPIR-V was disabled on 32-bit hosts in pocl#1883 after spirv/printf crashed on
Debian's i386, armel and armhf builds (pocl#1880), until the 32-bit support was
finished. The crash itself (a double free on the error path for 64-bit
modules) has since been fixed, and with the generated i386 SPIR wrapper and
the preserved struct layouts, SPIR-V works on i386 System V hosts.

ARM32, RISC-V32 and 32-bit Windows stay disabled: the wrapper generator
doesn't know their ABIs.
The search for the static SVML and IRC libraries in the oneAPI compiler
directory also ran on 32-bit x86 (I386 and X32 set X86 too), where it finds
the x86-64 archives, as oneAPI no longer ships ia32 libraries. These were
then linked into the 32-bit kernels, which crashed in vectorized math
builtins.
Run the CPU tests in an i386 Debian container on a GitHub-hosted runner.
Debian testing, because the CPU JIT needs LLVM 21 on i386 (R_386_GOT32X
support in JITLink), and with -msse2 because Debian's i386 baseline lacks
the SSE the FTZ handling needs (pocl#2097). The tests that fail on i686
regardless of SPIR-V are excluded.
maleadt added a commit to maleadt/Yggdrasil that referenced this pull request Nov 5, 2026
…he patch series

Add pocl/pocl#2375 and pocl/pocl#2430 to both tracks. They fold the local
and sub-group sizes before the barrier passes and fully unroll short
constant-trip loops with barriers, such as sub-group and work-group
reductions, instead of running each iteration as separate work-item loops.

Bring the other patches up to date with their sources: the merged
versions of pocl/pocl#2190, JuliaPackaging#2339 and JuliaPackaging#2423, the latest revisions of
JuliaPackaging#2359 and JuliaPackaging#2384, and on the v7.2 track also JuliaGPU/pocl#4, JuliaPackaging#2239,
JuliaPackaging#2394 and JuliaPackaging#2410, with JuliaPackaging#2381 and its mingw32 follow-up as separate
patches. The spirv32 series gains the SVML commit of JuliaGPU/pocl#3.
The v7.2 series is renumbered without its gap.
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.

1 participant