文章目录
前言
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
1.llvm常用命令总结
1.将c
语言代码转换为llvm
的IR
clang -emit-llvm -S multiply.c -o multiply.ll或 clang -cc1 -emit-llvm testfile.c -o testfile.ll
2.把ll
文件的LLVM
IR
转为bitcode
格式
llvm-as test.ll –o test.bc
3.通过以下命令可把bc文件中LLVM bitcode转换为汇编码:
llc test.bc –o test.s或clang -S test.bc -o test.s –fomit-frame-pointer
4.执行以下命令把bitcode文件转换为我们之前创建过的ll文件:
llvm-dis test.bc –o test.ll
5.使用以下命令用opt执行转换Pass
opt –passname input.ll –o output.ll
adce:入侵式无用代码消除。
bb-vectorize:基本块向量化。
constprop:简单常量传播。
dce:无用代码消除。
deadargelim:无用参数消除。
globaldce:无用全局变量消除。
globalopt:全局变量优化。
gvn:全局变量编号。
inline:函数内联。
instcombine:冗余指令合并。
licm:循环常量代码外提。
loop-unswitch:循环外提。
loweratomic:原子内建函数lowering。
lowerinvoke:invode指令lowering,以支持不稳定的代码生成器。
lowerswitch:switch指令lowering。
mem2reg:内存访问优化。
memcpyopt:MemCpy优化。
simplifycfg:简化CFG。
sink:代码提升。
tailcallelim:尾调用消除。
6.使用llvm-link命令链接两个LLVM bitcode文件:
llvm-link test1.bc test2.bc –o output.bc
7.lli工具命令执行LLVM bitcode格式程序
lli output.bc
8.把C语言代码转为bitcode格式
% clang -O3 -emit-llvm hello.c -c -o hello.bc
2.LLVM IR 基本语法
2.1 IR基本语法介绍
用vim编辑一个c程序代码
#include <stdio.h>
int main()
{
int a=10;
int b=11;
return a+b;
}
通过clang -emit-llvm -S test1.c -o test1.ll
将c的源码转换为LLVM IR,IR代码如下:
; ModuleID = 'test3.c'
source_filename = "test3.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
; Function Attrs: noinline nounwind optnone uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
store i32 0, i32* %retval, align 4
store i32 10, i32* %a, align 4
store i32 11, i32* %b, align 4
%0 = load i32, i32* %a, align 4
%1 = load i32, i32* %b, align 4
%add = add nsw i32 %0, %1
ret i32 %add
}
attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0}
!llvm.ident = !{!1}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 6.0.0 (tags/RELEASE_600/final)"}
注释以;
开头,直到当前行末尾,所以; Function Attrs: noinline nounwind optnone uwtable
为注释;
@
代表全局标识符(函数,全局变量);
%
代表局部标识符(寄存器名称也就是局部变量,类型)。
所以在llvm IR来看,int main 这个函数,或者说它的函数返回值是个全局变量,其内部的a和b是局部变量。
define i32 @main() #0 {
entry:
...
ret i32 %add
}
上面IR是定义一个main函数,函数的返回值类型是i32;#0后面是属性组中的属性;每个函数的定义都会包含一个基本块(BasicBlock),entry是基本块的入口,ret i32 %add 是返回值类型为i32,名称为%add的变量中存放的函数的值,也就是基本块的结束。
i32:32位的整数,对应C中的int类型,i后面跟几,这个整数就会占几位(bit),i32就是32位,4个字节;
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
alloca指令的官方解释:用于分配内存堆栈给当前执行的函数,当这个函数返回其调用者时自动释放。这里就是给%retval变量分配一个4字节的内存,待变量不再使用时,将内存释放,有点C语言中声明一个变量的意思,也有点像C中的malloc。
align 4:描述的是对齐方式,如果一个结构中包含两个int和一个char,那么他应该占用4*3 = 12个字节,虽然char本身占一个字节空间,但是要向int(4个字节)对齐,因此也要为它分配4个字节。
store i32 0, i32* %retval, align 4
store i32 10, i32* %a, align 4
store i32 11, i32* %b, align 4
store的解释是:将数据写入指定的内存中。所以这里意思是将i32类型的整数10存放到变量(寄存器名称)%a对应的内存中去,对齐方式是4byte。
%0 = load i32, i32* %a, align 4
%1 = load i32, i32* %b, align 4
load指令的意思是:读取指定内存中的数据,这里意思是读取变量%a对应的内存中的数据,将其存放到类型为i32的临时变量%0中去,%0的对齐方式为4Byte。load后面紧跟的类型是有限制的,必须为 first class type(即llvm IR原生的数据类型)。
%add = add nsw i32 %0, %1
add指令是一个二元运算符,返回它对应的两个操作数的和,操作数也是有要求的,必须为整数或整数值向量,且两个操作数的类型必须相同。有一个fadd指令,也是求两个操作数的和,不过对操作数的限制是必须为浮点数或者浮点值向量,且操作数类型相同。add加,sub减,mul乘,div除,rem取余。
2.2 if语句介绍
C程序代码:
#include <stdio.h>
int main()
{
int a = 10;
if(a%2 == 0)
{
return 0;
}
else
return 1;
}
LLVM IR 代码
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
store i32 0, i32* %retval, align 4
store i32 10, i32* %a, align 4
%0 = load i32, i32* %a, align 4
%rem = srem i32 %0, 2
%cmp = icmp eq i32 %rem, 0
br i1 %cmp, label %if.then, label %if.else
if.then: ; preds = %entry
store i32 0, i32* %retval, align 4
br label %return
if.else: ; preds = %entry
store i32 1, i32* %retval, align 4
br label %return
return: ; preds = %if.else, %if.then
%1 = load i32, i32* %retval, align 4
ret i32 %1
}
上面代码中出现的新指令有4个,分别为icmp、br、label和srem。
%rem = srem i32 %0, 2
srem是取余运算,i32 是操作数类型,上述表达式意思是对类型为i32的%0变量进行2取余运算,结果存储到%rem中。
%cmp = icmp eq i32 %rem, 0
icpm指令,根据比较规则,比较两个操作数,将比较的结果以布尔值或者布尔向量(vecor of boolean values)返回,且对于操作数的限定是操作数为整数或整数值向量、指针或指针向量。在这里,eq是比较规则,%rem和0是操作数,i32是操作数类型,比较%rem与0的值是否相等,将比较的结果存放到%cmp中。
br i1 %cmp, label %if.then, label %if.else
br指令有两种形式,分别对应条件分支和无条件分支。该指令的条件分支在形式上接受一个i1
值和两个label
值,用于将控制流传输到当前函数中的不同基本块,上面这条指令是条件分支,有点像C中的三目条件运算符;无条件分支就是不用判断,直接跳转到指定的分支,有点像C中的goto,比如说这个就是无条件分支br label % return。上面指令的意思是,i1类型的变量%cmp的值如果为真,执行if.then,否则执行if.else。
总结一下if条件语句
- 求出if语句表达式的值;
- icmp指令开始比较,并产生一个布尔结果值;
- br指令的条件分支借助上一步产生的布尔结果,跳转到相对应的分支入口;
- 分支执行完之后再用br指令的无条件分支跳转到if的结束分支。
2.3while语句介绍
C程序
#include <stdio.h>
int main()
{
int a = 0, b = 1;
while(a<5)
{
a++;
b * = a;
}
return b;
}
LLVM IR代码:
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
store i32 0, i32* %retval, align 4
store i32 0, i32* %a, align 4
store i32 1, i32* %b, align 4
br label %while.cond
while.cond: ; preds = %while.body, %entry
%0 = load i32, i32* %a, align 4
%cmp = icmp slt i32 %0, 5
br i1 %cmp, label %while.body, label %while.end
while.body: ; preds = %while.cond
%1 = load i32, i32* %a, align 4
%inc = add nsw i32 %1, 1
store i32 %inc, i32* %a, align 4
%2 = load i32, i32* %a, align 4
%3 = load i32, i32* %b, align 4
%mul = mul nsw i32 %3, %2
store i32 %mul, i32* %b, align 4
br label %while.cond
while.end: ; preds = %while.cond
%4 = load i32, i32* %b, align 4
ret i32 %4
}
while循环中没有新的指令出现,所以说所谓的while循环,也就是“跳转+分支”这一结构。
While语句的运行流程:首先跳转到while.cond:相关变量得到初始值后判断是否满足继续循环条件,若满足,就跳转到while body:进行循环实际操作,一次实际操作运行完后再跳转到while.cond:进行条件判断,如此循环;若否,直接跳转到while.end:终止循环;
2.4switch语句
C代码
int main (){
char grade = 'B';
int score;
switch(grade)
{
case 'A' :
score = 4;
break;
case 'B' :
score = 3;
break;
case 'C' :
score = 2;
break;
case 'D' :
score = 1;
break;
default :
score = 0;
}
printf("your score: %d\n", score );
return 0;
}
LLVM IR
@.str = private unnamed_addr constant [16 x i8] c"your score: %d\0A\00", align 1
define i32 @main(){
entry:
%grade = alloca i8, align 1
%score = alloca i32, align 4
store i8 66, i8* %grade, align 1
%0 = load i8, i8* %grade, align 1
%conv = sext i8 %0 to i32
switch i32 %conv, label %sw.default [
i32 65, label %sw.a
i32 66, label %sw.b
i32 67, label %sw.c
i32 68, label %sw.d
]
sw.a:
store i32 4, i32* %score, align 4
br label %sw.end
sw.b:
store i32 3, i32* %score, align 4
br label %sw.end
sw.c:
store i32 2, i32* %score, align 4
br label %sw.end
sw.d:
store i32 1, i32* %score, align 4
br label %sw.end
sw.default:
store i32 0, i32* %score, align 4
br label %sw.end
sw.end:
%1 = load i32, i32* %score, align 4
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i32 0, i32 0), i32 %1)
ret i32 0
}
declare i32 @printf(i8*, ...)
这里新出来了switch 指令,switch指令是一个终端指令,同br、ret一样,用在一个基本快的结束。也没啥难度,就是选一个与%conv值相等的基本块标签跳进去。
2.5 对指针的操作
C代码
int main(){
int i = 10;
int* pi = &i;
printf("i的值为:%d",i);
printf("*pi的值为:%d",*pi);
printf("&i的地址值为:",%d);
printf("pi的地址值为:",%d);
}
LLVM IR
@.str = private unnamed_addr constant [16 x i8] c"i\E7\9A\84\E5\80\BC\E4\B8\BA\EF\BC\9A%d\00", align 1
@.str.1 = private unnamed_addr constant [18 x i8] c"*pi\E7\9A\84\E5\80\BC\E4\B8\BA\EF\BC\9A%d\00", align 1
@.str.2 = private unnamed_addr constant [23 x i8] c"&i\E7\9A\84\E5\9C\B0\E5\9D\80\E5\80\BC\E4\B8\BA\EF\BC\9A%p\00", align 1
@.str.3 = private unnamed_addr constant [23 x i8] c"pi\E7\9A\84\E5\9C\B0\E5\9D\80\E5\80\BC\E4\B8\BA\EF\BC\9A%p\00", align 1
define i32 @main(){
entry:
%i = alloca i32, align 4
%pi = alloca i32*, align 8
store i32 10, i32* %i, align 4
store i32* %i, i32** %pi, align 8
%0 = load i32, i32* %i, align 4
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i32 0, i32 0), i32 %0)
%1 = load i32, i32* %i, align 4
%call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.1, i32 0, i32 0), i32 %1)
%call2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.2, i32 0, i32 0), i32* %i)
%2 = load i32*, i32** %pi, align 8
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.3, i32 0, i32 0), i32* %2)
ret i32 0
}
declare i32 @printf(i8*, ...)
对指针的操作就是开辟一块指针类型(i32*)类型的内存,里面放一个指针%pi = alloca i32*, align 8
。
2.6 对数组的操作
C代码
int main(){
char str[30];
char c;
int i;
for(c=65,i=0; c<=90; c++,i++){
str[i] = c;
}
printf("%s\n", str);
return 0;
}
LLVM IR
@.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
define i32 @main() {
entry:
%str = alloca [30 x i8], align 16
%c = alloca i8, align 1
%i = alloca i32, align 4
store i8 65, i8* %c, align 1
store i32 0, i32* %i, align 4
br label %for.cond
for.cond:
%0 = load i8, i8* %c, align 1
%sext = sext i8 %0 to i32
%cmp = icmp sle i32 %sext, 90
br i1 %cmp, label %for.body, label %for.end
for.body:
%1 = load i8, i8* %c, align 1
%2 = load i32, i32* %i, align 4
%array = getelementptr inbounds [30 x i8], [30 x i8]* %str, i32 0, i32 %2
store i8 %1, i8* %array, align 1
%add = add i8 %1, 1
store i8 %add, i8* %c, align 1
%3 = load i32, i32* %i, align 4
%add2 = add nsw i32 %3, 1
store i32 %add2, i32* %i, align 4
br label %for.cond
for.end:
%arraydecay = getelementptr inbounds [30 x i8], [30 x i8]* %str, i32 0, i32 0
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i8* %arraydecay)
ret i32 0
}
declare i32 @printf(i8*, ...)
数组新增了两个不同的地方: %str = alloca [30 x i8], align 16
和 %arraydecay = getelementptr inbounds [30 x i8], [30 x i8]* %str, i32 0, i32 0
第一条分配内存的指令alloca[30 x i8]
,就是分配了30个i8类型的连续内存空间。
第二条
2.7对结构体的操作
C代码
struct Grade{
int number;
}
struct Stu{
char *name;
int age;
char group;
float score;
struct Grade grade1;
};
int main(){
struct Stu stu1;
stu1.name = "Tom";
stu1.age = 18;
stu1.group = 'A';
stu1.score = 136.5;
stu1.grade1.number = 4;
printf("%s,%d,%c,%.1f, %d\n", stu1.name,stu1.age, stu1.group, stu1.score, stu1.grade1.number);
return 0;
}
LLVM IR
%struct.grade = type{ i32 }
%struct.Stu = type { i8*, i32, i8, float, %struct.grade}
@str.name = private unnamed_addr constant [4 x i8] c"Tom\00", align 1
@str.print = private unnamed_addr constant [25 x i8] c"%s\EF\BC\8C%d\EF\BC\8C%c\EF\BC\8C%.1f, %d\0A\00", align 1
define i32 @main(){
entry:
%stu1 = alloca %struct.Stu, align 8
%name = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 0
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @str.name, i32 0, i32 0), i8** %name, align 8
%age = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 1
store i32 18, i32* %age, align 8
%group = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 2
store i8 65, i8* %group, align 4
%score = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 3
store float 136.5, float* %score, align 8
%grade = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 4, i32 0
store i32 4, i32* %grade, align 4
%0 = load i8*, i8** %name, align 8
%1 = load i32, i32* %age, align 8
%2 = load i8, i8* %group, align 4
%sext = sext i8 %2 to i32
%3 = load float, float* %score, align 8
%4 = load i32, i32* %grade, align 4
%fpext = fpext float %3 to double
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([25 x i8], [25 x i8]* @str.print, i32 0, i32 0), i8* %0, i32 %1, i32 %sext, double %fpext, i32 %4)
ret i32 0
}
declare i32 @printf(i8*, ...)
IR中对于结构体的使用同C中很相似,都是先声明一个类型,然后在使用这个类型利用alloca指令开辟内存。第1节基本语法介绍中也说过,%代表局部标识符(寄存器名称也就是局部变量,类型),其中类型就是这里的结构体。
%struct.grade = type{ i32 }
%struct.Stu = type { i8*, i32, i8, float, %struct.grade}
结构体这一块需要注意的是这一条指令有三个索引,%grade = getelementptr inbounds %struct.Stu, %struct.Stu* %stu1, i32 0, i32 4, i32 0
。
2.8引用内置函数
C代码
int main (){
printf("值 8.0 ^ 3 = %lf\n", pow(8.0, 3));
printf("值 3.05 ^ 1.98 = %lf", llvm.pow.f32(3.05, 1.98));
return 0;
}
LLVM IR
@.str = private unnamed_addr constant [19 x i8] c"\E5\80\BC 8.0 ^ 3 = %lf\0A\00", align 1
@.str.1 = private unnamed_addr constant [22 x i8] c"\E5\80\BC 3.05 ^ 1.98 = %lf\00", align 1
declare i32 @printf(i8*, ...)
declare double @pow(double, double)
declare double @llvm.pow.f32(double, double)
define i32 @main(){
entry:
%pow1 = call double @pow(double 8.0, double 3.0)
call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str, i32 0, i32 0), double %pow1)
%pow2 = call double @llvm.pow.f32(double 3.05, double 1.98)
call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.1, i32 0, i32 0), double %pow2)
ret i32 0
}
2.9 引用外部函数
C代码
int func(int a) {
a = a*2;
return a;
}
----------------------------------------
#include<stdio.h>
extern int func(int a);
int main() {
int num = 5;
num = func(num);
printf("number is %d\n", num);
return num;
}
LLVM IR
define i32 @func(i32 %a){
entry:
%a.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
%0 = load i32, i32* %a.addr, align 4
%mul = mul nsw i32 %0, 2
store i32 %mul, i32* %a.addr, align 4
%1 = load i32, i32* %a.addr, align 4
ret i32 %1
}
-----------------------------------------------------
@.str = private unnamed_addr constant [14 x i8] c"number is %d\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%num = alloca i32, align 4
store i32 0, i32* %retval, align 4
store i32 5, i32* %num, align 4
%0 = load i32, i32* %num, align 4
%call = call i32 @func(i32 %0)
store i32 %call, i32* %num, align 4
%1 = load i32, i32* %num, align 4
%call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i32 %1)
%2 = load i32, i32* %num, align 4
ret i32 %2
}
declare i32 @func(i32)
declare i32 @printf(i8*, ...)
链接后的IR:
@.str = private unnamed_addr constant [14 x i8] c"number is %d\0A\00", align 1
define i32 @func(i32 %a){
entry:
%a.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
%0 = load i32, i32* %a.addr, align 4
%mul = mul nsw i32 %0, 2
store i32 %mul, i32* %a.addr, align 4
%1 = load i32, i32* %a.addr, align 4
ret i32 %1
}
define i32 @main(){
entry:
%num = alloca i32, align 4
store i32 5, i32* %num, align 4
%0 = load i32, i32* %num, align 4
%call = call i32 @func(i32 %0)
store i32 %call, i32* %num, align 4
%1 = load i32, i32* %num, align 4
%call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i32 %1)
%2 = load i32, i32* %num, align 4
ret i32 %2
}
declare i32 @printf(i8*, ...)
参考:https://blog.csdn.net/qq_42570601/article/details/107157224