首页
社区
课程
招聘
[原创] llvm NewPassManager API分析及适配方案
2022-5-15 22:15 18052

[原创] llvm NewPassManager API分析及适配方案

2022-5-15 22:15
18052

本文原发表于个人博客,分享到看雪,请多多指点。

 

本文从 llvmGetPassPluginInfo 出发,深入解读 PassBuilder 的各个 API,记录从 LegacyPassManager 迁移到 NewPassManager 的过程。本文使用llvm13,低版本相关API会不一致。

llvmGetPassPluginInfo 出发

根据背景知识,llvm13以上通过 -fpass-plugin=libPass.so 指定被加载的 library,加载后调用该共享库的导出符号 llvmGetPassPluginInfo 获取相关信息,该函数需要返回结构体 llvm::PassPluginLibraryInfo,位于 llvm/include/llvm/Passes/PassPlugin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
struct PassPluginLibraryInfo {
  /// The API version understood by this plugin, usually \c
  /// LLVM_PLUGIN_API_VERSION
  uint32_t APIVersion;
  /// A meaningful name of the plugin.
  const char *PluginName;
  /// The version of the plugin.
  const char *PluginVersion;
 
  /// The callback for registering plugin passes with a \c PassBuilder
  /// instance
  void (*RegisterPassBuilderCallbacks)(PassBuilder &);
};

直接看范例吧,很好理解。

1
2
3
4
5
6
7
8
9
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
    return {LLVM_PLUGIN_API_VERSION,
            "Skeleton",
            "1.0.0",
            [](PassBuilder &PB) {
                //xxxxxxx
            }};
}

第一个参数传递版本号,和编译依赖的llvm环境有关,从环境中取值即可。加载时会检查clang版本号和plugin版本号是否一致,不一致会加载失败。

 

第二个参数是插件名字,随便传。

 

第三个参数是插件版本号,随便传。

 

第四个参数非常重要,是一个回调函数,clang加载该插件后会调用它,传递 PassBuilder &PB 这个关键对象。

 

问题转化为了:PassBuilder &PB 如何使用。

PassBuilder 的 API

PassBuilder 声明位于 llvm/include/llvm/Passes/PassBuilder.h,有大量函数供调用,总结如下:

  • (xxx)Analyses,与加载pass不相关
  • build(xxx)Pipeline,与加载pass不相关
  • parsePassPipiline,尝试处理给定的处理串,可能用于测试
  • printPassNames,打印所有的Pass
  • registerAnalysisRegistrationCallback,与加载pass不相关
  • register(xxx)EPCallback,核心API,与Legacy比较像
  • registerPipelineParsingCallback,给opt用的,opt --load-pass-plugin=libPass.so -passes=passName1,passName2 可以触发到

阅读 register(xxx)EPCallback 系列的声明,EP表示ExtensionPoint,与 llvm/include/llvm/Transforms/IPO/PassManagerBuilder.h 的 ExtensionPointTy 对应,ExtensionPointTy这个枚举类,那味道可太熟悉了,LegacyPassManager的注册时刻。

 

随便选取一个,观察其实现,传入一个回调函数,将该函数添加到列表里。每个API传入的回调函数返回值都是 void,第一个参数是一个 PassManager,第二个参数是 OptimizationLevel,很容易理解,pass开发者可以根据不同的优化等级走不同的逻辑,并使用 PassManager 完成 pass 的注册。

1
2
3
4
void registerPeepholeEPCallback(
    const std::function<void(FunctionPassManager &, OptimizationLevel)> &C) {
  PeepholeEPCallbacks.push_back(C);
}

对我们有用的部分,总结如下表,这里有一个大变更,最早的回调 registerPipelineStartEPCallback 只能注册 ModulePass

回调函数 回调时提供的对象 对应 ExtensionPointTy
registerPeepholeEPCallback FunctionPassManager 对应EP_Peephole
registerLateLoopOptimizationsEPCallback LoopPassManager 对应EP_LoopOptimizerEnd
registerLoopOptimizerEndEPCallback LoopPassManager 对应EP_LateLoopOptimizations
registerScalarOptimizerLateEPCallback FunctionPassManager 对应 EP_ScalarOptimizerLate
registerCGSCCOptimizerLateEPCallback CGSCCPassManager 对应EP_CGSCCOptimizerLate
registerVectorizerStartEPCallback FunctionPassManager 对应EP_VectorizerStart
registerPipelineStartEPCallback ModulePassManager 对应EP_EarlyAsPossible
registerPipelineEarlySimplificationEPCallback ModulePassManager 对应 EP_ModuleOptimizerEarly
registerOptimizerLastEPCallback ModulePassManager 对应EP_OptimizerLast
 

ModulePassManager 是llvm传给plugin的,只提供一个API,addPass,传递一个对象,但这里很复杂。

简单样例v1

经过一些测试,总结了一个最小的样例,先看结构,再分析原理。

1
2
3
4
➜  /tmp clang -fpass-plugin=libSkeletonPass.so test.c
llvmGetPassPluginInfo
isRequired invoked
Module name is test.c!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PB.registerPipelineStartEPCallback(myCallback);
 
void myCallback(llvm::ModulePassManager &PM, llvm::PassBuilder::OptimizationLevel Level) {
    PM.addPass(TestPass());
}
 
class TestPass {
public:
    static StringRef name() {
        errs() << "name invoked\n";
        return "TestPass";
    }
 
    static bool isRequired() {
        errs() << "isRequired invoked\n";
        return true;
    }
 
    PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
        errs() << "Module name is " << M.getName() << "!\n";
        return PreservedAnalyses::all();
    }
};

现在不需要继承任何类了,编译时llvm会通过模板来检查 TestPass 的方法,共有 3 个。

  • static StringRef name 必须,表示 pass 的名字。可以使用 PassInfoMixin 模板快速实现该API。
  • static bool isRequired 可选,如果返回true,则该pass不会被跳过。
  • PreservedAnalyses run(Module &M, ModuleAnalysisManager &) 必选,ModulePass 需要这里是 llvm::ModuleModuleAnalysisManager,FunctionPass 需要这里是 FunctionFunctionAnalysisManager

有了整体认知后,介绍相关的模板,llvm/include/llvm/IR/PassManager.h

llvm::ModulePassManager &PM

这个对象由llvm传递给plugin,查看发现它是一个模板 PassManager

1
2
3
4
extern template class PassManager<Module>;
 
/// Convenience typedef for a pass manager over modules.
using ModulePassManager = PassManager<Module>;

PassManager 也是一个模板,由 PassInfoMixin 提供一个 name

 

IRUnitT 就是传入的 llvm::Module,然后推断出 AnalysisManagerT 的类型为 AnalysisManager<IRUnitT>,也就是AnalysisManager<Module>,恰好是 ModuleAnalysisManager

 

AnalysisManager也是一个模板类,就不展开了。

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
template <typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class PassManager : public PassInfoMixin<
                        PassManager<IRUnitT, AnalysisManagerT, ExtraArgTs...>> {
  //xxxxxxxx
}
 
/////////////////////
 
template <typename DerivedT> struct PassInfoMixin {
  /// Gets the name of the pass we are mixed into.
  static StringRef name() {
    static_assert(std::is_base_of<PassInfoMixin, DerivedT>::value,
                  "Must pass the derived type as the template argument!");
    StringRef Name = getTypeName<DerivedT>();
    if (Name.startswith("llvm::"))
      Name = Name.drop_front(strlen("llvm::"));
    return Name;
  }
};
 
/////////////////////
 
using ModuleAnalysisManager = AnalysisManager<Module>;

继续看 addPass的实现,有两个重载,第一个是传入 pass 时,将其加入到列表中;第二个是传入另一个 PassManager 时,将其内部的 passes 全部加入到列表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename IRUnitT,
          typename AnalysisManagerT = AnalysisManager<IRUnitT>,
          typename... ExtraArgTs>
class PassManager : public PassInfoMixin<
                        PassManager<IRUnitT, AnalysisManagerT, ExtraArgTs...>> {
 
  template <typename PassT>
  std::enable_if_t<!std::is_same<PassT, PassManager>::value>
  addPass(PassT &&Pass) {
    using PassModelT =
        detail::PassModel<IRUnitT, PassT, PreservedAnalyses, AnalysisManagerT,
                          ExtraArgTs...>;
 
    Passes.emplace_back(new PassModelT(std::forward<PassT>(Pass)));
  }
 
  template <typename PassT>
  std::enable_if_t<std::is_same<PassT, PassManager>::value>
  addPass(PassT &&Pass) {
    for (auto &P : Pass.Passes)
      Passes.emplace_back(std::move(P));
  }
}

重点关注第一个,detail::PassModel 位于 llvm/include/llvm/IR/PassManagerInternal.h,是一个模板类。IRUnitT 是传入的 llvm::ModulePassT是传入的用户自定义的结构体,PreservedAnalysesT是传入的 PreservedAnalysesAnalysisManagerT是传入的ModuleAnalysisManager

 

它的构造方法拿到用户自定义的结构体, run 方法调用传入的pass的run方法,需要满足 IRUnitTAnalysisManagerT 的类型约束,这和上文提到的

ModulePass 需要这里是 llvm::ModuleModuleAnalysisManager,FunctionPass 需要这里是 FunctionFunctionAnalysisManager

 

相互印证。它的 name 方法调用传入的结构体的 static name 方法。它的 passIsRequiredImpl 会根据传入的结构体的 isRequired 是否声明而定,未声明时默认为 false,已声明则调用传入的结构体的 static isRequired

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
template <typename IRUnitT, typename PassT, typename PreservedAnalysesT,
          typename AnalysisManagerT, typename... ExtraArgTs>
struct PassModel : PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...> {
  explicit PassModel(PassT Pass) : Pass(std::move(Pass)) {}
  // We have to explicitly define all the special member functions because MSVC
  // refuses to generate them.
  PassModel(const PassModel &Arg) : Pass(Arg.Pass) {}
  PassModel(PassModel &&Arg) : Pass(std::move(Arg.Pass)) {}
 
  //xxxxxxxxxxx
  PreservedAnalysesT run(IRUnitT &IR, AnalysisManagerT &AM,
                         ExtraArgTs... ExtraArgs) override {
    return Pass.run(IR, AM, ExtraArgs...);
  }
 
  StringRef name() const override { return PassT::name(); }
 
  template <typename T>
  using has_required_t = decltype(std::declval<T &>().isRequired());
 
  template <typename T>
  static std::enable_if_t<is_detected<has_required_t, T>::value, bool>
  passIsRequiredImpl() {
    return T::isRequired();
  }
  template <typename T>
  static std::enable_if_t<!is_detected<has_required_t, T>::value, bool>
  passIsRequiredImpl() {
    return false;
  }
 
  bool isRequired() const override { return passIsRequiredImpl<PassT>(); }
 
  PassT Pass;
};

PassModel 继承 PassConceptPassConcept 也是模板,整体没什么东西,全部都是关键方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename IRUnitT, typename AnalysisManagerT, typename... ExtraArgTs>
struct PassConcept {
  // Boiler plate necessary for the container of derived classes.
  virtual ~PassConcept() = default;
 
  /// The polymorphic API which runs the pass over a given IR entity.
  ///
  /// Note that actual pass object can omit the analysis manager argument if
  /// desired. Also that the analysis manager may be null if there is no
  /// analysis manager in the pass pipeline.
  virtual PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,
                                ExtraArgTs... ExtraArgs) = 0;
 
  /// Polymorphic method to access the name of a pass.
  virtual StringRef name() const = 0;
 
  /// Polymorphic method to to let a pass optionally exempted from skipping by
  /// PassInstrumentation.
  /// To opt-in, pass should implement `static bool isRequired()`. It's no-op
  /// to have `isRequired` always return false since that is the default.
  virtual bool isRequired() const = 0;
};

经过多层的模板套娃,我们已经完完全全理清楚了 ModulePassManager.addPass 的原理,开发起来更有底气!

简单样例v2

通过 PassInfoMixin 可以省掉 static StringRef name()

1
2
3
4
5
6
7
8
9
10
11
12
struct TestPass : PassInfoMixin<TestPass> {
public:
    static bool isRequired() {
        errs() << "isRequired invoked\n";
        return true;
    }
 
    PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
        errs() << "Module name is " << M.getName() << "!\n";
        return PreservedAnalyses::all();
    }
};

此时还差一个细节,返回值 PreservedAnalyses 是什么?阅读文档后认为是这样,不确定对不对:

  • 如果修改了IR,返回 PreservedAnalyses::none,表示之前的优化分析全部不需要保留
  • 如果修改了IR,返回一个集合,表示该集合中的pass不需要再次被执行了
  • 如果没有修改IR,则返回 PreservedAnalyses::all,表示分析集全部需要保留

这时,相当于 ModulePass 的注册方式已搞定,接下来是 FunctionPass

处理 FunctionPass

直接贴方案吧,不可挑剔的方案,使用 createModuleToFunctionPassAdaptor,来自 https://groups.google.com/g/llvm-dev/c/e_4WobR9WP0 。只要保证下文的 HelloWorldPreservedAnalyses run(Function &F, FunctionAnalysisManager &) 方法就行。

1
2
3
4
5
6
PB.registerPipelineStartEPCallback(
    [](ModulePassManager &MPM, OptimizationLevel Level) {
        FunctionPassManager FPM;               
        FPM.addPass(HelloWorld());
        MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
});

适配 opt

registerPipelineParsingCallback 有很多个重载,其有 FunctionPassManagerModulePassManager,区别不大。回调函数的第一个参数是传入的 Pass 名字,只要有 Pass 需要被执行,就会传递进来,因此需要判断是不是在调用自己,有兴趣的自己试试吧。

1
2
3
4
5
void registerPipelineParsingCallback(
    const std::function<bool(StringRef Name, ModulePassManager &,
                            ArrayRef<PipelineElement>)> &C) {
    ModulePipelineParsingCallbacks.push_back(C);
}

迁移方案

搞不动了,Skeleton的迁移放在github了, https://github.com/LeadroyaL/llvm-pass-tutorial ,写得很烂。

 

其他项目懒得搞了,等什么时候不忙了或者有好心人帮忙适配一下。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2022-6-3 09:43 被LeadroyaL编辑 ,原因:
收藏
点赞3
打赏
分享
打赏 + 80.00雪花
打赏次数 1 雪花 + 80.00
 
赞赏  Editor   +80.00 2022/06/13 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回