深入理解LLVM:代码生成
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.1 LLVM设计思路分析

LLVM项目起源于伊利诺伊大学香槟分校的研究型项目,在2000年由Chris Lattner和其导师Vikram Adve发起,并于2003年正式开源并发布1.0版本。2002年,Lattner在其硕士论文“LLVM: AN INFRASTRUCTURE FOR MULTI-STAGE OPTIMIZATION”中详细介绍了LLVM的设计思路,本节将简单总结这一思路。

LLVM的愿景是实现一个编译器的基础设施,能适配现代编程语言、硬件架构发展,它有3个目标。

1)具备多阶段优化能力(如过程内优化、过程间优化、配置文件驱动的优化),保证程序执行性能足够高。

2)提供基础机制,方便进行编译器研发。

3)兼容标准系统编译器的行为。

为了达到这些目标,LLVM设计了一套虚拟指令集,称为LLVM IR。虽然LLVM IR是低级的中间表示,但是它携带了程序的类型信息,这样的IR设计既方便了静态编译优化,又允许在链接时进行优化。Lattner设想在链接优化完成后生成的二进制文件中,既可以包含可执行代码,又可以包含IR,其中IR可以用于后续的JIT优化[1]。Lattner还设想在LLVM中提供运行时优化,通过监控程序的执行过程来收集反馈信息(profile information)并用于指导程序优化[2]

LLVM编译器整体架构图如图1-1所示。

图1-1 LLVM编译器整体架构图

从图1-1中可以看到,LLVM编译优化策略和程序的“编译-链接-执行”模式完全匹配,在编译期、链接期、执行期都可以进行优化。和其他编译器不同的是:LLVM借助了LLVM IR,大量的优化工作都是围绕LLVM IR展开的,不同的优化都由独立的模块完成。

1)编译时优化:各个语言的编译器前端将代码翻译成LLVM IR,LLVM优化器针对LLVM IR做尽可能多的优化。编译期优化大多数属于局部优化(少量优化是过程间优化),通常包含架构无关优化和架构相关优化。

2)链接时优化:编译器在编译时为函数提供过程间摘要信息,并附加到LLVM IR中,在连接时使用这些信息完成优化。

3)运行时和离线优化:基于收集的程序执行信息,再次对应用进行优化。

在这些优化工作中,LLVM IR是整个编译系统设计的关键,具有如下特点。

1)LLVM使用LLVM IR描述一个虚拟架构并捕获常规处理器的关键操作,同时消除了特定机器架构限制,如物理寄存器、流水线、调用约定、陷阱等方面的限制。

2)LLVM IR提供无限数量的类型化虚拟寄存器,并用这些寄存器来存储基础类型(如整型、浮点型、指针类型)的值。LLVM IR采用SSA形式,从而更便于进行编译优化。

3)在LLVM IR中提供了特有的指令,显式描述异常控制流信息。

4)LLVM IR约定虚拟寄存器和内存之间,仅靠load和store指令进行数据交换,交换数据时需要约定数据类型。内存被划分为全局区域、栈、堆(过程被视为全局对象),其中栈、堆上的对象分别使用alloca指令[3]和malloc指令操作分配空间,并通过这两个函数返回的指针值来访问相应的空间,栈对象在当前函数的栈帧中分配,控制流(线程)离开函数时自动释放栈对象,堆对象必须使用free指令进行显式释放。

5)LLVM IR集成了运行时和系统函数,如I/O、内存管理、信号量等的相关函数,这些函数由运行时库提供,可以被程序链接使用。同时LLVM IR提供文本、二进制、内存3种文件格式,以方便开发、存储和运行。

LLVM IR提供了各种分析和变换的Pass(Pass是指对编译对象进行一次处理,详细内容可以参考附录C),以及配套的工具集,如汇编、反汇编、解释器、优化器、编译器、测试套等相关工具,能帮助开发者快速入门和使用LLVM。