自定义LLVM中的PASS
所有的LLVM pass都是Pass类的子类,他们都继承并重写了Pass类中所定义的虚函数。根据需求,我们可以选择继承ModulePass ,CallGraphSCCPass,FunctionPass 或者LoopPass或者RegionPass 等类。通过继承这些父类,我们可以告诉编译系统我们的自定义pass大概要做哪些事情,并且如何去与其他的pass进行组合。
接下来我们尝试写一个简单的pass,从编码,到编译,再到运行,最后讨论如何编写一个高级的pass。
环境设置
下面我们编写一个Hello World pass。我们的pass将实现一个非常简单的功能--打印函数名,这意味着它不会修改源程序,只是简单地打印源程序的相关信息。
首先,配置并构建LLVM项目,然后在LLVM源码文件夹下新建一个目录。遵照官方文档,我们在llvm源码树的lib/Transforms目录下新建一个example目录。
然后将下面的代码添加到lib/Transforms/CMakeLists.txt中
add_subdirectory(example) |
这样CMake会将example.cpp文件链接到$(LEVEL)/lib/LLVMHello.so动态库文件中,在使用opt工具时只需要使用-load
选项即可将该动态库链接进来。
编写代码
接下来新建example.cpp作为源码文件。
首先引入LLVM相关的头文件:
#include "llvm/Pass.h" |
Pass头文件中有我们需要的父类,而我们将编写的是Function相关的pass,所以需要引入function.h。
然后引入llvm的命名空间:
using namespace llvm; |
由于该pass只是个示例,所以我们接下来创建一个匿名的命名空间:
namespace { |
匿名命名空间中的内容仅在当前文件中是可见的。
接下来,声明我们的自定义pass本体:
struct example : public FunctionPass { |
我们将example
声明为FunctionPass
的子类。稍后我们会介绍一些其他的内置pass子类,但是现在只需要记住,FunctionPass可以对函数进行各种操作。
然后声明Pass标识符,会被LVM用作识别pass:
static char ID; |
接下来继承并重写FunctionPass中的runOnFunction
虚函数:
bool runOnFunction(Function& F) override { |
然后,初始化pass ID。由于LLVM只是使用ID来定位一个pass,所以这个ID的初始值并不重要。
char Hello::ID = 0; |
static RegisterPass<example> X("exp", "Hello World Pass", |
最后,将我们的example类作为一个新的pass注册,命令行参数是exp
,名字是Hello World Pass
。
最后两个参数描述了它的行为:如果pass不修改CFG,那么第三个参数被设为true;如果该pass是一个分析pass,例如支配树pass,则第四个参数为true。
如果我们希望将pass注册为现有管道的一个步骤,LLVM也提供了一些扩展点。例如PassManagerBuilder::EP_EarlyAsPossible
会在其他优化进行之前调用我们的pass,又或者PassManagerBuilder::EP_FullLinkTimeOptimizationLast
会在链接优化之后调用我们的pass。
static llvm::RegisterStandardPasses Y( |
最后,整个文件如下:
|
最后,回到LLVM源码树的根目录,再次build项目,就可以在build文件夹下的lib文件夹中看到生成的LLVMExample.dylib共享库文件(macOS)。
在opt工具中使用
首先创建一个test.c文件:
|
然后生成llvm的bitcode:
clang -O3 -emit-llvm test.c -c -o test.bc |
最后加载运行即可:
~/Projects/llvm-project/build/bin/opt -enable-new-pm=0 -load ~/Projects/llvm-project/build/lib/LLVMExample.dylib -example test.bc > /dev/null |
输出如下:
Hello, this is example: main |
目前最新的LLVM已经使用了新的pass管理器来管理优化管道(新的pass管理器使用了新的编写pass的方法),但代码生成管道仍使用旧的pass管理器(如本文所述)。所以要在opt中使用旧的pass管理器,需要传入-enable-new-pm=0
参数。
参考文献
《LLVM Cookbook》 Mayur Pandey