自己动手写LLVM Pass 二

收集程序分析的任务也被建模为LLVM PassManager中的Pass,除了它们从不(也不应该)修改IR。此外,与传统的PassManager相比,分析数据在新的PassManager中以不同的方式进行管理和开发,我决定使用单独的文章来介绍它。

本文将讨论如何使用新的AnalysisManager检索分析数据。

所以抓住你最喜欢的编辑器,让我们开始吧!

您可能在传统Pass中找到的重要功能之一是分析管理器与PassManager深度集成。您将通过getAnalysis<…>方法获取某些分析数据,该方法是Pass类的成员。但是,在新的PassManager系统中,分析管理器是一个可以在任何地方单独使用的独立实例。为了给你留下深刻的印象,让我们在传统的Pass中使用新的AnalysisManager !这是骨架代码:

bool MyFuncPass::runOnFunction(Function& F) override {
PassBuilder PB;
FunctionAnalysisManager FAM;
PB.registerFunctionAnalyses(FAM);
// ...
return false;
}

看起来有一个我们熟悉的东西:PassBuilder。你是对的,我们仍然需要它的帮助来将所有可用的分析通过注册到管理器中,但是在完成之后,分析管理器FunctionAnalysisManager可以单独使用。An AnalysisManager负责安排已注册的分析通行证以及管理其分析结果。例如,缓存结果,直到它由于对应的IR单元的某些修改而变得无效。

那么我们如何从经理那里检索分析结果呢?答案类似于旧版Passes中的getAnalysis<…>接口,但名称和方式略有不同:

#include "llvm/Analysis/AliasAnalysis.h"

bool MyFuncPass::runOnFunction(Function& F) override {
PassBuilder PB;
FunctionAnalysisManager FAM;
PB.registerFunctionAnalyses(FAM);

// How we do in legacy Passes:
AAResultWrapperPass& WrapperPass = getAnalysis<AAResultsWrapperPass>();
AAresults& AAR1 = WrapperPass.getAAResults();

// How we do with new AnalysisManager
AAResults& AAR2 = FAM.getResult<AAManager>(F);

return false;
}

上面的代码使用AliasAnalysis作为我们想要得到的分析数据。如您所见,使用旧语法,您需要首先获取对Pass实例的引用,然后使用其中一个成员函数来获取分析结果。相反,要获得分析结果,您只需要使用所需的分析Pass类型(AAManager在本例中)调用getResult<…> ,单独使用目标IR单元(在本例中为Function)实例。

在这里,我想指出getAnalysisgetResult 的返回类型的不同。一方面,getAnalysis<T> 的返回类型是T,他的模板类型参数,这是你愿望的分析型Pass,而不是分析结果。在另一方面,模板类型参数TgetResult<T>类型仍表示您所需的分析通类型,但返回类型的getResultT::Result ,该Result嵌套类类型T。这种差异揭示了分析管理设计中的一个重要的重大更改:将分析结果从分析Pass中分离出来。这将使一些管理,数据失效,更容易和有效。

现在让我们回到新的PassManager。您可能已经注意到,新PassManager Pass中run 方法的第二个参数是相应的IR单元的 AnalysisManager 实例:

struct MyNewPass : public PassInfoMixin<MyNewPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
AAResults& AAR = FAM.getResult<AAManager>(F);
// ...
return PreservedAnalyses::all();
}
};

所以你可以在没有填充的情况下通过PassBuilder使用它。

如果您不熟悉新PassManager Pass的语法,可以转到我之前的文章中讨论为新的PassManager编写一个简单的”HelloWorld” Pass。

最后,我们将讨论分析数据失效。我们只会覆盖普通Pass中使用最多的部分,因为在开发分析Pass时会涉及更多的失效技术。

PreservedAanlyses,需要从run方法返回的必需类型记录一组在此Pass后仍然有效的分析数据。如果您只是在不触及任何东西的情况下查看IR,那么当然所有分析在此之后仍然有效,即我们在前面的示例中使用PreservedAnalyses::all()通用保留集。

但是,例如 ,如果您使用某些配置文件数据来更改分支概率并因此更改块频率信息,则需要将其从通用保留集中删除:

#include "llvm/Analysis/BlockFrequencyInfo.h"

struct MyNewPass : public PassInfoMixin<MyNewPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
// ...Use some profile data to change BasicBlock frequencies...
PreservedAnalyses PA = PreservedAnalyses::all();
PA.abandon<BlockFrequencyAnalysis>();
return PA;
}
};

而不是从PreservedSet 中删除分析,实际上更常见的是声明一些分析被保留。例如,您只知道您的Pass不会触及控制流图和函数中的循环信息:

#include "llvm/Analysis/LoopInfo.h"

struct MyNewPass : public PassInfoMixin<MyNewPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
// ...
PreservedAnalyses PA = PreservedAnalyses::none();
PA.preserve<LoopAnalysis>();
PA.preserveSet<CFGAnalyses>();
return PA;
}
};

Preserve<…>方法声明保留单个分析,您可以将所需的分析类型作为模板参数传递。另一方面,preserveSet<…>有点特别。它将保留一组分析,您需要传递分析集类型,这是分析Pass之外的概念,作为模板参数。有几个可用的分析集,例如,CFGAnalyses这里,代表所有与控制流相关的分析。