前言
之前写过一篇《关于LLVM,这些东西你必须知道!》,先已收录在《iOS成长之路》。
其中讲解了编译器的编译过程,以及通过LLVM可以做哪些有意思的事情,今天继续来跟着官方的文档继续了解Pass。
获取代码:
1 | git clone http://llvm.org/git/llvm.git |
编译成Xcode项目:
1 | mkdir build |
然后运行clang,准备工作做好后,继续Pass的学习。
什么是Pass?
LLVM Pass是LLVM系统中非常重要和有趣的一个模块,Pass处理编译过程中代码的转换以及优化工作。
所有的Pass都是Pass的子类,不同的Pass实现不同的作用可以继承ModulePass,CallGraphSCCPass,FunctionPass,LoopPass,RegionPass,BasicBlockPass等类。
接下来开始介绍如何编写一个Pass,编译,加载以及运行的过程。
编写hello world
先编写一个“Hello” Pass,只是简单的输出程序中每个非外部方法的方法名,不会修改程序本身的功能。官方Demo的源代码存在lib/Transforms/Hello文件夹。
配置编译环境
首先来看看环境的配置,如果你想编译一个新的Pass,你可以在lib/Transforms/下新建一个文件夹,这里我自己创建的是PassDemo,然后配置编译脚本去编译你的源代码。 在新建的目录下创建文件CMakeLists.txt:
1 | add_llvm_loadable_module(LLVMPassDemo |
然后增加下面一行代码到lib/Transforms/CMakeLists.txt:
1 | add_subdirectory(PassDemo) |
编译脚本指定了编译源文件Hello.cpp去编译生成动态库$(LEVEL)/lib/LLVMPassDemo.dylib。该文件可以被opt通过-load参数动态加载。如果不是Mac平台生成的后缀会不一样,比如Linux下是so。
配置好编译脚本后,打开Xcode项目,按command + B编译,然后在项目里面就可以看到刚刚增加的Target,打开Hello.cpp文件就可以编写代码了。

基础代码
首先编辑Hello.cpp文件,导入头文件:
1 | #include "llvm/Pass.h" |
因为需要编一个Pass然后操作Function, 还可能输出一些东西。
1 | using namespace llvm; |
由于导入文件里面的方法是属于llvm命名空间的,所以先指定使用的命名空间。
1 | namespace { |
编写一个匿名的命名空间,C++中的匿名命名空间和C语言里面的static关键词一样,定义在匿名空间中的变量仅仅对当前文件可见。
1 | struct Hello : public FunctionPass{ |
定义Hello类继承FunctionPass,后面会讲到不同用处的Pass会继承不同的父类,现在只需要知道,继承自FunctionPass可以操作程序中的方法。
1 | static char ID; |
定义可供LLVM标示的Pass ID。
1 | bool runOnFunction(Function &F) override{ |
这里定义了一个runOnFunction,重载继承自父类的抽象虚函数。在这个函数里面可以做针对于函数的特殊处理,这里只是打印出每个方法的名字。
1 | char Hello::ID = 0; |
初始化Pass ID,LLVM使用ID的地址去识别一个Pass,所以初始化的值不重要。
1 | static RegisterPass<Hello> X("hello", "Hello World Pass", false/* Only looks at CFG */, false/* Analysis Pass */); |
最后,注册我们的class Hello, 指定命令行参数为hello,名字说明Hello World Pass。
整个Hello.cpp文件如下:
1 | #include "llvm/Pass.h" |
选中Target LLVMPassDemo 按 command + B编译,编译完后会生成文件build/Debug/lib/LLVMPassDemo.dylib。
使用opt加载
上面在代码中通过RegisterPass注册了Pass,所以可以通过opt -load去加载动态库并指定参数hello。
准备源文件:
1 | #include <stdio.h> |
编译源文件生成bitcode:
1 | path/to/build/Debug/bin/clang -emit-llvm -c test.mm -o test.bc |
然后编译启动参数, 选择opttarget, 点击Edit Scheme。

填入启动参数:

command + R运行。
输出如下:
1 | Hello: _Z3addii |
当然还可以断点调试啦~

通过-help可以看到注册的pass参数:
1 | opt -load lib/LLVMPassDemo.dylib -help |
1 | ...... |
PassManager还提供--time-passes参数输出你的Pass和其它Pass执行时间的对比。
1 | opt -load lib/LLVMPassDemo.dylib -hello -time-passes -disable-output test.bc |
1 | Hello: _Z3addii |
tips:
为了在执行opt时能自动检测LLVMPassDemo模块有没有被修改,如果修改了,重新编译LLVMPassDemo模块,我们把LLVMPassDemo模块加到opt的依赖里面。
编辑tools/opt/CMakeLists.txt:
1 | add_llvm_tool(opt |
更多Pass类
当你写一个新的Pass的时候,你需要根据需要去选择需要继承的父类,根据不同的作用有 ImmutablePass、ModulePass、CallGraphSCCPass、FunctionPass、LoopPass、RegionPass、BasicBlockPass、MachineFunctionPass。具体可以参考官方文档。
调用其它Pass
如果在编写的Pass中需要使用到其它Pass提供的函数功能,需要在:
1 | virtual void getAnalysisUsage(AnalysisUsage &AU) const; |
说明,比如像要获取程序中存在循环的信息,可以在该函数里面申请需要依赖的Pass。
以上面的Pass为例,导入头文件#include "llvm/Analysis/LoopInfo.h",在getAnalysisUsage中调用:
1 | AU.addRequired<LoopInfoWrapperPass>(); |
然后就可以通过它提供接口获取存在循环的个数。
1 | LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo(); |
测试程序:
1 | #include <stdio.h> |
输出如下:
1 | Hello: _Z3addii |