编写 LLVM Pass¶
简介 — 什么是 Pass?¶
警告
本文档涉及新的 Pass 管理器。LLVM 使用旧版 Pass 管理器用于代码生成管道。有关更多详细信息,请参阅 编写 LLVM Pass(旧版 PM 版本) 和 使用新的 Pass 管理器。
LLVM Pass 框架是 LLVM 系统的重要组成部分,因为 LLVM Pass 是编译器大多数有趣部分所在的地方。Pass 执行构成编译器的转换和优化,它们构建这些转换使用的分析结果,最重要的是,它们是编译器代码的结构化技术。
与旧版 Pass 管理器下的 Pass 不同,旧版 Pass 管理器通过继承定义 Pass 接口,新 Pass 管理器下的 Pass 依赖于基于概念的多态性,这意味着没有显式接口(有关更多详细信息,请参阅 PassManager.h
中的注释)。所有 LLVM Pass 都继承自 CRTP 混合类 PassInfoMixin<PassT>
。Pass 应该有一个 run()
方法,该方法返回一个 PreservedAnalyses
并接收一些 IR 单元以及一个分析管理器。例如,函数 Pass 将具有 PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
方法。
我们首先向您展示如何构建 Pass,从设置构建、创建 Pass 到执行和测试它。查看现有的 Pass 始终是学习细节的好方法。
快速入门 — 编写 Hello World¶
在这里,我们描述如何编写 Pass 的“Hello World”。“HelloWorld” Pass 旨在简单地打印出正在编译的程序中存在的非外部函数的名称。它根本不会修改程序,只是检查它。
以下代码已存在;您可以随意创建具有不同名称的 Pass 以及 HelloWorld 源文件。
设置构建¶
首先,按照 LLVM 系统入门 中的说明配置和构建 LLVM。
接下来,我们将重用现有的目录(创建新目录涉及比我们想要的更多 CMake 文件)。在本例中,我们将使用 llvm/lib/Transforms/Utils/HelloWorld.cpp
,该文件已创建。如果您想创建自己的 Pass,请将新的源文件添加到 llvm/lib/Transforms/Utils/CMakeLists.txt
中(假设您希望您的 Pass 位于 Transforms/Utils
目录中)。
现在我们已经为新 Pass 设置了构建,我们需要编写 Pass 本身的代码。
基本代码要求¶
现在已经为新 Pass 设置了构建,我们只需编写它即可。
首先,我们需要在头文件中定义 Pass。我们将创建 llvm/include/llvm/Transforms/Utils/HelloWorld.h
。该文件应包含以下样板代码
#ifndef LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
#define LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
这创建了 Pass 的类,其中包含实际运行 Pass 的 run()
方法的声明。继承自 PassInfoMixin<PassT>
设置了一些样板代码,以便我们不必自己编写它。
我们的类位于 llvm
命名空间中,以便我们不会污染全局命名空间。
接下来,我们将创建 llvm/lib/Transforms/Utils/HelloWorld.cpp
,从以下内容开始
#include "llvm/Transforms/Utils/HelloWorld.h"
… 以包含我们刚刚创建的头文件。
using namespace llvm;
… 是必需的,因为 include 文件中的函数位于 llvm 命名空间中。这只能在非头文件中执行。
接下来,我们有 Pass 的 run()
定义
PreservedAnalyses HelloWorldPass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}
… 它只是将函数的名称打印到 stderr。Pass 管理器将确保 Pass 在模块中的每个函数上运行。 PreservedAnalyses
返回值表示所有分析(例如支配树)在此 Pass 之后仍然有效,因为我们没有修改任何函数。
这就是 Pass 本身。现在为了“注册”Pass,我们需要将其添加到几个地方。将以下内容添加到 llvm/lib/Passes/PassRegistry.def
中的 FUNCTION_PASS
部分
FUNCTION_PASS("helloworld", HelloWorldPass())
… 这将在名称为“helloworld”下添加 Pass。
llvm/lib/Passes/PassRegistry.def
多次 #include 到 llvm/lib/Passes/PassBuilder.cpp
中,原因各不相同。由于它构建了我们的 Pass,因此我们还需要在 llvm/lib/Passes/PassBuilder.cpp
中添加正确的 #include
#include "llvm/Transforms/Utils/HelloWorld.h"
这应该是 Pass 所需的所有代码,现在是时候编译和运行它了。
使用 opt
运行 Pass¶
现在您拥有了一个全新的 Pass,我们可以构建 opt 并使用它将一些 LLVM IR 通过 Pass 运行。
$ ninja -C build/ opt
# or whatever build system/build directory you are using
$ cat /tmp/a.ll
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
define void @bar() {
ret void
}
$ build/bin/opt -disable-output /tmp/a.ll -passes=helloworld
foo
bar
我们的 Pass 运行并按预期打印了函数名称!
测试 Pass¶
测试我们的 Pass 对于防止将来的回归非常重要。我们将在 llvm/test/Transforms/Utils/helloworld.ll
中添加一个 lit 测试。有关测试的更多信息,请参阅 LLVM 测试基础设施指南。
$ cat llvm/test/Transforms/Utils/helloworld.ll
; RUN: opt -disable-output -passes=helloworld %s 2>&1 | FileCheck %s
; CHECK: {{^}}foo{{$}}
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
; CHECK-NEXT: {{^}}bar{{$}}
define void @bar() {
ret void
}
$ ninja -C build check-llvm
# runs our new test alongside all other llvm lit tests
常见问题¶
必需的 Pass¶
定义返回 true 的静态 isRequired()
方法的 Pass 是必需的 Pass。例如
class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
static bool isRequired() { return true; }
};
必需的 Pass 是不能跳过的 Pass。必需的 Pass 的一个示例是 AlwaysInlinerPass
,它必须始终运行以保留 alwaysinline
语义。Pass 管理器是必需的,因为它们可能包含其他必需的 Pass。
Pass 如何跳过的示例是 optnone
函数属性,它指定不应在函数上运行优化。必需的 Pass 仍将在 optnone
函数上运行。
有关更多实现细节,请参阅 PassInstrumentation::runBeforePass()
。
将 Pass 注册为插件¶
LLVM 提供了一种机制来在各种工具(如 clang
或 opt
)中注册 Pass 插件。Pass 插件可以向默认优化管道添加 Pass,或者通过 opt
等工具手动运行。有关更多信息,请参阅 使用新的 Pass 管理器。
在 repo 的根目录中与其他项目一起创建一个 CMake 项目。此项目必须包含以下最小的 CMakeLists.txt
add_llvm_pass_plugin(MyPassName source.cpp)
有关更多 CMake 细节,请参阅 add_llvm_pass_plugin
的定义。
Pass 必须为新的 Pass 管理器提供至少两个入口点中的一个,一个用于静态注册,另一个用于动态加载的插件
llvm::PassPluginLibraryInfo get##Name##PluginInfo();
extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() LLVM_ATTRIBUTE_WEAK;
默认情况下,Pass 插件是动态编译和链接的。将 LLVM_${NAME}_LINK_INTO_TOOLS
设置为 ON
将项目变成静态链接的扩展。
有关树内示例,请参阅 llvm/examples/Bye/
。
要使 PassBuilder
了解静态链接的 Pass 插件
// Declare plugin extension function declarations.
#define HANDLE_EXTENSION(Ext) llvm::PassPluginLibraryInfo get##Ext##PluginInfo();
#include "llvm/Support/Extension.def"
...
// Register plugin extensions in PassBuilder.
#define HANDLE_EXTENSION(Ext) get##Ext##PluginInfo().RegisterPassBuilderCallbacks(PB);
#include "llvm/Support/Extension.def"
要使 PassBuilder
了解动态链接的 Pass 插件
// Load plugin dynamically.
auto Plugin = PassPlugin::Load(PathToPlugin);
if (!Plugin)
report_error();
// Register plugin extensions in PassBuilder.
Plugin.registerPassBuilderCallbacks(PB);