NoteZ icon indicating copy to clipboard operation
NoteZ copied to clipboard

LLVM Pass Dev Trick

Open jmpews opened this issue 6 years ago • 0 comments

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) 期间进行注入.

// 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 处理期间进行添加.

  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_ 直接调用, 所以这里通过函数指针直接绕过编译校验进行调用.

// 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 再更新

jmpews avatar Feb 20 '19 13:02 jmpews