LLVM pass framework is one of the most important and fundamental elements in the LLVM system. While a program is compiling, passes will be applied to perform any code transformation, code optimization and code analysis works. However, the official LLVM document does not mention a lot of important details of writing a working pass for the LLVM system. Therefore, this blog will work as a reference for my own experience of how to write a fully functional LLVM pass.
The blog will be divided into three parts, Part I of the blog will introduce a way to implement the most easy dynamic loaded pass. Beginning from the second blog, we will introduce the way to statically link the pass into the LLVM library, to be able to run your LLVM pass as a default optimization step in the LLVM pipeline. Furthermore, you can even implement your LLVM pass to be able to run at Linking time. These kinds of passes that run at link time are also being referred as LTO(Link Time Optimization) pass. And LTO-passes currently can only be implemented by statically linked into the LLVM library(currently latest LLVM 12.0.0), knowing how to statically link your pass into the LLVM library can be very important. At last, since the new PassManager Passes API has been officially exposed and used in the LLVM system, we will introduce both the Legacy PassManager interface (Part II of the blog) and the new PassManager Pass interface (Part III of the blog). Let us begin the journey!
Writing a statically linked pass. (Legacy PassManager)
I assume we have LLVM-9.0.0 source code downloaded and successfully compiled.
I will only introduce how to create a LLVM pass that is not Target Machine sensitive. For example, if you are creating a pass that is targeting only X86 platform, the creating process would be different. The pass we create in this blog should be applied to all different target architectures.
There are two different kinds of passes:
- Analysis Pass: That they gather information that can be used by other passes. These passes should be placed in llvm/lib/Analysis
folder.
- Transforms Pass: Apply transformations to the IR. These passes should be placed in llvm/lib/Transforms
folder.
Let us assume we are creating a Transform pass. In the llvm/lib/Transforms
folder, create a sub folder, named My_Pass
(Any names you want actually). Then we create four files: CMakeLists.txt
, LLVMBuild.txt
, MyCustomPass.cpp
and MyPass.cpp
. And in the llvm/include/llvm/Transforms
folder, create a sub folder too, named My_Pass
(Any names you want actually), create header files MyCustomPass.h
in llvm/include/llvm/Transforms/My_Pass
and MyPass.h
in llvm/include/llvm/Transforms
.
Let’s borrow the code from Part I, and copy and paste it into the llvm/lib/Transforms/MyCustomPass.cc
. Please notice that the registration function is different in this case!!!
In file CMakeLists.txt
|
|
In file LLVMBuild.txt
|
|
In file MyCustomPass.cpp
|
|
In MyCustomPass.h
: Potentially included some not used headers.
|
|
In MyPass.cpp
. Potentially included some not used headers.
|
|
In MyPass.h
.
|
|
Do you think adding the files into the library, then you are done? Nope…. There are a lot of other works you should do. Notice that in the MyCustomPass.cpp
file, the new INITIALIZE_PASS
registration function is actually a C++ macro that expand to a initilizeXXXpasspass function. Then we should place the pass initialization function to the correct calling locations:
- Steps:
- Place
initializeXXXPassPass
function intollvm/include/llvm/InitializePasses.h
- Place
createMyCustomPass
function intollvm/include/llvm/LinkAllPasses.h
- Very importantly, no matter where you inject your new pass code, remember, in these CMakeLists.txt, and LLVMBuild.txt, you have to manually write your build dependencies for your new pass code. The easiest way for me to do it, is to check another built-in pass libraries, such as Scaler. Anywhere in the CMakeLists.txt, LLVMBuild.txt when you see
Scalar
orScalarOpts
, also write in your own pass signatureMyPass
andMyPassOpts
.
- Place
At last, if you want your pass to be called by the compile and link pipeline, the normal way to do so is to call a populateXXXPassManager function. For example, you can place you custom pass manager’s create function in void PassManagerBuilder::populateModulePassManager
in llvm/lib/Transform/IPO/PassManagerBuilder.cpp
.
Check!