# What Are LLVM Passes? Architecture, Types, and Implementation Guide

> Discover LLVM passes, the modular units transforming LLVM IR. Learn their architecture, types, and how pass managers orchestrate optimizations for your compiler.

- Repository: [LLVM/llvm-project](https://github.com/llvm/llvm-project)
- Tags: architecture
- Published: 2026-09-09

---

**LLVM passes are modular units of transformation or analysis that operate on LLVM IR, serving as the building blocks of the compiler’s optimization pipeline and managed by pass managers that schedule, run, and coordinate their execution.**

LLVM passes form the core infrastructure of the LLVM compiler framework, enabling developers to implement optimizations and code analysis as discrete, composable components. Within the `llvm/llvm-project` repository, these passes operate on LLVM Intermediate Representation (IR) to either analyze code properties or transform the IR to improve performance. Understanding how LLVM passes work is essential for anyone extending the compiler toolchain or writing custom optimization routines.

## Core Architecture of LLVM Passes

All LLVM passes inherit from a common base class and are categorized by the granularity of IR they manipulate. The architecture distinguishes between infrastructure for pass management, metadata registration, and the execution model.

### The Pass Base Class and Hierarchy

Every LLVM pass inherits from `llvm::Pass`, defined in [`llvm/include/llvm/Pass.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Pass.h). This base class establishes the common interface including virtual methods such as `runOnModule`, `runOnFunction`, `getPassName`, and `print`. It also handles bookkeeping like the unique pass ID used to identify passes in the pipeline.

Pass metadata is stored in the `PassInfo` class, located in [`llvm/include/llvm/PassInfo.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/PassInfo.h). The `RegisterPass<>` macro creates instances of `PassInfo` to register a pass with the global `PassRegistry`, defining its command-line argument, description, and whether it is an analysis pass.

### Pass Kinds and IR Granularity

The `PassKind` enum distinguishes the IR granularity a pass operates on:

- **PT_Module**: Operates on the entire module (`ModulePass`)
- **PT_Function**: Operates on individual functions (`FunctionPass`)
- **PT_Loop**: Operates on loop nests (`LoopPass`)
- **PT_CallGraphSCC**: Operates on strongly connected components of the call graph

### Legacy vs. New Pass Manager

LLVM maintains two distinct pass management infrastructures:

**Legacy Pass Manager**: The original infrastructure using `legacy::PassManager` and `legacy::FunctionPassManager`. Passes inherit from `llvm::Pass` and override virtual methods like `runOnFunction`. This system remains widely used by tools like `opt`.

**New Pass Manager (PM)**: Introduced in LLVM 10, located in [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h). This infrastructure is type-safe and uses C++ concepts rather than virtual inheritance. Any class implementing a compatible `run(IR, AnalysisManager...)` method can function as a pass, eliminating the need to inherit from a specific base class.

## Types of LLVM Passes: Analysis and Transformation

LLVM passes fall into two fundamental categories based on their interaction with the IR.

**Analysis passes** compute information about the IR without modifying it. For example, `AliasAnalysis` determines which memory locations might alias. These passes return results that subsequent passes query, and the **analysis manager** caches these results to avoid recomputation.

**Transformation passes** modify the LLVM IR to optimize or canonicalize it. Examples include `InstCombine` (instruction combining) and `SimplifyCFG`. After a transformation runs, it returns a `PreservedAnalyses` object to indicate which cached analysis results remain valid. If a transformation modifies IR that an analysis depends on, that analysis is automatically invalidated and removed from the cache.

## How LLVM Passes Work in the Optimization Pipeline

The execution of LLVM passes follows a structured lifecycle managed by pass managers and coordinated through the `PassBuilder` infrastructure.

### Pipeline Construction with PassBuilder

The `PassBuilder` class, defined in [`llvm/include/llvm/Passes/PassBuilder.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Passes/PassBuilder.h), constructs optimization pipelines through helper methods like `buildModuleOptimizationPipeline` and `buildFunctionSimplificationPipeline`. It creates a hierarchy of pass managers: `ModulePassManager` owns `FunctionPassManager` instances, which in turn may own `LoopPassManager` instances.

### Scheduling and Execution

Each pass manager contains a vector of **pass concepts** (`PassConceptT`). When a manager’s `run` method is invoked, it iterates over its passes, invoking each pass’s `run` method with the appropriate IR unit and analysis manager.

### Analysis Caching and Invalidation

After each pass executes, the infrastructure updates the `PreservedAnalyses` set. This set tracks which analyses remain valid after the transformation. If a pass returns `PreservedAnalyses::none()`, all cached analyses are invalidated. If it returns `PreservedAnalyses::all()`, all analyses remain valid. This mechanism ensures that subsequent passes receive up-to-date analysis results without manual cache management.

## Writing Your First LLVM Pass

The following examples demonstrate how to implement passes using both the legacy and new pass manager infrastructures.

### Example 1: A Legacy Function Pass (Instruction Counter)

This pass counts instructions in each function without modifying the IR. It inherits from `FunctionPass` and uses the registration macro.

```cpp
// File: MyInstrCount.cpp
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
struct InstrCountPass : public FunctionPass {
  static char ID;
  InstrCountPass() : FunctionPass(ID) {}

  bool runOnFunction(Function &F) override {
    unsigned Count = 0;
    for (auto &BB : F)
      for (auto &I : BB)
        ++Count;
    errs() << "Function " << F.getName() << " has " << Count << " instructions.\n";
    return false;                     // does not modify IR
  }

  StringRef getPassName() const override {
    return "Instruction Count Pass";
  }
};
} // namespace

char InstrCountPass::ID = 0;
static RegisterPass<InstrCountPass>
    X("instr-count", "Counts instructions in each function", false, false);

```

Build and run the pass using the legacy `opt` tool:

```bash
clang++ -fno-exceptions -fno-rtti \
  `llvm-config --cxxflags --ldflags --system-libs --libs core` \
  MyInstrCount.cpp -o MyInstrCount.so
opt -load ./MyInstrCount.so -instr-count my.bc -o /dev/null

```

### Example 2: A New Pass Manager Function Pass

This example prints basic block names using the modern infrastructure. Note that no inheritance is required—only a compatible `run` method signature.

```cpp
// File: PrintBBNames.cpp
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

struct PrintBBNamesPass {
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
    for (auto &BB : F)
      errs() << "BasicBlock: " << BB.getName() << "\n";
    return PreservedAnalyses::all();   // analysis results remain valid
  }

  static StringRef name() { return "PrintBBNamesPass"; }
};

int main(int argc, char **argv) {
  LLVMContext C;
  SMDiagnostic Err;
  std::unique_ptr<Module> M = parseIRFile(argv[1], Err, C);
  if (!M) return 1;

  FunctionAnalysisManager FAM;
  PassBuilder PB;
  PB.registerFunctionAnalyses(FAM);

  FunctionPassManager FPM;
  FPM.addPass(PrintBBNamesPass{});
  for (Function &F : *M)
    FPM.run(F, FAM);
}

```

### Example 3: Integrating Custom Passes into Default Pipelines

Insert a custom transformation into a standard optimization pipeline using `PassBuilder` and adaptors:

```cpp
#include "llvm/Passes/PassBuilder.h"
#include "llvm/IR/PassManager.h"

struct MyCustomPass {
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
    // …your transformation logic…
    return PreservedAnalyses::none();   // all analyses must be recomputed
  }
};

int main() {
  PassBuilder PB;
  ModulePassManager MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O2);
  
  // Insert the custom pass using a module-to-function adaptor
  MPM.addPass(createModuleToFunctionPassAdaptor(
        MyCustomPass{}, /*EagerlyInvalidate=*/false));
  // …run MPM on a Module…
}

```

## Key Source Files for LLVM Pass Development

Understanding these header files is essential for developing passes within the llvm/llvm-project codebase:

- **[`llvm/include/llvm/Pass.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Pass.h)**: Defines the legacy `llvm::Pass` base class, `ModulePass`, `FunctionPass`, and core APIs like `doInitialization` and `runOnModule`.

- **[`llvm/include/llvm/PassInfo.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/PassInfo.h)**: Contains `PassInfo` and the `RegisterPass<>` macro for legacy pass registration metadata.

- **[`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h)**: Implements the new pass manager infrastructure, including `FunctionPassManager`, `PreservedAnalyses`, and the concept-based pass execution model.

- **[`llvm/include/llvm/Passes/PassBuilder.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Passes/PassBuilder.h)**: Provides the `PassBuilder` class for constructing standard optimization pipelines and registering analyses with analysis managers.

## Summary

- **LLVM passes** are modular components that analyze or transform LLVM IR within the compiler pipeline.
- **Two categories** exist: analysis passes (read-only, cached) and transformation passes (modify IR, trigger invalidation).
- **Two infrastructures** coexist: the legacy pass manager (virtual inheritance-based) and the new pass manager (type-safe, concept-based, LLVM 10+).
- **PassBuilder** constructs optimization pipelines, while **pass managers** schedule execution and handle the `PreservedAnalyses` protocol to manage cache validity.
- Key files include [`Pass.h`](https://github.com/llvm/llvm-project/blob/main/Pass.h) for legacy bases, [`PassManager.h`](https://github.com/llvm/llvm-project/blob/main/PassManager.h) for modern infrastructure, and [`PassBuilder.h`](https://github.com/llvm/llvm-project/blob/main/PassBuilder.h) for pipeline construction.

## Frequently Asked Questions

### What is the difference between the legacy and new pass managers in LLVM?

The **legacy pass manager** requires passes to inherit from `llvm::Pass` and override virtual methods like `runOnFunction`. It uses a polymorphic approach with deep inheritance hierarchies. The **new pass manager**, available since LLVM 10, uses C++ templates and concepts—passes only need to implement a `run()` method with the correct signature, eliminating virtual dispatch overhead and enabling better compile-time type checking.

### How do I register a custom LLVM pass with the opt tool?

For the **legacy pass manager**, include the `RegisterPass<>` macro from [`llvm/PassInfo.h`](https://github.com/llvm/llvm-project/blob/main/llvm/PassInfo.h) in your source file: `static RegisterPass<MyPass> X("pass-id", "Description", false, false);` Then compile as a shared library and load with `opt -load ./MyPass.so -pass-id`. For the **new pass manager**, registration typically occurs in your driver code by adding the pass to a `PassManager` instance, or by extending the `PassBuilder` callback mechanism for plugin registration.

### What is the difference between analysis passes and transformation passes?

**Analysis passes** examine the IR to compute information (such as dominator trees or alias analysis) without modifying it. They return analysis results that other passes consume. **Transformation passes** modify the IR to improve performance or canonicalize code, such as constant folding or dead code elimination. Transformation passes must specify which analyses they preserve using the `PreservedAnalyses` API so the analysis manager knows which cached results remain valid.

### When should I invalidate analyses in a transformation pass?

You should return `PreservedAnalyses::none()` when your transformation modifies IR in ways that could invalidate any analysis results, forcing recomputation for subsequent passes. Return `PreservedAnalyses::all()` only if the pass makes no changes or changes that cannot affect any analysis (rare). For granular control, use `PreservedAnalyses::set<SpecificAnalysis>()` to indicate exactly which analyses remain valid, allowing the infrastructure to preserve cache entries for unrelated analyses.