缺失的故事——树内遍整合
原文地址:https://medium.com/@mshockwave/writing-llvm-pass-in-2018-part-iii-d44cd0c2c354
作者:Bekket McClane
正如副标题所示,本文更像LLVM(源代码树内)开发的一个“缺失的指引”,而不是引入新东西。
官方的《Writing an LLVM Pass》教你编写一个确实简单的(旧式的)LLVM遍,它可以被opt动态载入。但现在你是个懒人,你希望在opt,甚至在clang里运行你酷酷的遍。本文提供几个方法将你的遍整合进旧式PassManager流水线。我下一篇文章将讨论如何添加定制的clang命令行选项,以启用定制特性。
要在遍流水线里默认运行一个遍,我们需要先弄清楚流水线是如何构造的。存在一个构建类,PassManagerBuilder,用于构建旧式PassManager以及缺省的遍流水线。不过,构建类所在的路径有点奇怪——它放在include/llvm/Transforms/IPO/PassManagerBuilder.h以及lib/Transform /IPO/PassManagerBuilder.cpp,而不是我们熟悉的IR或PassManager目录。
在类PassManagerBuilder中,我们看到许多方法的名字是自解释的,例如addInstructionCombiningPass以及addFunctionSimplificationPasses。这些方法可用于将特定类别的遍添加到流水线。除了显式调用它们来更新候选遍列表,在代码里有许多地方使用OptLevel属性,即我们熟悉的-O1、-O2命令行选项,通过它们的优先级级别添加遍。
让我们看一下其中一个通用入口。首先:populateFuntionPassManager
void PassManagerBuilder::populateFunctionPassManager( legacy::FunctionPassManager &FPM) { //...Some code skipped...
if (OptLevel == 0) return;
addInitialAliasAnalysisPasses(FPM);
FPM.add(createCFGSimplificationPass()); FPM.add(createSROAPass()); FPM.add(createEarlyCSEPass()); FPM.add(createLowerExpectIntrinsicPass()); } |
之前,我们只知道一个方式运行我们的遍:使用
static RegisterPass<MyPass> X("my-pass", ...);
注册它。通过命令行选项-load=MyPass.so -my-pass运行opt。不过,如果它已经在源代码树里了,每次动态地载入与运行一个遍是奇怪且不必要的。
因此,从上面的代码,我们看到如果一个遍已经在LLVM源代码树里,我们所需做的一切是创建一个带有几个工厂方法的遍,例如createSROAPass,显式调用legacy::FunctionPassManager:: add(…)将期望的遍加入流水线。
不幸,这还没完。事实证明,在你的树内遍都设置好之前,还有几个简单的设置(这是为什么我写这篇文章)。下面是检查清单:
- createXXXPass函数
- initializeXXXPassPass函数/InitializedPasses.h文件
- INITIALIZE_PASS_BEGIN/END/DEPENDENCY代码
- 将你的initializeXXXPassPass放在合适的地方
- LinkAllPasses.h文件
- 将你的createXXXPass放在合适的地方
上面的列表是完成这些任务通常的次序,当然它们间没有特定的优先级。让我们从上到下仔细检查它们。
第一项,早先提到的,是相当简单明了的,如常它仅要求3行代码来实现:
FunctionPass* llvm::createMyAwesomePass() {
return new MyAwesomePass();
}
只是new一个你遍的实例并返回它。注意它是在llvm名字空间里的一个全局静态函数,因此不要忘记前缀llvm::或者只是使用namespace llvm {…}围绕它。
下两个检查项,序号2和3,实际上是相同的——initializeXXXPassPass函数。这个函数将为一个遍创建一个内部遍信息项,并把它注册到PassManager,连同它要求的依赖性。这类似于用于动态载入遍的RegisterPass<MyPass> X(…)。为了实现这个函数,首先我们把函数声明放在include/llvm/initializePasses.h里:
// ...Previous lines...
void initializeModuleSummaryIndexWrapperPassPass(PassRegistry&);
void initializeMustExecutePrinterPass(PassRegistry&);
// -----------------------------
void initializeMyAwesomePassPass(PassRegistry&);
// -----------------------------
void initializeNameAnonGlobalLegacyPassPass(PassRegistry&);
// ...
在我们自己遍的源代码文件里,添加以下行:
INITIALIZE_PASS_BEGIN(MyAwesomePass, "my-awesome-pass", "Some description for the Pass", false, false) INITIALIZE_PASS_DEPENDENCY(LoopInfoWrapperPass) // Or whatever your Pass dependencies INITIALIZE_PASS_END(MyAwesomePass, "my-awesome-pass", "Some description for the Pass", false, false) |
这实际上构造initializeXXXPassPass函数的函数体。
现在我们有initializeXXXPassPass与createXXXPass以及函数。接下来我们所需要做的是在正确的地方放置/调用它们。让我们先处理前者。有两个地方我们需要调用initializeXXXPassPass函数。第一个在你的遍的类构造函数里:
MyAwesomePass() : FunctionPass(ID) {
initializeMyAwesomePassPass(*PassRegistry::getPassRegistry());
}
第二个地方是在上级初始化函数中。例如,如果你的遍是一个在lib/Analysis目录下的分析遍,那么在lib/Analysis/Analysis.cpp里添加以下行:
void llvm::initializeAnalysis(PassRegistry &Registry) {
//...Other initialization function calls
initializeMyAwesomePassPass(Registry);
//...
}
如果你的遍是放在lib/Transforms/Scalar下的一个转换遍,你要查找的文件是lib/Transforms/ Scalar/Scalar.cpp。
现在让我们转到createXXXPass函数。为了防止对这些createXXXPass符号进行某些激进的链接时优化,我们需要在include/llvm/LinkAllPasses.h里添加一个假的函数调用:
struct ForcePassLinking {
ForcePassLinking() {
//...
(void) llvm::createMyAwesomePass();
//...
}
}
最后,在本文前面提到的PassManagerBuilder里的createXXXPass函数上,我们准备添加一个真正的函数调用。你可以增加更多复杂的逻辑,在特定的优化层级或条件下,将你的遍插入流水线,但通常我把这个函数调用放在populateXXXPassManager中的某处,这也在前面提到了:
void PassManagerBuilder::populateFunctionPassManager( legacy::FunctionPassManager &FPM) { //...Some code skipped...
if (OptLevel == 0) return;
addInitialAliasAnalysisPasses(FPM);
FPM.add(createCFGSimplificationPass()); FPM.add(createSROAPass()); // Here you are! FPM.add(createMyAwesomePass()); FPM.add(createEarlyCSEPass()); FPM.add(createLowerExpectIntrinsicPass()); } |
不管是旧式还是新式,遍流水线构造一直是PassManager里一个有趣的话题。仍然有许多其他因素影响遍流水线的信息。前面章节仅提供最简单的方式在旧式PassManager里缺省运行你的遍。
我认为这是将你的遍放入LLVM源代码树所需的所有额外要求。希望这使得你的LLVM开发更容易?