# How to Write a Custom LLVM Pass: A Complete Guide to the New Pass Manager

> Learn to write a custom LLVM pass by deriving from PassInfoMixin, implementing run(), and registering with PassBuilder. Master the new LLVM Pass Manager for efficient code analysis and transformation.

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

---

**Write a custom LLVM pass by deriving from `llvm::PassInfoMixin<YourPass>`, implementing a `run()` method that returns `PreservedAnalyses`, and registering it via `RegisterPass` or the `PassBuilder` API to invoke it with `opt -passes=your-pass`.**

LLVM’s transformation engine relies on modular units called *passes* to optimize and analyze intermediate representation (IR). Since LLVM 13, the **new Pass Manager (NPM)** serves as the preferred infrastructure for writing custom LLVM passes, offering fine-grained analysis caching, parallel execution capabilities, and a clean CRTP-based API. This guide demonstrates how to extend the llvm/llvm-project codebase with custom transformations using the core components defined in [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h).

## Architecture of the New Pass Manager

The NPM is built around a generic, templated design that operates on specific IR units such as `Function`, `Module`, or `Loop`. At its core, the system uses the **CRTP (Curiously Recurring Template Pattern)** mix-in `PassInfoMixin`, which provides the `run` entry point and utilities for preserving analyses.

Key components include:

- **`PassInfoMixin`**: The base class template located in [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h) that supplies the static `name()` method and pipeline integration utilities
- **`AnalysisManager`**: Handles lazy computation, caching, and invalidation of analysis results
- **`PreservedAnalyses`**: A return type that communicates which analyses remain valid after a transformation executes

### Essential Source Files

| Component | Purpose | Key Source File |
|-----------|---------|-----------------|
| **Pass base class** | CRTP mix-in providing `run` entry point and preservation utilities | [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h) |
| **Pass registration** | Global registry for textual pipeline discovery | [`llvm/include/llvm/PassRegistry.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/PassRegistry.h) |
| **Pass manager implementation** | Run-logic, analysis caching, and invalidation semantics | [`llvm/lib/IR/PassManager.cpp`](https://github.com/llvm/llvm-project/blob/main/llvm/lib/IR/PassManager.cpp) |
| **Driver implementation** | `opt` tool integration for the NPM | [`llvm/tools/opt/NewPMDriver.cpp`](https://github.com/llvm/llvm-project/blob/main/llvm/tools/opt/NewPMDriver.cpp) |

## Implementing a Function-Level Transformation Pass

To write a custom LLVM pass that transforms IR, create a class inheriting from `PassInfoMixin<YourClass>`. The pass operates on a specific IR unit and must implement a `run` method that accepts the unit and an `AnalysisManager`.

### Minimal Pass Implementation

```cpp
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

struct MyFunctionPass : PassInfoMixin<MyFunctionPass> {
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
    // Example transformation: print every instruction.
    for (Instruction &I : instructions(F)) {
      outs() << "Inst: " << I << "\n";
    }
    
    // We did not modify the IR → preserve *all* analyses.
    return PreservedAnalyses::all();
  }
  
  // Optional – used by the textual pipeline parser.
  static StringRef name() { return "my-func-pass"; }
};

```

**Key implementation details:**

- **CRTP inheritance**: `PassInfoMixin<MyFunctionPass>` provides the boilerplate for pipeline integration and the static `name()` method
- **Return semantics**: `PreservedAnalyses::all()` tells the manager that no analyses were invalidated, allowing subsequent passes to reuse cached results; use `PreservedAnalyses::none()` or `PA.abandon<AnalysisType>()` when transformations invalidate specific analyses
- **Static name method**: Enables invocation via `opt -passes=my-func-pass`

## Registering and Executing Your Pass

### Command-Line Registration

Register your pass using the `RegisterPass` template to make it discoverable by the `opt` driver:

```cpp
static RegisterPass<MyFunctionPass>
    X("my-func-pass", "Demo pass that prints each instruction",
      false, false); // (isCFGOnly, isAnalysis)

```

The boolean parameters indicate whether the pass is CFG-only and whether it is an analysis pass. This registration interfaces with the global pass registry defined in [`llvm/include/llvm/PassRegistry.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/PassRegistry.h), which both the legacy and new `opt` drivers consult.

### Programmatic Pipeline Construction

For tools that embed LLVM, construct and execute pipelines using `PassBuilder` and the adaptor utilities:

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

PassBuilder PB;                     // Central builder for the NPM
FunctionPassManager FPM;
FPM.addPass(MyFunctionPass());     // Insert our pass

ModulePassManager MPM;
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));

// Execute on the module.
PB.runPipeline(*M, MPM);

```

The `createModuleToFunctionPassAdaptor` wrapper, defined near line 66 in [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h), enables a `FunctionPassManager` to run over each function in a module. You can also register custom pipeline names via `PassBuilder::registerPipelineParsingCallback` to expose them to the `-passes=` command-line interface.

## Writing Custom Analyses

Analysis passes compute information that transformation passes consume. They follow the same CRTP pattern but return a **result object** rather than `PreservedAnalyses`:

```cpp
struct MyFunctionAnalysis : PassInfoMixin<MyFunctionAnalysis> {
  struct Result { unsigned NumInsts = 0; };
  
  Result run(Function &F, FunctionAnalysisManager &) {
    Result R;
    for (auto &I : instructions(F))
      ++R.NumInsts;
    return R;
  }
  
  static StringRef name() { return "my-func-analysis"; }
};

static RegisterPass<MyFunctionAnalysis>
    Y("my-func-analysis", "Counts instructions in a function", true, false);

```

A transformation pass can then obtain this result via:

```cpp
auto &A = getAnalysis<MyFunctionAnalysis>(F);
outs() << "Function has " << A.NumInsts << " instructions\n";

```

Analyses are cached per IR unit and automatically recomputed only when a transformation pass explicitly abandons them via `PreservedAnalyses::abandon<AnalysisType>()`.

## Summary

- Derive custom passes from `PassInfoMixin<YourPass>` using CRTP inheritance; use `RequiredPassInfoMixin` for passes that must never be skipped
- Implement `PreservedAnalyses run(IRUnit &, AnalysisManager &)` to control which analyses remain valid after execution
- Register passes via `RegisterPass<YourPass>` in [`llvm/include/llvm/PassRegistry.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/PassRegistry.h) to expose them to the `opt` driver
- Construct pipelines programmatically using `PassBuilder` and adaptors like `createModuleToFunctionPassAdaptor` defined in [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h)
- Return `PreservedAnalyses::all()` for read-only passes, `none()` for destructive transformations, or use `preserve<AnalysisType>()` for selective invalidation

## Frequently Asked Questions

### What is the difference between the legacy Pass Manager and the new Pass Manager?

The legacy Pass Manager relied on explicit pass dependencies and lacked fine-grained analysis caching. The new Pass Manager, default since LLVM 13, provides a fully generic, templated API with automatic analysis preservation, parallel execution support, and unified pipeline construction through `PassBuilder` as implemented in [`llvm/lib/IR/PassManager.cpp`](https://github.com/llvm/llvm-project/blob/main/llvm/lib/IR/PassManager.cpp).

### Can I use the same pass implementation for both LLVM IR and MLIR?

While the architectural concepts are identical, the implementations are distinct. LLVM IR passes use `PassInfoMixin` from [`llvm/include/llvm/IR/PassManager.h`](https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/PassManager.h), whereas MLIR passes inherit from `OperationPass` defined in [`mlir/include/mlir/Pass/Pass.h`](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Pass/Pass.h). Both systems share design principles detailed in [`mlir/docs/PassManagement.md`](https://github.com/llvm/llvm-project/blob/main/mlir/docs/PassManagement.md).

### How do I prevent my pass from being skipped by the optimizer?

Derive your pass from `llvm::RequiredPassInfoMixin<YourPass>` instead of `PassInfoMixin`. This signals to the pass manager that the pass must always execute regardless of optimization level or previous transformations.

### When should I use `PreservedAnalyses::none()` versus `all()`?

Return `PreservedAnalyses::all()` only when your pass guarantees it did not modify the IR unit, allowing the manager to reuse cached analyses for subsequent passes. Return `PreservedAnalyses::none()` when your transformation invalidates all analysis results, forcing recomputation for any dependent passes in the pipeline.