CtrlK
BlogDocsLog inGet started
Tessl Logo

add-npm-pass

Add a New Pass Manager (NPM) pass to an LLVM 22 in-tree or out-of-tree project. Covers FunctionPass, ModulePass, LoopPass, and CGSCCPass kinds, registration, pipeline wiring, CMake, and lit testing.

72

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Skill: Add a New Pass Manager (NPM) Pass (LLVM 22)

Use this skill whenever the user wants to create a new optimization or analysis pass using the LLVM New Pass Manager (NPM). All passes in LLVM 22 use NPM — the legacy PassManager is removed.


Step 0 — Decide pass kind

Ask or infer the pass kind from the user's description:

KindUnit of IRBase classUse when
Functionllvm::FunctionInherits nothing; run(Function &, FunctionAnalysisManager &)Most transforms and analyses
Modulellvm::Modulerun(Module &, ModuleAnalysisManager &)Whole-program / cross-function
Loopllvm::Looprun(Loop &, LoopAnalysisManager &, LoopStandardAnalysisResults &, LPMUpdater &)Loop transforms
CGSCCllvm::LazyCallGraph::SCCrun(LazyCallGraph::SCC &, CGSCCAnalysisManager &, LazyCallGraph &, CGSCCUpdateResult &)Interprocedural over SCCs

For a transform pass use PreservedAnalyses as the return type. For an analysis pass inherit from AnalysisInfoMixin<YourPass> and return a result struct.


Step 1 — Create the header

Path (in-tree): include/llvm/Transforms/<Category>/<PassName>.h Path (out-of-tree): include/<PassName>.h

//===- <PassName>.h - <One-line description> --------------------*- C++ -*-===//
// LLVM 22 New Pass Manager pass
//===----------------------------------------------------------------------===//
#ifndef LLVM_TRANSFORMS_<CATEGORY>_<PASSNAME>_H
#define LLVM_TRANSFORMS_<CATEGORY>_<PASSNAME>_H

#include "llvm/IR/PassManager.h"

namespace llvm {

class <PassName> : public PassInfoMixin<<PassName>> {
public:
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);

  // Required for opt to skip this pass on optnone functions.
  static bool isRequired() { return false; }
};

} // namespace llvm

#endif // LLVM_TRANSFORMS_<CATEGORY>_<PASSNAME>_H

Replace Function / FunctionAnalysisManager with the appropriate IR unit from Step 0.


Step 2 — Implement the pass

Path (in-tree): lib/Transforms/<Category>/<PassName>.cpp Path (out-of-tree): lib/<PassName>.cpp or src/<PassName>.cpp

//===- <PassName>.cpp - <One-line description> ----------------------------===//
// LLVM 22 New Pass Manager pass implementation
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/<Category>/<PassName>.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

PreservedAnalyses <PassName>::run(Function &F, FunctionAnalysisManager &FAM) {
  // TODO: implement pass logic
  // Return PA indicating which analyses are preserved.
  // If nothing is modified:
  return PreservedAnalyses::all();
  // If everything may be modified:
  // return PreservedAnalyses::none();
  // If only the CFG is preserved:
  // PA.preserveSet<CFGAnalyses>(); return PA;
}

Key LLVM 22 APIs to know inside run():

  • FAM.getResult<DominatorTreeAnalysis>(F) — get a cached analysis result.
  • FAM.invalidate(F, PA) — explicitly invalidate analyses after modification.
  • llvm::make_range(F.begin(), F.end()) — iterate basic blocks.
  • llvm::PatternMatch — match IR patterns without manual casting.

Step 3 — Register the pass for opt pipeline strings (in-tree only)

3a. PassRegistry.def

Add one line in the appropriate section of llvm/lib/Passes/PassRegistry.def:

FUNCTION_PASS("<pass-name>", <PassName>())

Use MODULE_PASS, LOOP_PASS, or CGSCC_PASS for other kinds.

3b. PassBuilder.cpp includes

Add the header include near the other Transforms/<Category>/ includes in llvm/lib/Passes/PassBuilder.cpp:

#include "llvm/Transforms/<Category>/<PassName>.h"

No other changes needed — PassRegistry.def drives the registration.

Out-of-tree registration

If out-of-tree, register the pass with a PassPluginLibraryInfo entry point so opt --load-pass-plugin can find it:

#include "llvm/Passes/PassPlugin.h"

llvm::PassPluginLibraryInfo get<PassName>PluginInfo() {
  return {LLVM_PLUGIN_API_VERSION, "<pass-name>", LLVM_VERSION_STRING,
          [](PassBuilder &PB) {
            PB.registerPipelineParsingCallback(
                [](StringRef Name, FunctionPassManager &FPM,
                   ArrayRef<PassBuilder::PipelineElement>) {
                  if (Name == "<pass-name>") {
                    FPM.addPass(<PassName>());
                    return true;
                  }
                  return false;
                });
          }};
}

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
  return get<PassName>PluginInfo();
}

Step 4 — CMake

In-tree

In lib/Transforms/<Category>/CMakeLists.txt, add the source file:

add_llvm_component_library(LLVM<Category>
  ...
  <PassName>.cpp
  ...
)

Out-of-tree (plugin)

find_package(LLVM 22 REQUIRED CONFIG)

add_llvm_pass_plugin(<PassName>
  <PassName>.cpp
  DEPENDS
  intrinsics_gen
)

If add_llvm_pass_plugin is not available in your LLVM install, use:

add_library(<PassName> MODULE <PassName>.cpp)
target_include_directories(<PassName> PRIVATE ${LLVM_INCLUDE_DIRS})
target_compile_definitions(<PassName> PRIVATE ${LLVM_DEFINITIONS})
set_target_properties(<PassName> PROPERTIES
  CXX_STANDARD 17
  POSITION_INDEPENDENT_CODE ON
)

Step 5 — Write a lit test

Path (in-tree): test/Transforms/<Category>/<pass-name>.ll Path (out-of-tree): test/<pass-name>.ll

; RUN: opt -passes=<pass-name> -S %s | FileCheck %s
; RUN: opt -passes=<pass-name> -disable-output %s   ; smoke test — must not crash

define i32 @test_basic(i32 %x) {
entry:
  ; CHECK-LABEL: @test_basic
  ; CHECK: <expected IR pattern after pass>
  ret i32 %x
}

For an out-of-tree plugin:

; RUN: opt --load-pass-plugin=%llvmshlibdir/<PassName>%pluginext \
; RUN:     -passes=<pass-name> -S %s | FileCheck %s

Run tests:

# In-tree
llvm-lit test/Transforms/<Category>/<pass-name>.ll

# Out-of-tree (after cmake --build)
llvm-lit test/<pass-name>.ll

Step 6 — Smoke test manually

# In-tree
echo 'define void @f() { ret void }' | \
  opt -passes=<pass-name> -S -

# Out-of-tree plugin
echo 'define void @f() { ret void }' | \
  opt --load-pass-plugin=./<PassName>.so -passes=<pass-name> -S -

Expected: IR printed without crash, with any expected transforms applied.


Quick reference — LLVM 22 NPM cheat sheet

PreservedAnalyses::all()         — no IR changed
PreservedAnalyses::none()        — anything may have changed
PA.preserve<DominatorTreeAnalysis>() — CFG unchanged, DT still valid
PA.preserveSet<CFGAnalyses>()    — all CFG-only analyses preserved

FAM.getResult<X>(F)              — get (cached) analysis X for function F
FAM.getCachedResult<X>(F)        — get only if already computed (nullable)
FAM.invalidate(F, PA)            — tell manager what F's pass invalidated

FunctionPassManager FPM;
FPM.addPass(MyPass());           — build a pass pipeline manually
ModulePassManager MPM;
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));

Common mistakes to avoid

  • Do NOT use legacy::PassManager, FunctionPass, ModulePass for new passes — LLVM 22 requires NPM (PassInfoMixin) for new pass development (legacy types may still appear for codegen emission APIs).
  • Do NOT call AU.setPreservesAll() or getAnalysis<>() — NPM uses FAM.getResult<>().
  • Do NOT forget PassInfoMixin<YourPass> — required for pass identification.
  • Do NOT hold raw pointers to analysis results across IR mutations — re-query after any modification.
  • Do NOT use using namespace llvm; in headers.
Repository
hbarve1/tessl-llvm
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.