jmpews
## LLVM Pass Dev Trick ## Prologue 本文会介绍一些在开发 LLVM Pass 的一些骚操作. @UESTC-LXY @Naville @lauos #### 1. 快速编译 LLVM Pass 利用 **LLVM Pre-Built Binaries:** 可以避免编译 LLVM 的过程. ``` set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD 11) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fPIC -stdlib=libc++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -std=c++11") #SET(PREFIX ${CMAKE_INSTALL_PREFIX}) #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # ------------- LLVM REQUIRED START ------------- find_package(LLVM REQUIRED CONFIG) list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") include(HandleLLVMOptions) include(AddLLVM) include(LLVMConfig) include_directories(${LLVM_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) find_package(Clang REQUIRED CONFIG) include_directories(${CLANG_INCLUDE_DIRS}) add_definitions(${Clang_DEFINITIONS}) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LLVM_COMPILE_FLAGS} -Wall -fno-rtti -std=c++11 -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS") message(STATUS "LLVM_DEFINITIONS: ${LLVM_DEFINITIONS}") message(STATUS "LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}") message(STATUS "CLANG_DEFINITIONS: ${Clang_DEFINITIONS}") message(STATUS "CLANG_INCLUDE_DIRS: ${CLANG_INCLUDE_DIRS}") # ------------- LLVM REQUIRED END ------------- ``` ``` if(1) set (CMAKE_SHARED_LINKER_FLAGS "-undefined dynamic_lookup") else() set(LLVM_LINK_COMPONENTS ${LLVM_TARGETS_TO_BUILD} ) llvm_map_components_to_libnames(_llvm_libs CodeGen Core Support ${LLVM_LINK_COMPONENTS} ) llvm_expand_dependencies(llvm_libs ${_llvm_libs}) MESSAGE(STATUS ">>> ${llvm_libs}") MESSAGE(STATUS ">>> ${LLVM_BINARY_DIR}") target_link_libraries(CxxTrampoline ${llvm_libs}) endif() ``` 最后 cmake 时指定下 path. ``` export LLVM_PREBUILD_PATH=/Users/jmpews/project/llvm-7.0/clang+llvm-7.0.0-x86_64-apple-darwin cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_PREFIX_PATH="${LLVM_PREBUILD_PATH}/lib/cmake/llvm;${LLVM_PREBUILD_PATH}/lib/cmake/clang" -DBUILD_SHARED_LIBS=ON -DCMAKE_VERBOSE_MAKEFILE=ON ``` #### 2. 无感知改造 Xcode | NDK 虽然 NDK 使用的是开源 clang, 但 Xcode 的 clang 则是不开源的(不准确). 这就需要通过一定来加载我们的 Pass. 总体来说两个关键点. 1. Inject SHARED pass library 2. Hijack the specific function 这里的 Hijack 时机需要特别关注, 同时也要根据不同类型的 Pass, 去 Hijack 不同的函数, 这里这对不同 Pass 介绍下. #### 2.1. FunctionPass | ModulePass 对于 FunctionPass 和 ModulePass 可以在 `void PassManagerBuilder::populateModulePassManager(legacy::PassManagerBase &MPM)` 期间进行注入. ```c++ // Hijack __ZN4llvm18PassManagerBuilder25populateModulePassManagerERNS_6legacy15PassManagerBaseE // llvm::PassManagerBuilder::populateModulePassManager(llvm::legacy::PassManagerBase&) void (*PassManagerBuilder_populateModulePassManager)(PassManagerBuilder *this_, legacy::PassManagerBase *PM); void fake_PassManagerBuilder_populateModulePassManager(PassManagerBuilder *this_, legacy::PassManagerBase *PM) { PassManagerBuilder_populateModulePassManager(this_, PM); dbgs() << "Injiect MachineIR Pass\n"; PM.add(createTrampolineIR()); } void Hijack_PassManagerBuilder_populateModulePassManager() { void *_PassManagerBuilder_populateModulePassManager = ZzFindSymbol("__ZN4llvm18PassManagerBuilder25populateModulePassManagerERNS_6legacy15PassManagerBaseE"); ZzReplace(_PassManagerBuilder_populateModulePassManager, (void *)fake_PassManagerBuilder_populateModulePassManager, (void **)&PassManagerBuilder_populateModulePassManager); } ``` 当然还有另外一种思路通过 `PassManagerBuilder::addExtension` 在不同的 Pass 处理期间进行添加. ```c++ enum ExtensionPointTy { /// EP_EarlyAsPossible - This extension point allows adding passes before /// any other transformations, allowing them to see the code as it is coming /// out of the frontend. EP_EarlyAsPossible, /// EP_ModuleOptimizerEarly - This extension point allows adding passes /// just before the main module-level optimization passes. EP_ModuleOptimizerEarly, /// EP_LoopOptimizerEnd - This extension point allows adding loop passes to /// the end of the loop optimizer. EP_LoopOptimizerEnd, /// EP_ScalarOptimizerLate - This extension point allows adding optimization /// passes after most of the main optimizations, but before the last /// cleanup-ish optimizations. EP_ScalarOptimizerLate, /// EP_OptimizerLast -- This extension point allows adding passes that /// run after everything else. EP_OptimizerLast, /// EP_VectorizerStart - This extension point allows adding optimization /// passes before the vectorizer and other highly target specific /// optimization passes are executed. EP_VectorizerStart, /// EP_EnabledOnOptLevel0 - This extension point allows adding passes that /// should not be disabled by O0 optimization level. The passes will be /// inserted after the inlining pass. EP_EnabledOnOptLevel0, /// EP_Peephole - This extension point allows adding passes that perform /// peephole optimizations similar to the instruction combiner. These passes /// will be inserted after each instance of the instruction combiner pass. EP_Peephole, /// EP_LateLoopOptimizations - This extension point allows adding late loop /// canonicalization and simplification passes. This is the last point in /// the loop optimization pipeline before loop deletion. Each pass added /// here must be an instance of LoopPass. /// This is the place to add passes that can remove loops, such as target- /// specific loop idiom recognition. EP_LateLoopOptimizations, /// EP_CGSCCOptimizerLate - This extension point allows adding CallGraphSCC /// passes at the end of the main CallGraphSCC passes and before any /// function simplification passes run by CGPassManager. EP_CGSCCOptimizerLate, }; ``` #### 2.2. MachineFunctionPass 对于 MachineFunctionPass, 需要根据特定的需求选择要 hook 的函数, 在 ` TargetPassConfig::addMachinePasses()` 选择需要 hook 的时机. 例如这里希望能在 LLVM 将虚拟寄存器转化为物理寄存器前, 做一些事情, 则 hook `AArch64PassConfig::addPreRegAlloc()` 方法. 这里有一个小 Trick, `TargetPassConfig::addPass` 方法是一个 `Protected` 方法, 无法通过 `this_` 直接调用, 所以这里**通过函数指针直接绕过编译校验进行调用**. ```c++ // Hijack __ZN12_GLOBAL__N_117AArch64PassConfig14addPreRegAllocEv // AArch64PassConfig::addPreRegAlloc() void (*AArch64PassConfig_addPreRegAlloc)(TargetPassConfig *this_); void (*TargetPassConfig_addPass)(TargetPassConfig *this_, Pass *P, bool verifyAfter, bool printAfter); void fake_AArch64PassConfig_addPreRegAlloc(TargetPassConfig *this_) { TargetPassConfig_addPass(this_, createTrampolineMIR(), true, true); AArch64PassConfig_addPreRegAlloc(this_); } void Hijack_AArch64PassConfig_addPreRegAlloc() { TargetPassConfig_addPass = (void (*)(TargetPassConfig *this_, Pass *P, bool verifyAfter, bool printAfter))ZzFindSymbol("__ZN4llvm16TargetPassConfig7addPassEPNS_4PassEbb"); void *_AArch64PassConfig_addPreRegAlloc = ZzFindSymbol("__ZN12_GLOBAL__N_117AArch64PassConfig14addPreRegAllocEv"); ZzReplace(_AArch64PassConfig_addPreRegAlloc, (void *)fake_AArch64PassConfig_addPreRegAlloc, (void **)&AArch64PassConfig_addPreRegAlloc); } ``` #### 3. LLVM Pass SHARED Library 默认通过 `add_llvm_loadable_module` 编译插件的结果是 `bundle` 可以认为是静态库, 但是如果直接通过 `add_library( XXX SHARED XXX)` 和 `target_link_libraries` 编译动态库会导致, 与 clang / llc 符号冲突, 部分 option 初始化初始化两次导致失败. 这里通过 `set (CMAKE_SHARED_LINKER_FLAGS "-undefined dynamic_lookup")` 将于 LLVM 有关的符号设置成动态查找. ## Epilogue emmmmm 再更新