Skip to content

Commit

Permalink
Merge pull request #150 from gbaraldi/gb/refactor-code
Browse files Browse the repository at this point in the history
Refactor code to make it more amenable to cross compilation
  • Loading branch information
brenhinkeller authored Mar 4, 2024
2 parents 73a033c + 0eb680b commit 1e6116b
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 98 deletions.
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name = "StaticCompiler"
uuid = "81625895-6c0f-48fc-b932-11a18313743c"
authors = ["Tom Short and contributors"]
version = "0.6.3"
version = "0.7.0"


[deps]
Clang_jll = "0ee61d77-7f21-5576-8119-9fcc46b10100"
Expand Down
120 changes: 70 additions & 50 deletions src/StaticCompiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ using LLD_jll: lld
using StaticTools
using StaticTools: @symbolcall, @c_str, println
using Core: MethodTable

using Base:BinaryPlatforms.Platform, BinaryPlatforms.HostPlatform, BinaryPlatforms.arch, BinaryPlatforms.os_str, BinaryPlatforms.libc_str
using Base:BinaryPlatforms.platform_dlext
export load_function, compile_shlib, compile_executable
export native_code_llvm, native_code_typed, native_llvm_module, native_code_native
export static_code_llvm, static_code_typed, static_llvm_module, static_code_native
export @device_override, @print_and_throw
export StaticTarget

include("interpreter.jl")
include("target.jl")
Expand All @@ -32,6 +34,7 @@ compile_executable(f::Function, types::Tuple, path::String, [name::String=string
filename::String=name,
cflags=``, # Specify libraries you would like to link against, and other compiler options here
also_expose=[],
target::StaticTarget=StaticTarget(),
method_table=StaticCompiler.method_table,
kwargs...
)
Expand Down Expand Up @@ -96,16 +99,16 @@ Hello, world!
```
"""
function compile_executable(f::Function, types=(), path::String="./", name=fix_name(f);
also_expose=Tuple{Function, Tuple{DataType}}[],
also_expose=Tuple{Function, Tuple{DataType}}[], target::StaticTarget=StaticTarget(),
kwargs...)

compile_executable(vcat([(f, types)], also_expose), path, name; kwargs...)
compile_executable(vcat([(f, types)], also_expose), path, name; target, kwargs...)
end

function compile_executable(funcs::Union{Array,Tuple}, path::String="./", name=fix_name(first(first(funcs)));
filename = name,
demangle = true,
cflags = ``,
target::StaticTarget=StaticTarget(),
kwargs...
)

Expand All @@ -114,12 +117,12 @@ function compile_executable(funcs::Union{Array,Tuple}, path::String="./", name=f
isexecutableargtype = tt == Tuple{} || tt == Tuple{Int, Ptr{Ptr{UInt8}}}
isexecutableargtype || @warn "input type signature $types should be either `()` or `(Int, Ptr{Ptr{UInt8}})` for standard executables"

rt = last(only(native_code_typed(f, tt; kwargs...)))
rt = last(only(static_code_typed(f, tt; target, kwargs...)))
isconcretetype(rt) || error("`$f$types` did not infer to a concrete type. Got `$rt`")
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"

generate_executable(funcs, path, name, filename; demangle, cflags, kwargs...)
generate_executable(funcs, path, name, filename; demangle, cflags, target, kwargs...)
joinpath(abspath(path), filename)
end

Expand All @@ -129,13 +132,15 @@ compile_shlib(f::Function, types::Tuple, [path::String="./"], [name::String=stri
filename::String=name,
cflags=``,
method_table=StaticCompiler.method_table,
target::StaticTarget=StaticTarget(),
kwargs...)
compile_shlib(funcs::Array, [path::String="./"];
filename="libfoo",
demangle=true,
cflags=``,
method_table=StaticCompiler.method_table,
target::StaticTarget=StaticTarget(),
kwargs...)
```
As `compile_executable`, but compiling to a standalone `.dylib`/`.so` shared library.
Expand Down Expand Up @@ -169,33 +174,35 @@ julia> ccall(("test", "test.dylib"), Float64, (Int64,), 100_000)
"""
function compile_shlib(f::Function, types=(), path::String="./", name=fix_name(f);
filename=name,
target::StaticTarget=StaticTarget(),
kwargs...
)
compile_shlib(((f, types),), path; filename, kwargs...)
compile_shlib(((f, types),), path; filename, target, kwargs...)
end
# As above, but taking an array of functions and returning a single shlib
function compile_shlib(funcs::Union{Array,Tuple}, path::String="./";
filename = "libfoo",
demangle = true,
cflags = ``,
target::StaticTarget=StaticTarget(),
kwargs...
)
for func in funcs
f, types = func
tt = Base.to_tuple_type(types)
isconcretetype(tt) || error("input type signature `$types` is not concrete")

rt = last(only(native_code_typed(f, tt)))
rt = last(only(static_code_typed(f, tt; target, kwargs...)))
isconcretetype(rt) || error("`$f$types` did not infer to a concrete type. Got `$rt`")
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"
end

generate_shlib(funcs, true, path, filename; demangle, cflags, kwargs...)
generate_shlib(funcs, path, filename; demangle, cflags, target, kwargs...)

joinpath(abspath(path), filename * "." * Libdl.dlext)
end


"""
```julia
Expand Down Expand Up @@ -281,14 +288,18 @@ generate_executable(f, tt, args...; kwargs...) = generate_executable(((f, tt),),
function generate_executable(funcs::Union{Array,Tuple}, path=tempname(), name=fix_name(first(first(funcs))), filename=name;
demangle = true,
cflags = ``,
target::StaticTarget=StaticTarget(),
kwargs...
)
lib_path = joinpath(path, "$filename.$(Libdl.dlext)")
exec_path = joinpath(path, filename)
external = true
_, obj_path = generate_obj(funcs, external, path, filename; demangle, kwargs...)
_, obj_path = generate_obj(funcs, path, filename; demangle, target, kwargs...)
# Pick a compiler
cc = Sys.isapple() ? `cc` : clang()
if !isnothing(target.compiler)
cc = `$(target.compiler)`
else
cc = Sys.isapple() ? `cc` : clang()
end

# Compile!
if Sys.isapple()
# Apple no longer uses _start, so we can just specify a custom entry
Expand Down Expand Up @@ -318,8 +329,8 @@ end

"""
```julia
generate_shlib(f::Function, tt, [external::Bool=true], [path::String], [name], [filename]; kwargs...)
generate_shlib(funcs::Array, [external::Bool=true], [path::String], [filename::String]; demangle=true, kwargs...)
generate_shlib(f::Function, tt, [path::String], [name], [filename]; kwargs...)
generate_shlib(funcs::Array, [path::String], [filename::String]; demangle=true, target::StaticTarget=StaticTarget(), kwargs...)
```
Low level interface for compiling a shared object / dynamically loaded library
(`.so` / `.dylib`) for function `f` given a tuple type `tt` characterizing
Expand Down Expand Up @@ -356,75 +367,83 @@ julia> ccall(("test", "example/test.dylib"), Float64, (Int64,), 100_000)
5.2564961094956075
```
"""
function generate_shlib(f::Function, tt, external::Bool=true, path::String=tempname(), name=fix_name(f), filename=name; kwargs...)
generate_shlib(((f, tt),), external, path, filename; kwargs...)
function generate_shlib(f::Function, tt, path::String=tempname(), name=fix_name(f), filename=name; target=StaticTarget(), kwargs...)
generate_shlib(((f, tt),), path, filename; target, kwargs...)
end
# As above, but taking an array of functions and returning a single shlib
function generate_shlib(funcs::Union{Array,Tuple}, external::Bool=true, path::String=tempname(), filename::String="libfoo";
function generate_shlib(funcs::Union{Array,Tuple}, path::String=tempname(), filename::String="libfoo";
demangle = true,
cflags = ``,
target::StaticTarget=StaticTarget(),
kwargs...
)
if !isnothing(target.platform)
lib_path = joinpath(path, "$filename.$(platform_dlext(target.platform))")
else
lib_path = joinpath(path, "$filename.$(Libdl.dlext)")
end

lib_path = joinpath(path, "$filename.$(Libdl.dlext)")

_, obj_path = generate_obj(funcs, external, path, filename; demangle, kwargs...)
_, obj_path = generate_obj(funcs, path, filename; target, demangle, kwargs...)
# Pick a Clang
cc = Sys.isapple() ? `cc` : clang()
if !isnothing(target.compiler)
cc = `$(target.compiler)`
else
cc = Sys.isapple() ? `cc` : clang()
end
# Compile!
run(`$cc -shared $cflags $obj_path -o $lib_path `)

path, name
end

function native_code_llvm(@nospecialize(func), @nospecialize(types); kwargs...)
job, kwargs = native_job(func, types, true; kwargs...)
GPUCompiler.code_llvm(stdout, job; kwargs...)
function static_code_llvm(@nospecialize(func), @nospecialize(types); target::StaticTarget=StaticTarget(), kwargs...)
job, kwargs = static_job(func, types; target, kwargs...)
GPUCompiler.code_llvm(stdout, job; libraries=false, kwargs...)
end

function native_code_typed(@nospecialize(func), @nospecialize(types); kwargs...)
job, kwargs = native_job(func, types, true; kwargs...)
function static_code_typed(@nospecialize(func), @nospecialize(types); target::StaticTarget=StaticTarget(), kwargs...)
job, kwargs = static_job(func, types; target, kwargs...)
GPUCompiler.code_typed(job; kwargs...)
end

function native_code_native(@nospecialize(f), @nospecialize(tt), fname=fix_name(f); kwargs...)
job, kwargs = native_job(f, tt, true; fname, kwargs...)
GPUCompiler.code_native(stdout, job; kwargs...)
function static_code_native(@nospecialize(f), @nospecialize(tt), fname=fix_name(f); target::StaticTarget=StaticTarget(), kwargs...)
job, kwargs = static_job(f, tt; fname, target, kwargs...)
GPUCompiler.code_native(stdout, job; libraries=false, kwargs...)
end

# Return an LLVM module
function native_llvm_module(f, tt, name=fix_name(f); demangle, kwargs...)
function static_llvm_module(f, tt, name=fix_name(f); demangle=true, target::StaticTarget=StaticTarget(), kwargs...)
if !demangle
name = "julia_"*name
end
job, kwargs = native_job(f, tt, true; name, kwargs...)
job, kwargs = static_job(f, tt; name, target, kwargs...)
m = GPUCompiler.JuliaContext() do context
m, _ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false)
m, _ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false, libraries=false)
locate_pointers_and_runtime_calls(m)
m
end
return m
end

#Return an LLVM module for multiple functions
function native_llvm_module(funcs::Union{Array,Tuple}; demangle=true, kwargs...)
function static_llvm_module(funcs::Union{Array,Tuple}; demangle=true, target::StaticTarget=StaticTarget(), kwargs...)
f,tt = funcs[1]
mod = GPUCompiler.JuliaContext() do context
name_f = fix_name(f)
if !demangle
name_f = "julia_"*name_f
end
job, kwargs = native_job(f, tt, true; name = name_f, kwargs...)
mod,_ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false)
job, kwargs = static_job(f, tt; name = name_f, target, kwargs...)
mod,_ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false, libraries=false)
if length(funcs) > 1
for func in funcs[2:end]
f,tt = func
name_f = fix_name(f)
if !demangle
name_f = "julia_"*name_f
end
job, kwargs = native_job(f, tt, true; name = name_f, kwargs...)
tmod,_ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false)
job, kwargs = static_job(f, tt; name = name_f, target, kwargs...)
tmod,_ = GPUCompiler.codegen(:llvm, job; strip=true, only_entry=false, validate=false, libraries=false)
link!(mod,tmod)
end
end
Expand Down Expand Up @@ -458,8 +477,8 @@ end

"""
```julia
generate_obj(f, tt, external::Bool, path::String = tempname(), filenamebase::String="obj";
target = (),
generate_obj(f, tt, path::String = tempname(), filenamebase::String="obj";
target::StaticTarget=StaticTarget(),
demangle = true,
strip_llvm = false,
strip_asm = true,
Expand All @@ -471,7 +490,7 @@ a tuple type `tt` characterizing the types of the arguments for which the
function will be compiled.
`target` can be used to change the output target. This is useful for compiling to WebAssembly and embedded targets.
This is a named tuple with fields `triple`, `cpu`, and `features` (each of these are strings).
This is a struct of the type StaticTarget()
The defaults compile to the native target.
If `demangle` is set to `false`, compiled function names are prepended with "julia_".
Expand All @@ -481,7 +500,7 @@ If `demangle` is set to `false`, compiled function names are prepended with "jul
julia> fib(n) = n <= 1 ? n : fib(n - 1) + fib(n - 2)
fib (generic function with 1 method)
julia> path, name, table = StaticCompiler.generate_obj_for_compile(fib, Tuple{Int64}, "./test")
julia> path, name, table = StaticCompiler.generate_obj(fib, Tuple{Int64}, "./test")
("./test", "fib", IdDict{Any, String}())
shell> tree \$path
Expand All @@ -498,8 +517,8 @@ end

"""
```julia
generate_obj(funcs::Union{Array,Tuple}, external::Bool, path::String = tempname(), filenamebase::String="obj";
target = (),
generate_obj(funcs::Union{Array,Tuple}, path::String = tempname(), filenamebase::String="obj";
target::StaticTarget=StaticTarget(),
demangle =false,
strip_llvm = false,
strip_asm = true,
Expand All @@ -511,21 +530,22 @@ Low level interface for compiling object code (`.o`) for an array of Tuples
which will be compiled.
`target` can be used to change the output target. This is useful for compiling to WebAssembly and embedded targets.
This is a named tuple with fields `triple`, `cpu`, and `features` (each of these are strings).
This is a struct of the type StaticTarget()
The defaults compile to the native target.
"""
function generate_obj(funcs::Union{Array,Tuple}, external::Bool, path::String = tempname(), filenamebase::String="obj";
function generate_obj(funcs::Union{Array,Tuple}, path::String = tempname(), filenamebase::String="obj";
demangle = true,
strip_llvm = false,
strip_asm = true,
opt_level = 3,
target::StaticTarget=StaticTarget(),
kwargs...)
f, tt = funcs[1]
mkpath(path)
obj_path = joinpath(path, "$filenamebase.o")
mod = native_llvm_module(funcs; demangle, kwargs...)
mod = static_llvm_module(funcs; demangle, kwargs...)
obj = GPUCompiler.JuliaContext() do ctx
fakejob, _ = native_job(f, tt, external; kwargs...)
fakejob, _ = static_job(f, tt; target, kwargs...)
obj, _ = GPUCompiler.emit_asm(fakejob, mod; strip=strip_asm, validate=false, format=LLVM.API.LLVMObjectFile)
obj
end
Expand Down
Loading

2 comments on commit 1e6116b

@brenhinkeller
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/102239

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.7.0 -m "<description of version>" 1e6116bf512edd91b482ec92e8a63acdd1cfe325
git push origin v0.7.0

Please sign in to comment.