编译器 llvm clang 源码转换示例
编译器 llvm clang 源码转换示例
从git获取llvm项目的源码方式:
git clone https://github.com/llvm/llvm-project.git
下载源码后,进入llvm-project目录包括如下内容:
llvm-project/llvm目录包括如下内容:
CLANG实战
实战 利用Clang制作自己的编译器 source-to-source 源代码转换
参考:
https://github.com/Ewenwan/llvm-clang-samples/blob/master/src_clang/tooling_sample.cpp
void foo(int* a, int *b) {
if (a[0] > 1)
{
b[0] = 2;
}
}
void bar(float x, float y); // just a declaration
自动添加 添加注释
// Begin function foo returning void
void foo(int* a, int *b) {
if (a[0] > 1) // the 'if' part
{
b[0] = 2;
}
}
// End function foo
void bar(float x, float y); // just a declaration
LLVM实战
函数签名
C语言中的函数签名由以下几部分组成:
- 返回类型
- 函数名
- 参数个数及参数类型
比如
intadd(int
a,
int
b)
{
return
a
+
b;
}
这段C程序代码中的add函数的函数签名就是int add(int, int)
待处理的C程序代码
#include
<stdio.h>
#include
<stdlib.h>
voidkeep()
{
printf("\n");
}
intadd(int
a,
int
b)
{
return
a
+
b;
}
int*getArr(int
n)
{
return
(int*)malloc(sizeof(int)
*
n);
}
intmain(int
argc,
char**
argv)
{
return
0;
}
项目运行结果是
在待处理的代码中定义了包括main函数在内的四个函数,但是最终结果却是六个函数,这是因为调用了C标准库中的printf函数和malloc函数,编译器在预处理阶段将这两个函数的声明加入到了代码中。
另外一个值得关注之处是,与C语言中int
、char
等类型不同,打印出来的函数签名中的类型是i32
、i8
,这其实是因为我们首先需要把待处理的C程序代码转换为LLVM IR字节码,然后才会用自定义LLVM项目对其进行处理,打印出来的类型其实是LLVM IR的类型,除此之外,long
对应i64
、float
对应f32
、double
对应f64
,不过LLVM IR void
和指针两种类型还是与C语言相同的。
函数签名
C语言中的函数签名由以下几部分组成:
返回类型 函数名 (参数个数及参数类型)
// 本程序 输入 llvm IR文件 输出 IR中的函数签名
// 输入的IR文件 可以由clang编译得到
// 例如 clang -emit-llvm -c test.c -o test.bc // test.c为测试程序
// 本程序编译命令
// clang++ $(llvm-config --cxxflags --ldflags --libs) main.cpp -o main
// 运行程序
// ./main test.bc
// 引入相关LLVM头文件
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Module.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>
#include <llvm/Support/CommandLine.h>
using namespace llvm
;
// LLVM上下文全局变量
staticManagedStatic
<LLVMContext
>GlobalContext
;
// 命令行位置参数全局变量, 这个参数的含义是需要处理的LLVM IR字节码的文件名
staticcl
::opt
<std
::string
>InputFilename(
cl
::Positional
,cl
::desc("<filename>.bc"),cl
::Required
);
intmain(int
argc
,char
**
argv
){
// 诊断实例
SMDiagnostic Err
;
// 格式化命令行参数,
cl
::ParseCommandLineOptions(argc
,argv
);
// 读取并格式化LLVM IR字节码文件, 返回LLVM Module(Module是LLVM IR的顶级容器)
std
::unique_ptr
<Module
>M
=parseIRFile(
InputFilename
,Err
,*
GlobalContext
);
// 错误处理
if
(!
M
){
Err
.print(argv
[0],errs());
return
1;
}
// 遍历Module中的每一个Function
for
(
Function
&F
:*M
){
// c++ 语法 范围for F是 IR模块中的每一个函数的引用
// 过滤掉那些以llvm.开头的无关函数
if
(!
F
.isIntrinsic()){
// 打印函数返回类型
outs()
<<
*(
F
.getReturnType());
// 打印函数名
outs()
<<
' '
<<
F
.getName()<<
'(';
// 函数名有可能和c文件里的不同(加了一些属性描述)
// 遍历函数的每一个参数
for
(
Function
::arg_iterator it
=F
.arg_begin(),ie
=F
.arg_end();it
!=ie
;it
++){
// 打印参数类型
outs()
<<
*(
it
->getType());
if
(
it
!=ie
-1)
{
outs()
<<
", ";
}
}
outs()
<<
")\n";
}
}
}
项目编译运行
在编译项目之前,需要确认一下编译运行环境 :
操作系统:Ubuntu 18.04 64位
LLVM版本:9.0.0
待处理的C程序代码文件:test.c
项目代码文件:main.cpp
然后获取待处理的C程序代码的LLVM IR字节码
clang -emit-llvm -c test.c -o test.bc
再编译项目代码
clang++
$(llvm-config --cxxflags --ldflags --libs
)main.cpp -o main
最后运行得到上文图示的结果
./main test.bc
参考链接:
https://www.freesion.com/article/3548547366/
https://www.freesion.com/article/4240352588/
https://zhuanlan.zhihu.com/p/102270840
https://github.com/Ewenwan/llvm-clang-samples/blob/master/src_clang/tooling_sample.cpp