前言
之前写过一篇《关于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 |
然后编译启动参数, 选择opt
target, 点击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 |