1.LLVM的IR语言

LLVM IR

根据编译原理知识,编译器不是直接将源语言翻译为目标语言,而是翻译为一种“中间语言”,我们编译器从业人员称之为“IR”
传统的静态编译器分为三个阶段:前端、优化和后端。

LLVM IR主要有三种格式:一种是在内存中的编译中间语言(我们无法通过文件的形式得到);一种是硬盘上存储的二进制中间语言(以.bc结尾),最后一种是可读的中间格式(以.ll结尾)。这三种中间格式是完全相等的。
LLVM Language Reference Manual
LLVM的IR基本语法简介
llvm之IR手册翻译
LLVM Language Reference Manual翻译

Module Structure

在一般情况下,一个模块的组成:函数和全局变量类的全局值、一个指向一个存储器位置指针(在本例,一个指向字符数组,和一个指针的函数),和linker type(详见下文)。

Named Metadata

1
2
3
4
5
6
; Some unnamed metadata nodes, which are referenced by the named metadata.
!0 = !{!"zero"}
!1 = !{!"one"}
!2 = !{!"two"}
; A named metadata.
!name = !{!0, !1, !2}

可以通过!1 Metadata 获得 IR 指令对应的源代码的行数,文件名等信息。

Linkage Types

所有的全局变量和函数具有以下链接类型之一:

  • private(私有):全局值“私有”只能直接被当前模块中的对象访问。特别是,连接代码到一个存在私有全局值的模块可能导致私有全局值必须被重新命名,以避免冲突。因为该符号是该模块私有的,因此所有的引用可以被更新。这不会显示在对象文件的任何符号表中。

  • internal(内部):类似于私有的,但该值在对象文件中作为本地符号(在ELF情况下是STB_LOCAL)。这对应于C的“static”关键字的概念。

  • available_externally(外部可用):。链接类型为“外部可用”的全局值不会被输出到对象文件,即对应于LLVM模块。它们的存在是为了当给定“全局定义”(它是被某个模块以外的地方所知道的)的知识时,允许内联和其他优化发生。全局值链接类型为外部可用时允许被随意丢弃,并且允许内联和其他优化。这种链接类型只允许定义,没有声明。

  • linkonce:链接类型为“linkonce”的全局值在链接时和其他同名的全局变量进行合并。这可以被用来实现某些形式的内联函数,模板,或其他必须在每个使用它的转换单元中生成的代码,这些代码可以被稍后更明确的定义覆盖(重写)。未引用的linkonce全局值允许被丢弃。Note that linkonce linkage does not actually allow the optimizer to inline the body of this function into callers。需要注意的是链接类型为linkonce的全局值实际上并不允许优化来使这个函数内联到调用者,因为它不知道函数的这个定义是在这个程序中最后的定义,还是将会被一个更强的定义所覆盖。为了enable(使能,启用)内联和其他优化,使用“linkonce odr”的链接类型。

  • weak:“weak”链接类型和“linkonece”在合并的语义上是相同的,所不同的是未引用的”weak”全局变量可能不会被丢弃。这是用于C语言源代码中声明为“weak”的全局变量。
  • common:“common”链接类型和“weak”最相似,但它在C中被用于在全局范围内试探性的定义,如“int X;”。“common”链接类型的符号和“weak”以同样的方式被合并,并且它们如果未被引用可能不被删除。”common”符号可能没有一个明确的值,必须有一个零初始化,并且可能不被标记为“常量”。函数和别名可能没有“common”链接类型。
  • appending:“appending”链接类型仅可以被应用到指向数组类型的指针的全局变量。当两个“appending”链接类型的全局变量被链接在一起时,这两个全局数组附加在一起。这是LLVM,类型安全,相当于当.o文件链接时,其系统链接将具有完全相同的名称的“部分”附加在一起。
    很不幸,这不对应.o文件中的任何特征,因此它只能用于像llvm.global_ctors这种被llvm专门解释的变量。

  • extern_weak:这种链接类型的语义遵循ELF目标文件模型:符号在被链接前是”weak”类型,如果没有被链接,该符号变为null,而不是作为一个未被定义的引用。

  • linkonce_odr, weak_odr:某些语言允许不同的全局值被合并,比如两个具有不同语义的函数。其他语言,如C++,确保只有相同的全局值可以被合并(即“one definition rule” - “ODR”)。这些语言可以使用linkonce_odr和weak_odr链接类型用来指示该全局值将只与相同的全局值合并。否则这些链接类型是非ODR版本。
  • external:如果上述标识符都没有被使用,那么这个全局变量就是外部可见的,这意味着它参与链接,并可以用于解决外部符号引用。
    对于一个函数只能声明为external或者extern_weak链接类型。

    Calling Conventions

  • “ccc”
  • “fastcc”
  • “coldcc”
  • “cc 10”
  • “cc 11”
  • “webkit_jscc”
  • “anyregcc”
  • “preserve_mostcc”
  • “preserve_allcc”
  • “cxx_fast_tlscc”
  • “swiftcc”
  • “cc

    Visibility Styles

  • “default”
  • “hidden”
  • “protected”

    DLL Storage Classes

  • “dllimport”
  • “dllexport”

简单举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//hello.c
#include<stdio.h>

int main()
{

int a = 1;
int b = 2;
int c;
int count = 3;
while(count > 0)
{
if(a > b)
{
c = a+b;
printf("c1 = %d\n",c);
}
else
{
c = a-b;
printf("c2 = %d\n",c);
}
--count;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//hello.ll
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@.str = private unnamed_addr constant [9 x i8] c"c1 = %d\0A\00", align 1
@.str1 = private unnamed_addr constant [9 x i8] c"c2 = %d\0A\00", align 1

define i32 @main() nounwind uwtable {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
%c = alloca i32, align 4
%count = alloca i32, align 4
store i32 0, i32* %retval
store i32 1, i32* %a, align 4
store i32 2, i32* %b, align 4
store i32 3, i32* %count, align 4
br label %while.cond

while.cond: ; preds = %if.end, %entry
%0 = load i32* %count, align 4
%cmp = icmp sgt i32 %0, 0
br i1 %cmp, label %while.body, label %while.end

while.body: ; preds = %while.cond
%1 = load i32* %a, align 4
%2 = load i32* %b, align 4
%cmp1 = icmp sgt i32 %1, %2
br i1 %cmp1, label %if.then, label %if.else

if.then: ; preds = %while.body
%3 = load i32* %a, align 4
%4 = load i32* %b, align 4
%add = add nsw i32 %3, %4
store i32 %add, i32* %c, align 4
%5 = load i32* %c, align 4
%call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([9 x i8]* @.str, i32 0, i32 0), i32 %5)
br label %if.end

if.else: ; preds = %while.body
%6 = load i32* %a, align 4
%7 = load i32* %b, align 4
%sub = sub nsw i32 %6, %7
store i32 %sub, i32* %c, align 4
%8 = load i32* %c, align 4
%call2 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([9 x i8]* @.str1, i32 0, i32 0), i32 %8)
br label %if.end

if.end: ; preds = %if.else, %if.then
%9 = load i32* %count, align 4
%dec = add nsw i32 %9, -1
store i32 %dec, i32* %count, align 4
br label %while.cond

while.end: ; preds = %while.cond
ret i32 0
}

declare i32 @printf(i8*, ...)

@代表全局变量,%代表局部变量

alloca

%a = alloca i32, align 4
alloca指令用于分配内存堆栈给当前执行的函数,当这个函数返回其调用者(我自己对于caller的翻译)时自动释放。
i32表示32位,4字节,LLVM将整数类型定义为iN,其中N是整数占用的位数,位数为1到223-1
align 4表示向4“对齐”:即便数据没有占用4个字节,也要为其分配4字节,这样使得llvm IR在保证了数据格式一致性的前提条件下,定义数据型时非常灵活,不仅可以任意定义整形和浮点型的长度(iX,iXX,iXXX………),甚至还允许使用不同的数制,比如你需要使用64进制数字,那就只要i48, align 6即可。

getelementptr

getelementptr 的第一个参数是全局字符串变量的指针。要单步执行全局变量的指针,则需要使用第一个索引,即 i32 0。因为 getelementptr 指令的第一个参数必须始终是 pointer 类型的值,所以第一个索引会单步调试该指针。0 值表示从该指针起偏移 0 元素偏移量。我的开发计算机运行的是 32 位 Linux®,所以该指针是 4 字节。第二个索引 (i32 0) 用于选择字符串的第 0 个元素,该元素是作为 printf 的参数来提供的。i32 %8表示传入参数是%8这个整数

load/store

%0 = load i32* %a, align 4
load是“装载”,即读出内容

store i32 1, i32* %a, align 4
store是写入内容

判断及跳转icmp/br

1
2
%cmp1 = icmp sgt i32 %1, %2
br i1 %cmp1, label %if.then, label %if.else

二元运算( f)add/( f)sub/( f)mul/(usf)div/(usf)rem

1
2
<result> = add nuw nsw <ty> <op1>, <op2>
%add = add nsw i32 %0, %1

nsw:no signed wrap
nuw:no unsigned wrap
当溢出时,计算得到的值是poison value
There is currently no way of representing a poison value in the IR; they only exist when produced by operations such as add with the nsw flag.

1
2
<result> = fadd [fast-math flags]* <ty> <op1>, <op2>
<result> = fadd float 4.0, %var

前头加f的是floating point or vector
其中的参数fast-math flags是最优化提示,用来enable otherwise unsafe floating point operations

加u的返回结果是无符号的int型
加s的返回结果是有符号的int型

Terminator Instructions

每个basic block(基本块)以终结指令结束,表明下一个被执行的块

ret

1
2
ret i32 0
ret void

br

分为跳转到条件分支和无条件分支

1
2
br i1 <cond>, label <iftrue>, label <iffalse>
br label <dest> ; Unconditional branch

switch

1
2
3
4
5
6
7
8
9
10
11
12
13
switch <intty> <value>, label <defaultdest> [ <intty> <val>, label <dest> ... ]

; Emulate a conditional br instruction
%Val = zext i1 %value to i32
switch i32 %Val, label %truedest [ i32 0, label %falsedest ]

; Emulate an unconditional br instruction
switch i32 0, label %dest [ ]

; Implement a jump table:
switch i32 %val, label %otherwise [ i32 0, label %onzero
i32 1, label %onone
i32 2, label %ontwo ]

indiretbr

invoke

resume

catchswitch

catchret

cleanupret

unreachable

‘getelementptr‘ Instruction

http://llvm.org/docs/GetElementPtr.html

Source Level Debugging with LLVM

http://blog.csdn.net/wuhui_gdnt/article/details/68923937?locationNum=12&fps=1
clang -O0 -g可生成完整的调试信息