A Tour to LLVM IR(下)
内容概要
- IR的全局变数
- IR中的Aggregate Types
getelementptr
指令的使用
参考文献
- LangRef
1. 全局变数
IR中的全局变数定义了一块在编译期分配的内存区域,其类型是一个指针,跟指令alloca
的返回值用法一样。我们看一下一段使用全局变数简单的C代码对应的IR是什么样子
// a.c
static const int a=0;
const int b=1;
const int c=1;
int d=a+1;
; a.ll
@b = dso_local constant i32 1, align 4
@c = dso_local constant i32 1, align 4
@d = dso_local global i32 1, align 4
前面已经讲过dso_local
是一个Runtime Preemption,表明该变数会在同一个链接单元内解析符号,align 4
表示4位元组对齐。global
和constant
关键字都可以用来定义一个全局变数,全局变数名必须有@
前缀,因为全局变数会参与链接,所以除去前缀外,其名字会跟你用C语言定义时的相同。
因为我们定义变数a
时使用了C语言的static
关键字,也就是说a
是local to file的,不参与链接,因此我们可以在生成的IR中可以看到,其被优化掉了。
// b.c
extern const int b;
extern const int c;
extern const int d;
int f() {
return b*c+d;
}
; b.ll
@b = external dso_local constant i32, align 4
@c = external dso_local constant i32, align 4
@d = external dso_local constant i32, align 4
define dso_local i32 @f() #0 {
entry:
%0 = load i32, i32* @b, align 4
%1 = load i32, i32* @c, align 4
%mul = mul nsw i32 %0, %1
%2 = load i32, i32* @d, align 4
%add = add nsw i32 %mul, %2
ret i32 %add
}
从函数f
的IR可以看到,全局变数其实是一个指针,在使用其时需要load
指令(赋值时需要store
指令)。那gloal
和constant
有什么区别呢?constant
相比global
,多赋予了全局变数一个const属性(对应C++的底层const
的概念,表示指针指向的对象是一个常量)。
跟C/C++类似,IR中可以在定义全局变数时使用global
,而在声明全局变数时使用constant
,表示该变数在本文件内不改变其值。
我们可以使用opt -S --globalopt <filename>
命令对全局变数进行优化
$ opt -S --globalopt a.ll -o a-opt.ll
@b = dso_local local_unnamed_addr constant i32 1, align 4
@c = dso_local local_unnamed_addr constant i32 1, align 4
@d = dso_local local_unnamed_addr global i32 1, align 4
可以看到优化过,全局变数前多了local_unnamed_addr
的attribute, 该属性表明在这个module内,这个变数的地址是不重要的,只要关心它的值就好。有什么作用呢?譬如说这里b
和c
都是常量且等于1,又有local_unnamed_addr
属性,编译器就可以把b
和c
合并成一个变数。
2. Aggregate Types
这里我们使用英文Aggregate Types主要是想跟C++的Aggregate Class区分开。IR的Aggregate Types包括数组和结构体。
2.1 数组
语法
[<elementnumber> x <elementtype>]
概述
跟C++的模板类template<class T, std::size_t N > class array
类似,数组元素在内存中是连续分布的,元素个数必须是编译器常量,未被提供初始值的元素会被零初始化,只是下标的使用方式有点区别。
Example
@array = global [17 x i8] ; 17个i8都是0
%array2 = alloca [17 x i8] [i8 1, i8 2] ; 前两个是1、2,其余是0
%array3 = alloca [3 x [4 x i32]] ; 3行4列的i32数组
@array4 = global [2 x [3 x [4 x i16]]] ; 2x3x4的i16数组
2.2 结构体
语法
%T1 = type { <type list> } ; Identified normal struct type
%T2 = type <{ <type list> }> ; Identified packed struct type
概述
与C语言中的struct
相同,不过IR提供了两种版本,normal版元素之间是由padding的,packed版没有。
Example
%struct1 = type { i32, i32, i32 } ; 一个i32的triple
%struct2 = type { float, i32 (i32) * } ; 一个pair,第一个元素是float,第二个元素是一个函数指针,该函数有一个i32的形参,返回一个i32
%struct3 = type <{ i8, i32 }> ; 一个packed的pair,大小为5位元组
2.3 getelementptr
指令(GEP)
我们可以使用 getelementptr
指令来获得指向数组的元素和指向结构体成员的指针。
语法
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
概述
第一个ty
是第一个索引使用的基本类型,第二个ty
表示其后的基址ptrval
的类型,inbounds
和inrange
关键字的含义这里不讲,有兴趣可以去LangRef查阅。 <ty> <idx>
是第一组索引的类型和值,<ty> <idx>
可以出现多次,其后出现的就是第二组、第三组等等索引的类型和值。要注意索引的类型和索引使用的基本类型是不一样的,索引的类型一般为i32
或i64
,而索引使用的基本类型确定的是增加索引值时指针的偏移量。
GEP的几个要点
理解第一个索引
- 第一个索引不会改变返回的指针的类型,也就是说
ptrval
前面的<ty>*
对应什么类型,返回就是什么类型 - 第一个索引的偏移量的是由第一个索引的值和第一个
ty
指定的基本类型共同确定的。
下面看个例子