| # Writing an LLVM Pass | |
| ```{program} opt | |
| ``` | |
| ## Introduction --- What is a pass? | |
| :::{warning} | |
| This document deals with the new pass manager. LLVM uses the legacy pass | |
| manager for the codegen pipeline. For more details, see | |
| {doc}`WritingAnLLVMPass` and {doc}`NewPassManager`. | |
| ::: | |
| The LLVM pass framework is an important part of the LLVM system, because LLVM | |
| passes are where most of the interesting parts of the compiler exist. Passes | |
| perform the transformations and optimizations that make up the compiler, they | |
| build the analysis results that are used by these transformations, and they | |
| are, above all, a structuring technique for compiler code. | |
| Unlike passes under the legacy pass manager where the pass interface is | |
| defined via inheritance, passes under the new pass manager rely on | |
| concept-based polymorphism, meaning there is no explicit interface (see | |
| comments in `PassManager.h` for more details). All LLVM passes inherit from | |
| the CRTP mix-in `OptionalPassInfoMixin<PassT>` or | |
| `RequiredPassInfoMixin<PassT>`. The pass should have a `run()` | |
| method which returns a `PreservedAnalyses` and takes in some unit of IR | |
| along with an analysis manager. For example, a function pass would have a | |
| `PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);` method. | |
| We start by showing you how to construct a pass, from setting up the build, | |
| creating the pass, to executing and testing it. Looking at existing passes is | |
| always a great way to learn details. | |
| ## Quick Start --- Writing hello world | |
| Here we describe how to write the "hello world" of passes. The "HelloWorld" | |
| pass is designed to simply print out the name of non-external functions that | |
| exist in the program being compiled. It does not modify the program at all, | |
| it just inspects it. | |
| The code below already exists; feel free to create a pass with a different | |
| name alongside the HelloWorld source files. | |
| (writing-an-llvm-npm-pass-build)= | |
| ### Setting up the build | |
| First, configure and build LLVM as described in {doc}`GettingStarted`. | |
| Next, we will reuse an existing directory (creating a new directory involves | |
| messing around with more CMake files than we want). For this example, we'll use | |
| `llvm/lib/Transforms/Utils/HelloWorld.cpp`, which has already been created. | |
| If you'd like to create your own pass, add a new source file into | |
| `llvm/lib/Transforms/Utils/CMakeLists.txt` (assuming you want your pass in | |
| the `Transforms/Utils` directory. | |
| Now that we have the build set up for a new pass, we need to write the code | |
| for the pass itself. | |
| (writing-an-llvm-npm-pass-basiccode)= | |
| ### Basic code required | |
| Now that the build is setup for a new pass, we just have to write it. | |
| First we need to define the pass in a header file. We'll create | |
| `llvm/include/llvm/Transforms/Utils/HelloWorld.h`. The file should | |
| contain the following boilerplate: | |
| ```c++ | |
| #ifndef LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H | |
| #define LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H | |
| #include "llvm/IR/PassManager.h" | |
| namespace llvm { | |
| class HelloWorldPass : public OptionalPassInfoMixin<HelloWorldPass> { | |
| public: | |
| PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); | |
| }; | |
| } // namespace llvm | |
| #endif // LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H | |
| ``` | |
| This creates the class for the pass with a declaration of the `run()` | |
| method which actually runs the pass. Inheriting from | |
| `OptionalPassInfoMixin<PassT>` or `RequiredPassInfoMixin<PassT>` sets up | |
| some more boilerplate so that we don't have to write it ourselves. | |
| `RequiredPassInfoMixin` should be used for passes that cannot be skipped | |
| (e.g. `AlwaysInlinerPass`), while `OptionalPassInfoMixin` should be used | |
| for passes that can be skipped (e.g. optimization passes). | |
| Our class is in the `llvm` namespace so that we don't pollute the global | |
| namespace. | |
| Next we'll create `llvm/lib/Transforms/Utils/HelloWorld.cpp`, starting | |
| with | |
| ```c++ | |
| #include "llvm/Transforms/Utils/HelloWorld.h" | |
| ``` | |
| ... to include the header file we just created. | |
| ```c++ | |
| using namespace llvm; | |
| ``` | |
| ... is required because the functions from the include files live in the llvm | |
| namespace. This should only be done in non-header files. | |
| Next we have the pass's `run()` definition: | |
| ```c++ | |
| PreservedAnalyses HelloWorldPass::run(Function &F, | |
| FunctionAnalysisManager &AM) { | |
| errs() << F.getName() << "\n"; | |
| return PreservedAnalyses::all(); | |
| } | |
| ``` | |
| ... which simply prints out the name of the function to stderr. The pass | |
| manager will ensure that the pass will be run on every function in a module. | |
| The `PreservedAnalyses` return value says that all analyses (e.g. dominator | |
| tree) are still valid after this pass since we didn't modify any functions. | |
| That's it for the pass itself. Now in order to "register" the pass, we need | |
| to add it to a couple places. Add the following to | |
| `llvm/lib/Passes/PassRegistry.def` in the `FUNCTION_PASS` section | |
| ```c++ | |
| FUNCTION_PASS("helloworld", HelloWorldPass()) | |
| ``` | |
| ... which adds the pass under the name "helloworld". | |
| `llvm/lib/Passes/PassRegistry.def` is #include'd into | |
| `llvm/lib/Passes/PassBuilder.cpp` multiple times for various reasons. Since | |
| it constructs our pass, we need to also add the proper #include in | |
| `llvm/lib/Passes/PassBuilder.cpp`: | |
| ```c++ | |
| #include "llvm/Transforms/Utils/HelloWorld.h" | |
| ``` | |
| This should be all the code necessary for our pass, now it's time to compile | |
| and run it. | |
| ### Running a pass with `opt` | |
| Now that you have a brand new shiny pass, we can build {program}`opt` and use | |
| it to run some LLVM IR through the pass. | |
| ```console | |
| $ ninja -C build/ opt | |
| # or whatever build system/build directory you are using | |
| $ cat /tmp/a.ll | |
| define i32 @foo() { | |
| %a = add i32 2, 3 | |
| ret i32 %a | |
| } | |
| define void @bar() { | |
| ret void | |
| } | |
| $ build/bin/opt -disable-output /tmp/a.ll -passes=helloworld | |
| foo | |
| bar | |
| ``` | |
| Our pass ran and printed the names of functions as expected! | |
| ### Testing a pass | |
| Testing our pass is important to prevent future regressions. We'll add a lit | |
| test at `llvm/test/Transforms/Utils/helloworld.ll`. See | |
| {doc}`TestingGuide` for more information on testing. | |
| ```llvm | |
| $ cat llvm/test/Transforms/Utils/helloworld.ll | |
| ; RUN: opt -disable-output -passes=helloworld %s 2>&1 | FileCheck %s | |
| ; CHECK: {{^}}foo{{$}} | |
| define i32 @foo() { | |
| %a = add i32 2, 3 | |
| ret i32 %a | |
| } | |
| ; CHECK-NEXT: {{^}}bar{{$}} | |
| define void @bar() { | |
| ret void | |
| } | |
| $ ninja -C build check-llvm | |
| # runs our new test alongside all other llvm lit tests | |
| ``` | |
| ## FAQs | |
| ### Required passes | |
| A pass that inherits from `RequiredPassInfoMixin<PassT>` is a required pass. For example: | |
| ```c++ | |
| class HelloWorldPass : public RequiredPassInfoMixin<HelloWorldPass> { | |
| public: | |
| PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); | |
| }; | |
| ``` | |
| A required pass is a pass that may not be skipped. An example of a required | |
| pass is `AlwaysInlinerPass`, which must always be run to preserve | |
| `alwaysinline` semantics. Pass managers are required since they may contain | |
| other required passes. | |
| An example of how a pass can be skipped is the `optnone` function | |
| attribute, which specifies that optimizations should not be run on the | |
| function. Required passes will still be run on `optnone` functions. | |
| For more implementation details, see | |
| `PassInstrumentation::runBeforePass()`. | |
| ### Registering passes as plugins | |
| LLVM provides a mechanism to register pass plugins within various tools like | |
| `clang` or `opt`. A pass plugin can add passes to default optimization | |
| pipelines or to be manually run via tools like `opt`. For more information, | |
| see {doc}`NewPassManager`. | |
| Create a CMake project at the root of the repo alongside | |
| other projects. This project must contain the following minimal | |
| `CMakeLists.txt`: | |
| ```cmake | |
| add_llvm_pass_plugin(MyPassName source.cpp) | |
| ``` | |
| See the definition of `add_llvm_pass_plugin` for more CMake details. | |
| The pass must provide at least one of two entry points for the new pass manager, | |
| one for static registration and one for dynamically loaded plugins: | |
| - `llvm::PassPluginLibraryInfo get##Name##PluginInfo();` | |
| - `extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() LLVM_ATTRIBUTE_WEAK;` | |
| Pass plugins are compiled and linked dynamically by default. Setting | |
| `LLVM_${NAME}_LINK_INTO_TOOLS` to `ON` turns the project into a statically | |
| linked extension. | |
| For an in-tree example, see `llvm/examples/Bye/`. | |
| To make `PassBuilder` aware of statically linked pass plugins: | |
| ```c++ | |
| // Declare plugin extension function declarations. | |
| #define HANDLE_EXTENSION(Ext) llvm::PassPluginLibraryInfo get##Ext##PluginInfo(); | |
| #include "llvm/Support/Extension.def" | |
| ... | |
| // Register plugin extensions in PassBuilder. | |
| #define HANDLE_EXTENSION(Ext) get##Ext##PluginInfo().RegisterPassBuilderCallbacks(PB); | |
| #include "llvm/Support/Extension.def" | |
| ``` | |
| To make `PassBuilder` aware of dynamically linked pass plugins: | |
| ```c++ | |
| // Load plugin dynamically. | |
| auto Plugin = PassPlugin::load(PathToPlugin); | |
| if (!Plugin) | |
| report_error(); | |
| // Register plugin extensions in PassBuilder. | |
| Plugin.registerPassBuilderCallbacks(PB); | |
| ``` |