Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## tb/runtime-sret #1063 +/- ##
===================================================
- Coverage 92.39% 91.07% -1.32%
===================================================
Files 31 31
Lines 7327 7578 +251
===================================================
+ Hits 6770 6902 +132
- Misses 557 676 +119 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
On 32-bit x86 (other than Windows), Julia aligns Int64 and Float64 to 4 bytes, so e.g. `struct P; a::Int32; b::Int64; end` has `b` at offset 4. The spirv32 data layout aligned them to 8 instead, and Julia's codegen relies on the module's data layout: Julia 1.10 and 1.11 map byte offsets onto struct elements with it, so `p.b` was loaded from offset 0. Julia 1.12 and later address fields by byte offset, but the strides of `unsafe_load` and the layout of allocas, aggregate loads and stores and globals still follow the data layout. SPIR-V consumers furthermore lay out structs by OpenCL C's rules, aligning long and double to 8, so the struct types in kernel arguments, allocas and globals described memory that the host lays out differently. This only went unnoticed with PoCL because it swaps in the host's data layout, which is a bug of its own. Generate code with Julia's alignments, and before handing the module to the back-end, replace every struct type whose layout differs from the consumers' by a packed struct with explicit padding wherever it describes memory, which the back-ends encode as CPacked. Addresses into such structs become byte offsets, and aggregate loads and stores are split into their parts. Nothing changes on 64-bit hosts, where both layouts agree.
On 32-bit hosts, the Khronos translator got the triple spirv-unknown-unknown. In LLVM, bare spirv denotes logical SPIR-V with 64-bit pointers, so the translator emitted the Physical64 addressing model even though pointers are 32 bits wide. Use spir-unknown-unknown, the 32-bit counterpart of the spir64 triple used on 64-bit hosts, which gives Physical32 like the LLVM back-end does.
maleadt
force-pushed
the
tb/spirv32-layout
branch
from
October 28, 2026 19:45
a5cd997 to
65a4291
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1062.
On 32-bit x86 Linux, Julia aligns
Int64andFloat64to 4 bytes:struct Params; a::Int32; b::Int64; c::Float64; d::Int32; endhas its fields at offsets 0, 4, 12 and 20, and is 24 bytes. SPIR-V kernels compiled for such a host read those structs wrong, in two ways.The spirv32 data layout aligned
i64anddoubleto 8, and Julia 1.10 and 1.11 map byte offsets onto LLVM struct elements using the module's data layout. On Julia 1.10, the IR GPUCompiler produced forp.b + p.c + p.dloadedp.bfrom offset 0,p.cfrom 8 andp.dfrom 16. On a KernelAbstractions PoCL kernel withParams(1, 1000, 0.5, 7),p.bcame out as(1000 << 32) | 1. Julia 1.12 addresses fields by byte offset, but array strides, allocas, aggregate loads and stores and globals still follow the data layout.SPIR-V also carries no data layout: consumers lay out
OpTypeStructs by OpenCL C's rules, which alignlonganddoubleto 8. So even with Julia's byte offsets, the struct types of kernel arguments, allocas and globals describe memory that the host lays out differently. KernelAbstractions passes its kernel state as a byval struct to every kernel, and any user struct with anInt64orFloat64field after a smaller one is affected.This PR generates code for Julia's alignments, and before handing the module to the back-end, replaces every struct type whose layout differs from the consumers' by a packed struct with explicit padding wherever it describes memory: kernel arguments (when
wrap_byvalwraps them), other byval-like arguments, allocas and globals with their initializers. Addresses into such structs become byte offsets, and aggregate loads and stores are split into their parts. Both SPIR-V back-ends encode packed structs asCPacked, which the translator, IGC, rusticl and PoCL honour. This is what Clang does when compiling single-source offload code (HIP, SYCL) tospirv32for an i386 host: it uses the host's alignments for the device and packs records that LLVM's natural layout can't express. Aggregate values in registers keep their types, and on 64-bit hosts, where both layouts agree, nothing changes.Rewriting every struct type into a packed one with renumbered fields (instead of byte addressing) would have to touch every GEP,
extractvalueand function signature, and with typed pointers every pointer type. Accesses to an 8-byte field at a 4-byte offset remain underaligned, which OpenCL leaves undefined but CPU devices (PoCL) handle. Splitting them is left for when a strict device on a 32-bit host shows up.Int128is not handled: Julia 1.12 aligns it to 16 but neither data layout says so, which also affects 64-bit hosts and needs a separate change (SPIR-V has no 128-bit integers without an extension anyway).The second commit fixes the triple of the Khronos back-end on 32-bit hosts: it was
spirv-unknown-unknown, which LLVM treats as logical SPIR-V with 64-bit pointers, so the translator emitted thePhysical64addressing model, which 32-bit OpenCL devices reject. It is nowspir-unknown-unknown.The tests check that LLVM's field offsets under the SPIR-V target's data layout match
fieldoffset, the address a consumer computes for a field of an array element, that the SPIR-V usesCPackedexactly when the host alignsInt64to 4, the addressing model of both back-ends, and the rewriting itself on hand-written IR with typed and opaque pointers. The SPIR-V suites pass on i686 Julia 1.10, 1.12 and 1.13 and on x86_64 1.10 and 1.12.End to end, with PoCL built with 32-bit SPIR-V support (maleadt/pocl#2) and a KernelAbstractions fix for its exception-info address on 32-bit hosts, AcceleratedKernels' test suite on the PoCL back-end passes (59973/59973) on i686 Julia 1.10 through 1.13. Without this PR, kernels reading struct fields or arrays of structs return wrong values (21–22 of 31 struct and memory checks pass instead of 29, the remaining two being Int128), and AK's reductions corrupt the heap.