LLVM's Pass
infrastructure is one of the many important features of the LLVM system. There are a number of analysis and optimization passes that can be run using this infrastructure. The starting point for LLVM passes is the Pass
class, which is a superclass of all the passes. We need to inherit from some predefined subclasses taking into account what our pass is going to implement.
ModulePass
subclass and overloads the runOnModule
function.Before going ahead with the discussion of other Pass
classes, let's look into the three virtual methods that the Pass
classes override:
runOnFunction
for FunctionPass
, runOnLoop
for LoopPass
, and so on.runOn{Passtype}
has finished doing the job for every function in the program.FunctionPass
we might need to overload the three virtual functions mentioned earlier by implementing in the runOnFunction
method.FunctionPass
is not allowed to. To implement, they can override the doInitialization
and doFinalization
methods of FunctionPass
, or overload their own virtual methods for the two methods mentioned earlier and the runOnBasicBlock
method.LoopPass
we need to overload the doInitialization
, doFinalization
, and runOnLoop
methods.Now, let's see how to get started with writing a custom pass. Let's write a pass that will print the names of all the functions.
Before getting started with writing the implementation of the pass, we need to make changes in a few places in the code so that the pass is recognized and can be run.
We need to create a directory under the LLVM tree. Let's make a directory, lib/Transforms/FnNamePrint
. In this directory, we need to create a Makefile
with the following contents, which will allow our pass to be compiled:
LEVEL = ../../.. LIBRARYNAME = FnNamePrint LOADABLE_MODULE = 1 include $(LEVEL)/Makefile.common
This specifies that all .cpp
files should be compiled and linked into a shared object that will be available in the lib
folder of the build-folder
(build-folder/lib/FnNamePrint.so
).
Now, let's get started with writing the actual pass implementation. We need to create the source file for the pass in lib/Transforms/FnNamePrint
: let's name it FnNamePrint.cpp
. The first step now is to choose the correct subclass. In this case, as we are trying to print names of each function, the FunctionPass
class will serve our purpose by processing one function at a time. Also, we are only printing the name of function and not modifying anything within it, so we are choosing FunctionPass
for simplicity. We could use ModulePass
as well because it is an Immutable Pass
.
Now, let's write the source code for the pass implementation, which looks like this:
#include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { struct FnNamePrint: public FunctionPass { static char ID; FnNamePrint () : FunctionPass(ID) {} bool runOnFunction(Function &F) override { errs() << "Function " << F.getName() << ' '; return false; } }; } char FnNamePrint::ID = 0;static RegisterPass< FnNamePrint > X("funcnameprint","Function Name Print", false, false);
In the preceding code we include
the necessary headers first and use an llvm
namespace:
#include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" using namespace llvm;
We declare our pass as a structure, FnNamePrint
, which is a subclass of FunctionPass
. In runOnFunction
we implement the logic to print the function name. The bool
value returned in the end signifies whether we have made any modification within the function. A True
value is returned if some modifications was made, otherwise, false
is returned. In our case, we are not making any modifications, so we return false
.
struct FnNamePrint: public FunctionPass { static char ID; FnNamePrint () : FunctionPass(ID) {} bool runOnFunction(Function &F) override { errs() << "Function " << F.getName() << ' '; return false; } }; }
Then, we declare the ID
for the pass, which is used to identify the pass:
char FnNamePrint::ID = 0;
Finally, we need to register the passes with the Pass Manager. The first argument is the Pass name used by the opt
tool to identify this pass. The second argument is the actual Pass name. The third and fourth arguments specify whether the pass modified the cfg
and whether it is an analysis pass.
static RegisterPass< FnNamePrint > X("funcnameprint","Function Name Print", false, false);
Now, we can run the pass over a test case using the opt
tool in the following way:
$ opt -load path-to-llvm/build/lib/FnNamePrint.so -funcnameprint test.ll
The load
command line option specifies the path from where to pick the shared object of the pass and –funcnameprint
is the option to opt tool to tell it to run the pass we have written. The Pass will print the names of all the function present in the testcase. For the example in the first section it will print out:
Function test Function caller Function callercaller
So, we got started with writing a Pass. Now, we will see the significance of the PassManager
class in LLVM.
The PassManager
class schedules the passes to be run efficiently. The PassManager
is used by all LLVM tools that run passes for the execution of these passes. It is the responsibility of the PassManager
to make sure the interaction between the passes is correctly done. As it tries to execute the passes in an optimized way, it must have information regarding how the passes interact with each other and what the different dependencies between the passes are.
A pass itself can specify the dependency on other passes, that is, which passes need to be run before the execution of the current pass. Also, it can specify the passes that will be invalidated by the execution of the current pass. The PassManager
gets the analysis results before a pass is executed. We will later see how a pass can specify such dependencies.
The main work of the PassManager
is to avoid the calculation of analysis results time and again. This is done by keeping track of which analyses are available, which are invalidated, and which analyses are required. The PassManager
tracks the lifetimes of the analysis results and frees the memory holding the analysis results when not required, allowing for optimal memory use.
The PassManager
pipelines the passes together to get better memory and cache results, improving the cache behavior of the compiler. When a series of consecutive FunctionPass
are given, it will execute all the FunctionPass
on the first function, then all the FunctionPass
on the second function, and so on. This improves cache behavior as it is only dealing with the single function part of the LLVM representation and not the entire program.
The PassManager
also specifies the –debug-pass
option with which we can see how one pass interacts with other passes. We can see what all passes are run using the –debug-pass=Argument
option. We can use the –debug-pass=Structure
option to see how the passes had run. It will also give us the names of the passes that ran. Let's take the example of the test code in the first section of this chapter:
$ opt -O2 -S test.ll -debug-pass=Structure $ opt -load /build-folder/lib/LLVMFnNamePrint.so test.ll -funcnameprint -debug-pass=Structure Pass Arguments: -targetlibinfo -tti -funcnameprint -verify Target Library Information Target Transform Information ModulePass Manager FunctionPass Manager Function Name Print Module Verifier Function test Function caller Function callercaller
In the output, the Pass Arguments
gives us the passes that are run and the following list is the structure used to run each pass. The Passes just after ModulePass
Manager
will show the passes run per module (here, it is empty). The passes in hierarchy of FunctionPass
Manager
show that these passes were run per function (Function Name Print
and Module Verifier
), which is the expected result.
The PassManger
also provides some other useful flags, some of which are the following:
–stats
must also be Passes to the opt tool so that the results of instcount
are visible.18.188.178.181