首页
社区
课程
招聘
[翻译]现代化地编写LLVM Pass -- part II
2020-2-14 20:06 9138

[翻译]现代化地编写LLVM Pass -- part II

2020-2-14 20:06
9138

Writing LLVM Pass in 2018-part II

第二部分原文
Analysis —Thing that deserves its own article
在LLVM PassManager中,收集程序的分析任务也被构建为了Passes,它们不会也不应该修改IR内容。而且,和旧版PM相比,在新PM中,分析数据的管理和开发有很大的改变,所以我单独用一篇文章来阐述。这篇文章将会谈论如何用新的 AnalysisManager 来接受分析数据。打开你上手的编辑器,开始吧~

analysis pass

在旧版Pass中,你会发现一个重要的特性就是analysis manager和PassManager高度融合。你可以通过 getAnalysis<...> 方法来获取某个分析数据,这也是 Pass 类的成员之一。然而在新版的PassManager中,analysis manager是一个单独的实例,可以在任何地方独立使用。为了让你理解这个特点,让我们在 旧版Pass 中使用 新版 AnalysisManager .下面是主干代码

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

PassBuilder我们很熟悉,需要他来向PM注册所有可用的Pass。在这之后,这里的FunctionAnalysisManager可以独立使用。 AnalysisManager 负责管理所有已注册的analysis Pass和它们的分析结果。比如,缓存一个analysis pass的结果,直到它对应的IR单元被修改。

 

所以我们应该如何从manager中获取分析结果呢?和旧版Pass中的 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的一个实例,然后用其中的一个成员函数来获取分析结果。在新语法中,你只需要用analysis Pass的类型(此处为 AAManager ),连同你的目标IR单元实例(此处为 Function )来调用 getResult<...>

 

这里指出 getAnalysisgetResult 返回类型是不同的。一方面, getAnalysis<T> 的返回类型是 T ,它的模板类型T是你希望的analysis Pass的类型,而不是analysis的结果。另一方面, getResult<T> 中的模板类型T仍代表你希望的analysis pass的类型,但 getResult 的返回类型是 T::Result (T中的Result成员)。这个区别揭示了analyses management设计中的一个重要变化: analysis结果与analysis Pass解耦。这将使得某些管理、数据验证更简单且高效。

AnalysisManager

再回头来看新的PassManager. run 方法的第二个参数是对应的IR单元的 AnalysisManager 实例

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

因此你可以直接使用而不用通过PassBuilder来构建。

analysis data invalidation

最后让我们谈论下分析数据的invalidation. 我们打算仅讨论normal Pass中最常用的部分。
PreservedAanlyses 是run方法所需的返回类型,记录着在Pass后仍然有效的一组分析数据。如果你只是想查看IR而不是修改它们,那么所有的分析在这之后都有效,只需要返回 PreservedAnalyses::all() 即可。

 

但如果你使用一些数据修改了分支的可能性并因此改变了block的频率信息,你需要从被保留的set中移除它们

#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在函数内不会修改控制流(control flow graph)和循环信息。如下面的代码

#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<...> 方法声明了单个analysis的保留集(通过向其模板类型中传入analysis 类型)。 而preserveSet<...> 有些区别,它会保留一组analyse,你需要传入一个analysis set的类型(不同于Pass的概念)。有很多可用的analysis set的类型,比如此处的 CFGAnalyses 表示所有的控制流相关的analyse.

 

PS:
还有一些重要话题没有讨论,例如:

  • 如何写一个analysis pass
  • 如何查询一个analysis是否被保留

或许你可以从源码中找到答案 :-)


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-2-14 20:08 被微笑明天编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (5)
雪    币: 4631
活跃值: (760)
能力值: ( LV7,RANK:101 )
在线值:
发帖
回帖
粉丝
微笑明天 2020-2-14 20:08
2
0
板块放错了,望版主移动到翻译板块
雪    币: 2510
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xghoecki 2020-2-15 10:25
3
0
感谢分享
雪    币: 29414
活跃值: (18690)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2020-2-26 16:59
4
0
微笑明天 板块放错了,望版主移动到翻译板块
移过来了,建议同一篇文章,文章不长时,用跟帖的形式发上来。
雪    币: 4631
活跃值: (760)
能力值: ( LV7,RANK:101 )
在线值:
发帖
回帖
粉丝
微笑明天 2020-3-8 13:53
5
0
Writing LLVM Pass in 2018-part III
原文https://medium.com/@mshockwave/writing-llvm-pass-in-2018-part-iii-d44cd0c2c354
你已经能够写一个pass并用opt来动态加载。但你希望能够在 opt 或者 clang 中自动运行你的pass。本文将介绍一些将你的pass整合进legacy PassManager pipeline的方法。下篇文章将介绍如何整合进clang的命令选项。
为了在Pass pipeline中默认运行Pass,我们需要了解pipeline是如何开始构建的. PassManagerBuilder 这个builder类用于构建legacy PassManager和默认的pass pipeline.但这个builder class文件的路径有些奇怪,在 include/llvm/Transforms/IPO/PassManagerBuilder.h 和 lib/Transform/IPO/PassManagerBuilder.cpp 中,而不是在 IR 或者 PasManager 文件夹中。
在 PassManagerBuilder 类中,有很多顾名思义的函数,比如 addInstructionCombiningPass 和 addFunctionSimplificationPasses 这些函数可以向pipeline中添加某个pass的目录。 除了显式调用它们来更新候选Pass列表,在代码里有许多地方使用OptLevel属性,即我们熟悉的 -O1 , -O2 命令行选项,通过它们的优先级级别添加Pass
让我们看一下其中一个通用入口 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());
}
在此之前,我们只知道一种运行我们pass的方法:用下面的代码来注册
static RegisterPass<MyPass> X("my-pass", ...);
然后用 opt 加上 -load=MyPass.so -my-pas 选项来运行。
然而,如果它已经在源代码树里了,每次动态地载入与运行一个遍是奇怪且不必要的。
因此,从上面的代码,我们看到如果一个Pass已经在LLVM源代码树里,我们所需做的一切是创建一个带有几个工厂方法的Pass,例如 createSROAPass ,然后显式调用 legacy::FunctionPassManager:: add(…) 将期望的Pass加入pipeline。
不幸,这还没完。事实证明,在构建好源码内建的Pass之前,还有几个简单的设置(这是为什么我写这篇文章)。下面是检查清单:
1. createXXXXPass 函数
2. initializeXXXPassPass 函数/ InitializedPasses.h 文件
3. INITIALIZE_PASS_BEGIN / END / DEPENDENCY  代码
4. 将你的 initializeXXXPassPass 放在合适的地方
5. LinkAllPasses.h 文件
6. 将你的 createXXXPass 放在合适的地方
上面的列表清单是完成这些任务通常的次序,当然它们间没有特定的优先级。让我们从上到下来解读。
创建函数
createXXXXPass函数
第一项,早先提到的,是相当简单明了的,通常它仅需要3行代码来实现:
FunctionPass* llvm::createMyAwesomePass() {
 return new MyAwesomePass();
}
new一个Pass的实例并返回它。注意它是在llvm名字空间里的一个全局静态函数,因此不要忘记前缀 llvm:: 或者用 namespace llvm{...} 围绕它。
initializeXXXPassPass函数
接下来的两个设置,实际上都是 initializeXXXPassPass 函数。这个函数将为Pass创建一个内部Pass信息入口(internal Pass info entry),并把它和其依赖项注册到 PassManager 。这类似于用于动态载入Pass的 RegisterPass<MyPass> (...) 。为了实现这个函数,首先我们把函数声明放在 include/llvm/initializePasses.h 里
// ...Previous lines...
void initializeModuleSummaryIndexWrapperPassPass(PassRegistry&);
void initializeMustExecutePrinterPass(PassRegistry&);
// -----------------------------
void initializeMyAwesomePassPass(PassRegistry&);
// -----------------------------
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 函数
调用函数
现在我们已经实现了 initializeXXXPassPass 和 createXXXPass 函数。接着我们要把他们放到合适的地方。现来看前者。
initializeXXXPassPass
有两个地方我们需要调用initializeXXXPassPass函数。第一个是在你的Pass类的构造函数中
MyAwesomePass() : FunctionPass(ID) {
  initializeMyAwesomePassPass(*PassRegistry::getPassRegistry());
}
第二个是在上层的初始化函数中(initialization function).例如,如果你的pass是一个在 lib/Analysis 文件夹下面的analysis pass,将下面几行加入 lib/Analysis/Analysis.cpp 文件中:
void llvm::initializeAnalysis(PassRegistry &Registry) {
  //...Other initialization function calls
  initializeMyAwesomePassPass(Registry);
  //...
}
如果pass是一个在 lib/Transforms/Scalar 下的transformation pass,那么要修改的文件就是 lib/Transforms/Scalar/Scalar.cpp 
createXXXPass
让我们接着来看 createXXXPass 函数。为了防止对这些 createXXXPass 符号进行某些激进的链接时优化,我们需要在 include/llvm/LinkAllPasses.h 里添加一个假的函数调用
struct ForcePassLinking {
    ForcePassLinking() {
      //...
      (void) llvm::createMyAwesomePass();
      //...
    }
}
最后,在本文前面提到的 PassManagerBuilder 里的 createXXXPass 函数中添加一个真正的函数调用。你可以增加更多复杂的逻辑,在特定的优化层级条件下,将你的pass插入pipeline,但我通常把这个函数调用放在 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 pipeline一直是PassManager里一个有趣的话题。仍然有许多其他因素影响pass pipeline的信息。前面章节仅提供在旧版PassManager中默认运行你的pass的最简单的方式。
我认为这是将你的pass放入LLVM源代码树所需的所有额外要求。希望这使得你的LLVM开发更容易 :-)
雪    币: 2924
活跃值: (2562)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
小调调 2020-4-11 17:24
6
0
微笑明天 Writing LLVM Pass in 2018-part III 原文https://medium.com/@mshockwave/writing-llvm-pass-in-2018-part- ...
还是整整吧,这个帖的排版不友好,谢谢分享
游客
登录 | 注册 方可回帖
返回