Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:

jobs:
release_test:
name: Julia ${{ matrix.version }} ${{ matrix.llvm_args }} - ${{ matrix.os }}
name: Julia ${{ matrix.version }} ${{ matrix.llvm_args }} - ${{ matrix.os }}${{ matrix.arch && format(' ({0})', matrix.arch) || '' }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
Expand All @@ -22,6 +22,9 @@ jobs:
version: ['1.10', '1.11', '1.12', '1.13', 'nightly']
os: [ubuntu-24.04, ubuntu-24.04-arm, macOS-15, windows-2025]
llvm_args: ['']
# the runner's architecture by default (a dimension, so that the 32-bit jobs below
# are added rather than merged into the existing ones)
arch: ['']
include:
# starting with Julia 1.10, we can enable opaque pointers
# from Julia 1.12 on, this is the default.
Expand Down Expand Up @@ -49,13 +52,30 @@ jobs:
- version: '1.11'
os: 'windows-2025'
llvm_args: '--opaque-pointers'
# 32-bit hosts
- version: '1.10'
os: 'ubuntu-24.04'
arch: 'x86'
- version: '1.11'
os: 'ubuntu-24.04'
arch: 'x86'
- version: '1.12'
os: 'ubuntu-24.04'
arch: 'x86'
- version: '1.13'
os: 'ubuntu-24.04'
arch: 'x86'
- version: 'nightly'
os: 'ubuntu-24.04'
arch: 'x86'
steps:
- uses: actions/checkout@v7

- name: Install Julia
uses: julia-actions/setup-julia@v3
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch || 'default' }}

- name: Set-up cache
uses: julia-actions/cache@v3
Expand Down
39 changes: 32 additions & 7 deletions src/rtlib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ function emit_function!(mod, relocs::Relocations, config::CompilerConfig,
# Keep this intermediate module relocatable even when the final back-end resolves
# relocations eagerly. The caller links a fresh copy and lowers the merged sites.
new_mod, meta = compile_unhooked(:llvm, rt_job; resolve_relocations=false)
ft = meta.entry.function_type
entry = meta.entry
expected_ft = runtime_function_type(rt_job, method)
if ft.return_type != expected_ft.return_type
error("Invalid return type for runtime function '$(method.name)': expected $(expected_ft.return_type), got $(ft.return_type)")
if entry.function_type.return_type != expected_ft.return_type
entry = sret_wrapper!(new_mod, entry, expected_ft, method)
end

# recent Julia versions include prototypes for all runtime functions, even if unused
Expand All @@ -125,12 +125,12 @@ function emit_function!(mod, relocs::Relocations, config::CompilerConfig,
# rename to the final `gpu_*` name on the per-function module, so the cached bitcode
# is immediately link-ready (no per-session rename pass on a cache hit).
decl = get(new_mod.functions, name, nothing)
if decl !== nothing && decl != meta.entry
@assert decl.value_type == meta.entry.value_type
replace_uses!(decl, meta.entry)
if decl !== nothing && decl != entry
@assert decl.value_type == entry.value_type
replace_uses!(decl, entry)
erase!(decl)
end
meta.entry.name = name
entry.name = name

io = IOBuffer()
write(io, new_mod)
Expand All @@ -145,6 +145,31 @@ function emit_function!(mod, relocs::Relocations, config::CompilerConfig,
return ci::CodeInstance
end

# Julia returns values wider than a pointer (e.g. `Int64` on 32-bit hosts) through an
# `sret` argument, while callers of runtime functions expect them to be returned by value.
# Bridge the two with a wrapper, like Julia does for `@cfunction`.
function sret_wrapper!(mod::LLVM.Module, f::LLVM.Function, ft::LLVM.FunctionType, method)
actual_ft = f.function_type
sret = isempty(actual_ft.parameters) ? nothing :
get(f.parameter_attributes[1], :sret, nothing)
T = ft.return_type
if actual_ft.return_type != LLVM.VoidType() || sret === nothing || sret.value != T ||
actual_ft.parameters[2:end] != ft.parameters
error("Invalid signature for runtime function '$(method.name)': expected $ft, got $actual_ft")
end

wrapper = LLVM.Function(mod, "", ft)
f.linkage = LLVM.Linkage.Internal
@dispose builder=IRBuilder() begin
position!(builder, LLVM.at_end(BasicBlock(wrapper, "top")))
slot = alloca!(builder, T)
call = call!(builder, actual_ft, f, [slot, wrapper.parameters...])
push!(LLVM.argument_attributes(call)[1], TypeAttribute(:sret, T))
ret!(builder, load!(builder, T, slot))
end
return wrapper
end

function runtime_results(::Type{V}, ci::CodeInstance, config::CompilerConfig) where {V}
@static if HAS_INTEGRATED_CACHE
if supports_relocatable_ir()
Expand Down
4 changes: 4 additions & 0 deletions test/helpers/native.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ end
module Runtime end

NativeCompilerJob = CompilerJob{NativeCompilerTarget,CompilerParams}

# the host's data layout, for hand-written IR whose pointers must be as large as the host's
datalayout() = GPUCompiler.llvm_datalayout(NativeCompilerTarget())

GPUCompiler.runtime_module(::NativeCompilerJob) = Runtime

GPUCompiler.method_table(@nospecialize(job::NativeCompilerJob)) = job.config.params.method_table
Expand Down
4 changes: 4 additions & 0 deletions test/helpers/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ using LLVM, LLVM.IR, LLVM.Build, LLVM.Interop
value
end

# the LLVM type of `Int` and `UInt` (and thus of a relocation word), for checks of IR that
# depends on the host's word size
const llvm_int = "i$(8sizeof(Int))"

# typed/opaque pointer detection for conditional FileCheck checks

const typed_ptrs = JuliaContext() do ctx
Expand Down
Loading
Loading