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