From 948ac96cd8281b49f10f41224f6bc622480a0ed0 Mon Sep 17 00:00:00 2001 From: Ludovic Raess Date: Tue, 6 Oct 2026 13:29:37 +0200 Subject: [PATCH] GCN: spell out the host's i128 alignment in the datalayout Since AMDGPU_LLVM_Backend_jll 23.1.1+3, the external back-end aligns i128 to 16 bytes, while Julia before 1.12 aligns Int128 to 8 bytes on the host, so kernel arguments after an Int128 were read at the wrong offset (JuliaGPU/AMDGPU.jl#1002). The back-end replaces the module's datalayout with its own, but since 23.1.1+4 keeps a 64-bit i128 alignment when the module asks for one. Append the host's alignment for the external back-end; on 1.12+ this also makes the middle end agree with the back-end's i128:128. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Tim Besard --- src/gcn.jl | 12 ++++++++++++ test/gcn.jl | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/gcn.jl b/src/gcn.jl index cb4146f5..7e4a1cfa 100644 --- a/src/gcn.jl +++ b/src/gcn.jl @@ -49,6 +49,18 @@ function llvm_machine(target::GCNCompilerTarget) return tm end +# the external back-end replaces the module's datalayout with its own, which aligns i128 to +# 16 bytes. it does keep a 64-bit alignment if the module asks for one (since +# AMDGPU_LLVM_Backend_jll 23.1.1+4), which Julia needs before 1.12 to lay out kernel +# arguments like the host does. +function llvm_datalayout(target::GCNCompilerTarget) + dl = @invoke llvm_datalayout(target::AbstractCompilerTarget) + (dl === nothing || target.backend !== :external) && return dl + @dispose dl=dl begin + string(dl) * "-i128:$(8 * Base.datatype_alignment(Int128))" + end +end + # AMDGPU calls the device scope `agent`, and the subgroup one `wavefront` llvm_syncscope(::GCNCompilerTarget, name::String) = diff --git a/test/gcn.jl b/test/gcn.jl index 90ce90a7..243cdc60 100644 --- a/test/gcn.jl +++ b/test/gcn.jl @@ -338,6 +338,35 @@ end end end +@testset "Int128 kernel arguments" begin + # the back-end must lay out kernel arguments like the host does, which before Julia 1.12 + # aligns Int128 to 8 bytes (JuliaGPU/AMDGPU.jl#1002) + mod = @eval module $(gensym()) + kernel(a::Int32, b::Int128, c::Tuple{Int32,Int128}, d::Int64) = return + end + types = Tuple{Int32, Int128, Tuple{Int32,Int128}, Int64} + arg_offset(i) = fieldoffset(types, i) + arg_size(i) = sizeof(fieldtype(types, i)) + + @test @filecheck begin + @check "amdhsa.kernels:" + @check_next "- .args:" + @check ".offset: $(arg_offset(1)){{\$}}" + @check_next ".size: $(arg_size(1)){{\$}}" + @check_next ".value_kind: by_value" + @check ".offset: $(arg_offset(2)){{\$}}" + @check_next ".size: $(arg_size(2)){{\$}}" + @check_next ".value_kind: by_value" + @check ".offset: $(arg_offset(3)){{\$}}" + @check_next ".size: $(arg_size(3)){{\$}}" + @check_next ".value_kind: by_value" + @check ".offset: $(arg_offset(4)){{\$}}" + @check_next ".size: $(arg_size(4)){{\$}}" + @check_next ".value_kind: by_value" + GCN.code_native(mod.kernel, types; dump_module=true, kernel=true) + end +end + @testset "skip scalar trap" begin mod = @eval module $(gensym()) workitem_idx_x() = ccall("llvm.amdgcn.workitem.id.x", llvmcall, Int32, ())