lichao 发表于 2024-9-12 15:36:38

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

&emsp;&emsp;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]
查看完整版本: OLLVM学习之四 —— 使用CMake构建LLVM Pass