介绍 —— 什么是 pass
LLVM Pass框架是LLVM系统的重要组成部分,pass执行构成编译器的转换和优化,构建这些转换所使用的分析结果,最重要的是,它们是编译器代码的结构化技术。
所有的pass是 Pass 的子类,通过重写 Pass
中的方法来实现功能。根据你的 pass 是如何工作的,可以从 ModulePass , CallGraphSCCPass, FunctionPass , LoopPass, RegionPass 继承,这样系统就能更多地了解你的 pass 的作用,以及如何与其他 pass 相结合。LLVM Pass框架的主要特性之一是,它根据 pass 满足的约束(由它们派生的类来指示),以一种有效的方式调度 pass 运行。
快速开始 —— hello world
这里我们介绍如何书写 pass 的 "hello world",这个 pass 的作用是打印出正在编译的程序中存在的非外部函数的名称。它不会修改程序,它只是检查它,该 pass 的源代码和文件可在 LLVM 源代码树中找到 lib/Transforms/Hello
。Hello.cpp
可以参考 llvm 进行阅读实现。
设置 build 环境
先 configure 以及 build LLVM,然后在 LLVM 源代码中的某个位置创建一个新的目录,在该例子中,假设建立了 lib/Transforms/Hello
,然后要设置脚本文件编译 pass。可以将下面代码复制到 Hello 目录下的 CMakeLists.txt
:
add_llvm_library( LLVMHello MODULE
Hello.cpp
PLUGIN_TOOL
opt
)
add_llvm_library() 是 LLVM 的内部 cmake 宏函数,它接受不同的参数来指示将构建指定源文件的库类型,包括 SHARED, BUILDTREE_ONLY, MODULE, 和 INSTALL_WITH_TOOLCHAIN
在 lib/Transforms/CMakeLists.txt
里面加入:
add_subdirectory(Hello)
(请注意,已经有一个名为 Hello 的目录,其中包含一个示例 "Hello" pass)
这个 build 脚本指定工作目录中的 Hello.cpp
文件将被编译并链接到一个共享对象 $(LEVEL)/lib/LLVMHello.so
,这个文件可以通过 opt 工具的 -load
选项被动态加载。如果您的操作系统使用了其他后缀,不是 .so
(如 Windows 或 macOS) ,将使用适当的扩展。
现在我们已经建立了 build 脚本,我们只需要为 pass 本身编写代码。
所需的基本代码
因为我们在写 Pass,我们需要操作 Functions,并且我们需要输出一些东西,所以首先添加头文件:
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
然后使用命名空间:
using namespace llvm;
然后使用:
namespace {
开始是一个匿名命名空间。匿名命名空间对于 C++ 就像 static 关键字对于 C 一样(在全局范围内)。它使匿名命名空间内声明的内容只对当前文件可见。如果你不熟悉它们,可以查阅一本像样的 C++ 书籍以获得更多信息。
然后声明我们要书写的 pass:
struct Hello : public FunctionPass {
Hello 是 FunctionPass 的子类,可以在 classes 中查看不同 pass 子类的区别。现在我们只需要知道 FunctionPass
一次操作一个函数。
static char ID;
Hello() : FunctionPass(ID) {}
这声明了 LLVM 用于标识 pass 的 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",最后两个参数描述了它的行为:如果一个 pass 在没有修改的情况下遍历 CFG,那么第三个参数设置为 true; 如果 pass 是 analysis pass,例如 dominator tree pass,那么第四个参数设置为 true。
如果我们希望将 pass 注册为现有管道的一个步骤,则会提供一些扩展点,如:PassManagerBuilder::EP_EarlyAsPossible 在任何优化之前应用我们的 pass,或者 PassManagerBuilder::EP_FullLinkTimeOptimizationLast 在链接时间优化之后应用它。
static llvm::RegisterStandardPasses Y(
llvm::PassManagerBuilder::EP_EarlyAsPossible,
[](const llvm::PassManagerBuilder &Builder,
llvm::legacy::PassManagerBase &PM) { PM.add(new Hello()); });
最后, .cpp
文件为:
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.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 */);
static RegisterStandardPasses Y(
PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) { PM.add(new Hello()); });
现在一切都完成了,使用简单的 gmake
命令从构建目录的顶层编译该文件,您应该得到一个新文件 lib/LLVMHello.so
。请注意,这个文件中的所有内容都包含在一个匿名命名空间中ーー这反映了这样一个事实,即 pass 是自包含的单元,不需要外部接口(尽管它们可以有外部接口)才能发挥作用。
使用 opt 运行 pass
按照 LLVM 系统入门 末尾的示例将 "Hello World" 编译为 LLVM。 我们现在可以通过像这样的转换来运行程序的位码文件(hello.bc)(当然,任何位码文件都可以工作):
$ 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 名称被添加为 pass 的信息字符串,为 opt 的用户提供了一些文档。一旦你让它全部工作和测试,它可以用于找出你的 pass 有多快。PassManager 提供了一个很好的命令行选项(-time-passes),它允许你获取关于你的 pass 以及你排队等待的其他 pass 的执行时间的信息。例如:
$ 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
如您所见,我们上面的实现非常快。列出的其他 pass 会自动被 opt 工具插入,以验证你的 pass 发出的 LLVM 是否仍然有效且格式良好,且未以某种方式被破坏。
网友评论