LLVM IRBuilder and pass:1.llvm基础命令


前言

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


1.llvm常用命令总结

1.将c语言代码转换为llvmIR

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


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值