LLVM - 编写一个 LLVM Pass

介绍 – 什么是 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 注册为插件,从而可以在诸如 clangopt 等工具中动态加载。这些插件可以添加新的优化 Pass,或者手动通过工具调用。

如需了解更多信息,请参考 https://llvm.org/docs/NewPassManager.html。

要创建一个 Pass 插件,您需要:

  1. 在 LLVM 源码树的根目录(或其他位置)创建一个 CMake 项目;
  2. 该项目的 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdddn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值