llvmlite
llvmlite is a lightweight Python binding to LLVM, primarily used for just-in-time (JIT) compilation. It allows Python code to construct LLVM Intermediate Representation (IR) in pure Python, which is then passed to LLVM for compilation and optimization. Maintained by the Numba team, it releases several times a year, often in conjunction with Numba updates and LLVM version bumps. The current stable version is 0.46.0.
Warnings
- breaking The `llvm.binding.initialize()` function, previously used to initialize LLVM, is deprecated and removed. In `llvmlite` 0.45.x, it raises a `RuntimeError`, and in 0.46.x it is completely removed. LLVM core initialization is now automatic.
- breaking Support for Typed Pointers in the binding layer was removed in `llvmlite` 0.45.0. Future versions (>=0.46, specifically >=0.47) will make Opaque Pointers the default for the IR layer and eventually remove Typed Pointer support entirely, aligning with LLVM 17+. Code relying on Typed Pointers will break if not migrated.
- gotcha Each `llvmlite` version is specifically targeted and compatible with particular LLVM feature versions. Using a mismatched LLVM version (e.g., when building from source or with dynamic linking) can lead to build failures or runtime issues. Consult the `llvmlite` documentation for the compatibility matrix. For example, `llvmlite` 0.45.0+ requires LLVM 20.x.x.
- gotcha Building `llvmlite` from source is a complex process requiring specific development tools (C++ compiler, CMake, LLVM development libraries). It is generally recommended to install pre-built binaries via `pip` or `conda` unless you have specific reasons to build from source.
- deprecated Official support for Python 3.9 was dropped with `llvmlite` 0.44.0. The minimum supported Python version is now 3.10.
Install
-
pip install llvmlite -
conda install llvmlite
Imports
- ir
from llvmlite import ir
- binding
import llvmlite.binding as llvm
Quickstart
from ctypes import CFUNCTYPE, c_double
from llvmlite import ir
import llvmlite.binding as llvm
# All these initializations are required for JIT compilation
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
# Define the LLVM IR for a simple function (double fpadd(double a, double b) { return a + b; })
double = ir.DoubleType()
fnty = ir.FunctionType(double, (double, double))
module = ir.Module(name=__file__)
func = ir.Function(module, fnty, name="fpadd")
block = func.append_basic_block(name="entry")
builder = ir.IRBuilder(block)
a, b = func.args
result = builder.fadd(a, b, name="res")
builder.ret(result)
# Convert textual LLVM IR into in-memory representation
llvm_module = llvm.parse_assembly(str(module))
llvm_module.verify()
# Create an ExecutionEngine suitable for JIT code generation
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
engine = llvm.create_mcjit_compiler(llvm_module, target_machine)
# Finalize the engine to make the function available
engine.finalize_object()
engine.run_static_constructors()
# Look up the function pointer and cast it to a C function
func_ptr = engine.get_function_address("fpadd")
cfunc = CFUNCTYPE(c_double, c_double, c_double)(func_ptr)
# Run the function
res = cfunc(1.0, 2.5)
print(f"Result of fpadd(1.0, 2.5): {res}") # Expected: 3.5