LLVM:从零开始实现 Function Pass
本文是我在学习 LLVM 的过程中,从零开始实现一个 Function Pass 的过程。具有如下特点:
-
基于 apt 安装 LLVM。不需要构建源码。
-
使用 C++14 标准。使用 LLVM 14 版本。
-
使用新的 PassInfoMixin 机制。不使用旧的 PassManager 机制。
-
使用最简化的 Makefile/CMake 更好地理解编译过程。
-
使用 Ubuntu 上的 VSCode 进行开发,完善的开发体验。
LLVM 的安装
首先你需要安装 LLVM,我直接用 apt 安装了。
1sudo apt install llvm-14
对于我现在版本的 Ubuntu,头文件和库文件在 /usr/include/llvm-14
和 /usr/lib/llvm-14
下。
VSCode 的配置
mkdir mypass-project
用于存放项目文件。
如下配置可以让 VSCode 识别 LLVM 头文件。
Path .vscode/c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/llvm-14/**",
"/usr/include/llvm-c-14/**"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++14",
"intelliSenseMode": "linux-clang-x64"
}
],
"version": 4
}
编写代码
接下来是 src
目录,用于存放源文件。
Path src/MyPass.h
1#pragma once
2
3#include "llvm/IR/PassManager.h"
4#include "llvm/Passes/PassBuilder.h"
5#include "llvm/Passes/PassPlugin.h"
6#include "llvm/Support/raw_ostream.h"
7
8namespace llvm {
9
10class MyPass : public PassInfoMixin<MyPass> {
11 public:
12 PreservedAnalyses run(Function& F, FunctionAnalysisManager& AM);
13};
14
15} // namespace llvm
16
17// Register the pass
18extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
19llvmGetPassPluginInfo() {
20 return {
21 LLVM_PLUGIN_API_VERSION, "MyPass", "v0.1",
22 [](llvm::PassBuilder &PB) {
23 PB.registerPipelineParsingCallback(
24 [](llvm::StringRef Name, llvm::FunctionPassManager &FPM,
25 llvm::ArrayRef<llvm::PassBuilder::PipelineElement>) {
26 if(Name == "mypass"){
27 FPM.addPass(llvm::MyPass());
28 return true;
29 }
30 return false;
31 }
32 );
33 }
34 };
35}
Path src/MyPass.cpp
这里我们实现一个功能:每个函数重命名为 f0
、f1
、f2
、f3
……。
1#include "MyPass.h"
2namespace llvm {
3static int f_number = 0;
4PreservedAnalyses MyPass::run(Function& F, FunctionAnalysisManager& AM) {
5 // rename function to f_number
6 F.setName("f" + std::to_string(f_number++));
7 return PreservedAnalyses::all();
8}
9} // namespace llvm
以及一个 build
目录,用于存放编译后的文件。
使用 Makefile 构建
在项目根目录编写 Makefile,用于编译和链接。
Path Makefile
1CXX = clang++
2CXXFLAGS = -O3 -fPIC -fno-rtti $(shell llvm-config --cxxflags)
3LDFLAGS = -shared $(shell llvm-config --ldflags)
4
5TARGET = build/libmypass.so
6SOURCES = MyPass.cpp
7OBJECTS = $(SOURCES:.cpp=.o)
8OBJECTS := $(addprefix build/,$(OBJECTS))
9SOURCES = $(addprefix src/,$(SOURCES))
10
11all: $(TARGET)
12
13build_dir:
14 @mkdir -p build
15
16$(TARGET): $(OBJECTS) build_dir
17 $(CXX) $(OBJECTS) $(LDFLAGS) -o $@
18
19build/%.o: src/%.cpp build_dir
20 $(CXX) $(CXXFLAGS) -c $< -o $@
21
22clean:
23 rm -f $(TARGET) $(OBJECTS)
24
25.PHONY: all clean
然后执行 make
即可。成功之后生成 build/mypass.so
。
使用 CMake 构建
更常用的是使用 CMake 构建。
Path CMakeLists.txt
1cmake_minimum_required(VERSION 3.10)
2project(MyPass)
3
4set(CMAKE_CXX_STANDARD 14)
5
6set(LLVM_PATH /usr/lib/llvm-14)
7
8include_directories(${LLVM_PATH}/include)
9
10add_library(mypass MODULE src/MyPass.cpp)
11
12set_target_properties(mypass PROPERTIES
13 COMPILE_FLAGS "-fno-rtti"
14 )
15
16target_link_libraries(mypass ${LLVM_PATH}/lib/libLLVM-14.so)
然后执行如下命令配置:
1cmake -B build
执行如下命令构建:
1cmake --build build
成功之后生成 build/libmypass.so
。
使用 LLVM Opt 执行 Pass
写一个测试用的 C 代码。
Path tmp/foo.c
1int f1(int a, int b, int c) {
2 return a + b + c;
3}
4
5int f2(int a, int b) {
6 return f1(a, b, 0);
7}
编译为 LLVM IR,注意去掉 optnone。
1clang -S -emit-llvm -O0 -Xclang -disable-O0-optnone tmp/foo.c -o tmp/foo.ll
执行 Pass。
1opt -S -load-pass-plugin=build/libmypass.so -passes="mypass" tmp/foo.ll -o tmp/foo_mypass.ll
效果
运行前:
Path tmp/foo.ll
1; Function Attrs: noinline nounwind uwtable
2define dso_local i32 @three_sum(i32 noundef %0, i32 noundef %1, i32 noundef %2) #0 {
3 %4 = alloca i32, align 4
4 %5 = alloca i32, align 4
5 %6 = alloca i32, align 4
6 store i32 %0, i32* %4, align 4
7 store i32 %1, i32* %5, align 4
8 store i32 %2, i32* %6, align 4
9 %7 = load i32, i32* %4, align 4
10 %8 = load i32, i32* %5, align 4
11 %9 = add nsw i32 %7, %8
12 %10 = load i32, i32* %6, align 4
13 %11 = add nsw i32 %9, %10
14 ret i32 %11
15}
16
17; Function Attrs: noinline nounwind uwtable
18define dso_local i32 @two_sum(i32 noundef %0, i32 noundef %1) #0 {
19 %3 = alloca i32, align 4
20 %4 = alloca i32, align 4
21 store i32 %0, i32* %3, align 4
22 store i32 %1, i32* %4, align 4
23 %5 = load i32, i32* %3, align 4
24 %6 = load i32, i32* %4, align 4
25 %7 = call i32 @three_sum(i32 noundef %5, i32 noundef %6, i32 noundef 0)
26 ret i32 %7
27}
运行后:
Path tmp/foo_mypass.ll
1; Function Attrs: noinline nounwind uwtable
2define dso_local i32 @f0(i32 noundef %0, i32 noundef %1, i32 noundef %2) #0 {
3 %4 = alloca i32, align 4
4 %5 = alloca i32, align 4
5 %6 = alloca i32, align 4
6 store i32 %0, i32* %4, align 4
7 store i32 %1, i32* %5, align 4
8 store i32 %2, i32* %6, align 4
9 %7 = load i32, i32* %4, align 4
10 %8 = load i32, i32* %5, align 4
11 %9 = add nsw i32 %7, %8
12 %10 = load i32, i32* %6, align 4
13 %11 = add nsw i32 %9, %10
14 ret i32 %11
15}
16
17; Function Attrs: noinline nounwind uwtable
18define dso_local i32 @f1(i32 noundef %0, i32 noundef %1) #0 {
19 %3 = alloca i32, align 4
20 %4 = alloca i32, align 4
21 store i32 %0, i32* %3, align 4
22 store i32 %1, i32* %4, align 4
23 %5 = load i32, i32* %3, align 4
24 %6 = load i32, i32* %4, align 4
25 %7 = call i32 @f0(i32 noundef %5, i32 noundef %6, i32 noundef 0)
26 ret i32 %7
27}