Skip to content
Back to skills

Llvm Passes

ASecurity

Use when writing an LLVM pass plugin, registering it for opt -passes, using DominatorTree or LoopInfo, or testing with FileCheck and lit. Not for reading IR: use llvm-ir-and-passes.

  • 54 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 20, 2026
ai-agentspythongobashnodetestingapi

Works with

  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 20, 2026

npx -y skills add OutlineDriven/outline-driven-development --skill llvm-passes --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Llvm Passes?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Llvm Passes
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/outlinedriven-llvm-passes-outline-driven-development/badge)](https://www.skillsdirectory.com/skills/outlinedriven-llvm-passes-outline-driven-development)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: llvm-passes
description: 'Use when writing an LLVM pass plugin, registering it for opt -passes, using DominatorTree or LoopInfo, or testing with FileCheck and lit. Not for reading IR: use llvm-ir-and-passes.'
---

# LLVM passes

## Contract

| Field | Bound contract |
|---|---|
| Trigger | A user adds a custom transform or analysis to an LLVM-based compiler, needs a plugin that `opt -load-pass-plugin` accepts, uses dominators, loops, or alias analysis inside a pass, or debugs a pass that corrupts IR or leaves analyses stale. |
| Authority | Reversible local: writes only the pass source, its CMake file, and its lit tests inside the project, plus the build directory; rollback is version control and deleting the build directory. No remote mutation. |
| Side effect | A plugin shared object is built and run through `opt` on test IR; the verifier and the lit test decide the result. |
| Done | The plugin loads into the installed `opt`, the pass name resolves in `-passes=`, `-verify-each` reports no broken IR on the test inputs, and the lit test passes. |

## Inputs

1. The transform or analysis to implement (required): what IR pattern it matches and what it produces or reports.
2. Pass scope (required if not obvious): per function or per module.
3. Test IR (required): a `.ll` input that contains the pattern and one that does not.
4. Installed LLVM (gathered by the skill): `llvm-config --version` and `--cmakedir`. Grounded current stable is LLVM 23.1.0; header paths, macros, and flags below are confirmed against it. The plugin must build against the same LLVM that `opt` was built from.

## Procedure

1. Fix the scope. Under the new pass manager, a `ModulePassManager` runs module passes and nests a `FunctionPassManager` per function; an `AnalysisManager` caches analyses and invalidates them when a pass reports that it did not preserve them. A pass that reads only one function at a time is a function pass; a pass that adds globals or functions is a module pass. Done when: the scope is stated with the reason.
2. Write the pass as a `PassInfoMixin` with a `run` method, and export the plugin entry point. The plugin header lives at `llvm/Plugins/PassPlugin.h` in LLVM 23.1.0. Erase instructions through `make_early_inc_range`; erasing inside a plain range loop invalidates the iterator.

   ```cpp
   #include "llvm/ADT/STLExtras.h"
   #include "llvm/IR/PassManager.h"
   #include "llvm/Passes/PassBuilder.h"
   #include "llvm/Plugins/PassPlugin.h"
   using namespace llvm;

   namespace {
   struct RemoveDeadFuncCalls : PassInfoMixin<RemoveDeadFuncCalls> {
       PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
           bool Changed = false;
           for (BasicBlock &BB : F)
               for (Instruction &I : make_early_inc_range(BB))
                   if (auto *Call = dyn_cast<CallInst>(&I))
                       if (Function *Callee = Call->getCalledFunction();
                           Callee && Callee->getName() == "dead_func") {
                           Call->eraseFromParent();
                           Changed = true;
                       }
           return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
       }
   };
   } // namespace

   extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo() {
       return {LLVM_PLUGIN_API_VERSION, "RemoveDeadFuncCalls", "v0.1", [](PassBuilder &PB) {
           PB.registerPipelineParsingCallback(
               [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) {
                   if (Name != "remove-dead-func-calls") return false;
                   FPM.addPass(RemoveDeadFuncCalls());
                   return true;
               });
       }};
   }
   ```

   A module pass has the signature `PreservedAnalyses run(Module &M, ModuleAnalysisManager &)` and registers through the `ModulePassManager` overload of the same callback. Skip declarations with `F.isDeclaration()` when iterating a module. Done when: the source compiles with the pipeline name chosen.
3. Build it as a `MODULE` library against the installed LLVM:

   ```cmake
   cmake_minimum_required(VERSION 3.20)
   project(RemoveDeadFuncCalls C CXX)
   find_package(LLVM REQUIRED CONFIG)
   add_library(RemoveDeadFuncCalls MODULE RemoveDeadFuncCalls.cpp)
   target_include_directories(RemoveDeadFuncCalls SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS})
   target_compile_definitions(RemoveDeadFuncCalls PRIVATE ${LLVM_DEFINITIONS})
   set_target_properties(RemoveDeadFuncCalls PROPERTIES CXX_STANDARD 17)
   ```

   ```bash
   cmake -B build -DLLVM_DIR=$(llvm-config --cmakedir)
   cmake --build build
   ```

   CMake prefixes the module with `lib`. The plugin needs no explicit LLVM link line: `opt` provides the symbols at load time, and the build above links and loads on LLVM 23.1.0. Done when: `build/libRemoveDeadFuncCalls.so` exists.
4. Run it through `opt`, alone and inside a pipeline, with the verifier on:

   ```bash
   opt -load-pass-plugin ./build/libRemoveDeadFuncCalls.so -passes=remove-dead-func-calls -verify-each -S input.ll -o output.ll
   opt -load-pass-plugin ./build/libRemoveDeadFuncCalls.so -passes='function(instcombine,remove-dead-func-calls,dce)' -S input.ll -o out.ll
   ```

   `-S` writes textual IR; without it the output is bitcode. Inside a pipeline string a function pass must sit inside `function(...)`; naming it at module level fails with "unknown module pass". Done when: the pattern is gone from `output.ll` and `-verify-each` prints nothing.
5. Use analyses through the analysis manager; the dependency is tracked by the request, so no `getAnalysisUsage` declaration exists under the new pass manager:

   ```cpp
   #include "llvm/Analysis/AliasAnalysis.h"
   #include "llvm/Analysis/LoopInfo.h"
   #include "llvm/IR/Dominators.h"

   PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
       auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
       auto &LI = AM.getResult<LoopAnalysis>(F);
       auto &AA = AM.getResult<AAManager>(F);
       for (Loop *L : LI) { BasicBlock *Header = L->getHeader(); /* hoist here */ }
       DomTreeNode *Root = DT.getNode(&F.getEntryBlock());
       return PreservedAnalyses::all();
   }
   ```

   Return `PreservedAnalyses::all()` only when the IR is unchanged. After a structural change, either return `none()` or name the analyses that survive (`PA.preserve<DominatorTreeAnalysis>()`). Done when: each analysis the pass reads is requested through `AM`, and the preserved set matches what the pass changed.
6. Modify IR through the builder and the standard utilities, and keep SSA intact:

   ```cpp
   IRBuilder<> Builder(&I);
   Value *NewVal = Builder.CreateAdd(I.getOperand(0), ConstantInt::get(I.getType(), 1));
   I.replaceAllUsesWith(NewVal);
   I.eraseFromParent();

   ValueToValueMapTy VMap;
   BasicBlock *Clone = CloneBasicBlock(OrigBB, VMap, ".clone", &F);   // llvm/Transforms/Utils/Cloning.h

   FunctionCallee Fn = M.getOrInsertFunction("my_fn", FunctionType::get(Builder.getVoidTy(), false));
   ```

   Replace every use before erasing a value; an erased value with remaining uses is the verifier's "use of undefined value" failure. Done when: `-verify-each` passes on every test input after the change.
7. Write a lit test with FileCheck. `lit` is a Python package (`pip install lit`) and `FileCheck` ships with LLVM; a `lit.cfg.py` next to the tests defines the `%shlibdir` substitution to the build directory.

   ```
   ; RUN: opt -load-pass-plugin %shlibdir/libRemoveDeadFuncCalls.so -passes=remove-dead-func-calls -S %s | FileCheck %s
   declare void @dead_func()
   define void @test() {
     call void @dead_func()
     ret void
   }
   ; CHECK-LABEL: define void @test()
   ; CHECK-NOT: dead_func
   ; CHECK: ret void
   ```

   ```bash
   lit test/ -v
   ```

   Done when: the lit run reports the test as passed.
8. When the pass misbehaves, use the pass manager's own instrumentation before adding prints:

   ```bash
   opt -load-pass-plugin ./build/libRemoveDeadFuncCalls.so -passes=remove-dead-func-calls -print-after-all input.ll -o /dev/null
   opt -passes=remove-dead-func-calls -time-passes input.ll -o /dev/null
   opt -passes=remove-dead-func-calls -debug-pass-manager input.ll -o /dev/null
   ```

   `-print-after-all` shows the IR after every pass; `-debug-pass-manager` shows which passes and analyses ran and which were invalidated. Done when: the first pass whose output is wrong is identified.

## Failure and recovery

| Failure class | Behavior |
|---|---|
| `Pass not found` | The plugin did not load or the pipeline name differs from the string in the parsing callback. Confirm the `.so` path and the name; `opt --print-passes` does not list plugin passes. |
| Verifier failure | SSA broken by the transform. Run with `-verify-each`, read the first message, and fix the use-def order in the transform. |
| Analysis stale | The pass changed IR and returned `all()`. Return `none()` or an explicit preserved set. |
| Plugin fails to load | Built against a different LLVM than `opt`. Rebuild with `-DLLVM_DIR=$(llvm-config --cmakedir)` for the `opt` in use. |
| Empty or binary output | `-S` missing. Add it for textual IR. |
| lit test fails on CHECK | The pass output differs from the expectation. Run the `RUN` line by hand with `-S` and compare before editing CHECK lines. |

No partial result is claimed complete. If a step cannot finish, the report states which steps passed and which are blocked.

## Output

A pass delivery containing:
1. Files written: the pass source, CMake file, and lit tests.
2. Build and load evidence: the `opt` command that loaded the plugin and the pipeline string used.
3. Verification: the `-verify-each` result on each test input and the lit result.
4. Preserved analyses: the set the pass declares and why.

Files in this skill

  • SKILL.md9.6 KB
  • agents/openai.yaml202 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.