关于自动驾驶:DeepRoute-Lab-LLVM-IR-Tutorial观后感

60次阅读

共计 4802 个字符,预计需要花费 13 分钟才能阅读完成。


最近又重温了一把 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.cpp
int 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 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 的别名

  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.cpp
void m2(bool r, bool y){
  bool l = false;
  if (r)
    l = true;
  if (l)
    l = true;
}

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

; Function Attrs: noinline nounwind optnone ssp uwtable
define 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 arg1
l = load arg0
if 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;咱们也会在这里分享咱们对行业的了解,期待越来越多的同学意识主动驾驶,退出这个行业!

正文完
 0