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
8 changes: 7 additions & 1 deletion src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ else
end

const CONFIG_KWARGS = [:kernel, :name, :entry_abi, :always_inline, :opt_level,
:libraries, :optimize, :cleanup, :validate, :strip]
:debug_level, :libraries, :optimize, :cleanup, :validate, :strip]

"""
CompilerConfig(target, params; kernel=true, entry_abi=:specfunc, name=nothing,
Expand Down Expand Up @@ -369,6 +369,12 @@ runtime_module(@nospecialize(job::CompilerJob)) = error("Not implemented")
# check if a function is an intrinsic that can assumed to be always available
isintrinsic(@nospecialize(job::CompilerJob), fn::String) = false

# the Julia type of the string pointers passed to runtime methods declared with the
# `Runtime.StringPointer` placeholder (`report_exception` & co). The strings are emitted
# as globals in the target's global address space, so targets where that is not the default
# should return an `LLVMPtr` in the matching address space.
runtime_cstring_type(@nospecialize(job::CompilerJob)) = Ptr{Cchar}

# provide a specific interpreter to use.
@static if HAS_INTEGRATED_CACHE
function get_interpreter(@nospecialize(job::CompilerJob))
Expand Down
8 changes: 4 additions & 4 deletions src/irgen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,23 @@ function emit_exception!(@nospecialize(job::CompilerJob), builder, name, inst)
if job.config.debug_level >= 1
name = globalstring_ptr!(builder, name, "exception")
if job.config.debug_level == 1
call!(builder, Runtime.get(:report_exception), [name])
call!(builder, Runtime.get(:report_exception), [name]; job)
else
call!(builder, Runtime.get(:report_exception_name), [name])
call!(builder, Runtime.get(:report_exception_name), [name]; job)
end
end

# report each frame
if job.config.debug_level >= 2
rt = Runtime.get(:report_exception_frame)
ft = convert(LLVM.FunctionType, rt)
ft = runtime_function_type(job, rt)
bt = backtrace(inst)
for (i,frame) in enumerate(bt)
idx = ConstantInt(parameters(ft)[1], i)
func = globalstring_ptr!(builder, String(frame.func), "di_func")
file = globalstring_ptr!(builder, String(frame.file), "di_file")
line = ConstantInt(parameters(ft)[4], frame.line)
call!(builder, rt, [idx, func, file, line])
call!(builder, rt, [idx, func, file, line]; job)
end
end

Expand Down
33 changes: 27 additions & 6 deletions src/rtlib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@

## higher-level functionality to work with runtime functions

function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[])
# the Julia argument types of a runtime method for a specific job, resolving
# target-dependent placeholders (see `Runtime.StringPointer`)
function runtime_types(@nospecialize(job::CompilerJob), rt::Runtime.RuntimeMethodInstance)
Runtime.resolve_types(rt.types, runtime_cstring_type(job))
end

function runtime_function_type(@nospecialize(job::CompilerJob),
rt::Runtime.RuntimeMethodInstance)
Runtime.llvm_function_type(rt, runtime_types(job, rt))
end

# `job` is required for runtime methods with target-dependent argument types
function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[];
job::Union{Nothing,CompilerJob}=nothing)
if job === nothing && Runtime.StringPointer in rt.types
error("A compiler job is required to call runtime method '$(rt.name)'")
end

bb = position(builder)
f = LLVM.parent(bb)
mod = LLVM.parent(f)
Expand All @@ -17,7 +34,11 @@ function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[
f = functions(mod)[rt.llvm_name]
ft = function_type(f)
else
ft = convert(LLVM.FunctionType, rt)
ft = if job === nothing
convert(LLVM.FunctionType, rt)
else
runtime_function_type(job, rt)
end
f = LLVM.Function(mod, rt.llvm_name, ft)
end
if !isdeclaration(f) && (rt.name !== :gc_pool_alloc && rt.name !== :report_exception)
Expand Down Expand Up @@ -45,8 +66,8 @@ function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[
elseif value_type(arg) isa LLVM.PointerType &&
parameters(ft)[i] isa LLVM.PointerType &&
addrspace(value_type(arg)) != addrspace(parameters(ft)[i])
# runtime functions are always in the default address space,
# while arguments may come from globals in other address spaces.
# arguments may come from globals in other address spaces than the
# one the runtime function expects (see `runtime_cstring_type`).
addrspacecast!(builder, args[i], parameters(ft)[i])
else
error("Don't know how to convert ", arg, " argument to ", parameters(ft)[i])
Expand Down Expand Up @@ -92,7 +113,7 @@ function emit_function!(mod, relocs::Relocations, config::CompilerConfig,
# 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 = function_type(meta.entry)
expected_ft = convert(LLVM.FunctionType, method)
expected_ft = runtime_function_type(rt_job, method)
if return_type(ft) != return_type(expected_ft)
error("Invalid return type for runtime function '$(method.name)': expected $(return_type(expected_ft)), got $(return_type(ft))")
end
Expand Down Expand Up @@ -154,7 +175,7 @@ function runtime_method_instance(@nospecialize(job::CompilerJob), method)
# table, where ahead-of-time compilation would compile the GPU-only code for the
# host; see JuliaGPU/GPUCompiler.jl#611).
return generic_methodinstance(
typeof(def), Base.to_tuple_type(method.types), job.world;
typeof(def), Base.to_tuple_type(runtime_types(job, method)), job.world;
method_table_view=method_table_view(job))
end

Expand Down
23 changes: 17 additions & 6 deletions src/runtime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ using LLVM.Interop

## representation of a runtime method instance

# Sentinel for NUL-terminated string arguments whose pointer type depends on the target.
struct StringPointer end

struct RuntimeMethodInstance
# either a function defined here, or a symbol to fetch a target-specific definition
def::Union{Function,Symbol}
Expand All @@ -33,9 +36,12 @@ struct RuntimeMethodInstance
llvm_name::String
end

function Base.convert(::Type{LLVM.FunctionType}, rt::RuntimeMethodInstance)
resolve_types(types::Tuple, string_type::Type) =
map(typ -> typ === StringPointer ? string_type : typ, types)

function llvm_function_type(rt::RuntimeMethodInstance, types::Tuple)
types = if rt.llvm_types === nothing
LLVMType[convert(LLVMType, typ; allow_boxed=true) for typ in rt.types]
LLVMType[convert(LLVMType, typ; allow_boxed=true) for typ in types]
else
rt.llvm_types()
end
Expand All @@ -49,6 +55,9 @@ function Base.convert(::Type{LLVM.FunctionType}, rt::RuntimeMethodInstance)
LLVM.FunctionType(return_type, types)
end

Base.convert(::Type{LLVM.FunctionType}, rt::RuntimeMethodInstance) =
llvm_function_type(rt, resolve_types(rt.types, Ptr{Cchar}))

const methods = Dict{Symbol,RuntimeMethodInstance}()
function get(name::Symbol)
methods[name]
Expand Down Expand Up @@ -86,8 +95,10 @@ function compile(def, return_type, types, llvm_return_type=nothing, llvm_types=n
# the strong.
if def isa Symbol
args = [gensym() for typ in types]
# The stub only satisfies host-side symbol resolution.
stub_types = resolve_types(types, Ptr{Cchar})
stub = LLVM.Context() do _
build_runtime_stub(llvm_name, return_type, types, args)
build_runtime_stub(llvm_name, return_type, stub_types, args)
end
@eval @inline $def($(args...)) = $stub
end
Expand Down Expand Up @@ -160,12 +171,12 @@ end
compile(:signal_exception, Nothing, ())

# expected functions for simple exception handling
compile(:report_exception, Nothing, (Ptr{Cchar},))
compile(:report_exception, Nothing, (StringPointer,))
compile(:report_oom, Nothing, (Csize_t,))

# expected functions for verbose exception handling
compile(:report_exception_frame, Nothing, (Cint, Ptr{Cchar}, Ptr{Cchar}, Cint))
compile(:report_exception_name, Nothing, (Ptr{Cchar},))
compile(:report_exception_frame, Nothing, (Cint, StringPointer, StringPointer, Cint))
compile(:report_exception_name, Nothing, (StringPointer,))

# NOTE: no throw functions are provided here, but replaced by an LLVM pass instead
# in order to provide some debug information without stack unwinding.
Expand Down
6 changes: 6 additions & 0 deletions src/spirv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ end
# SPIRV is not supported by our LLVM builds, so we can't get a target machine
llvm_machine(::SPIRVCompilerTarget) = nothing

function runtime_cstring_type(job::CompilerJob{SPIRVCompilerTarget})
DataLayout(llvm_datalayout(job.config.target)) do dl
Core.LLVMPtr{Cchar, globals_addrspace(dl)}
end
end

llvm_datalayout(::SPIRVCompilerTarget) = Int===Int64 ?
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1" :
"e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1"
Expand Down
30 changes: 30 additions & 0 deletions test/spirv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ end
end
end

@testset "exception strings" begin
# the exception name and backtrace strings are globals in the cross-workgroup address
# space, so the reporting runtime should accept them there without a cast.
mod = @eval module $(gensym())
kernel() = throw(DivideError())
end
# Keep the IR unoptimized because the test runtime ignores and otherwise drops the strings.
@test @filecheck begin
@check_label "define spir_kernel void @_Z6kernel"
@check "gpu_report_exception_name("
@check_same cond=opaque_ptrs "ptr addrspace(1) @exception"
@check_same cond=typed_ptrs "i8 addrspace(1)* getelementptr inbounds ({{.*}} @exception"
@check "gpu_report_exception_frame(i32 1,"
@check_same cond=opaque_ptrs "ptr addrspace(1) @di_func"
@check_same cond=opaque_ptrs "ptr addrspace(1) @di_file"
@check_same cond=typed_ptrs "i8 addrspace(1)* getelementptr inbounds ({{.*}} @di_func"
@check_same cond=typed_ptrs "i8 addrspace(1)* getelementptr inbounds ({{.*}} @di_file"
SPIRV.code_llvm(mod.kernel, Tuple{}; backend, kernel=true, debug_level=2, optimize=false)
end

# Exercise translation too, and ensure this does not introduce generic pointers.
@test @filecheck begin
@check_not "OpCapability GenericPointer"
@check "OpEntryPoint Kernel"
@check_not "OpPtrCastToGeneric"
SPIRV.code_native(mod.kernel, Tuple{}; backend, kernel=true,
debug_level=2, optimize=false)
end
end

@testset "failed runtime boxing" begin
mod = @eval module $(gensym())
import ..GPUCompiler
Expand Down