一.配置环境

配置环境的话指路whitebird大佬的博客:

https://whitebird0.github.io/post/%E4%BB%8Ellvm%E5%88%B0ollvm%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html

或者llvm的官方文档https://llvm.org/docs/GettingStarted.html

二.注意的点

一个是whitebird博客中是使用clion对llvm进行编译,在编译过程中如果llvm是在虚拟机上的话要在clion的设置中将toolchain换一下,从windows换成wsl

二是大佬用的是旧版指令和编写llvm pass 的方式,新版已经不能使用他博客中的方式编写

三.正式开始

首先入门可以查看官方文档,根据官方文档进行llvm pass的编写

屏幕截图 2024-07-24 121209

可以发现其实官方文档所写的helloworld.h已经被创建了,那么我们根据他再在该文件夹中创建一个encode.h

写出如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef LLVM_TRANSFORMS_UTILS_ENCODE_H
#define LLVM_TRANSFORMS_UTILS_ENCODE_H

#include "llvm/IR/PassManager.h"

namespace llvm {

class EncodePass : public PassInfoMixin<EncodePass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
};

} // namespace llvm

#endif // LLVM_TRANSFORMS_UTILS_ENCODE_H

这段代码写完可能会有run标红,不用管,否则编译opt的时候会报错

然后再在llvm/lib/Transforms/Utils中创建Encode.cpp,代码如下

1
2
3
4
5
6
7
#include "llvm/Transforms/Utils/Encode.h"
using namespace llvm;
PreservedAnalyses EncodePass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}

然后在llvm/lib/Transforms/Utils的Cmakelists.txt中添加一个Encode.cpp

写完后在llvm/lib/Passes/PassRegistry.def中添加如下

1
FUNCTION_PASS("encode",EncodePass())

在llvm/lib/Passes/PassBuilder.cpp添加

1
#include "llvm/Transforms/Utils/Encode.h"

然后在clion中重新编译

屏幕截图 2024-07-25 120615

选择重新加载cmake项目

然后进入cmake-build-release文件夹重新编译

1
ninja -j2 opt

不过编译过程中我莫名其妙跳出来一个循环依赖问题

屏幕截图 2024-07-25 115432

不过解决也很容易,只要在clion中进入lib/support/BLAKE3/里面的cmakelists.txt,把最后一行和Encode.cpp有关的内容删除即可(虽然我还不知道有什么影响,不过好像没什么影响)

后来发现出现循环的原因是clion中新建.cpp文件的时候会有一个添加到目标,默认值就是lib/support/BLAKE3/

屏幕截图 2024-07-31 180820

正好是出现循环依赖中的一环(只探究到这里了,再深还没有什么发现)

然后编译完成就可以使用opt将pass用于我们的IR代码上啦

四.使用pass

先浅浅的写一个c语言的代码,创建成hello_clang.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>

void test_hello1(){

printf("test_hello1\n");
return ;
}
void test_hello2(){

printf("test_hello2\n");

return ;
}
int main(int argc ,char const *argv[]){

printf("hello clang\n");

return 0;
}

比如这样子,然后用clang进行编译,命令如下

1
clang -O3 -emit-llvm hello_clang.c -S -o hello_clang.ll

注意这个-O3一定要加上,它对.ll文件进行了优化,如果不加上,面临的就是

屏幕截图 2024-07-30 124224

就是不管怎么样你编译出来的东西都没有办法被可爱的F.getname()识别,不管怎么样都抓耳挠腮但是官方给的.ll却可以正常运行让你怀疑你是不是有问题

总之当时被困了蛮久的,后来是在知乎上的某篇文章启发了我

然后后面就是正常的啦

用了两种方式去运行了pass,一种是官方文档描述的

屏幕截图 2024-07-31 175215

还有一种是老版的就是搞个.so文件(不过说实话感觉有点没有必要)

屏幕截图 2024-07-31 175148