快速开始(实现Hello World Pass)

在这里,我们描述如何编写“hello world” Pass。“Hello”Pass旨在简单地打印出正在编译的程序中存在的非外部函数的名称。它根本不修改程序,它只是检查它。此过程的源代码和文件位于lib/Transforms/Hello目录中的LLVM源代码树中。

设置构建环境

首先,配置和构建LLVM。接下来,您需要在LLVM源代码库中的某个位置创建新目录。对于这个例子,我们假设你做了lib/Transforms/Hello。最后,您必须设置一个构建脚本,该脚本将编译新传递的源代码。为此,请将以下内容复制到CMakeLists.txt

add_llvm_library( LLVMHello MODULE
Hello.cpp

PLUGIN_TOOL
opt
)

以及以下行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"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

因为我们正在编写Pass,所以我们正在使用Function,我们将进行一些打印。
我们需要下面一行代码:

using namespace llvm;

这是必需的,因为包含文件中的函数存在于llvm命名空间中。
下面的代码也是需要的:

namespace {

这回开始一个新的匿名命名空间。在C++中匿名命名空间会引入静态全局作用域,就像C语言中的“static”关键字,它使在匿名命名空间内声明的内容仅对当前文件可见。
接着,声明我们的Pass:

struct Hello : public FunctionPass {

这声明了一个Hello类,它是FunctionPass的子类。稍后将详细描述不同的内置pass子类,不过现在,我们只需要知道 FunctionPass类是对函数操作的一个类。

static char ID;
Hello() : FunctionPass(ID) {}

这声明了LLVM用于标识传递的标识符Pass。这允许LLVM避免使用昂贵的C++运行时信息。

  bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

我们声明了一个runOnFunction方法,它覆盖了从FunctionPass继承的抽象虚方法。这是我们应该做的事情,所以我们只用每个函数的名称打印出我们的消息。

char Hello::ID = 0;

我们在这里初始化Pass ID。LLVM使用ID的地址来标识Pass,因此初始化值并不重要。

static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);

最后,我们注册我们的类Hello,给它一个命令行参数hello,并命名为“Hello World Pass”。最后两个参数描述了它的行为:如果传递遍历CFG而不修改它,则第三个参数设置为true; 如果传递是分析传递,例如支配树传递,则true提供第四个参数。

整体而言,该.cpp文件如下所示:

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);

现在它们在一起,使用来自构建目录顶层的简单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
Hello: __main
Hello: puts
Hello: main

-load选项指定opt应将您的Pass作为共享对象加载,这使得-hello成为有效的命令行参数(这是您需要注册Pass的一个原因)。因为Hello pass不以任何有趣的方式修改程序,所以我们只丢弃opt的结果 (发送给它/dev/null)。

要查看您注册的其他字符串发生了什么,请尝试使用以下-help选项运行 opt

$ opt -load lib/LLVMHello.so -help
OVERVIEW: llvm .bc -> .bc modular optimizer and analysis printer

USAGE: opt [subcommand] [options] <input bitcode file>

OPTIONS:
Optimizations available:
...
-guard-widening - Widen guards
-gvn - Global Value Numbering
-gvn-hoist - Early GVN Hoisting of Expressions
-hello - Hello World Pass
-indvars - Induction Variable Simplification
-inferattrs - Infer set function attributes
...

Pass名称将作为传递的信息字符串添加,为opt的用户提供一些文档。既然你有一个工作Pass,你就可以继续前进,让它做你想要的酷转换。一旦你完成所有的工作和测试,找出你的传球速度可能会很有用。PassManager提供了一个很好的命令行选项(--time-passes),使您可以获取有关你的传球以及其他通过你排队的执行时间信息。例如:

$ opt -load lib/LLVMHello.so -hello -time-passes < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main
===-------------------------------------------------------------------------===
... Pass execution timing report ...
===-------------------------------------------------------------------------===
Total Execution Time: 0.0007 seconds (0.0005 wall clock)

---User Time--- --User+System-- ---Wall Time--- --- Name ---
0.0004 ( 55.3%) 0.0004 ( 55.3%) 0.0004 ( 75.7%) Bitcode Writer
0.0003 ( 44.7%) 0.0003 ( 44.7%) 0.0001 ( 13.6%) Hello World Pass
0.0000 ( 0.0%) 0.0000 ( 0.0%) 0.0001 ( 10.7%) Module Verifier
0.0007 (100.0%) 0.0007 (100.0%) 0.0005 (100.0%) Total

如您所见,我们上面的实现非常快。opt工具会自动插入列出的其他Pass,以验证Pass发出的LLVM是否仍然有效并且格式良好的LLVM(尚未以某种方式被破坏)。

现在你已经看到了传递背后的机制的基础知识,我们可以讨论它们如何工作以及如何使用它们的更多细节。