本文是个人发表在个人博客的两篇文章的合集,介绍 llvm 动态加载 pass 的原理细节。
不知道什么时候,不知道在哪看到,说llvm的 PassManager 更新了。
2021年有读者反馈llvm14加载pass失败,我当时没空处理,而且认为是读者自身问题。
2022年5月7日,immortal学弟使用 llvm13 prebuilt library 加载pass,不报错也没输出。刚巧我一直没验证过 prebuilt的可用性,本地复现后,发现llvm12 prebuilt是完全没问题的,llvm13就不行,才发现可能是版本更替引起的。
于是,找到了高版本 llvm 对 Pass Manager 的变更,https://releases.llvm.org/13.0.0/docs/ReleaseNotes.html#changes-to-the-llvm-ir , https://releases.llvm.org/14.0.0/docs/ReleaseNotes.html#changes-to-the-llvm-ir ,确认了该问题。
对于 llvm 13 和 14,临时解决方案是使用 -flegacy-pass-manager
,让之前写的 pass 继续生效。
最终,只剩一个问题,如何正确使用新版的 PassManager。在这之前,我需要先对 legacy pass manager 做一个总结。
旧版新版并存已经5年了,很多开发者在 opt 里进行 transform,新版的API长期也没人适配clang。
llvm13 是 2021年10月发布的,过去半年了,居然没人来更新相关的方案,属实不应该。
本文带大家从源码的角度,分析从命令行输入,到pass动态注册的整个过程,以之前常用做测试的 clang -Xclang -load -Xclang libSkeletonPass.so test.c
为例。
环境准备,见第十九篇 ( https://leadroyal.cn/p/2206/ ),肉眼读源码很费劲,结合调试,可以更快找到核心代码位置,更加理解整个逻辑
clang
并不仅仅是c语言前端,它是一个编译器合集,包括了编译、优化、链接的各个过程,可以使用 -v
参数简单观察一下编译全程的细节。它会调用clang -cc1
,clang -cc1
是真正编译器。
clang -Xclang -load -Xclang libSkeletonPass.so test.c
的 -Xclang
就是将参数传递给 clang -cc1
,最终调用命令是:clang -cc1 -load libSkeletonPass.so test.c
,而load功能是 clang -cc1
提供的。
二者的help内容也是完全不一样的,使用 -Xclang
进行参数传递。
clang
的可执行文件位于:clang/tools/driver/driver.cpp
,代码如下:
当第一个参数是 -cc1
时,走一套逻辑,否则,走另一套逻辑。
此时,问题转化为:clang -cc1 -load libSkeletonPass.so test.c
的 load 功能在哪实现。
这里盲猜,肯定是用到命令行参数解析技术、动态加载技术,与PassManager有关,朝着相关方向思考。
根据 help 内容搜索关键字,在 clang/include/clang/Driver/Options.td
中找到,它是个描述文件,生成头文件的名字已超出我的认知范围,这条路行不通,但生成的头文件一定是某种格式的。
根据编写 Pass 的API,找到 RegisterStandardPasses
的实现,位于llvm/include/llvm/Transforms/IPO/PassManagerBuilder.h
。
调用了 PassManagerBuilder::addGlobalExtension(Ty, std::move(Fn))
,传参为生效时刻、注册回调函数,并返回插件ID。
实现在 llvm/lib/Transforms/IPO/PassManagerBuilder.cpp
中。
将生效时刻、注册回调函数放到 GlobalExtensions
里,之后我们下断点,观察栈回溯,发现很多收获。
可以得知,Clang->getFrontendOpts().Plugins
中存放动态加载library。
得到阶段性结论:虽然我们不知道 -load
的实现在哪里,但它的作用是将传参结果放到了 Plugins 里,加载 library 已结束,但 pass 并没有加载或执行。此时,问题转化为了:GlobalExtensions
存放的注册时机和回调函数,是何时被 LegacyPassManager 加载的。
搜索 GlobalExtensions
的引用,很容易找到一处 llvm/lib/Transforms/IPO/PassManagerBuilder.cpp
:
显然这里将 PassManager 传递到了回调函数中,API也是我们非常熟悉的,在恰当的时机,将PassManager 传递给用户自定义的处理函数,用户可以使用 PassManager 的 API将 pass 添加进去。
同样,调试,观察代码和栈回溯:
我随手使用的是llvm11版,默认不使用 NewPassManager,走 else 分支,也就是 LegacyPassManager。甚至还找到了 NewPassManager 的处理逻辑。
我这个案例走的是 EmitAssembly -> CreatePass -> populateFunctionPassManager,触发的是 EP_EarlyAsPossible ,这个注册时机有多个,搜索 addExtensionsToPM 可以找到其他的 pass 被注册的时机,不是本文的重点。
此时,所有问题都解决了。
不出所料只是简单修改了判定条件,clang/lib/CodeGen/BackendUtil.cpp
文件同样的位置:
上文提到了,我偶然发现了 CGOpts.ExperimentalNewPassManager
这个配置项,显然就是 llvm13以上使用的 NewPassManager 了,整好省下了重新编译的时间。因此,下一篇将介绍,如何在 llvm11 上,使用 NewPassManager 动态加载用户的 Pass 的原理。
上回我们提到,llvm11 通过一个配置项,可以开启新版的 PassManager,此刻,我并不知道新版的怎么用,我想通过阅读代码来找到灵感。
添加 -fexperimental-new-pass-manager
来进入另一个分支,断点调试。
代码非常清晰,EmitAssemblyWithNewPassManager
中:
调试时发现 PassPlugins
默认是空的,由
进行赋值,因此需要传参:fpass-plugin=libPass.so
这里有两个函数,一个是加载plugin,一个是触发 Callback,并传递关键对象 PassBuilder。
代码逻辑简单而且很好理解,就是获取 Pass 信息并做简单检查:
这个 Info
就是该插件的信息,由外部加载的Library提供,调用其回调函数,函数传参为 PassBuilder &PB
的索引。
显然,这个 PassBuilder 就是新版的,对应之前的 legacy::PassManagerBase。没想到逻辑这么短,一下子就理清楚了。
搜索关键词,llvmGetPassPluginInfo,在官方example里找到一个非常适合学习的,llvm/examples/Bye/Bye.cpp
。
它同时适配了 LegacyPassManager 和 NewPassManager,和阅读代码的结论相互印证。
网上找到两个有用的链接,分别是:https://github.com/banach-space/llvm-tutor 和 https://groups.google.com/g/llvm-dev/c/e_4WobR9WP0 。前者有一些 llvmGetPassPluginInfo
的实践,可惜它用的是 opt
,我用 clang 时无法触发;后者和我日常测试的需求相似,也给了我较大的帮助。
简而言之,要想适配 NewPassManager,就需要深刻理解 PassBuilder &PB
这个对象的用法,它的 API 实在太多太复杂,编写本文时还没有研究透彻。
暂时抄groups的讨论:动态注册使用这段,在llvm13上可以成功,但是在 llvm 11 上不成功,原因未知,将来有空研究一下。
NewPassManager 还有很多值得探索的地方,我会尽快找到一个 NewPassManager 完美的适配方案。
版本 |
默认行为 |
可选参数 |
llvm5~llvm12 |
使用 LegacyPassManager |
-fno-experimental-new-pass-manager 启用 NewPassManager |
llvm13~llvm14 |
使用 NewPassManager |
-flegacy-pass-manager 启用 LegacyPassManager |
llvm15(开发中) |
使用 NewPassManager |
可能移除 LegacyPassManager |
clang
-
-
help
...
-
Xclang <arg> Pass <arg> to the clang compiler
clang
-
cc1
-
-
help
...
-
load <dsopath> Load the named plugin (dynamic shared
object
)
clang
-
-
help
...
-
Xclang <arg> Pass <arg> to the clang compiler
clang
-
cc1
-
-
help
...
-
load <dsopath> Load the named plugin (dynamic shared
object
)
int
main(
int
argc_, const char
*
*
argv_) {
/
/
*
*
*
*
*
*
*
*
*
*
if
(FirstArg !
=
argv.end() && StringRef(
*
FirstArg).startswith(
"-cc1"
)) {
/
/
If
-
cc1 came
from
a response
file
, remove the EOL sentinels.
if
(MarkEOLs) {
auto newEnd
=
std::remove(argv.begin(), argv.end(), nullptr);
argv.resize(newEnd
-
argv.begin());
}
return
ExecuteCC1Tool(argv);
}
/
/
*
*
*
*
*
*
*
*
*
*
}
int
main(
int
argc_, const char
*
*
argv_) {
/
/
*
*
*
*
*
*
*
*
*
*
if
(FirstArg !
=
argv.end() && StringRef(
*
FirstArg).startswith(
"-cc1"
)) {
/
/
If
-
cc1 came
from
a response
file
, remove the EOL sentinels.
if
(MarkEOLs) {
auto newEnd
=
std::remove(argv.begin(), argv.end(), nullptr);
argv.resize(newEnd
-
argv.begin());
}
return
ExecuteCC1Tool(argv);
}
/
/
*
*
*
*
*
*
*
*
*
*
}
def
load : Separate<[
"-"
],
"load"
>, MetaVarName<
"<dsopath>"
>,
HelpText<
"Load the named plugin (dynamic shared object)"
>;
def
load : Separate<[
"-"
],
"load"
>, MetaVarName<
"<dsopath>"
>,
HelpText<
"Load the named plugin (dynamic shared object)"
>;
static RegisterStandardPasses RegisterMyPass(
PassManagerBuilder::EP_EarlyAsPossible,registerSkeletonPass
);
static RegisterStandardPasses RegisterMyPass(
PassManagerBuilder::EP_EarlyAsPossible,registerSkeletonPass
);
class
RegisterStandardPasses {
PassManagerBuilder::GlobalExtensionID ExtensionID;
public:
RegisterStandardPasses(PassManagerBuilder::ExtensionPointTy Ty,
PassManagerBuilder::ExtensionFn Fn) {
ExtensionID
=
PassManagerBuilder::addGlobalExtension(Ty, std::move(Fn));
}
class
RegisterStandardPasses {
PassManagerBuilder::GlobalExtensionID ExtensionID;
public:
RegisterStandardPasses(PassManagerBuilder::ExtensionPointTy Ty,
PassManagerBuilder::ExtensionFn Fn) {
ExtensionID
=
PassManagerBuilder::addGlobalExtension(Ty, std::move(Fn));
}
llvm::PassManagerBuilder::addGlobalExtension(llvm::PassManagerBuilder::ExtensionPointTy, std::function<…>) PassManagerBuilder.cpp:
225
xxxxxxx
[Inlined] llvm::sys::DynamicLibrary::HandleSet::DLOpen DynamicLibrary.inc:
28
llvm::sys::DynamicLibrary::getPermanentLibrary DynamicLibrary.cpp:
154
[Inlined] llvm::sys::DynamicLibrary::LoadLibraryPermanently DynamicLibrary.h:
87
clang::ExecuteCompilerInvocation ExecuteCompilerInvocation.cpp:
209
cc1_main cc1_main.cpp:
240
ExecuteCC1Tool driver.cpp:
330
xxxxx
llvm::PassManagerBuilder::addGlobalExtension(llvm::PassManagerBuilder::ExtensionPointTy, std::function<…>) PassManagerBuilder.cpp:
225
xxxxxxx
[Inlined] llvm::sys::DynamicLibrary::HandleSet::DLOpen DynamicLibrary.inc:
28
llvm::sys::DynamicLibrary::getPermanentLibrary DynamicLibrary.cpp:
154
[Inlined] llvm::sys::DynamicLibrary::LoadLibraryPermanently DynamicLibrary.h:
87
clang::ExecuteCompilerInvocation ExecuteCompilerInvocation.cpp:
209
cc1_main cc1_main.cpp:
240
ExecuteCC1Tool driver.cpp:
330
xxxxx
void PassManagerBuilder::addExtensionsToPM(ExtensionPointTy ETy,
legacy::PassManagerBase &PM) const {
/
/
*
*
*
*
*
*
*
*
*
for
(unsigned i
=
0
, e
=
Extensions.size(); i !
=
e;
+
+
i)
if
(Extensions[i].first
=
=
ETy)
Extensions[i].second(
*
this, PM);
}
void PassManagerBuilder::addExtensionsToPM(ExtensionPointTy ETy,
legacy::PassManagerBase &PM) const {
/
/
*
*
*
*
*
*
*
*
*
for
(unsigned i
=
0
, e
=
Extensions.size(); i !
=
e;
+
+
i)
if
(Extensions[i].first
=
=
ETy)
Extensions[i].second(
*
this, PM);
}
void PassManagerBuilder::populateFunctionPassManager(
legacy::FunctionPassManager &FPM) {
addExtensionsToPM(EP_EarlyAsPossible, FPM);
FPM.add(createEntryExitInstrumenterPass());
void PassManagerBuilder::populateFunctionPassManager(
legacy::FunctionPassManager &FPM) {
addExtensionsToPM(EP_EarlyAsPossible, FPM);
FPM.add(createEntryExitInstrumenterPass());
if
(!CGOpts.LegacyPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
else
AsmHelper.EmitAssembly(Action, std::move(OS));
if
(!CGOpts.LegacyPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
else
AsmHelper.EmitAssembly(Action, std::move(OS));
if
(CGOpts.ExperimentalNewPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
else
AsmHelper.EmitAssembly(Action, std::move(OS));
if
(CGOpts.ExperimentalNewPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-6-15 19:12
被LeadroyaL编辑
,原因: