llvm学习(二十一):动态注册Pass的加载过程(下)

本文主要讲 clang 使用 New Pass Manager 动态加载Pass的原理。

从 EmitAssemblyWithNewPassManager 开始

上回我们提到,llvm11 通过一个配置项,可以开启新版的 PassManager,此刻,我并不知道新版的怎么用,我想通过阅读代码来找到灵感。

进入分支

1
2
3
4
if (CGOpts.ExperimentalNewPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
else
AsmHelper.EmitAssembly(Action, std::move(OS));

添加 -fexperimental-new-pass-manager 来进入另一个分支,断点调试。

代码非常清晰,EmitAssemblyWithNewPassManager中:

1
2
3
4
5
6
7
8
9
10
// Attempt to load pass plugins and register their callbacks with PB.
for (auto &PluginFN : CodeGenOpts.PassPlugins) {
auto PassPlugin = PassPlugin::Load(PluginFN);
if (PassPlugin) {
PassPlugin->registerPassBuilderCallbacks(PB);
} else {
Diags.Report(diag::err_fe_unable_to_load_plugin)
<< PluginFN << toString(PassPlugin.takeError());
}
}

调试时发现 PassPlugins 默认是空的,由

1
Opts.PassPlugins = Args.getAllArgValues(OPT_fpass_plugin_EQ);

进行赋值,因此需要传参:fpass-plugin=libPass.so

这里有两个函数,一个是加载plugin,一个是触发 Callback,并传递关键对象 PassBuilder。

PassPlugin::Load

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Expected<PassPlugin> PassPlugin::Load(const std::string &Filename) {
std::string Error;
auto Library =
sys::DynamicLibrary::getPermanentLibrary(Filename.c_str(), &Error);
if (!Library.isValid())
return make_error<StringError>(Twine("Could not load library '") +
Filename + "': " + Error,
inconvertibleErrorCode());

PassPlugin P{Filename, Library};
intptr_t getDetailsFn =
(intptr_t)Library.SearchForAddressOfSymbol("llvmGetPassPluginInfo");

if (!getDetailsFn)
// If the symbol isn't found, this is probably a legacy plugin, which is an
// error
return make_error<StringError>(Twine("Plugin entry point not found in '") +
Filename + "'. Is this a legacy plugin?",
inconvertibleErrorCode());

P.Info = reinterpret_cast<decltype(llvmGetPassPluginInfo) *>(getDetailsFn)();

if (P.Info.APIVersion != LLVM_PLUGIN_API_VERSION)
return make_error<StringError>(
Twine("Wrong API version on plugin '") + Filename + "'. Got version " +
Twine(P.Info.APIVersion) + ", supported version is " +
Twine(LLVM_PLUGIN_API_VERSION) + ".",
inconvertibleErrorCode());

if (!P.Info.RegisterPassBuilderCallbacks)
return make_error<StringError>(Twine("Empty entry callback in plugin '") +
Filename + "'.'",
inconvertibleErrorCode());

return P;
}

代码逻辑简单而且很好理解,就是获取 Pass 信息并做简单检查:

  1. 首先加载library
  2. 然后寻找 llvmGetPassPluginInfo 的符号
  3. 然后调用 llvmGetPassPluginInfo 并且获得返回,返回类型应为 PassPluginLibraryInfo,存放到 Info字段中。
  4. 检查 APIVersionLLVM_PLUGIN_API_VERSION
  5. 检查 RegisterPassBuilderCallbacks 不为空

registerPassBuilderCallbacks

1
2
3
4
/// Invoke the PassBuilder callback registration
void registerPassBuilderCallbacks(PassBuilder &PB) const {
Info.RegisterPassBuilderCallbacks(PB);
}

这个 Info 就是该插件的信息,由外部加载的Library提供,调用其回调函数,函数传参为 PassBuilder &PB 的索引。

显然,这个 PassBuilder 就是新版的,对应之前的 legacy::PassManagerBase。没想到逻辑这么短,一下子就理清楚了。

简单适配

搜索关键词,llvmGetPassPluginInfo,在官方example里找到一个非常适合学习的,llvm/examples/Bye/Bye.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* Legacy PM Registration */
static llvm::RegisterStandardPasses RegisterBye(
llvm::PassManagerBuilder::EP_VectorizerStart,
[](const llvm::PassManagerBuilder &Builder,
llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyBye()); });

/* New PM Registration */
llvm::PassPluginLibraryInfo getByePluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "Bye", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerVectorizerStartEPCallback(
[](llvm::FunctionPassManager &PM,
llvm::PassBuilder::OptimizationLevel Level) {
PM.addPass(Bye());
});
PB.registerPipelineParsingCallback(
[](StringRef Name, llvm::FunctionPassManager &PM,
ArrayRef<llvm::PassBuilder::PipelineElement>) {
if (Name == "goodbye") {
PM.addPass(Bye());
return true;
}
return false;
});
}};
}

#ifndef LLVM_BYE_LINK_INTO_TOOLS
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return getByePluginInfo();
}
#endif

它同时适配了 LegacyPassManager 和 NewPassManager,和阅读代码的结论相互印证。

网上找到两个有用的链接,分别是:https://github.com/banach-space/llvm-tutorhttps://groups.google.com/g/llvm-dev/c/e_4WobR9WP0 。前者有一些 llvmGetPassPluginInfo 的实践,可惜它用的是 opt ,我用 clang 时无法触发;后者和我日常测试的需求相似,也给了我较大的帮助。

简而言之,要想适配 NewPassManager,就需要深刻理解 PassBuilder &PB 这个对象的用法,它的 API 实在太多太复杂,编写本文时还没有研究透彻。

临时demo

暂时抄groups的讨论:动态注册使用这段,在llvm13上可以成功,但是在 llvm 11 上不成功,原因未知,将来有空研究一下。

1
2
3
4
5
6
PB.registerPipelineStartEPCallback(
[](ModulePassManager &MPM, OptimizationLevel Level) {
FunctionPassManager FPM;
FPM.addPass(HelloWorld());
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
});
1
2
3
4
5
$LLVM_HOME/bin/clang --version
clang version 13.0.0 (https://github.com/llvm/llvm-project/ 24c8eaec9467b2aaf70b0db33a4e4dd415139a50)
$ $LLVM_HOME/bin/clang -fpass-plugin=/tmp/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so /tmp/test.c
registerPipelineStartEPCallback
I saw a function called main!

未完待续。。。

NewPassManager 还有很多值得探索的地方,我会尽快找到一个 NewPassManager 完美的适配方案。