LLVM: 多阶段优化的基础设施 第二章(翻译)

1,292 阅读9分钟

二、LLVM 系统架构

LLVM 系统围绕着多阶段编译方法而设计,本章描述 LLVM 系统各个组件的设计与接口。该编译策略的独特之处在于,在应用整个生命周期内,进行积极的优化,并保证实用性。

2.1 基于 LLVM 的编译器的高级设计

与当前编译系统相比,LLVM 系统被设计用于,在链接时、运行时、软件安装之后,进行更加复杂的转换。为了保证在实际环境中可部署性,LLVM 编译器必须能和已有的构建模式很好的集成在一起,必须在常见的场景中足够高效。这部分描述编译的整体方案,阐述如何解决这些需求。2.1 是 LLVM 系统的整体架构图:

传统编译器会把编译过程划分为两个阶段:编译和链接,每个阶段可独立地编译,只有被修改的翻译单元需要重新编译(这时需要重新链接整个程序)。传统编译器会把源代码编译成,包含机器码的目标文件(.o文件),链接器会把目标文件和库组合到一起,形成一个可执行程序。在简单系统中,链接器的作用只是组合目标文件和解析标号引用而已。

LLVM 保留了编译时和链接时划分,继续使用编译分离的优势。但 LLVM 不会将源码直接编译成机器码,其静态编译器前端后生成 LLVM 虚拟指令集(可参考 2.2)。LLVM 优化链接器(可参考 2.3)会组合这些 LLVM 目标文件,优化它们,并最终把它们集成为一个本地可执行文件,并写入磁盘。目标文件中存储虚拟指令,可在链接时进行复杂的过程间优化(这是最为高效的地方)

优化链接器写入的可执行文件,包含本地机器码,可直接在宿主机架构上执行,同时也会包含一份应用程序的 LLVM 字节码。当应用在执行时,一个运行时优化器可监视程序的执行,收集有关应用使用模式的描述信息。

从应用行为检测到的优化机会,能引发运行时再优化器动态地重新编译和再优化应用的部分(使用存储的 LLVM 字节码)。然而,某些转变对于运行时来说代价太高,LLVM 采用在机器空闲时使用离线优化器处理这样的优化:使用积极的过程间优化技术和精确的描述信息(根据当前用户的使用模式最近使用模式生产)重新编译应用。

高级 LLVM 系统设计的关键点在于,使用 LLVM 虚拟指令集在不同的工具间通信(第三章),并且 LLVM 的工具要适应标准的开发框架。使用标准的表现允许系统中不同组件直接进行转换的共享

2.2 编译时:编译前端与静态优化器

LLVM 系统支持多种语言前端,编译前端会把对应的语言翻译成 LLVM 虚拟指令集每个静态编译器,会在每个翻译单元上执行尽可能多的优化操作,降低链接时优化器的工作量。

LLVM 字节码存储在可执行文件的一个特殊的节(section)中,当运行时优化器访问它时,才会读入内存

语言前端的主要工作是,把源语言转变为 LLVM 虚拟指令集,也可以执行语言特定的优化。如一个 C 或 C++ 的前端,可以把 “printf("hello\n");” 优化为 “puts("hello");”,因为 printf 函数的高级语意是由 C 语言定义的。

所有的 LLVM 转变都是模块化的、共享的,静态编译器可以选择使用某些或是全部的转变,来提高代码生成能力。链接时优化器使用的过程间优化也是共享的可供静态编译器使用,同时过程间优化可用于,翻译单元的更多限制性的范围和链接时更大的范围(翻译的不准确)。

Note that this includes the interprocedural optimizations used by the link-time op- timizer, which may be used on the more limited scope of a translation unit as well as the larger scope at link-time. 

LLVM 虚拟机指令集设计的关键点是,通过共用的低级类型系统,支持任意源语言的能力。与高级虚拟机不同,LLVM 类型系统不指明目标模型、内存管理系统、特定异常语义。相反,LLVM 只直接支持低级类型构造函数,如指针、结构体、数组,依靠源语言把高级类型系统映射到低级类型。LLVM 因此做到了语言无关,类似于微处理器中所有的高级特性都映射到简单的结构中一样。

2.3 链接时:链接器与过程间优化器

在编译过程中,链接时第一个可获得程序的大量组件进行分析和转换的阶段。因此,LLVM 优化链接器是整个程序范围内,一个最适合进行积极的过程间优化的地方。

在链接时,共享库和系统库不可获得

LLVM 中的所有的转换操作,都被模块化。LLVM 优化链接器可以使用传统的标量优化(被静态编译器使用)来清理大规模的过程间优化。与静态编译器一样,链接时优化直接操作 LLVM 字节码,能利用编码到其中的高级信息使它们更加有效。如第 4.3 节中描述的自动池分配转换,需要 LLVM 提供的类型信息。而第 4.2 节中的数据结构分析转换,由于 SSA 使用了 LLVM 虚拟指令集,而更加精确。  

SSA

编译时和链接时优化器的设计,允许应用一种众所周知的技术,来加速过程间分析编译时可以为程序中的每个函数计算过程间摘要,并附加到 LLVM 字节码中。链接时过程间优化器,可以将这些过程间摘要作为输入进行处理,避免从头开始计算结果。当只需重新编译几个翻译单元时,这种技术减少了分析的总量,节省大量的编译时间[7]。  

链接时优化完成后,选择适合目标的代码生成器,将 LLVM 码转换为当前平台的本机代码。如果用户决定使用后置链接优化器,压缩的 LLVM 字节码的副本将包含在可执行文件本身中。将字节码直接包含在生成的可执行文件中,可以消除运行时或离线优化器,获取错误字节码的可能性。  

2.4 运行时:描述信息与重优化

LLVM 工程的一个关键目标是为运行时优化开发出一个新的策略。该策略是根据运行时收集描述信息的模型而建立,用来控制使用 LLVM 字节码进行再优化和重编译

2.4.1 运行时收集描述信息

避免传统的使用描述文件指导的优化,是 LLVM 系统的一个重要的目标。传统的基于描述文件的优化有两个缺点:描述文件记录了开发人员使用应用的模式,开发者几乎不用描述信息指导的反馈信息。使用运行时的描述信息消除了上面两个不足,同时开发者不需要做任何事情。

在这个过程中没有构建程序数据库或是延迟输入源码的编译,这消除了程序数据库与目标文件的同步问题

运行时重优化器会使用多种不同的技术收集描述信息,从 PC 采样技术[2],到路径描述[6]。在应用程序的声明周期内,运行时优化器将最终进入休眠(dormant),只有在出现强阶段行为(strong phase behaviors)发生时才改变程序。

2.4.2 LLVM 运行时优化方法

与其它基于虚拟机的系统不同,LLVM 运行时优化器,可直接在预编译的本机代码上执行轻量级的优化,同时引用 LLVM 字节码,以获取有关数据流和类型的高级信息。可通过详细的映射信息,程序的本地机器码和 LLVM 字节码表现之间的映射,开启运行时优化。

该信息允许简单的转换,如代码布局,被安全(归因于 LLVM  码的控制流信息)且高效(因为机器代码已经生成)地实现。更积极的转换(如基于值分析[8]的结果)可能修改程序的 LLVM 字节码,并根据字节码重新生成机器代码。对于中等复杂度的优化,这种方法是有用的,而对于非常昂贵的优化,需要使用离线优化器。  

2.5 系统空闲时:离线优化器

某些类型的应用不适合运行时优化:通常会有大量的代码,但是没有任何代码是高频运行的。因此,运行时优化器无法拿出足够的时间来优化任何代码段,尽管它可以检测到经常执行的代码段。

为了支持这类的应用类型和可能需要大量分析的优化,LLVM 提供了离线优化器。当程序空闲时,系统会自动执行离线优化器,进行比运行时优化器更加积极的优化。

离线优化器综合运用运行,时优化器收集到的描述信息和 LLVM 字节码,对应用进行再优化和重编译。这样既避免了与应用竞争宝贵的处理器时间,也可完成描述信息驱动的过程间优化。随着应用使用模式的改变,运行时与离线优化器协作,确保应用程序发保持最高性能。

资料

juejin.cn/post/687549…  第一章(翻译)

LLVM- An Infrastructure for Multi-Stage Optimization LATTNER 硕士论文