在这里,我们描述如何编写“hello world” Pass。“Hello”Pass旨在简单地打印出正在编译的程序中存在的非外部函数的名称。它根本不修改程序,它只是检查它。此过程的源代码和文件位于lib/Transforms/Hello
目录中的LLVM源代码树中。
设置构建环境
首先,配置和构建LLVM。接下来,您需要在LLVM源代码库中的某个位置创建新目录。对于这个例子,我们假设你做了lib/Transforms/Hello
。最后,您必须设置一个构建脚本,该脚本将编译新传递的源代码。为此,请将以下内容复制到CMakeLists.txt
:
add_llvm_library( LLVMHello MODULE |
以及以下行lib/Transforms/CMakeLists.txt
:
add_subdirectory(Hello) |
(请注意,已经有一个以Hello
示例“Hello”Pass命名的目录;您可以使用它 -在这种情况下您不需要修改任何CMakeLists.txt
文件 -或者,如果您想从头开始创建所有内容,请使用其他名称。)
此构建脚本指定Hello.cpp
要编译当前目录中的文件并将其链接到共享对象$(LEVEL)/lib/LLVMHello.so
,该对象可由opt工具通过其-load
选项动态加载。如果您的操作系统使用除.so
(例如Windows或Mac OS X)之外的后缀,则将使用相应的扩展名。
现在我们已经设置了构建脚本,我们只需要为pass本身编写代码。
基本代码
现在我们有了编译新pass的方法,我们只需要给她编写代码。我们可以从以下开始:
#include "llvm/Pass.h" |
因为我们正在编写Pass
,所以我们正在使用Function
,我们将进行一些打印。
我们需要下面一行代码:
using namespace llvm; |
这是必需的,因为包含文件中的函数存在于llvm命名空间中。
下面的代码也是需要的:
namespace { |
这回开始一个新的匿名命名空间。在C++中匿名命名空间会引入静态全局作用域,就像C语言中的“static”关键字,它使在匿名命名空间内声明的内容仅对当前文件可见。
接着,声明我们的Pass:
struct Hello : public FunctionPass { |
这声明了一个Hello
类,它是FunctionPass
的子类。稍后将详细描述不同的内置pass子类,不过现在,我们只需要知道 FunctionPass
类是对函数操作的一个类。
static char ID; |
这声明了LLVM用于标识传递的标识符Pass。这允许LLVM避免使用昂贵的C++运行时信息。
bool runOnFunction(Function &F) override { |
我们声明了一个runOnFunction
方法,它覆盖了从FunctionPass
继承的抽象虚方法。这是我们应该做的事情,所以我们只用每个函数的名称打印出我们的消息。
char Hello::ID = 0; |
我们在这里初始化Pass ID。LLVM使用ID的地址来标识Pass,因此初始化值并不重要。
static RegisterPass<Hello> X("hello", "Hello World Pass", |
最后,我们注册我们的类Hello
,给它一个命令行参数hello
,并命名为“Hello World Pass”。最后两个参数描述了它的行为:如果传递遍历CFG而不修改它,则第三个参数设置为true
; 如果传递是分析传递,例如支配树传递,则true
提供第四个参数。
整体而言,该.cpp
文件如下所示:
#include "llvm/Pass.h" |
现在它们在一起,使用来自构建目录顶层的简单gmake
命令编译文件,你应该得到一个新文件“ lib/LLVMHello.so
”。请注意,此文件中的所有内容都包含在匿名命名空间中 这反映了pass是自包含单元,不需要外部接口(尽管它们可以使用它们)。
使用opt命令运行pass
现在您有了一个全新的闪亮共享对象文件,我们可以使用 opt命令通过您的Pass来运行LLVM程序。由于您已使用RegisterPass
注册了Pass,因此一旦加载,您就可以使用 opt 工具访问它。
要测试它,请按照LLVM系统入门末尾的示例将“Hello World”编译为LLVM。我们现在可以通过这样的转换运行程序的bitcode文件(hello.bc)(或者当然,任何bitcode文件都可以):
$ opt -load lib/LLVMHello.so -hello < hello.bc > /dev/null |
-load
选项指定opt应将您的Pass作为共享对象加载,这使得-hello
成为有效的命令行参数(这是您需要注册Pass的一个原因)。因为Hello pass不以任何有趣的方式修改程序,所以我们只丢弃opt的结果 (发送给它/dev/null
)。
要查看您注册的其他字符串发生了什么,请尝试使用以下-help选项运行 opt:
$ opt -load lib/LLVMHello.so -help |
Pass名称将作为传递的信息字符串添加,为opt的用户提供一些文档。既然你有一个工作Pass,你就可以继续前进,让它做你想要的酷转换。一旦你完成所有的工作和测试,找出你的传球速度可能会很有用。PassManager提供了一个很好的命令行选项(--time-passes
),使您可以获取有关你的传球以及其他通过你排队的执行时间信息。例如:
$ opt -load lib/LLVMHello.so -hello -time-passes < hello.bc > /dev/null |
如您所见,我们上面的实现非常快。opt工具会自动插入列出的其他Pass,以验证Pass发出的LLVM是否仍然有效并且格式良好的LLVM(尚未以某种方式被破坏)。
现在你已经看到了传递背后的机制的基础知识,我们可以讨论它们如何工作以及如何使用它们的更多细节。