LLVM NEW PASS ALL IN ONE
学习笔记
LLVM 通过一系列的passes来优化IR文件,不同的Pass作用在IR文件不同粒度上,如Function,Module。而Pass中的操作分为两种, transformation 与 analysis ,分别用于转化IR文件与收集IR文件中的信息,而一系列的pass则合集则被称为pass pipeline,
LLVM COMPILE AND INSTALL
这里在win11的wsl2下编译,环境为Ubuntu 22.04,参考 https://llvm.org/docs/CMake.html
浅克隆LLVM,不然我克隆不下来
1 2
| git clone --depth 1 -b release/16.x https://github.com/llvm/llvm-project.git mkdir build
|
编译命令
1 2
| cmake -G Ninja -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64" -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_RTTI=ON -DLLVM_OBFUSCATION_LINK_INTO_TOOLS= ON -DCMAKE_INSTALL_PREFIX=./build/ ../llvm-project/llvm
|
编译完成后使用以下命令完成安装
这里最好还是安装一下,不然没有LLVMConfig.cmake此文件,无法在CMakeLists.txt中使用find_package()
安装成功,可以看到安装位置为前面预设的位置

接下来对编译命令中出现的各项进行解释
-G Ninja:指定生成构建文件的系统为 Ninja。
-DLLVM_ENABLE_PROJECTS="clang;lld":设置了要构建的 LLVM 子项目。
-DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64 ":这指定了构建 LLVM 时要支持的目标架构。
-DCMAKE_BUILD_TYPE=Release:设置构建类型为 Release,意味着生成的构建将会进行优化,需要调试的话选择Debug,但是编译会慢很多,也需要更大的内存
-DLLVM_INCLUDE_TESTS=OFF:此选项关闭了测试的构建。这样做可以减少构建时间和输出的大小。
-DLLVM_ENABLE_RTTI=ON:这开启了运行时类型信息(Run-Time Type Information,RTTI)。RTTI 允许在运行时识别对象的类型。
../llvm-project/llvm:这指定了源代码目录的路径。CMake 会在这个目录下查找 CMakeLists.txt 文件来确定如何构建项目。
-DCMAKE_INSTALL_PREFIX指定了安装的位置
重点讲下-DLLVM_OBFUSCATION_LINK_INTO_TOOLS=ON,这个选项与New Pass 相适配,设置 LLVM_${NAME}_LINK_INTO_TOOLS,可以将项目转变为静态链接的扩展。这意味着 pass 插件将直接被链接到 LLVM 工具中,而不是作为动态库在运行时加载。(这东西翻了我好久 google上的实现方案都又老又臭 没想到官方已经适配了 不认真读文档的后果www)
The LLVM pass manager
为了提升框架的灵活程度,LLVM将源文件->可执行文件的步骤打碎,分为不同的Passes

而控制这些Passes正确运行的,就是Pass Manager
Pass 往往按照其工作范围分为以下几类:
在 LLVM 中,一个 pass 通常根据其作用的范围来分类:
Module Pass
模块 pass 以整个模块为输入。它在给定模块上执行其工作,可以用于模块内的过程间操作。由于它处理的是整个模块,模块 pass 可以访问和修改模块中的所有函数和全局变量。这使得它特别适合那些需要跨多个函数进行分析或优化的任务。例如,全局常量传播和内联(inlining)优化可以通过模块 pass 来实现。
Call Graph Pass
调用图 pass 操作的是调用图的强连通分量(SCCs)。调用图是一个图结构,其中节点表示函数,边表示函数之间的调用关系。SCC 是图中所有节点互相可达的最大子图。调用图 pass 从底向上遍历这些组件,即从被调用函数开始,逐步向上处理调用它们的函数。这种 pass 特别适合于分析和优化函数间的调用关系,例如函数内联优化或递归调用的优化。
Function Pass
函数 pass 以单个函数为输入,只对该函数进行操作。它在函数的级别上执行分析或转换,不涉及其他函数。这使得函数 pass 简单且高效,因为它只需要处理一个函数的代码,而无需考虑模块中其他部分的状态。典型的函数 pass 例子包括死代码消除(DCE)、常量传播(constant propagation)和循环优化(loop optimization)。
Loop Pass
循环 pass 作用于函数内的循环。它只处理特定循环的内容,进行针对性的优化和转换。例如,循环展开(loop unrolling)、循环分割(loop splitting)和循环向量化(loop vectorization)等优化可以通过循环 pass 实现。循环 pass 需要详细分析和优化循环结构,并且通常只关注单个循环的特性,而不涉及函数的其他部分。
在 LLVM 中,除了操作 IR 代码外,一个 pass 还可能需要获得、更新或丢弃部分或者全部的分析结果。有许多不同的分析,如果一个 pass 需要这些分析结果,它可以从分析管理器请求这些信息。如果信息已经计算好,则会返回缓存的结果。否则,会进行相应的计算。如果一个 pass 改变了 IR 代码,那么它需要声明哪些分析结果被保留,以便在必要时丢弃部分的缓存的分析信息。在底层,pass 管理器确保以下几点:
分析结果在 pass 之间共享
分析结果在不同的 pass 之间共享。这需要跟踪每个 pass 需要哪些分析以及每个分析的状态。其目标是避免不必要的分析预计算,并在可能的情况下尽快释放被分析结果占用的内存。这种机制可以提高编译器的效率,因为它减少了重复计算分析结果的开销。
pass 管理器的执行机制
pass 以流水线方式执行。例如,如果有几个函数 pass 应按顺序执行,pass 管理器会在第一个函数上运行每个函数 pass,然后在第二个函数上运行所有函数 pass,依此类推。这种方法的基本思想是通过在有限的数据集(一个 IR 函数)上执行转换,然后移动到下一个有限的数据集,来改善缓存行为。
Implementing a new pass
这里以一个插桩(instrumentation)Pass为例
代码实现如下:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #include "llvm/ADT/Statistic.h" #include "llvm/IR/Function.h" #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/Debug.h"
using namespace llvm;
#define DEBUG_TYPE "ppprofiler"
ALWAYS_ENABLED_STATISTIC( NumOfFunc, "Number of instrumented functions.");
namespace {
class PPProfilerIRPass : public llvm::PassInfoMixin<PPProfilerIRPass> { public: llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM);
private: void instrument(llvm::Function &F, llvm::Function *EnterFn, llvm::Function *ExitFn); }; }
void PPProfilerIRPass::instrument(llvm::Function &F, Function *EnterFn, Function *ExitFn) { ++NumOfFunc;
IRBuilder<> Builder(&*F.getEntryBlock().begin());
GlobalVariable *FnName = Builder.CreateGlobalString(F.getName());
Builder.CreateCall(EnterFn->getFunctionType(), EnterFn, {FnName});
for (BasicBlock &BB : F) { for (Instruction &Inst : BB) { if (Inst.getOpcode() == Instruction::Ret) { Builder.SetInsertPoint(&Inst); Builder.CreateCall(ExitFn->getFunctionType(), ExitFn, {FnName}); } } } }
PreservedAnalyses PPProfilerIRPass::run(Module &M, ModuleAnalysisManager &AM) { if (M.getFunction("__ppp_enter") || M.getFunction("__ppp_exit")) { return PreservedAnalyses::all(); }
Type *VoidTy = Type::getVoidTy(M.getContext()); PointerType *PtrTy = PointerType::getUnqual(M.getContext()); FunctionType *EnterExitFty = FunctionType::get(VoidTy, {PtrTy}, false); Function *EnterFn = Function::Create( EnterExitFty, GlobalValue::ExternalLinkage, "__ppp_enter", M); Function *ExitFn = Function::Create( EnterExitFty, GlobalValue::ExternalLinkage, "__ppp_exit", M);
for (auto &F : M.functions()) { if (!F.isDeclaration() && F.hasName()) instrument(F, EnterFn, ExitFn); } return PreservedAnalyses::none(); }
void RegisterCB(PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "ppprofiler") { MPM.addPass(PPProfilerIRPass()); return true; } return false; }); PB.registerPipelineStartEPCallback( [](ModulePassManager &PM, OptimizationLevel Level) { PM.addPass(PPProfilerIRPass()); }); }
llvm::PassPluginLibraryInfo getPPProfilerPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "PPProfiler", "v0.1", RegisterCB}; }
#ifndef LLVM_PPPROFILER_LINK_INTO_TOOLS extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getPPProfilerPluginInfo(); } #endif
|
有两种方式可以将我们的Pass通过Passbuilder注册,静态注册与动态注册,静态链接需要提供getPluginInfo()函数,而动态链接需要提供llvmGetPassPluginInfo() ,详情参考代码
Adding the pass to the LLVM source tree
Utilizing the plugin mechanisms inside the LLVM source tree
1. 创建新的插件目录和源文件
首先,你需要在 LLVM 源代码树中创建一个新的目录,并将插件源文件放入其中。
- 在
llvm-project/llvm/lib/Transforms 目录下创建一个名为 PPProfiler 的新目录。
- 将
PPProfiler.cpp 文件复制到这个新目录中。
1 2
| mkdir llvm-project/llvm/lib/Transforms/PPProfiler cp path/to/PPProfiler.cpp llvm-project/llvm/lib/Transforms/PPProfiler/
|
2. 创建 CMake 配置文件
在 PPProfiler 目录中创建一个名为 CMakeLists.txt 的文件,内容如下:
1
| add_llvm_pass_plugin(PPProfiler PPProfiler.cpp)
|
3. 修改上级目录的 CMakeLists.txt 文件
在 PPProfiler 目录的上一级目录(即 llvm-project/llvm/lib/Transforms)的 CMakeLists.txt 文件中,添加一行以包含新创建的 PPProfiler 目录:
1
| add_subdirectory(PPProfiler)
|
4. 构建并安装 LLVM
进入 LLVM 的构建目录,并运行 ninja install 命令来构建和安装 LLVM,同时包含新的 PPProfiler 插件。
1 2
| cd path/to/llvm/build ninja install
|
在运行 ninja install 期间,CMake 会检测到构建描述文件的变化,并重新运行配置步骤。你会看到类似如下的输出:
1
| -- Registering PPProfiler as a pass plugin (static build: OFF)
|
这表示插件被检测到并已作为共享库构建。安装步骤完成后,你会在 <install directory>/lib 目录中找到共享库 PPProfiler.so。
5. 静态链接插件
如果你希望将插件静态链接到 LLVM 工具中,需要重新运行 CMake 配置,并添加 -DLLVM_PPPROFILER_LINK_INTO_TOOLS=ON 选项:
1 2 3
| cd path/to/llvm/build cmake -DLLVM_PPPROFILER_LINK_INTO_TOOLS=ON .. ninja install
|
在运行 ninja install 期间,CMake 会再次检测到构建描述文件的变化,你会看到类似如下的输出:
1
| -- Registering PPProfiler as a pass plugin (static build: ON)
|
6. 检查静态链接的结果
编译和安装 LLVM 后,以下变化将发生:
- 插件被编译成静态库
libPPProfiler.a,并安装到 <install directory>/lib 目录中。
- LLVM 工具(如
opt)将链接到该静态库。
- 插件注册为扩展,你可以在
<install directory>/include/llvm/Support/Extension.def 文件中看到如下行:
1
| HANDLE_EXTENSION(PPProfiler)
|
Fully integrating the pass into the pass registry
这段文字详细描述了如何将一个新的 LLVM pass 完全集成到 LLVM 系统中,而不仅仅是作为一个插件。以下是详细的解释和逐步操作指南:
1. 创建头文件
首先,你需要在 llvm-project/llvm/include/llvm/Transforms/PPProfiler 目录下创建一个新的头文件 PPProfiler.h,并在其中定义 pass 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #ifndef LLVM_TRANSFORMS_PPPROFILER_PPPROFILER_H #define LLVM_TRANSFORMS_PPPROFILER_PPPROFILER_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class PPProfilerIRPass : public llvm::PassInfoMixin<PPProfilerIRPass> { public: llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM); private: void instrument(llvm::Function &F, llvm::Function *EnterFn, llvm::Function *ExitFn); };
}
#endif
|
解释:
- **
#ifndef / #define**:防止头文件重复包含。
llvm 命名空间:将类定义放在 llvm 命名空间中。
PPProfilerIRPass 类:定义了 run 方法和 instrument 私有方法。
2. 更新源文件
将 PPProfiler.cpp 文件复制到 llvm-project/llvm/lib/Transforms/PPProfiler 目录中,并进行如下更新:
- 删除类定义,因为它现在已经在头文件中定义。
- 添加头文件的
#include 指令。
1 2 3
| #include "llvm/Transforms/PPProfiler/PPProfiler.h"
|
3. 更新 CMake 配置文件
在 llvm-project/llvm/lib/Transforms/PPProfiler 目录中创建一个新的 CMakeLists.txt 文件,内容如下:
1 2 3 4 5 6
| add_llvm_component_library(LLVMPPProfiler PPProfiler.cpp LINK_COMPONENTS Core Support )
|
解释:
- **
add_llvm_component_library**:声明一个新的 LLVM 组件库。
- **
LLVMPPProfiler**:新组件的名称。
- **
PPProfiler.cpp**:包含的源文件。
- **
LINK_COMPONENTS**:依赖的 LLVM 组件。
在 llvm-project/llvm/lib/Transforms 目录中的 CMakeLists.txt 文件中,添加如下行以包含新的源目录:
1
| add_subdirectory(PPProfiler)
|
4. 更新
在 llvm/lib/Passes/PassRegistry.def 文件中,找到模块 pass 定义部分(可以搜索 MODULE_PASS 宏),并添加如下行:
1
| MODULE_PASS("ppprofiler", PPProfilerIRPass())
|
解释:
- **
MODULE_PASS**:宏定义模块级别的 pass。
- **
"ppprofiler"**:pass 的名称。
- **
PPProfilerIRPass()**:pass 类的实例。
5. 更新
在 llvm/lib/Passes/PassBuilder.cpp 文件中,包含新的头文件:
1
| #include "llvm/Transforms/PPProfiler/PPProfiler.h"
|
6. 更新 CMakeLists.txt 文件中的依赖
在 llvm/lib/Passes/CMakeLists.txt 文件中,添加对新组件的依赖:
1 2 3
| LINK_COMPONENTS ... PPProfiler
|
7. 构建和安装 LLVM
进入 LLVM 的构建目录,并运行 ninja install 命令来构建和安装包含新 pass 的 LLVM:
1 2
| cd path/to/llvm/build ninja install
|
8. 验证新 pass
通过前面的步骤,你已经将新的 ppprofiler pass 完全集成到 LLVM 中。这个 pass 现在对所有 LLVM 工具都可用,并且已编译成 libLLVMPPProfiler.a 库,可以作为 PPProfiler 组件在构建系统中使用。
Run the pass plugin in opt
这段文字详细描述了如何使用 LLVM 工具链来加载和运行一个自定义 pass,并如何通过运行时支持来记录函数进入和退出事件的简单实现。以下是详细解释和逐步操作指南:
1. 使用 opt 工具运行 pass
首先,我们需要使用 opt 工具加载并运行自定义 pass:
1
| $ opt --load-pass-plugin=./PPProfile.so --passes="ppprofiler" --stats hello.ll -o hello_inst.bc
|
解释:
- **
--load-pass-plugin=./PPProfile.so**:加载包含自定义 pass 的共享库。
- **
--passes="ppprofiler"**:指定要运行的 pass 名称。
- **
--stats**:启用统计信息收集。
- **
hello.ll**:输入的 LLVM IR 文件。
- **
-o hello_inst.bc**:输出优化后的 bitcode 文件。
2. 查看统计信息
如果统计信息收集启用,你会看到如下输出:
1 2 3 4
| ===--------------------------------------------------------=== ... Statistics Collected ... ===--------------------------------------------------------=== 1 ppprofiler - Number of instrumented functions.
|
如果统计信息未启用,你会看到提示信息:
1
| Statistics are disabled. Build with asserts or with -DLLVM_FORCE_ENABLE_STATS
|
3. 使用 llvm-dis 工具查看生成的 IR
使用 llvm-dis 工具将 bitcode 文件转换为可读的 LLVM IR:
1
| $ llvm-dis hello_inst.bc -o -
|
这将输出类似如下内容:
1 2 3 4 5 6 7 8 9
| @.str = private unnamed_addr constant [6 x i8] c"Hello\00", align 1 @0 = private unnamed_addr constant [5 x i8] c"main\00", align 1
define dso_local i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) { call void @__ppp_enter(ptr @0) %3 = tail call i32 @puts(ptr noundef nonnull dereferenceable(1) @.str) call void @__ppp_exit(ptr @0) ret i32 0 }
|
4. 实现运行时支持
为了将 IR 转换为可执行文件并运行,我们需要提供 __ppp_enter 和 __ppp_exit 函数的实现。创建一个名为 runtime.c 的文件,内容如下:
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 36 37 38 39 40 41 42 43 44 45 46
| #include <stdio.h> #include <stdlib.h> #include <time.h>
static FILE *FileFD = NULL;
static void cleanup() { if (FileFD != NULL) { fclose(FileFD); FileFD = NULL; } }
static void init() { if (FileFD == NULL) { FileFD = fopen("ppprofile.csv", "w"); atexit(&cleanup); } }
typedef unsigned long long Time;
static Time get_time() { struct timespec ts; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); return 1000000000L * ts.tv_sec + ts.tv_nsec; }
void __ppp_enter(const char *FnName) { init(); Time T = get_time(); void *Frame = __builtin_frame_address(1); fprintf(FileFD, "enter|%s|%llu|%p\n", FnName, T, Frame); }
void __ppp_exit(const char *FnName) { init(); Time T = get_time(); void *Frame = __builtin_frame_address(1); fprintf(FileFD, "exit|%s|%llu|%p\n", FnName, T, Frame); }
|
5. 编译和运行
将 instrumented bitcode 文件与运行时代码一起编译,并运行生成的可执行文件:
1 2
| $ clang hello_inst.bc runtime.c $ ./a.out
|
这将生成一个名为 ppprofile.csv 的文件,内容如下:
1 2 3
| $ cat ppprofile.csv enter|main|3300868|0x1 exit|main|3760638|0x1
|
SPECIFYING A PASS PIPELINE
可以使用 --passes 选项指定一个包含多个 pass 的管道。例如,运行 ppprofiler pass 然后运行默认的 O2 优化管道,可以使用以下命令
1
| $ opt --load-pass-plugin=./PPProfile.so --passes="ppprofiler,default<O2>" --stats hello.ll -o hello_inst.bc
|
Plugging the new pass into clang
LLVM Pass Manger有着默认的Pass执行流,被称作 default pass pipeline,我们可以使用opt指定pipeline,但是,当我们想要在非常特定的点加入我们的Pass的时候,比如在loop optimization processes 的结尾。而我们可以通过PassBuilder在这些 extension points进行注册我们的Pass,比如我们可以通过**registerPipelineStartEPCallback()**来将我们的Pass添加到优化管道的开始。每当pass manager填充默认pass管道时,它都会调用扩展点的所有回调。
可以通过以下方式使用
1
| clang -fpass-plugin=./PPProfiler.so hello.c runtime.c
|
集成到更大的项目中
要将这些步骤集成到更大的项目中,可以在 CMake 命令行中指定编译器和链接器标志。例如,假设要编译 tinylang 编译器:
1 2
| -DCMAKE_CXX_FLAGS="-fpass-plugin=<PluginPath>/PPProfiler.so" -DCMAKE_EXE_LINKER_FLAGS="<RuntimePath>/runtime.o"
|
其中 <PluginPath> 和 <RuntimePath> 分别替换为插件和运行时代码的绝对路径。
同时,确保使用 Clang 作为编译器:
1 2
| export CC=clang export CXX=clang++
|
How to make a plugin out of the source code
自己总结的一章,总在源码树里编译不太舒服
这里依旧使用以上代码,即
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #include "llvm/ADT/Statistic.h" #include "llvm/IR/Function.h" #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/Debug.h"
using namespace llvm;
#define DEBUG_TYPE "ppprofiler"
ALWAYS_ENABLED_STATISTIC( NumOfFunc, "Number of instrumented functions.");
namespace {
class PPProfilerIRPass : public llvm::PassInfoMixin<PPProfilerIRPass> { public: llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM);
private: void instrument(llvm::Function &F, llvm::Function *EnterFn, llvm::Function *ExitFn); }; }
void PPProfilerIRPass::instrument(llvm::Function &F, Function *EnterFn, Function *ExitFn) { ++NumOfFunc;
IRBuilder<> Builder(&*F.getEntryBlock().begin());
GlobalVariable *FnName = Builder.CreateGlobalString(F.getName());
Builder.CreateCall(EnterFn->getFunctionType(), EnterFn, {FnName});
for (BasicBlock &BB : F) { for (Instruction &Inst : BB) { if (Inst.getOpcode() == Instruction::Ret) { Builder.SetInsertPoint(&Inst); Builder.CreateCall(ExitFn->getFunctionType(), ExitFn, {FnName}); } } } }
PreservedAnalyses PPProfilerIRPass::run(Module &M, ModuleAnalysisManager &AM) { if (M.getFunction("__ppp_enter") || M.getFunction("__ppp_exit")) { return PreservedAnalyses::all(); }
Type *VoidTy = Type::getVoidTy(M.getContext()); PointerType *PtrTy = PointerType::getUnqual(M.getContext()); FunctionType *EnterExitFty = FunctionType::get(VoidTy, {PtrTy}, false); Function *EnterFn = Function::Create( EnterExitFty, GlobalValue::ExternalLinkage, "__ppp_enter", M); Function *ExitFn = Function::Create( EnterExitFty, GlobalValue::ExternalLinkage, "__ppp_exit", M);
for (auto &F : M.functions()) { if (!F.isDeclaration() && F.hasName()) instrument(F, EnterFn, ExitFn); } return PreservedAnalyses::none(); }
void RegisterCB(PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "ppprofiler") { MPM.addPass(PPProfilerIRPass()); return true; } return false; }); PB.registerPipelineStartEPCallback( [](ModulePassManager &PM, OptimizationLevel Level) { PM.addPass(PPProfilerIRPass()); }); }
llvm::PassPluginLibraryInfo getPPProfilerPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "PPProfiler", "v0.1", RegisterCB}; }
#ifndef LLVM_PPPROFILER_LINK_INTO_TOOLS extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getPPProfilerPluginInfo(); } #endif
|
同一目录下CMakeLists.txt如下
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
| cmake_minimum_required(VERSION 3.20.0) project(ppprofilerpass)
set(LLVM_DIR "/home/zzzccc/llvm-project/build/lib/cmake/llvm") find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
include(AddLLVM) include(HandleLLVMOptions)
include_directories("${LLVM_INCLUDE_DIR}") add_definitions("${LLVM_DEFINITIONS}")
link_directories("${LLVM_LIBRARY_DIR}")
add_llvm_pass_plugin(PPProfiler MODULE PPProfiler.cpp DEPENDS intrinsics_gen )
|
这里CMakeLists搞了好一阵子,主要是研究了下find_package的用法,参考以下两篇文章:https://blog.csdn.net/qq_39466755/article/details/130912344 、https://zhuanlan.zhihu.com/p/50829542
这里首先明确find_package有两种模式,如下
module模式
在这个模式下会查找一个名为find.cmake的文件,首先去CMAKE_MODULE_PATH指定的路径下去查找,然后去cmake安装提供的查找模块中查找(安装cmake时生成的一些cmake文件)。找到之后会检查版本,生成一些需要的信息。
config模式
在这个模式下会查找一个名为-config.cmake(<小写包名>-config.cmake)或者Config.cmake 的文件,如果指定了版本信息也会搜索名为-config-version.cmake 或者 ConfigVersion.cmake的文件。
而LLVM编译安装后会在/install/lib/llvm下存在相应的LLVMConfig.cmake,而其主要搜寻路径如下
1 2 3 4 5
| <package>_DIR CMAKE_PREFIX_PATH CMAKE_FRAMEWORK_PATH CMAKE_APPBUNDLE_PATH PATH
|
故只需要设置LLVM_DIR并将find_package改为搜寻config模式即可
1 2
| set(LLVM_DIR "/home/zzzccc/llvm-project/build/lib/cmake/llvm") find_package(LLVM REQUIRED CONFIG)
|
编译通过

测试是否能够正常使用
测试代码如下:
1 2 3 4 5
| #include <stdio.h> int main(){ printf("hello world!"); return 0; }
|
使用以下命令编译
1
| /home/zzzccc/llvm-project/build/bin/clang -S -emit-llvm test.c -o test.ll
|
结果如下
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
| ; ModuleID = 'test.c' source_filename = "test.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu"
@.str = private unnamed_addr constant [13 x i8] c"hello world!\00", align 1
; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @main() #0 { %1 = alloca i32, align 4 store i32 0, ptr %1, align 4 %2 = call i32 (ptr, ...) @printf(ptr noundef @.str) ret i32 0 }
declare i32 @printf(ptr noundef, ...) #1
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4} !1 = !{i32 8, !"PIC Level", i32 2} !2 = !{i32 7, !"PIE Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"clang version 18.1.2 (https://github.com/llvm/llvm-project.git ea6c457b8dd2d0e6a7f05b4a5bdd2686085e1ec0)"}
|
使用以下命令
1
| /home/zzzccc/llvm-project/build/bin/opt --load-pass-plugin=./PPProfiler.so --passes="ppprofiler" --stats test.ll -o test.bc
|
1
| /home/zzzccc/llvm-project/build/bin/llvm-dis test.bc -o test_1.ll
|
可以看到优化后的IR文件如下:
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 36
| ; ModuleID = 'test.bc' source_filename = "test.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu"
@.str = private unnamed_addr constant [13 x i8] c"hello world!\00", align 1 @0 = private unnamed_addr constant [5 x i8] c"main\00", align 1
; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @main() #0 { call void @__ppp_enter(ptr @0) %1 = alloca i32, align 4 store i32 0, ptr %1, align 4 %2 = call i32 (ptr, ...) @printf(ptr noundef @.str) call void @__ppp_exit(ptr @0) ret i32 0 }
declare i32 @printf(ptr noundef, ...) #1
declare void @__ppp_enter(ptr)
declare void @__ppp_exit(ptr)
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4} !1 = !{i32 8, !"PIC Level", i32 2} !2 = !{i32 7, !"PIE Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"clang version 18.1.2 (https://github.com/llvm/llvm-project.git ea6c457b8dd2d0e6a7f05b4a5bdd2686085e1ec0)"}
|
成功了捏
Adding an optimization pipeline to your compiler
后续关于Pipeline的这部分似乎还没看到啥资料,会更加认真的学习(但是还是有需要了再补吧)
参考
主要参考《Learn_LLVM_17_A_beginner’s_guide_to_learning_LLVM_2nd》