LLVM ==== .. figure:: https://img.zhaoweiguo.com/knowledge/images/cores/compilers/LLVM_logo.png caption LLVM 元素:: 1. 模块 LLVM 程序是由模块构成的,这个文件就是一个模块。 模块里可以包括函数、全局变量和符号表中的条目。 链接的时候,会把各个模块拼接到一起,形成可执行文件或库文件。 在模块中,你可以定义目标数据布局(target datalayout): 例: target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 说明: 开头的小写 “e” 是低字节序(Little Endian)的意思 对超过一字节的数据,低位字节排放在内存的低地址端,高位字节排放在内存的高地址端 “target triple” 用来定义模块的目标主机,它包括架构、厂商、操作系统三个部分 例: target triple = "x86_64-apple-macosx10.14.0" 2. 函数 以 define 开头的函数的声明,还带着花括号 函数声明时可以带很多修饰成分,比如链接类型、调用约定等 缺省值: 链接类型是 external 调用约定也有很多种选择,缺省是 “ccc” C 语言的调用约定(C Calling Convention) “swiftcc” 则是 swift 语言的调用约定 3. 标识符 分为全局的(Glocal)和本地的(Local) 全局标识符以 @开头,包括函数和全局变量 本地标识符以 % 开头 有的标识符是有名字的,比如 @fun1 或 % a 有的是没有名字的,用数字表示就可以了,如 %1 4. 操作码 alloca: 栈上分配空间 store: 写入内存 load: 从内存中读取 add: 加法运算 ret: 从过程中返回 5. 类型系统 汇编是无类型的,LLVM 汇编则带有一个类型系统 LLVM 汇编类型系统包括`基础数据类型`、`函数类型`和 `void 类型` 6. 全局变量和常量 7. 元数据 8. 基本块 类型系统 -------- :: 1. 基础数据类型 2. 函数类型 包括对返回值和参数的定义,比如:i32 (i32) 3. void 类型 不代表任何值,也没有长度 1. 基础数据类型:: a. iN 表示各种长度的整形 1) i1: 1比特整形 2) i32: 32位整形 b. 多种精度的浮点型 1) half: 16位浮点 2) float: 32位浮点 3) double: 64位浮点 4) fp128: 128位浮点 c. 指针: 1) [4*i32]*: 指向4个i32整形的数组指针 2) i32(i32*)*: 函数指针, 一个 i32整形指针参数, 返回一个i32值 d. 向量 1) <4 x float>: 4个浮点数的向量 f. 数组 1) [4 x i32]: 4个 i32整数的数组 g. 结点体 1) 普通结构体: {float, i32(i32)*} 两个元素的结构体,一个浮点数,一个整数 2) 紧凑结构体: <{i8, i32}>: 比普通结构体多了一个尖括号 它的元素在存储时是紧挨着的,不考虑内存对齐 这个结构体占40比特 i. 其他 1) 标签类型 2) Token 类型 3) 元数据类型 小例子 ------ ``fun1.c``:: int fun1(int a, int b){ int c = 10; return a + b + c; } 自动生成的LLLM 汇编码:: ; ModuleID = 'fun1.c' source_filename = "fun1.c" target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx10.14.0" ; Function Attrs: noinline nounwind optnone ssp uwtable define i32 @fun1(i32, i32) #0 { %3 = alloca i32, align 4 // 为 3 个变量申请空间 %4 = alloca i32, align 4 %5 = alloca i32, align 4 store i32 %0, i32* %3, align 4 // 参数 1 赋值给变量 1 store i32 %1, i32* %4, align 4 // 参数 2 赋值给变量 2 store i32 10, i32* %5, align 4 // 常量 10 赋值给变量 3 %6 = load i32, i32* %3, align 4 // %7 = load i32, i32* %4, align 4 %8 = add nsw i32 %6, %7 %9 = load i32, i32* %5, align 4 %10 = add nsw i32 %8, %9 ret i32 %10 } attributes #0 = { noinline nounwind optnone ssp 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"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "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 10, i32 14]} !1 = !{i32 1, !"wchar_size", i32 4} !2 = !{i32 7, !"PIC Level", i32 2} !3 = !{!"Apple LLVM version 10.0.1 (clang-1001.0.46.4)"} 优化后的LLLM 汇编码:: define i32 @fun1(i32, i32) local_unnamed_addr #0 { %3 = add i32 %0, 10 %4 = add i32 %3, %1 ret i32 %4 } 后端技术的重用 -------------- 两个编译器后端框架:: 1. LLVM: 开源的编译器基础设施项目,主要聚焦于编译器的后端功能(代码生成、代码优化、JIT……) 它最早是美国伊利诺伊大学的一个研究性项目 核心主持人员是 Chris Lattner(克里斯・拉特纳) a. 编译器 Clang 就是基于 LLVM 的 最新的 Swift 编程语言也是基于 LLVM,支撑了无数的移动应用和桌面应用 b. 开发语言 Kotlin,也支持基于 LLVM 编译成本地代码 c. Mozilla 公司开发的系统级编程语言 RUST d. 一门相对小众的科学计算领域的语言,叫做 Julia 它既能像脚本语言一样灵活易用,又可以具有 C 语言一样的速度 在数据计算方面又有特别的优化 e. OpenGL 和一些图像处理领域 f. TensorFlow 框架,在后端也是用 LLVM 来编译 把机器学习的 IR 翻译成 LLVM 的 IR,然后再翻译成支持 CPU、GPU 和 TPU 的程序 2. GCC: LLVM 优点:: 1. LLVM 有良好的模块化设计和接口 2. LLVM 同时支持 JIT(即时编译)和 AOT(提前编译)两种模式 3. 有很多可以学习借鉴的项目。Swift、Rust、Julia 4. 全过程优化的设计思想 Lattner 和 Adve 最早关于 LLVM 设计思想的文章《LLVM: 一个全生命周期分析和转换的编译框架》 5. LLVM 的授权更友好