Back to articles
Compilers

Understanding LLVM Compiler Passes

Explore how LLVM transforms code through its pass infrastructure and how to write custom passes.

kos1
January 3, 2026
10 min read

Introduction

LLVM has revolutionized compiler development. Instead of writing a complete compiler from scratch, you can generate LLVM IR and let LLVM handle optimization and code generation for dozens of target architectures.

LLVM Architecture Overview

LLVM is organized into three main components:

  • Frontend - Parses source code, generates LLVM IR (Clang, rustc, etc.)
  • Middle-end - Target-independent optimizations on IR
  • Backend - Target-specific code generation

Understanding LLVM IR

LLVM IR is a typed, SSA-based representation. Here's a simple function:

define i32 @add(i32 %a, i32 %b) {
entry:
    %result = add i32 %a, %b
    ret i32 %result
}

Key characteristics:

  • Every value has a type (i32, i64, float, etc.)
  • SSA form - each variable is assigned exactly once
  • Explicit control flow with basic blocks and terminators

Generating IR with the C++ API

#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"

LLVMContext context;
Module module("my_module", context);
IRBuilder<> builder(context);

// Create function: i32 add(i32 a, i32 b)
Type* i32 = Type::getInt32Ty(context);
FunctionType* funcType = FunctionType::get(i32, {i32, i32}, false);
Function* func = Function::Create(funcType, 
    Function::ExternalLinkage, "add", module);

// Create entry block
BasicBlock* entry = BasicBlock::Create(context, "entry", func);
builder.SetInsertPoint(entry);

// Generate: return a + b
auto args = func->args().begin();
Value* a = args++;
Value* b = args;
Value* sum = builder.CreateAdd(a, b, "sum");
builder.CreateRet(sum);

Running Optimization Passes

LLVM provides hundreds of optimization passes. Common ones include:

#include "llvm/Passes/PassBuilder.h"

PassBuilder PB;
ModulePassManager MPM;

// Add standard optimization pipeline
PB.parsePassPipeline(MPM, "default");

// Run passes on module
MPM.run(module, MAM);

Emitting Machine Code

Generate native code for the target architecture:

// Initialize target
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();

// Get target machine
auto targetTriple = sys::getDefaultTargetTriple();
auto target = TargetRegistry::lookupTarget(targetTriple, error);
auto targetMachine = target->createTargetMachine(
    targetTriple, "generic", "", {}, {});

// Emit object file
legacy::PassManager pass;
targetMachine->addPassesToEmitFile(pass, dest, 
    nullptr, CodeGenFileType::ObjectFile);
pass.run(module);

Conclusion

LLVM provides industrial-strength compiler infrastructure. Whether you're building a new language or optimizing existing code, LLVM's modular design lets you focus on what matters most.

Tags

#llvm#compilers#optimization#ir