OLLVM学习之四 —— 使用CMake构建LLVM Pass
本帖最后由 lichao 于 2024-9-26 10:40 编辑## 前言
  LLVM Pass是LLVM提供的用于优化/分析/处理IR的组件, 第三方可以自由开发Pass从而干涉编译过程, 实现代码优化/静态分析/代码混淆. 上一节学习了CMake的基本用法, 现在来用CMake实现最简单的LLVM Pass. 笔者的环境仍是MacOS. 需要注意的是LLVM Pass从LLVM版本支持可分为Legacy Pass和New Pass, 前者是历史遗留, 具体兼容性如下表.
|LLVM|默认 |可选 |
|------|-------------------|--------------------------------------------------|
|5-12|LegacyPassManager|-fexperimental-new-pass-manager 启用New, 该功能有限 |
|13-14 |NewPassManager |-flegacy-pass-manager 启用 Legacy |
|15-?|NewPassManager | |
   Pass从类型可分为FunctionPass, ModulePass, LoopPass, RegionPass, MachineFunctionPass, AnalysePass, CallGraphSCCPass等, FunctionPass用于做函数层面的操作, ModulePass用于做模块层面的操作(模块包括函数, 全局变量等, 所以也可以包含FunctionPass的功能), AnalysePass主要做性能测试/压力测试/调优/分析/日志等.
### 什么情况下使用LLVM Pass
  如第一篇所述, LLVM衍生出众多编译器前端, 如clang, swiftc, rustc等. 目前第三方代码嵌入LLVM有如下三种方式:
* 动态Pass方式, 前端运行时动态加载Pass, 开发成本最低
* 静态Pass方式, 编译时静态链接Pass
* 修改LLVM源码强行嵌入, 是大部分Ollvm采用的方式; 如果编译器前端不支持Pass则是唯一选择
## 编码
  只需要`demo.cpp`和`CMakeLists.txt`两个文件. 因为LLVM的版本较多, 网上开源的LLVM Pass项目只支持部分版本, 笔者根据刚学习的CMake将其改造为兼容LLVM8-18.
项目地址:<https://github.com/lich4/llvm-pass-hikari>
demo.cpp
```cpp
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#if LLVM_VERSION_MAJOR <= 15
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif
#include "llvm/IRReader/IRReader.h"
#include "llvm/Transforms/Utils/Cloning.h"
using namespace llvm;
#include <iostream>
#define PASSNAME "MyPassDemo"
#if LLVM_VERSION_MAJOR <= 13
#define getPtElemType getPointerElementType
#else
#define getPtElemType getNonOpaquePointerElementType
#endif
// ---------------- Legacy Pass ---------------- //
class MyPassDemoLegacy : public FunctionPass {
public:
static char ID;
MyPassDemoLegacy() : FunctionPass(ID) {}
virtual bool runOnFunction(Function& F) override {
errs() << "MyPassDemoLegacy\n";
return false;
}
};
char MyPassDemoLegacy::ID = 0;
#if LLVM_VERSION_MAJOR <= 15
static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &, legacy::PassManagerBase &PM) {
PM.add(new MyPassDemoLegacy());
}
);
#else
static RegisterPass<MyPassDemoLegacy> RegisterMyPass(PASSNAME, PASSNAME, false, false);
#endif
// ---------------- Legacy Pass ---------------- //
// ---------------- New Pass ---------------- //
#if LLVM_VERSION_MAJOR <= 13
#define OptimizationLevel PassBuilder::OptimizationLevel
#endif
class MyPassDemo : public PassInfoMixin<MyPassDemo> {
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
errs() << "MyPassDemo\n";
return PreservedAnalyses::all();
};
static bool isRequired() { return true; }
};
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {
.APIVersion = LLVM_PLUGIN_API_VERSION,
.PluginName = PASSNAME,
.PluginVersion = "1.0",
.RegisterPassBuilderCallbacks = [](PassBuilder &PB) {
PB.registerPipelineStartEPCallback(
[](ModulePassManager &MPM
#if LLVM_VERSION_MAJOR >= 12
, OptimizationLevel Level
#endif
) {
MPM.addPass(MyPassDemo());
});
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager& MPM, ArrayRef<PassBuilder::PipelineElement>) {
MPM.addPass(MyPassDemo());
return true;
});
}
};
}
// ---------------- New Pass ---------------- //
__attribute__((constructor)) void onInit() {
printf("MyPassDemo onInit\n");
}
```
CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.6)
project(MyPassDemo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE "Debug")
find_package(LLVM REQUIRED CONFIG) # LLVMConfig.cmake初始化环境
list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}")# 兼容LLVM<=13
include(AddLLVM) # 导入add_llvm_pass_plugin函数
include(HandleLLVMOptions)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
if(NOT COMMAND add_llvm_pass_plugin) # 兼容LLVM<=9
message(WARNING "add_llvm_pass_plugin not exist")
function(add_llvm_pass_plugin name)
cmake_parse_arguments(ARG "NO_MODULE" "SUBPROJECT" "" ${ARGN})
set(link_into_tools_default OFF)
add_llvm_library(${name} MODULE ${ARG_UNPARSED_ARGUMENTS})
message(STATUS "Registering ${name} as a pass plugin (static build: ${LLVM_${name_upper}_LINK_INTO_TOOLS})")
endfunction(add_llvm_pass_plugin)
endif()
add_llvm_pass_plugin(MyPassDemo${LLVM_VERSION_MAJOR}
demo.cpp
)
```
## 测试
编译:
```bash
export LLVM_DIR=/path/to/llvm12/build/lib/cmake/llvm
cmake -B build --fresh
cmake --build build
```
```bash
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -Xclang -load -Xclang build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM<=12 Legacy Pass (等价于上面)
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fplugin=build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM=9/10/11 New Pass (O3生效)
llvm11/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fexperimental-new-pass-manager -fpass-plugin=build/MyPassDemo11.dylib /tmp/1.cpp -O3
# for LLVM=12 New Pass
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fexperimental-new-pass-manager -fpass-plugin=build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM=13/14 Legacy Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -flegacy-pass-manager -fplugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM=13/14 New Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM>=15 New Pass
llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib /tmp/1.cpp
```
opt支持ll需要llvm>=15
```bash
llvm15/build/bin/opt --O3 -S -o /tmp/test_new.ll /tmp/test.ll
llvm15/build/bin/opt -load-pass-plugin build/MyPassDemo15.dylib -passes all -S -o /tmp/test_new.ll /tmp/test.ll
```
经过测试可以发现, Pass的LLVM版本需要和LLVM大版本一致, 否则也会产生错误, 但总体来说Pass解决了LLVM编译代码量过大的问题, 所以仍然值得采用, 测试结果:
`hello MyPassDemo`
## AppleClang的Pass
  AppleClang, 即XCode自带的Clang, 苹果因为安全性考虑阉割掉了LLVM Pass, 因此常规方法并不能加载起来.
当然如果如果逆向技术过关, 也很容易将Pass改为兼容AppleClang的.
## ExtensionPointTy
```cpp
// EP_Peephole PeepholeEPCallbacks
void registerPeepholeEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_LoopOptimizerEnd LateLoopOptimizationsEPCallbacks
void registerLateLoopOptimizationsEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_LateLoopOptimizations LoopOptimizerEndEPCallbacks
void registerLoopOptimizerEndEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_ScalarOptimizerLate ScalarOptimizerLateEPCallbacks
void registerScalarOptimizerLateEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_CGSCCOptimizerLate CGSCCOptimizerLateEPCallbacks
void registerCGSCCOptimizerLateEPCallback(const std::function<void(CGSCCPassManager&, OptimizationLevel)>&);
// EP_VectorizerStart VectorizerStartEPCallbacks
void registerVectorizerStartEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_EarlyAsPossible PipelineStartEPCallbacks
void registerPipelineStartEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_ModuleOptimizerEarly PipelineEarlySimplificationEPCallbacks
void registerPipelineEarlySimplificationEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerOptimizerEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_OptimizerLast OptimizerLastEPCallbacks
void registerOptimizerLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
```
使用helloworld测试EP顺序:
* Debug: PipelineStart -> PipelineEarlySimplification -> OptimizerLast
* Release: PipelineStart -> PipelineEarlySimplification -> Peephole -> Peephole -> Peephole -> ScalarOptimizerLate -> Peephole -> VectorizerStart -> OptimizerLast
页:
[1]