Skip to content
Merged
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
4 changes: 3 additions & 1 deletion src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ A loader may hold several compiled functions in one symbol namespace without ren
anything: record names are namespaced per compiler job, and where two objects do define one
record — a relocation-carrying runtime-library function keeps its own namespace in every
kernel it is linked into — `:patch` defines it weakly, so the definitions coalesce and
patching the survivor serves both.
patching the survivor serves both. Record globals have protected visibility: they stay
visible to the loader by name, yet are never preemptible, matching the `dso_local`
references Julia emits.

The manifest is frozen once lowered: it then describes emitted code, so adding, dropping or
reordering a record errors. Take a [`copy`](@ref) to work on one afterwards, as
Expand Down
11 changes: 9 additions & 2 deletions src/relocation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,9 @@ end
emit_patchable_relocations!(mod, relocs)

Emit slots as writable, null-initialized definitions, and leave interior records as the
`extinit` definitions they already are. The loader must patch every record by `(name,
offset)` after loading the object ([`resolved_relocations`](@ref)).
`extinit` definitions they already are. Every record global is a weak, protected-visibility
definition. The loader must patch every record by `(name, offset)` after loading the object
([`resolved_relocations`](@ref)).
"""
function emit_patchable_relocations!(mod::LLVM.Module, relocs::Relocations)
used = GlobalVariable[]
Expand All @@ -681,6 +682,12 @@ function emit_patchable_relocations!(mod::LLVM.Module, relocs::Relocations)
# anchors them against DCE, and `externally_initialized` still stops the optimizer
# from believing the null initializer.
linkage!(gv, LLVM.API.LLVMWeakODRLinkage)
# Julia emits these globals `dso_local`, so backends address them PC-relatively
# (e.g. `@rel32` on AMDGPU). A weak definition with default visibility is however
# preemptible in an ELF shared link, which `ld.lld` rejects ("recompile with -fPIC").
# Protected visibility keeps the symbol in the dynamic symbol table, so loaders can
# still find it by name, while honouring the non-preemptible promise.
visibility!(gv, LLVM.API.LLVMProtectedVisibility)
push!(used, gv)
end
isempty(used) || set_used!(mod, used...)
Expand Down
24 changes: 24 additions & 0 deletions test/gcn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,30 @@ end
############################################################################################
@testset "assembly" begin

@testset "patchable relocation visibility" begin
# AMDGPU.jl links the object into a shared library with ld.lld. Julia references the
# record globals `dso_local` (PC-relative `@rel32`), so a weak default-visibility
# definition would be rejected as preemptible ("recompile with -fPIC"); the symbol
# must be protected.
if GPUCompiler.supports_relocatable_ir()
mod = @eval module $(gensym())
function kernel(out::Ptr{Bool}, s::Symbol)
unsafe_store!(out, s === :foo)
return
end
end
asm = sprint(io->GCN.code_native(io, mod.kernel, Tuple{Ptr{Bool},Symbol};
kernel=true, patch=true))
m = match(r"(?m)^\s*\.protected\s+(\S+jl_sym_foo\S*)", asm)
@test m !== nothing
if m !== nothing
name = m.captures[1]
@test occursin(r"(?m)^\s*\.weak\s+" * name, asm)
@test occursin("$(name)@rel32@lo", asm)
end
end
end

@testset "s_load for kernarg struct access" begin
mod = @eval module $(gensym())
struct MyStruct
Expand Down
14 changes: 11 additions & 3 deletions test/helpers/gcn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ module GCN
using ..GPUCompiler
import ..TestRuntime

struct CompilerParams <: AbstractCompilerParams end
struct CompilerParams <: AbstractCompilerParams
patch::Bool
CompilerParams(patch::Bool=false) = new(patch)
end
GPUCompiler.runtime_module(::CompilerJob{<:Any,CompilerParams}) = TestRuntime

function create_job(@nospecialize(func), @nospecialize(types); backend::Symbol=:external, kwargs...)
# `patch=true` keeps relocations symbolic (as AMDGPU.jl does); plain jobs resolve them in IR.
GPUCompiler.relocation_lowering(@nospecialize(job::CompilerJob{<:Any,CompilerParams})) =
job.config.params.patch ? :patch : :bake

function create_job(@nospecialize(func), @nospecialize(types); backend::Symbol=:external,
patch::Bool=false, kwargs...)
config_kwargs, kwargs = split_kwargs(kwargs, GPUCompiler.CONFIG_KWARGS)
source = methodinstance(typeof(func), Base.to_tuple_type(types), Base.get_world_counter())
target = GCNCompilerTarget(dev_isa="gfx900"; backend)
params = CompilerParams()
params = CompilerParams(patch)
config = CompilerConfig(target, params; kernel=false, config_kwargs...)
CompilerJob(source, config), kwargs
end
Expand Down