自己动手写LLVM Pass 三

LLVM Pass官方教程会教您如何编译一个相当简单的(Legacy)LLVM Pass,她能够被opt动态加载。但现在你是一个懒惰的家伙,你想自动的在opt 甚至clang中运行您的Pass。本文提供了将Pass集成到旧版PassManager管道的几种方法之一。然后,我的下一篇文章将讨论如何添加自定义clang命令行选项以启用自定义功能。

默认情况下,要在Pass管道中运行Pass,我们需要先弄清楚管道是如何构建的。有一个构建器类,PassManagerBuilder用于构建旧版PassManager以及默认的Pass管道。其中生成器类所在的路径,但是,它有点陌生-它放在include/llvm/Transforms/IPO/PassManagerBuilder.hlib/Transform/IPO/PassManagerBuilder.cpp,而不是我们所熟悉的IRPassManager文件夹。

PassManagerBuilder类中,我们可以发现,有许多可以通过名称自我解释的方法,例如,addInstructionCombiningPassaddFunctionSimplificationPasses。这些方法可用于向管道添加某些类别的Passes。除了调用他们明确地更新condidate Passes的列表,有在代码中使用了许多地方OptLevel的地方,即我们熟悉-O1-O2命令行选项,通过他们的优化级别来添加Pass。

让我们先看一下一般的切入点:populateFunctionPassManager

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", ...);

并使用 opt命令行选项运行-load=MyPass.so -my-pass。但是,如果它已经放入源树中,那么每次动态加载和运行Pass 都是很奇怪和没有必要的。

因此,从上面的代码中,我们看到如果Pass已经在LLVM源代码树中,我们需要做的就是创建一个带有一些工厂函数的Pass(例如:createSROAPass),并且调用legacy::FunctionPassManager::add(…)以显式将所需的Pass添加到管道中。

不幸的是,这并没有结束。事实证明,在让你的in-tree Passes全部设置之前,有几个微不足道设置:

  1. 实现createXXXPass函数.
  2. 实现initializeXXXPassPass函数/ InitializedPasses.h文件.
  3. INITIALIZE_PASS_BEGIN/END/DEPENDENCY code.
  4. initializeXXXPassPass 放到正确的位置.
  5. 实现LinkAllPasses.h文件.
  6. createXXXPass放到正确的位置.

以上列表是我完成这些任务的通常命令,但当然其中没有具体的优先级。让我们自上而下依次来看。

前面提到的第一个项目,它非常简单,通常只需要三行代码来实现:

FunctionPass * llvm :: createMyAwesomePass(){
return new MyAwesomePass();
}

这个过程中,仅仅是创建一个自己的Pass的实例并返回它。请注意,它是llvm命名空间中的全局静态函数,因此不要忘记前缀llvm::或只是用它包围它namespace llvm {…}

接下来的两项,第二和第三,实际上是相同的 initializeXXXPassPass功能。此函数将为Pass创建一个内部Pass信息条目,并将其注册到PassManager,单独注册其所需的依赖项。它与RegisterPass<MyPass> X(…)动态加载的Passes 类似。要实现此功能,首先我们将函数声明放在include/llvm/InitializePasses.h文件中

// ...Previous lines...<br>void initializeModuleSummaryIndexWrapperPassPass(PassRegistry&);
void initializeMustExecutePrinterPass(PassRegistry&)
// -----------------------------
void initializeMyAwesomePassPass(PassRegistry&);
// --------<br>void initializeNameAnonGlobalLegacyPassPass(PassRegistry&);
// ...

在我们自己的Pass的源代码文件中,添加以下行:

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函数的函数体。

现在我们有leinitializeXXXPassPasscreateXXXPass两个函数。我们接下来需要做的就是将它们放在正确的位置。

我们需要两个地方来调用initializeXXXPassPass函数。第一个是在Pass类构造函数中:

MyAwesomePass():FunctionPass(ID){
initializeMyAwesomePassPass(* PassRegistry :: getPassRegistry());
}

第二地方是上层初始化函数。例如,如果您的Pass是分析Pass放在lib/Analysis文件夹下,那么添加以下行lib/Analysis/Analysis.cpp

void llvm::initializeAnalysis(PassRegistry &Registry) {
//...Other initialization function calls
initializeMyAwesomePassPass(Registry);
//...
}

如果你是一个放在lib/Transforms/Scalar下的 转换Pass,你要找的文件就是lib/Transforms/Scalar/Scalar.cpp

现在让我们关注createXXXPass。为了防止对这些createXXXPass符号进行一些积极的链接时间优化,我们需要添加一个假函数调用include/llvm/LinkAllPasses.h

struct ForcePassLinking {
ForcePassLinking() {
//...
(void) llvm::createMyAwesomePass();
//...
}
}

最后,我们将添加一个真正的在PassManagerBuilder中被createXXXPass调用的函数。您可以添加更复杂的逻辑,以便在某些优化级别或条件下将Pass插入管道,但通常我只是将函数调用放入某处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());
}

构建Pass管道在PassManager中始终是一个有趣的主题,包括旧版和新版。还有许多其他因素会影响Pass管道的形成。上一节仅提供了在传统PassManager中默认运行Pass的最简单方法。