| name | llvm-backend |
| description | LLVM IR generation and optimization. Use when generating LLVM intermediate representation, performing compiler optimizations, or targeting multiple architectures. |
LLVM Backend
Generate and optimize LLVM Intermediate Representation (IR) for compilation.
Installation
pip install llvmlite
# or for full LLVM bindings:
pip install llvmpy
Quick Start
from llvmlite import ir, binding as llvm
# Initialize LLVM
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
# Create module
module = ir.Module(name='my_module')
# Define function type: int add(int, int)
fnty = ir.FunctionType(ir.IntType(32), [ir.IntType(32), ir.IntType(32)])
# Create function
func = ir.Function(module, fnty, name='add')
# Create basic block
block = func.append_basic_block(name='entry')
builder = ir.IRBuilder(block)
# Generate IR: add two parameters and return
result = builder.add(func.args[0], func.args[1])
builder.ret(result)
# Print LLVM IR
print(module)
Function Generation
from llvmlite import ir
# Create module and builder
module = ir.Module(name='example')
builder = None
def create_function(name, param_types, return_type):
"""Create function with given signature."""
fnty = ir.FunctionType(return_type, param_types)
func = ir.Function(module, fnty, name=name)
return func
def generate_factorial(module):
"""Generate factorial function in LLVM IR."""
# int factorial(int n)
i32 = ir.IntType(32)
fnty = ir.FunctionType(i32, [i32])
func = ir.Function(module, fnty, name='factorial')
# Entry block
entry = func.append_basic_block('entry')
builder = ir.IRBuilder(entry)
# Base case check: n <= 1
n = func.args[0]
cond = builder.icmp_signed('<=', n, ir.Constant(i32, 1))
# Create blocks
then_block = func.append_basic_block('then')
else_block = func.append_basic_block('else')
merge_block = func.append_basic_block('merge')
builder.cbranch(cond, then_block, else_block)
# Then block: return 1
builder.position_at_end(then_block)
builder.branch(merge_block)
then_value = ir.Constant(i32, 1)
# Else block: return n * factorial(n-1)
builder.position_at_end(else_block)
n_minus_1 = builder.sub(n, ir.Constant(i32, 1))
rec_call = builder.call(func, [n_minus_1])
else_value = builder.mul(n, rec_call)
builder.branch(merge_block)
# Merge block: phi node
builder.position_at_end(merge_block)
phi = builder.phi(i32)
phi.add_incoming(then_value, then_block)
phi.add_incoming(else_value, else_block)
builder.ret(phi)
return func
Control Flow
def generate_if_statement(builder, condition, then_gen, else_gen=None):
"""Generate if-then-else control flow."""
func = builder.function
then_block = func.append_basic_block('if.then')
else_block = func.append_basic_block('if.else') if else_gen else None
merge_block = func.append_basic_block('if.end')
# Branch on condition
if else_block:
builder.cbranch(condition, then_block, else_block)
else:
builder.cbranch(condition, then_block, merge_block)
# Generate then block
builder.position_at_end(then_block)
then_value = then_gen(builder)
if not builder.block.is_terminated:
builder.branch(merge_block)
# Generate else block
else_value = None
if else_block:
builder.position_at_end(else_block)
else_value = else_gen(builder)
if not builder.block.is_terminated:
builder.branch(merge_block)
# Merge block
builder.position_at_end(merge_block)
return merge_block
Local Variables
def create_alloca(builder, var_type, var_name):
"""Create stack-allocated variable."""
# Allocate at function entry
entry_block = builder.function.entry_basic_block
with builder.goto_entry_block():
alloca = builder.alloca(var_type, name=var_name)
return alloca
def generate_local_variables():
"""Example with local variables."""
i32 = ir.IntType(32)
module = ir.Module()
fnty = ir.FunctionType(i32, [])
func = ir.Function(module, fnty, name='test')
block = func.append_basic_block('entry')
builder = ir.IRBuilder(block)
# Allocate local variable
x_ptr = builder.alloca(i32, name='x')
# Store value
builder.store(ir.Constant(i32, 42), x_ptr)
# Load value
x_val = builder.load(x_ptr)
# Return value
builder.ret(x_val)
return module
Optimization
from llvmlite import binding as llvm
# Initialize optimization passes
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
def optimize_module(llvm_ir):
"""Apply optimization passes to LLVM IR."""
# Parse LLVM IR
mod = llvm.parse_assembly(llvm_ir)
mod.verify()
# Create pass manager
pmb = llvm.create_pass_manager_builder()
pmb.opt_level = 2 # Optimization level (0-3)
pmb.size_level = 0 # Size optimization level (0-2)
pm = llvm.create_module_pass_manager()
pmb.populate(pm)
# Run optimization passes
pm.run(mod)
return str(mod)
Compilation to Machine Code
from llvmlite import binding as llvm
def compile_to_native(llvm_ir):
"""Compile LLVM IR to native machine code."""
# Parse IR
mod = llvm.parse_assembly(llvm_ir)
mod.verify()
# Create target machine
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
# Compile to object file
obj_code = target_machine.emit_object(mod)
# Save to file
with open('output.o', 'wb') as f:
f.write(obj_code)
return obj_code
def create_execution_engine(llvm_ir):
"""Create JIT execution engine."""
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
mod = llvm.parse_assembly(llvm_ir)
mod.verify()
engine = llvm.create_mcjit_compiler(mod, target_machine)
return engine, mod
Type System
from llvmlite import ir
# Integer types
i1 = ir.IntType(1) # bool
i8 = ir.IntType(8) # byte
i32 = ir.IntType(32) # int
i64 = ir.IntType(64) # long
# Floating point types
f32 = ir.FloatType()
f64 = ir.DoubleType()
# Pointer types
i32_ptr = i32.as_pointer()
# Array types
i32_array = ir.ArrayType(i32, 10) # int[10]
# Struct types
struct_ty = ir.LiteralStructType([i32, i32, i32_ptr])
# Function types
fn_ty = ir.FunctionType(i32, [i32, i32])