llvm使用心得
常用llvm命令
# 将.c编译为bitcode
clang -emit-llvm -c test.c
# 将bitcode反汇编为ir
llvm-dis test.bc
# 将ir转成bitcode
llvm-as test.ll
# 用lli执行bitcode或ir
lli test.bc
lli test.ll
# llc将bitcode或ir转成目标汇编
llc test.bc
llc test.ll
# as将汇编转成目标文件
as -o test.o test.s
# ld将目标文件链接成可执行文件
ld test.o -lc -o test_exe
basic block
llvm ir的构建,基于的是basic block,指令是放在basic block里。basic block正是编译原理里的概念:除了最后一条指令外,不包含条件或无条件跳转的指令集合。
定义字符串
llvm里,字符串有个类似字符串常量池的东西,里面定义了代表字符串内容的global var:
@".str0" = private unnamed_addr constant [2 x i8] c"a\00", align 1
使用字符串的时候,用getelementptr命令引用:
define i8* @"run"()
{
entry:
%"__t0" = getelementptr [2 x i8], [2 x i8]* @".str0", i32 0, i32 0
ret i8* %"__t0"
}
注意后面2个indice=0的参数,第一个0表示,getelementptr从[2 x i8]*里取得第一个也是唯一一个元素(索引为0)的地址,其值类型是[2 x i8],第二个0表示再取这个char array的第一个元素的地址,相当于C里的&arr[0],返回的自然是i8*。
可见,getelementptr是很强大的,它可以取得一个嵌套结构S里的任意成员。
特别的,对于裸指针使用getelementptr,比如i8* ,要这么写:
%"__t6" = getelementptr inbounds [2 x i8*], [2 x i8*]* %"__t5", i32 0, i32 0
%"__t7" = load i8*, i8** %"__t6"
%"__t8" = getelementptr inbounds i8, i8* %"__t7", i32 2 ;这里只能使用一个参数,表示取&__t7[2]
%"__t9" = load i8, i8* %"__t8"
不像[2 x i8]* ,对i8* 使用getelementptr时不需要那个特殊的首参数0,直接取位置下标就好了
字符串可用[N*i8]來表示,也可用i8**表示,例如:
@".str0.storage" = private unnamed_addr constant [4 x i8] c"abc\00", align 1
@".str0" = private unnamed_addr constant i8* getelementptr ([4 x i8], [4 x i8]* @".str0.storage", i32 0, i32 0), align 1
這裡,[N*i8]作爲字符串的存儲,i8** 才是字符串的対外呈現。
获得列表元素
列表在llvm里使用ArrayType表示,若要支持嵌套列表,可定义成ArrayType< PointerType<ArrayType> >,类似于C里的双重或多重指针。
若要获取列表里的某个元素,例如{1,2}[1],有两种选择,第一,可用getelementptr+load指令:
@"__g0" = private unnamed_addr constant [2 x i32] [i32 1, i32 2]
define i32 @"run"()
{
entry:
%"__t0" = getelementptr inbounds [2 x i32], [2 x i32]* @"__g0", i32 0, i32 1
%"__t1" = load i32, i32* %"__t0"
ret i32 %"__t1"
}
第二,可用load+extractvalue:
@"__g0" = private unnamed_addr constant [2 x i32] [i32 1, i32 2]
define i32 @"run"()
{
entry:
%"__t0" = load [2 x i32], [2 x i32]* @"__g0"
%"__t1" = extractvalue [2 x i32] %"__t0", 1
ret i32 %"__t1"
}
gep更灵活,因为它的索引值可以是一个变量,比如:
@"__g0" = private unnamed_addr constant [2 x i32] [i32 1, i32 2]
define i32 @"run"()
{
entry:
%"__t0" = add i32 0, 1
%"__t1" = getelementptr inbounds [2 x i32], [2 x i32]* @"__g0", i32 0, i32 %"__t0"
%"__t2" = load i32, i32* %"__t1"
ret i32 %"__t2"
}
而extractvalue的索引值必须是一个常量,无法接受通用值。
【总结】gep和extractvalue的不同:
gep用于结构体S的指针,得到的也是S成员的指针;
extractvalue则用于结构体S的值,得到的是S成员的值
调用C函数
分为两种,一是调C库函数,一是调自定义函数。
前者的话,可以直接在ir里声明库函数(不需要实现函数体),用call指令调用即可。
后者的话,可采取以下步骤:
- 将自定义函数放ext.c里
- 使用clang -emit-llvm -c ext.c将ext.c转成bitcode
- 使用llvm::ParseBitcodeFile(C/C++下)或llvmlite.binding.parse_bitcode(python下)加载bitcode,转成一个Module对象,由ExecutionEngine.add_module负责加载该module
- 在ir里声明并调用ext.c里的方法
如何用引用传递函数参数
llvm不支持引用方式传递函数参数,要使用指针传递。
关于GlobalVariable
linkage属性
private,相当于匿名的全局变量
internal,相当于正式的全局变量,会出现在目标文件里,类似于C的static。
两者的区别:internal可使用ExecutionEngine.get_global_value_address获得,private则拿不到。
external,相当于C的extern声明。
initializer属性
initializer属性与GlobalVariable是否const无关
类型系统
类似于C,llvm有严格的类型约束。不同类型间不允许相互操作,但有命令支持类型间转换,比如sitofp(将signed int转成float)。
llvm的array或vector类型是包含element size的,这样,如果我们想将不同size的array或vector视作同一类型,llvm本身是不允许的,需要一些技巧,比如使用如下结构代替array表示float类型的列表,无需考虑其size:
{i32, float*}
其中,struct的第一个参数表示列表长度。
如果我们要构建一门语言,则该语言的类型系统为顶层类型,llvm的类型系统为中间类型,最后jit执行的是C类型,类型系统间的转换如下:
顶层类型 -> llvm中间类型 -> C类型
load/store问题
变量、索引下标和属性都可能存在load/store问题(即,到底是作为左值还是右值),当我们用自底向上的方法归约时,没办法在第一趟扫描完成前得知这一点。对此,解决思路如下:
第一趟扫描构建出ast树,对ast树的变量、索引下标和属性节点重置load/store标记后,在递归遍历ast树时再处理load/store问题
经验总结
- 使用IR构建DSL的过程,就像用汇编开发程序的过程
- 如果不清楚IR如何表达,可用C写一段简单代码,再用clang -emit-llvm -S转成ir后看一下人家怎么翻译的。