本文主要讲 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 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) 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 信息并做简单检查:
首先加载library
然后寻找 llvmGetPassPluginInfo
的符号
然后调用 llvmGetPassPluginInfo
并且获得返回,返回类型应为 PassPluginLibraryInfo
,存放到 Info
字段中。
检查 APIVersion
和 LLVM_PLUGIN_API_VERSION
检查 RegisterPassBuilderCallbacks
不为空
registerPassBuilderCallbacks 1 2 3 4 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 static llvm::RegisterStandardPasses RegisterBye ( llvm::PassManagerBuilder::EP_VectorizerStart, [](const llvm::PassManagerBuilder &Builder, llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyBye()); }) ;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-tutor 和 https://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 完美的适配方案。