最近又重温了一把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

先筹备一个小例子

// filename main.cppint main() {  return 0;}//clang -S -emit-llvm main.cpp//能够看到了产生了ir文件main.ll

关上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 uwtabledefine 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的别名

  1. ret
    进行返回,即return
    为了不便进行跳转条件的可视化,SSA外面的条件也能够转化为CFG通过命令opt –analyze –dot-cfg x.ll 如Fig2可见

    Fig2
    留神的是每个函数都有一个暗藏的label entry入口
     

    LLVM PHI语句

    PHI语句能够说是LLVM的灵魂.
    为什么LLVM外面须要有PHI指令呢,因为上次讲到LLVM的IR是基于SSA,每个变量只能被赋值一次,因而PHI指令用来解决一些带条件的跳转状况是十分好用了。
    咱们这里还是举一个例子,来自于phi_example下面,感激提供的例子。咱们仍旧插入一段简略的代码。

// phi.cpp clang phi.cpp -emit-llvm -S  -o phi.ll void m(bool r, bool y){  bool l = y || r ;}// 实践上来说l就两种后果0,1,当然了,这段代码咱们也能够用上面的if,// 然而这样生成进去的ir文件就不一样了 // phi2.cppvoid m2(bool r, bool y){  bool l = false;  if (r)    l = true;  if (l)    l = true;}

编译最初失去ll文件,关上之后大体是以下的模式(省略了attribute)

; Function Attrs: noinline nounwind optnone ssp uwtabledefine void @_Z1mbb(i1 zeroext %0, i1 zeroext %1) #0 {  %3 = alloca i8, align 1  %4 = alloca i8, align 1  %5 = alloca i8, align 1  %6 = zext i1 %0 to i8  store i8 %6, i8* %3, align 1  %7 = zext i1 %1 to i8  store i8 %7, i8* %4, align 1  %8 = load i8, i8* %4, align 1  %9 = trunc i8 %8 to i1  br i1 %9, label %13, label %10 10:                                               ; preds = %2  %11 = load i8, i8* %3, align 1  %12 = trunc i8 %11 to i1  br label %13 13:                                               ; preds = %10, %2  %14 = phi i1 [ true, %2 ], [ %12, %10 ]  %15 = zext i1 %14 to i8  store i8 %15, i8* %5, align 1  ret void}

咱们能够剖析 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的值确定。
上述的形容其实在高级语言中很简略,转化为伪代码就是

r = load arg1l = load arg0if l:  %14 = true;else:  if l:    %14 = true;  else:    $14 = false;save %14 to %out

然而在这个时候因为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是不容许对内存进行操作。

 

void m3(int* a){  int s = a[0];}// ir 文件define void @_Z2m3Pi(i32* %0) #0 {  %2 = alloca i32*, align 8  %3 = alloca i32, align 4  store i32* %0, i32** %2, align 8  %4 = load i32*, i32** %2, align 8  %5 = getelementptr inbounds i32, i32* %4, i64 0  %6 = load i32, i32* %5, align 4  store i32 %6, i32* %3, align 4  ret void}

 
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/quest

对于DeepRoute Lab

深圳元戎启行科技有限公司(DEEPROUTE.AI)是一家专一于研发 L4级主动驾驶技术的科技公司,聚焦出行和同城货运两大场景,领有“元启行”(Robotaxi主动驾驶乘用车)和“元启运”(Robotruck主动驾驶轻卡)两大产品线。

【Deeproute Lab】是咱们开办的主动驾驶学术产业前沿常识共享平台。咱们将会把公司外部的paper reading分享在这里,让你轻松读懂paper;咱们也会在这里分享咱们对行业的了解,期待越来越多的同学意识主动驾驶,退出这个行业!