介绍 – 什么是 Pass
⚠️ 警告:
本文档讨论的是 新 Pass 管理器(New Pass Manager)。LLVM 的代码生成(codegen)管线目前仍使用 旧 Pass 管理器(Legacy Pass Manager)。如需了解旧管理器的详细信息,请参阅 https://llvm.org/docs/WritingAnLLVMPass.html 以及 https://llvm.org/docs/NewPassManager.html。
LLVM 的 Pass 框架是该系统的重要组成部分,因为 LLVM 中大部分核心功能(比如优化和转换)都通过 Pass 实现。Pass 执行了构成编译器的各种转换和优化操作,它们构建了供这些转换使用的分析结果,最重要的是,Pass 是组织编译器代码的一种结构化技术。
与旧 Pass 管理器中通过继承定义 Pass 接口的方式不同,新 Pass 管理器基于概念(concept)的多态性,没有显式的接口类(更多细节请参见 PassManager.h 中的注释)。所有 LLVM Pass 都继承自 CRTP(Curiously Recurring Template Pattern)混入类 PassInfoMixin<PassT>。Pass 应当实现一个 run() 方法,该方法接收某种 IR 单元(例如函数或模块)及一个分析管理器(Analysis Manager),并返回一个 PreservedAnalyses 对象。例如,一个函数级 Pass 会有如下方法签名:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
我们将从搭建构建环境、创建 Pass,到执行和测试 Pass 的完整流程进行讲解。查看现有的 Pass 源码也是学习细节的好方法。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id2https://llvm.org/docs/WritingAnLLVMNewPMPass.html#quick-start-writing-hello-world “链接到此标题”
下面将介绍如何编写一个最简单的 Pass,即 “Hello World” Pass。这个 Pass 的作用是打印出被编译程序中所有非外部(non-external)函数的名称,它不会对程序做任何修改,仅用于检查(Inspection)。
下面是完整的示例代码,您也可以在 HelloWorld 源文件旁边创建一个不同名称的 Pass。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id3https://llvm.org/docs/WritingAnLLVMNewPMPass.html#setting-up-the-build “链接到此标题”
首先,按照 https://llvm.org/docs/GettingStarted.html 的说明配置并编译 LLVM。
接下来,我们复用一个已经存在的目录(新建目录需要修改较多 CMake 文件,较为繁琐)。本例中,我们将使用 llvm/lib/Transforms/Utils/HelloWorld.cpp(该文件可能已经存在)。如果您想创建自己的 Pass,可以在 llvm/lib/Transforms/Utils/CMakeLists.txt 中添加一个新的源文件(假设您希望将 Pass 放在 Transforms/Utils 目录下)。
现在,我们已经为新的 Pass 设置好了构建环境,接下来需要编写 Pass 的代码本身。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id4https://llvm.org/docs/WritingAnLLVMNewPMPass.html#basic-code-required “链接到此标题”
现在构建环境已经配置好,我们只需要编写代码。
首先,我们在一个头文件中定义这个 Pass。我们将创建文件:llvm/include/llvm/Transforms/Utils/HelloWorld.h,其内容如下:
#ifndef LLVM_TRANSFORMS_UTILS_HELLOWORLD_H
#define LLVM_TRANSFORMS_UTILS_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_UTILS_HELLOWORLD_H
这段代码声明了一个名为 HelloWorldPass 的类,它继承自 PassInfoMixin<HelloWorldPass>,并声明了一个 run() 方法,这个方法就是 Pass 的核心逻辑所在。继承 PassInfoMixin<PassT> 为我们处理了很多模板和样板代码。
我们的类位于 llvm 命名空间内,以避免污染全局命名空间。
接下来,我们创建源文件 llvm/lib/Transforms/Utils/HelloWorld.cpp,开头包含我们刚刚创建的头文件:
#include "llvm/Transforms/Utils/HelloWorld.h"
由于 LLVM 的相关代码都在 llvm 命名空间中,所以我们使用:
using namespace llvm;
注意:这个用法只应在 非头文件 中使用。
然后,我们实现 run() 方法:
PreservedAnalyses HelloWorldPass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}
这个方法非常简单:它将当前函数(Function)的名称打印到标准错误输出(stderr),然后返回 PreservedAnalyses::all(),表示所有的分析结果在此 Pass 后依然有效(因为我们没有对函数做任何修改)。
到这里,Pass 的主体就完成了。但为了让 Pass 能够被 Pass 管理器识别和调用,我们还需要对其进行注册。
我们需要在 llvm/lib/Passes/PassRegistry.def 文件中的 FUNCTION_PASS 部分添加如下一行:
FUNCTION_PASS("helloworld", HelloWorldPass())
这会将我们的 Pass 注册为名称为 "helloworld" 的函数级 Pass。
此外,由于 llvm/lib/Passes/PassRegistry.def 会被包含进 llvm/lib/Passes/PassBuilder.cpp,我们还需要在该文件中添加对应的头文件引用:
#include "llvm/Transforms/Utils/HelloWorld.h"
至此,我们的 Pass 就编写完成了,下面可以编译并运行它。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id5https://llvm.org/docs/WritingAnLLVMNewPMPass.html#running-a-pass-with-opt “链接到此标题”
现在,您已经成功创建了一个新的 Pass,接下来我们可以编译 opt 工具并使用它来运行一些 LLVM IR 代码,以测试我们的 Pass。
$ ninja -C build/ opt
# 或者根据您使用的构建系统和目录进行调整
然后,准备一个简单的 LLVM IR 文件,例如 /tmp/a.ll,其内容如下:
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
define void @bar() {
ret void
}
接着,使用以下命令运行我们的 Pass:
$ build/bin/opt -disable-output /tmp/a.ll -passes=helloworld
您应该会看到如下输出:
foo
bar
这说明我们的 Pass 成功运行,并且打印出了每个函数的名称!
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id6https://llvm.org/docs/WritingAnLLVMNewPMPass.html#testing-a-pass “链接到此标题”
为避免未来出现回归问题,测试 Pass 是非常重要的。我们将为这个 Pass 添加一个 lit 测试,文件位于:llvm/test/Transforms/Utils/helloworld.ll。关于测试的更多信息,请参考 https://llvm.org/docs/TestingGuide.html。
测试文件内容如下:
; 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
这将运行我们新添加的测试,以及 LLVM 的其他 lit 测试。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id7https://llvm.org/docs/WritingAnLLVMNewPMPass.html#faqs “链接到此标题”
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id8https://llvm.org/docs/WritingAnLLVMNewPMPass.html#required-passes “链接到此标题”
如果一个 Pass 定义了一个静态方法 isRequired() 并返回 true,那么该 Pass 就是一个 必需 Pass。这意味着该 Pass 不能被跳过。
举个例子:
class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
static bool isRequired() { return true; }
};
一个典型的必需 Pass 示例是 AlwaysInlinerPass,它必须始终运行以确保 alwaysinline 属性的语义得以保留。
即使函数带有 optnone 属性(表示不做优化),必需 Pass 也仍然会运行在这些函数上。
关于实现细节,可参考 PassInstrumentation::runBeforePass()。
https://llvm.org/docs/WritingAnLLVMNewPMPass.html#id9https://llvm.org/docs/WritingAnLLVMNewPMPass.html#registering-passes-as-plugins “链接到此标题”
LLVM 提供了一种机制,允许将 Pass 注册为插件,从而可以在诸如 clang 或 opt 等工具中动态加载。这些插件可以添加新的优化 Pass,或者手动通过工具调用。
如需了解更多信息,请参考 https://llvm.org/docs/NewPassManager.html。
要创建一个 Pass 插件,您需要:
- 在 LLVM 源码树的根目录(或其他位置)创建一个 CMake 项目;
- 该项目的
CMakeLists.txt文件中至少包含如下内容:
add_llvm_pass_plugin(MyPassName source.cpp)
关于 add_llvm_pass_plugin 的详细定义,请查阅其文档。
每个 Pass 插件必须提供至少一个入口点,用于支持静态注册或动态加载:
llvm::PassPluginLibraryInfo get##Name##PluginInfo();extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() LLVM_ATTRIBUTE_WEAK;
插件默认以动态方式编译和链接。如果希望将其静态链接进工具,可以设置:
LLVM_${NAME}_LINK_INTO_TOOLS=ON
关于如何在 PassBuilder 中使静态插件生效,可以参考如下代码:
// 声明插件扩展函数
#define HANDLE_EXTENSION(Ext) llvm::PassPluginLibraryInfo get##Ext##PluginInfo();
#include "llvm/Support/Extension.def"
// 注册插件回调
#define HANDLE_EXTENSION(Ext) get##Ext##PluginInfo().RegisterPassBuilderCallbacks(PB);
#include "llvm/Support/Extension.def"
关于如何使动态插件生效,可以参考如下代码:
// 动态加载插件
auto Plugin = PassPlugin::Load(PathToPlugin);
if (!Plugin)
report_error();
// 注册插件回调
Plugin->registerPassBuilderCallbacks(PB);
原文地址:https://llvm.org/docs/WritingAnLLVMNewPMPass.html
1450

被折叠的 条评论
为什么被折叠?



