大数跨境
0
0

DeepRoute Lab | LLVM IR Tutorial观后感

DeepRoute Lab | LLVM IR Tutorial观后感 元戎启行招聘
2021-11-05
2
导读:DeepRoute Lab知识分享栏目更新啦~



AUTHOR-元戎感知组

最近又重温了一把LLVM Developer Conference一个tutorial,记录一下之前的一些观后感。整个tutorial还是非常适合没有LLVM基础的人入门跟手操一段LLVM代码的。


Prerequisite

IR->intermediate representation也就是所谓的中间表示形式。一般来说编译器会使用的IR包含了DAG,三地址码(靠近目标机器),CFG(控制流图),SSA(比较常见的,single static assignment),CPS(更加一般的SSA)。其中SSA由于每个变量仅被赋值一次更加容易做整个IR的分析以及其他的优化包含(constant propagation)。其他的IR形式这边就不多少了,需要的话再一一开坑。


文件格式:

1.bc bitcode   

2. ll 中间表示文件


有用的工具:

1. llvm-dis 反汇编工具将bc文件转为ll文件

2. llvm-as 汇编工具将ll文件转为bc文件

3. clang/clang++这两者分别都是LLVM的前端parser也就是编译器工具

4. opt用来check或者优化或者转化IR文件, e.g. opt --verify x.ll


Example

先准备一个小例子



打开main.ll文件



; ModuleID = 'main.cpp'

source_filename = "main.cpp"

target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"

target triple = "arm64-apple-macosx11.0.0"


; Function Attrs: noinline norecurse nounwind optnone ssp uwtable

define i32 @main() #0 {

  %1 = alloca i32, align 4

  store i32 0, i32* %1, align 4

  ret i32 0

}


attributes #0 = { noinline norecurse nounwind optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="non-leaf" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "probe-stack"="__chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="apple-a12" "target-features"="+aes,+crc,+crypto,+fp-armv8,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+v8.3a,+zcm,+zcz" "unsafe-fp-math"="false" "use-soft-float"="false" }


!llvm.module.flags = !{!0, !1, !2}

!llvm.ident = !{!3}


!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 11, i32 3]}

!1 = !{i32 1, !"wchar_size", i32 4}

!2 = !{i32 7, !"PIC Level", i32 2}

!3 = !{!"Apple clang version 12.0.5 (clang-1205.0.22.9)"}



解释一下,llvm的ir文件注释;打头


target datalayout中e表示小端m:o表示elf的name mangling(为了保证名字的唯一性引入的重编码技术),i64:64 abi对齐字节对于i64的类型,同理i128:128,n32:64原生的整型,S128栈空间的对齐量


target triple = 

"arm64-apple-macosx11.0.0"


arm64架构,apple供应商,macosx11.0.0系统以及abi,来自M1的mac。


1.LLVM IR的命名习惯有两种%数字(%1), %变量名(%name)

2.LLVM IR的做了无限寄存器的假设

3.LLVM IR是一个typed language,任何一个语句都会带类型,比如%1 = alloca i32, align 4分配一个i32的local变量,另外这句话其实是通过CreateAlloca函数生成的

4.LLVM IR不允许任何的隐式转换


Bad example

example


最后如何将ll文件最后编译为可执行的文件呢



clang main.ll -o main

./main




LLVM IR BasicBlock

Fig 1.


我们还是通过Fig.1进行解释,整个ll文件是为了计算阶乘产生的。基本的解释就是判断args是不是等于0,是的话返回为1,不是的话将val减去1然后进行递归计算。注意block一定要有ret,block会一个一个执行下去如果不发生跳转的话


1.br

br就是branch,用来进行分支的跳转,每个label相当于一个整个block的别名


2. ret

进行返回,即return

为了方便进行跳转条件的可视化,SSA里面的条件也可以转化为CFG通过命令opt –analyze –dot-cfg x.ll 如Fig2可见

Fig 2.


注意的是每个函数都有一个隐藏的label entry入口


LLVM PHI语句

PHI语句可以说是LLVM的灵魂。

为什么LLVM里面需要有PHI指令呢,由于上次讲到LLVM的IR是基于SSA,每个变量只能被赋值一次,因此PHI指令用来处理一些带条件的跳转情况是非常好用了。

我们这里还是举一个例子,来自于phi_example上面,感谢提供的例子。我们依旧插入一段简单的代码。




编译最后得到ll文件,打开之后大体是以下的形式(省略了attribute)



我们可以分析 Block%13的第一句话,就是llvm-phi指令


%14 = phi i1 [ true, %2 ], [ %12, %10 ]


因为我们想对%14赋不同的值在不同的情况下,如果%14的前驱节点来自于%2就是entry函数则意味着r的值是true,那么%14就是true,如果来自于block %10,那么说明r就是false,那么这时候%14的值就由l的值确定,也就是IR中的%12的值确定。


上述的描述其实在高级语言中很简单,转化为伪代码就是



但是在这个时候由于SSA的原因%14只能被赋值一次,所以继而引入了phi指令。简单来说,就是根据当前block的前驱block(preprocessor)来决定当前的值。

phi指令


当然了,phi指令的用处更广,在这个例子中你也可以用if等改写,tutorial中还介绍了一种情况下,phi语句可以保证变量的更新,大家可以想象一下在SSA中循环体内,怎么根据条件更新某个变量,感兴趣的话也可以去原始的tutorial里面品味一下。最后tutorial里面还提供一些欺骗SSA的方式, see godbolt_example


LLVM Type and GEP

最后tutorial介绍了llvm的类型系统比较泛泛的讲了,另外就是描述了GEP,就是llvm里面怎么对指针进行操作,注意GEP是不允许对内存进行操作。




getelementptr->GEP, GEP用处可以再对指针进行索引,如果你想得到指针指向的地址的内容,如果进行加载load

C/C++ API

llvm::IRBuilderBase::CreatePHI()

The Often Misunderstood GEP Instruction


Reference

LLVM Language Reference Manual

Tutorial-Bridgers-LLVM_IR_tutorial

https://stackoverflow.com/questions/11485531/what-exactly-phi-instruction-does-and-how-to-use-it-in-llvm



Welcome to discuss


往期精彩回顾

点击阅读

点击 阅读原文 加入我们吧!

【声明】内容源于网络
0
0
元戎启行招聘
深圳元戎启行科技有限公司招聘官方公众号
内容 27
粉丝 0
元戎启行招聘 深圳元戎启行科技有限公司招聘官方公众号
总阅读15
粉丝0
内容27