(LLVM)中间语言(IR)基本语法简介 您所在的位置:网站首页 slt翻译 (LLVM)中间语言(IR)基本语法简介

(LLVM)中间语言(IR)基本语法简介

2023-10-09 07:03| 来源: 网络整理| 查看: 265

(LLVM)中间语言(IR)基本语法简介

转自:http://blog.sina.com.cn/s/blog_49b6b6d001011gik.html

根据编译原理知识,编译器不是直接将源语言翻译为目标语言,而是翻译为一种“中间语言”,我们编译器从业人员称之为“IR”--指令集,之后再由中间语言,利用后端程序和设备翻译为目标平台的汇编语言;

无疑,不同编译器的中间语言IR是不一样的,而IR可以说是集中体现了这款编译器的特征—-他的算法,优化方式,汇编流程等等,想要完全掌握某种编译器的工作和运行原理,分析和学习这款编译器的中间语言无疑是重要手段,另外,由于中间语言相当于一款编译器前端和后端的“桥梁”,如果我们想进行基于llvm的后端移植,无疑需要开发出对应目标平台的编译器后端,想要顺利完成这一工作,透彻了解llvm的中间语言无疑是非常必要的工作。 Llvm相对于gcc的一大改进就是大大提高了中间语言的生成效率和可读性,我个人感觉llvm的中间语言是一种介于c语言和汇编语言的格式,他既有高级语言的可读性,又能比较全面地反映计算机底层数据的运算和传输的情况,精炼而又高效,相对而言,gcc的中间代码有如科幻小说一般~

首先用vim命令创建一个新的c程序代码文件try1.c:

int main() { int a,b; return a+b; }

Clang try1.c -o try1 生成可执行文件 Clang -emit-llvm try1.c -S -o try1.ll 生成中间代码文件 Vim try1.ll 查看:

// ; ModuleID = ‘try1.c’ target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128” target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind { entry: %retval = alloca i32, align 4 %a = alloca i32, align 4 %b = alloca i32, align 4 store i32 0, i32* %retval %0 = load i32* %a, align 4 %1 = load i32* %b, align 4 �d = add nsw i32 %0, %1 ret i32 �d }

//

根据llvm.org上的描述,@代表全局变量,%代表局部变量,那么无疑,在llvm IR看来,int main这个函数,或者说他的函数返回值是个全局变量,其内部的a 和b是局部变量。

在这段代码里,我们找到了我们之前定义的a和b %a = alloca i32, align 4 %b = alloca i32, align 4 那么其他字符分别代表什么操作呢? Alloca Alloca相当于变量声明:

在llvm文档上对于Alloca的解释是:The ‘alloca’ instruction allocates memory on the stack frame of the currently executing function, to be automatically released when this function returns to its caller. “alloca指令用于分配内存堆栈给当前执行的函数,当这个函数返回其调用者(我自己对于caller的翻译)时自动释放。”

感觉跟c语言里的malloc差不多,不过当然,llvm更加“底层”。

i32:

可以得知这其实是在设置整数位长度 document里说的很明白:i是几这个整数就会占几位(bit),i32的话就是32位,4字节;i后面的数字可以随意写,这体现的就是llvm中间语言类似汇编的特征;

align : 在Language Reference Manual似乎没有这个关键字的注释,align 的意思是“对齐”那么这个对齐的意思究竟是什么?

“对齐”的意义是:若一个结构中含有一个int,一个char,一个int则他应该占用4*3=12字节,虽然char本身只占用一个字节的空间,但由于要向4“对齐”所以其占用内存空间仍为4(根据大端小端分别存储)

//

int main() { double a=128;

}

// 它生成的中间代码是这样的: //

; ModuleID = ‘try5.c’ target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128” target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind { entry: %retval = alloca i32, align 4 %a = alloca double, align 8 store i32 0, i32* %retval store double 1.280000e+02, double* %a, align 8 %0 = load i32* %retval ret i32 %0 } // %a = alloca double, align 8 可以看到align 后面跟的数字变成4而不是8了~~

i32, align 4的意义就应该是:向4对齐,即便数据没有占用4个字节,也要为其分配4字节,这样使得llvm IR在保证了数据格式一致性的前提条件下,定义数据型时非常灵活,不仅可以任意定义整形和浮点型的长度(iX,iXX,iXXX………),甚至还允许使用不同的数制,比如你需要使用64进制数字(?),那就只要i48, align 6即可。

这是a和b的情况,至于那个 %retval = alloca i32, align 4 中的retval,它无疑是return value 返回值的缩写,但它很有意思,它存储的值不一定就是返回值,它在上述return a+b的时候除了得到个0值之外根本不参与任何运算和传输,而且根据试验情况,这个retval似乎只在main函数中出现,而且由于main的返回值必须是int,这个retval也总是“ %retval = alloca i32, align 4 ”事实上,当提供高优化等级之后,retval就不会再出现,这个变量可以被认为是非必要的;

证明: // Try.c: double dou() {

double a,b; return a+b;

}

int main() {

int c,d; return c+d;

}

………………………………………………………………………………… Try.ll:

define double @dou() nounwind { entry: %a = alloca double, align 8 %b = alloca double, align 8 %0 = load double* %a, align 8 %1 = load double* %b, align 8 �d = fadd double %0, %1 ret double �d }

define i32 @main() nounwind { entry: %retval = alloca i32, align 4 %c = alloca i32, align 4 %d = alloca i32, align 4 store i32 0, i32* %retval %0 = load i32* %c, align 4 %1 = load i32* %d, align 4 �d = add nsw i32 %0, %1 ret i32 �d }

//

我猜测这个retval可能是为后端留的某个接口,因为我是在x86下运行llvm所以默认数据型是int,但是这也仅仅是我的猜测,我自己并不知道retval是什么,我在文档上和网上也没找到答案;

研究了以上这些后,之后的程序语句: %0 = load i32* %a, align 4 %1 = load i32* %b, align 4 就好理解了;

它与alloca和store均属于“Memory Access and Addressing Operations” Load是“装载”,即读出内容,store则是写入;

这之后是运算命令:

Add是加 Sub是减 Mul是乘 Div是除 Rems是求余 前头加f的是浮点运算,加u的是返回无符号整型值(unsigned integer)加s返回的是有符号的;

ret i32 �d表示返回加的结果,如果是void型的函数,就ret void;

4个c语言基本条件语句和循环语句: If which for switch

If: Try3.c:

// int main() {

int a,b,c;

a=1; b=2; c=0; if(a>b) {c=1;} else {c=2;}

}

生成中间代码文件:

; ModuleID = ‘try3.c’ target datalayout = “e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128” target triple = “i386-pc-linux-gnu”

define i32 @main() nounwind { entry: %retval = alloca i32, align 4 %a = alloca i32, align 4 %b = alloca i32, align 4 %c = 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 0, i32* %c, align 4 %0 = load i32* %a, align 4 %1 = load i32* %b, align 4 %cmp = icmp sgt i32 %0, %1 br i1 %cmp, label %if.then, label %if.else

if.then: ; preds = %entry store i32 1, i32* %c, align 4 br label %if.end

if.else: ; preds = %entry store i32 2, i32* %c, align 4 br label %if.end

if.end: ; preds = %if.else, %if.then %2 = load i32* %retval ret i32 %2 }

// 在if的中间语言里,主要有这么几个陌生关键字: icmp br label

逐个分析: Icmp:

Llvm.org explanation:

Syntax:

= icmp , ; yields {i1} or {}:result

Overview:

The ‘icmp’ instruction returns a boolean value or a vector of boolean values based on comparison of its two integer, integer vector, pointer, or pointer vector operands. Icmp可以根据两个整数值的比较(op1,op2)返回一个布尔类型的值或者布尔矢量(?) 比较规则由参数cond确定; 具体比较规则如下: eq: yields true if the operands are equal, false otherwise. No sign interpretation is necessary or performed. ne: yields true if the operands are unequal, false otherwise. No sign interpretation is necessary or performed. ugt: interprets the operands as unsigned values and yields true if op1 is greater than op2. uge: interprets the operands as unsigned values and yields true if op1 is greater than or equal to op2. ult: interprets the operands as unsigned values and yields true if op1 is less than op2. ule: interprets the operands as unsigned values and yields true if op1 is less than or equal to op2. sgt: interprets the operands as signed values and yields true if op1 is greater than op2. sge: interprets the operands as signed values and yields true if op1 is greater than or equal to op2. slt: interprets the operands as signed values and yields true if op1 is less than op2. sle: interprets the operands as signed values and yields true if op1 is less than or equal to op2.

sgt: interprets the operands as signed values and yields true if op1 is greater than op2. 也就是说:Sgt的意思就是若整数op1大于op2的话,cmp 就是true,否则就是false 无疑,icmp是用于判断的指令; 但是仅仅判断出结果来还不够,仍需要根据判断结果进行相应的选择性操作,if语句才完整;

Br Llvm.org explanation:

Syntax:

br i1 , label , label br label ; Unconditional branch

Overview:

Llvm.org explanation:

The ‘br’ instruction is used to cause control flow to transfer to a different basic block in the current function. There are two forms of this instruction, corresponding to a conditional branch and an unconditional branch. Br提供一个选择分支结构,可根据cond的情况使程序转向label 或label ; 另外br也有一种特殊形式:无条件分支(Unconditional branch):当在某种情况时;不必进行条件判断而直接跳转至某一个特定的程序入口标签(label)处(感觉类似于一个“goto”); 如: if.then: ; preds = %entry store i32 1, i32* %c, align 4 br label %if.end If then完事后,直接跳转到if.end

label 严格的讲它也是一种数据类型(type),但它可以标识入口,相当于代码标签; 综上我们可知: 一个if工作的流程是: 1.开始 2.得到两个操作数的值和比较条件; 3.开始比较,得到比较布尔值(true或者false) 4.根据布尔比较值使程序跳转到分支入口去;

While: 例子: Try7.c: // int main() { int a,i; while (i



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有