by: Tim Besard (GitHub: maleadt
)
Julia wrapper of the LLVM C API:
Supports LLVM 3.9+, Julia 0.5+
CUDAnative.jl needs LLVM to:
Move PTX back-end out of codegen → birth of LLVM.jl
Why not target the C++ API directly, using Cxx.jl?
High-level, "Julian" API
Build your own compiler
Generate IR for Julia
Optimize or compile code generated by Julia
...
Pkg.add("LLVM")
Linux, macOS (unsupported)
Julia source build: llvm-config
, headers, same host compiler ...
LLVM_ASSERTIONS=1
using LLVM
mod = LLVM.Module("demo")
; ModuleID = 'demo' source_filename = "demo"
context(mod) == GlobalContext()
true
Context() do ctx
mod = LLVM.Module("temp_module", ctx)
# ...
dispose(mod)
end
No automatic collection, use do
blocks or call dispose
!
When emitting code for Julia, use the appropriate context:
jlctx = LLVM.Context(cglobal(:jl_LLVMContext, Void));
Let's create a function:
sum(::Int32, ::Int32)::Int32
param_types = [LLVM.Int32Type(), LLVM.Int32Type()]
ret_type = LLVM.Int32Type();
LLVM types are created by functions, bound to a context.
fun_type = LLVM.FunctionType(ret_type, param_types)
sum = LLVM.Function(mod, "sum", fun_type)
declare i32 @sum(i32, i32)
mod
; ModuleID = 'demo' source_filename = "demo" declare i32 @sum(i32, i32)
builder = Builder();
Builder() do builder
# ...
end
bb = BasicBlock(sum, "entry")
position!(builder, bb)
tmp = add!(builder, parameters(sum)[1], parameters(sum)[2], "tmp")
%tmp = add i32 %0, %1
ret!(builder, tmp)
ret i32 %tmp
dispose(builder)
mod
; ModuleID = 'demo' source_filename = "demo" define i32 @sum(i32, i32) { entry: %tmp = add i32 %0, %1 ret i32 %tmp }
verify(mod)
ir = convert(String, mod);
engine = Interpreter(mod);
args = [GenericValue(LLVM.Int32Type(), 1),
GenericValue(LLVM.Int32Type(), 2)];
res = LLVM.run(engine, sum, args);
convert(Int, res)
3
dispose.(args)
dispose(res)
dispose(engine)
mod
has been consumed by the ExecutionEngine
:
mod
; ModuleID = 'demo' source_filename = "demo"
println(ir)
; ModuleID = 'demo' source_filename = "demo" define i32 @sum(i32, i32) { entry: %tmp = add i32 %0, %1 ret i32 %tmp }
mod = parse(LLVM.Module, ir, jlctx)
source_filename = "demo" define i32 @sum(i32, i32) { entry: %tmp = add i32 %0, %1 ret i32 %tmp }
context(mod) == jlctx
true
sum = get(functions(mod), "sum")
define i32 @sum(i32, i32) { entry: %tmp = add i32 %0, %1 ret i32 %tmp }
call_sum(x, y) = Base.llvmcall(LLVM.ref(sum), Int32,
Tuple{Int32, Int32},
convert(Int32,x), convert(Int32,y))
call_sum (generic function with 1 method)
call_sum(Int32(1),Int32(2))
3
@code_llvm call_sum(Int32(1),Int32(2))
define i32 @julia_call_sum_61382(i32, i32) #0 !dbg !5 { top: %2 = call i32 @jl_llvmcall1(i32 %0, i32 %1) ret i32 %2 }
push!(function_attributes(sum), EnumAttribute("alwaysinline"))
@code_llvm call_sum(Int32(1),Int32(2))
define i32 @julia_call_sum_61382(i32, i32) #0 !dbg !5 { top: %tmp.i = add i32 %1, %0 ret i32 %tmp.i }
ctx = context(mod)
Builder() do builder
bb = first(blocks(sum))
inst = first(instructions(bb))
position!(builder, inst)
alloca!(builder, LLVM.Int32Type(ctx), "unused")
end
sum
; Function Attrs: alwaysinline define i32 @sum(i32, i32) #0 { entry: %unused = alloca i32 %tmp = add i32 %0, %1 ret i32 %tmp }
Always start with a pass manager:
mpm = ModulePassManager();
ModulePassManager() do mpm
# ...
end
aggressive_dce!(mpm)
run!(mpm, mod)
true
mod
source_filename = "demo" ; Function Attrs: alwaysinline define i32 @sum(i32, i32) #0 { entry: %tmp = add i32 %0, %1 ret i32 %tmp } attributes #0 = { alwaysinline }
You can populate a pass manager using a PassManagerBuilder
:
PassManagerBuilder() do pmb
optlevel!(pmb, 0)
sizelevel!(pmb, 0)
populate!(mpm, pmb)
run!(mpm, mod)
end
false
function runOnFunction(fn::LLVM.Function)
println("Processing $(name(fn))")
return false
end
pass = FunctionPass("DummyFunctionPass", runOnFunction);
FunctionPassManager(mod) do fpm
add!(fpm, pass)
run!(fpm, sum)
end
Processing sum
false