Execution
If instead of compiling the LLVM module to native code, you just want to execute it, LLVM offers different mechanisms to do so. We'll be using the following LLVM IR code for the examples in this section:
julia> add
define i64 @add(i64 %0, i64 %1) {
top:
%2 = add i64 %1, %0
ret i64 %2
}
Interpreter
LLVM's interpreter is a simple way to execute LLVM IR code, and can be constructed from just a module. Executing code is done using the run
function, which takes a reference to the function to execute, and an array of GenericValue
arguments, returning a GenericValue
result:
julia> engine = Interpreter(mod);
julia> res = run(engine, add, [GenericValue(LLVM.Int64Type(), 1),
GenericValue(LLVM.Int64Type(), 2)]);
julia> convert(Int, res)
3
After having constructed an engine, more modules can be added to it using push!
, and removed from it using delete!
.
MCJIT
Interpreting IR is obviously slow, so for all but the simplest programs you'll want to use the JIT engine instead, which is based on LLVM's MCJIT. Usage of the JIT engine is almost identical to the interpreter, using JIT
objects instead.
One crucial difference is that MCJIT does not support the run
function with arguments. Instead, you need to look up the address of the compiled function, and call it directly:
julia> engine = JIT(mod);
julia> addr = lookup(engine, "add");
julia> res = ccall(addr, Int64, (Int64, Int64), 1, 2)
3
ORCJIT
The ORCJIT engine is a more modern JIT engine, which is more flexible and powerful than MCJIT. LLVM.jl only supports the ORCv2 API, which is the latest version of ORCJIT.
Documentation for ORCJIT is a work in progress.
Thread-safe operation
Because of ORC often compiling code lazily, thread-safety is a concern. To ensure that everything is thread-safe, the ORC APIs use thread-safe wrappers of LLVM objects like contexts and modules.
Thread safe contexts are similar to regular contexts, but they require taking a lock when using them. On the Julia side, they are created much like regular contexts:
julia> ts_ctx = ThreadSafeContext()
ThreadSafeContext(Ptr{LLVM.API.LLVMOrcOpaqueThreadSafeContext} @0x00006000035fb8a0)
julia> dispose(ts_ctx)
LLVM.jl also maintains a task-bound thread-safe context, simplifying API usage much in the same way as regular contexts:
julia> ts_context()
ERROR: No LLVM thread-safe context is active
julia> ts_ctx = ThreadSafeContext();
julia> ts_context()
ThreadSafeContext(Ptr{LLVM.API.LLVMOrcOpaqueThreadSafeContext} @0x00006000035844a0)
julia> dispose(ts_ctx)
julia> ts_context()
ERROR: No LLVM thread-safe context is active
Similarly, ThreadSafeModule
is a thread-safe wrapper around a module, and can be used much like a regular module:
julia> ts_mod = ThreadSafeModule("SomeModule")
ThreadSafeModule(Ptr{LLVM.API.LLVMOrcOpaqueThreadSafeModule} @0x00006000037fb640)
julia> dispose(ts_mod)
Whereas thread-safe contexts are just for use with LLVM C APIs, it is possible to access the underlying module from a thread-safe module (taking a lock in the process):
julia> ts_mod = ThreadSafeModule("SomeModule");
julia> ts_mod() do mod
string(mod)
end
"; ModuleID = 'SomeModule'\nsource_filename = \"SomeModule\"\n"