Skip to content

Commit

Permalink
Remove FunctionSpec typevars and slightly refactor CompilerJob.
Browse files Browse the repository at this point in the history
  • Loading branch information
maleadt committed Feb 22, 2023
1 parent 2b95a79 commit 35ad5a0
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 59 deletions.
2 changes: 1 addition & 1 deletion examples/kernel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function main()
source = FunctionSpec(typeof(kernel), Tuple{})
target = NativeCompilerTarget()
params = TestCompilerParams()
job = CompilerJob(target, source, params)
job = CompilerJob(source, target, params)

println(GPUCompiler.compile(:asm, job)[1])
end
Expand Down
4 changes: 2 additions & 2 deletions src/driver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,10 @@ const __llvm_initialized = Ref(false)
# get a job in the appopriate world
dyn_job = if dyn_val isa CompilerJob
dyn_spec = FunctionSpec(dyn_val.source; world=job.source.world)
similar(dyn_val, dyn_spec)
CompilerJob(dyn_val; source=dyn_spec)
elseif dyn_val isa FunctionSpec
dyn_spec = FunctionSpec(dyn_val; world=job.source.world)
similar(job, dyn_spec)
CompilerJob(job; source=dyn_spec)
else
error("invalid deferred job type $(typeof(dyn_val))")
end
Expand Down
82 changes: 46 additions & 36 deletions src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,23 @@ export FunctionSpec

# what we'll be compiling

struct FunctionSpec{F,TT}
f::Type{F}
tt::Type{TT}
struct FunctionSpec
f::Type
tt::Type
world::UInt

kernel::Bool
name::Union{Nothing,String}

FunctionSpec(f::Type, tt::Type, world::Integer=Base.get_world_counter();
kernel=true, name=nothing) =
new(f, tt, world, kernel, name)
end

# copy constructor
FunctionSpec(spec::FunctionSpec; f=spec.f, tt=spec.tt, world=spec.world,
kernel=spec.kernel, name=spec.name) =
FunctionSpec(f, tt, world; kernel, name)

function Base.hash(spec::FunctionSpec, h::UInt)
h = hash(spec.f, h)
Expand All @@ -78,18 +86,9 @@ function Base.hash(spec::FunctionSpec, h::UInt)

h = hash(spec.kernel, h)
h = hash(spec.name, h)
h
end

# put the function and argument types in typevars
# so that we can access it from generated functions
FunctionSpec(f::Type, tt::Type, world::Integer=Base.get_world_counter();
kernel=true, name=nothing) =
FunctionSpec{f, tt}(f, tt, world, kernel, name)

FunctionSpec(spec::FunctionSpec; f=spec.f, tt=spec.tt, world=spec.world,
kernel=spec.kernel, name=spec.name) =
FunctionSpec(f, tt, world; kernel, name)
return h
end

function signature(@nospecialize(spec::FunctionSpec))
fn = something(spec.name, spec.f.name.mt == Symbol.name.mt ? nameof(spec.f) : spec.f.name.mt.name)
Expand All @@ -110,49 +109,60 @@ export CompilerJob
# a specific invocation of the compiler, bundling everything needed to generate code

"""
CompilerJob(target, source, params, entry_abi)
Construct a `CompilerJob` for `source` that will be used to drive compilation for
the given `target` and `params`. The `entry_abi` can be either `:specfunc` the default,
or `:func`. `:specfunc` expects the arguments to be passed in registers, simple
return values are returned in registers as well, and complex return values are returned
on the stack using `sret`, the calling convention is `fastcc`. The `:func` abi is simpler
with a calling convention of the first argument being the function itself (to support closures),
the second argument being a pointer to a vector of boxed Julia values and the third argument
being the number of values, the return value will also be boxed. The `:func` abi
will internally call the `:specfunc` abi, but is generally easier to invoke directly.
`always_inline` specifies if the Julia front-end should inline all functions into one if possible.
CompilerJob(source, target, params; entry_abi=:specfunc, always_inline=false)
Construct a `CompilerJob` for `source` that will be used to drive compilation for the given
`target` and `params`.
The `entry_abi` can be either `:specfunc` the default, or `:func`. `:specfunc` expects the
arguments to be passed in registers, simple return values are returned in registers as well,
and complex return values are returned on the stack using `sret`, the calling convention is
`fastcc`. The `:func` abi is simpler with a calling convention of the first argument being
the function itself (to support closures), the second argument being a pointer to a vector
of boxed Julia values and the third argument being the number of values, the return value
will also be boxed. The `:func` abi will internally call the `:specfunc` abi, but is
generally easier to invoke directly.
`always_inline` specifies if the Julia front-end should inline all functions into one if
possible.
"""
struct CompilerJob{T,P,F}
struct CompilerJob{T,P}
target::T
source::F
params::P
source::FunctionSpec

entry_abi::Symbol
always_inline::Bool

function CompilerJob(target::AbstractCompilerTarget, source::FunctionSpec, params::AbstractCompilerParams, entry_abi::Symbol, always_inline=true)
function CompilerJob(source::FunctionSpec,
target::AbstractCompilerTarget,
params::AbstractCompilerParams;
entry_abi::Symbol=:specfunc, always_inline=false)
if entry_abi (:specfunc, :func)
error("Unknown entry_abi=$entry_abi")
end
new{typeof(target), typeof(params), typeof(source)}(target, source, params, entry_abi, always_inline)
new{typeof(target), typeof(params)}(target, params, source, entry_abi, always_inline)
end
end
CompilerJob(target::AbstractCompilerTarget, source::FunctionSpec, params::AbstractCompilerParams; entry_abi=:specfunc, always_inline=false) =
CompilerJob(target, source, params, entry_abi, always_inline)

Base.similar(@nospecialize(job::CompilerJob), @nospecialize(source::FunctionSpec)) =
CompilerJob(job.target, source, job.params, job.entry_abi, job.always_inline)
# copy constructor
CompilerJob(job::CompilerJob; source=job.source, target=job.target, params=job.params,
entry_abi=job.entry_abi, always_inline=job.always_inline) =
CompilerJob(source, target, params; entry_abi, always_inline)

function Base.show(io::IO, @nospecialize(job::CompilerJob{T})) where {T}
print(io, "CompilerJob of ", job.source, " for ", T)
end

function Base.hash(job::CompilerJob, h::UInt)
h = hash(job.target, h)
h = hash(job.source, h)
h = hash(job.target, h)
h = hash(job.params, h)

h = hash(job.entry_abi, h)
h
h = hash(job.always_inline, h)

return h
end


Expand Down
6 changes: 3 additions & 3 deletions src/rtlib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ end

function emit_function!(mod, @nospecialize(job::CompilerJob), f, method; ctx::JuliaContextType)
tt = Base.to_tuple_type(method.types)
new_mod, meta = codegen(:llvm, similar(job, FunctionSpec(f, tt, job.source.world;
kernel=false));
source = FunctionSpec(f, tt, job.source.world; kernel=false)
new_mod, meta = codegen(:llvm, CompilerJob(job; source);
optimize=false, libraries=false, validate=false, ctx)
ft = eltype(llvmtype(meta.entry))
expected_ft = convert(LLVM.FunctionType, method; ctx=context(new_mod))
Expand Down Expand Up @@ -103,7 +103,7 @@ function build_runtime(@nospecialize(job::CompilerJob); ctx)
# derive a job that represents the runtime itself (notably with kernel=false).
source = FunctionSpec(typeof(identity), Tuple{Nothing}, job.source.world;
kernel=false, name=nothing)
job = CompilerJob(job.target, source, job.params)
job = CompilerJob(source, job.target, job.params)

for method in values(Runtime.methods)
def = if isa(method.def, Symbol)
Expand Down
2 changes: 1 addition & 1 deletion test/definitions/bpf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function bpf_job(@nospecialize(func), @nospecialize(types);
source = FunctionSpec(typeof(func), Base.to_tuple_type(types); kernel)
target = BPFCompilerTarget()
params = TestCompilerParams()
CompilerJob(target, source, params; always_inline), kwargs
CompilerJob(source, target, params; always_inline), kwargs
end

function bpf_code_llvm(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion test/definitions/gcn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function gcn_job(@nospecialize(func), @nospecialize(types);
source = FunctionSpec(typeof(func), Base.to_tuple_type(types); kernel)
target = GCNCompilerTarget(dev_isa="gfx900")
params = TestCompilerParams()
CompilerJob(target, source, params; always_inline), kwargs
CompilerJob(source, target, params; always_inline), kwargs
end

function gcn_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion test/definitions/metal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function metal_job(@nospecialize(func), @nospecialize(types);
source = FunctionSpec(typeof(func), Base.to_tuple_type(types); kernel)
target = MetalCompilerTarget(; macos=v"12.2")
params = TestCompilerParams()
CompilerJob(target, source, params; always_inline), kwargs
CompilerJob(source, target, params; always_inline), kwargs
end

function metal_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down
25 changes: 15 additions & 10 deletions test/definitions/native.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ end

# create a native test compiler, and generate reflection methods for it

NativeCompilerJob = CompilerJob{NativeCompilerTarget,TestCompilerParams}
struct NativeCompilerParams <: AbstractCompilerParams
entry_safepoint::Bool
NativeCompilerParams(entry_safepoint::Bool=false) = new(entry_safepoint)
end
GPUCompiler.runtime_module(::CompilerJob{<:Any,NativeCompilerParams}) = TestRuntime

NativeCompilerJob = CompilerJob{NativeCompilerTarget,NativeCompilerParams}

# local method table for device functions
@static if isdefined(Base.Experimental, Symbol("@overlay"))
Expand All @@ -19,14 +25,13 @@ end
GPUCompiler.method_table(@nospecialize(job::NativeCompilerJob)) = method_table
GPUCompiler.can_safepoint(@nospecialize(job::NativeCompilerJob)) = job.params.entry_safepoint

function native_job(@nospecialize(func), @nospecialize(types);
kernel::Bool=false, entry_abi=:specfunc, entry_safepoint::Bool=false,
always_inline=false, llvm_always_inline=true, jlruntime::Bool=false,
function native_job(@nospecialize(func), @nospecialize(types); kernel::Bool=false,
entry_abi=:specfunc, entry_safepoint::Bool=false, always_inline=false,
kwargs...)
source = FunctionSpec(typeof(func), Base.to_tuple_type(types); kernel)
target = NativeCompilerTarget(; llvm_always_inline, jlruntime)
params = TestCompilerParams(entry_safepoint)
CompilerJob(target, source, params, entry_abi, always_inline), kwargs
target = NativeCompilerTarget()
params = NativeCompilerParams(entry_safepoint)
CompilerJob(source, target, params; entry_abi, always_inline), kwargs
end

function native_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down Expand Up @@ -253,15 +258,15 @@ module LazyCodegen
end

import GPUCompiler: deferred_codegen_jobs
import ..TestCompilerParams
import ..NativeCompilerParams
@generated function deferred_codegen(f::F, ::Val{tt}, ::Val{world}) where {F,tt,world}
# manual version of native_job because we have a function type and world age
source = FunctionSpec(F, Base.to_tuple_type(tt), world; kernel=false)
target = NativeCompilerTarget(; jlruntime=true, llvm_always_inline=true)
# XXX: do we actually require the Julia runtime?
# with jlruntime=false, we reach an unreachable.
params = TestCompilerParams()
job = CompilerJob(target, source, params; entry_abi=:specfunc)
params = NativeCompilerParams()
job = CompilerJob(source, target, params)

addr = get_trampoline(job)
trampoline = pointer(addr)
Expand Down
2 changes: 1 addition & 1 deletion test/definitions/ptx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function ptx_job(@nospecialize(func), @nospecialize(types); kernel::Bool=false,
minthreads, maxthreads,
blocks_per_sm, maxregs)
params = TestCompilerParams()
CompilerJob(target, source, params; always_inline), kwargs
CompilerJob(source, target, params; always_inline), kwargs
end

function ptx_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion test/definitions/spirv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function spirv_job(@nospecialize(func), @nospecialize(types);
source = FunctionSpec(typeof(func), Base.to_tuple_type(types); kernel)
target = SPIRVCompilerTarget()
params = TestCompilerParams()
CompilerJob(target, source, params; always_inline), kwargs
CompilerJob(source, target, params; always_inline), kwargs
end

function spirv_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
Expand Down
1 change: 1 addition & 0 deletions test/native.jl
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ end
@test call_real(1.0+im) == 1.0

# Test ABI removal
# XXX: this relies on llvm_always_inline, which it shouldn't
ir = sprint(io->native_code_llvm(io, call_real, Tuple{ComplexF64}))
@test !occursin("alloca", ir)

Expand Down
2 changes: 0 additions & 2 deletions test/testhelpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,5 @@ module TestRuntime
end

struct TestCompilerParams <: AbstractCompilerParams
entry_safepoint::Bool
end
TestCompilerParams() = TestCompilerParams(false)
GPUCompiler.runtime_module(::CompilerJob{<:Any,TestCompilerParams}) = TestRuntime

0 comments on commit 35ad5a0

Please sign in to comment.