本文是LLVM连接时优化文档的笔记。
1.LTO:链接时优化
链接时优化就是中链接时进行模块间的优化,在编译阶段生成bc字节码文件,链接时将所有的bc文件合并成一个单一的模块,进行IPA和IPO,最后代码生成,产生可执行文件。
链接器使用libLTO(共享对象)来处理LLVM位代码文件。
2.链接时间优化示例
通过系统链接器,可以用文档中描述的接口支持LTO,clang将透明地调用系统链接器。
·输入源文件a.c被编译为LLVM位代码形式。
·输入源文件main.c被编译为本机目标代码。
·foo2()将被识别为LLVM位代码中定义的外部可见符号,当链接器完成符号解析过程,发现foo2()未被使用,LLVM优化器将得到这个信息并删除它。
·foo2()被删除,优化程序会发现条件i<0永远无法达成,所以data=foo3()也不该被调用,所以foo3()也会被删除。
·同样地,优化程序也会删除foo4()。
--- a.h ---
extern int foo1(void);
extern void foo2(void);
extern void foo4(void);
--- a.c ---
#include "a.h"
static signed int i = 0;
void foo2(void) {
i = -1;
}
static int foo3() {
foo4();
return 10;
}
int foo1(void) {
int data = 0;
if (i < 0)
data = foo3();
data = data + 42;
return data;
}
--- main.c ---
#include <stdio.h>
#include "a.h"
void foo4(void) {
printf("Hi\n");
}
int main() {
return foo1();
}
编译时可以运行:
% clang -flto -c a.c -o a.o # <-- a.o is LLVM bitcode file
% clang -c main.c -o main.o # <-- main.o is native object file
% clang -flto a.o main.o -o main # <-- standard link command with -flto
3.libLTO是什么?
链接器可以使用libLTO,它是LLVM中的的共享对象。libLTO会提供抽象C接口,这样用户使用LLVM过程优化器的同时不会暴露LLVM内部接口。
4.libLTO与linker链接器的多阶段通信
链接器会收集符号定义相关的信息,供链接时各种对象使用。LLVM优化器收集控制流信息,数据流信息。
在各个链接阶段共享这些信息(多阶段通信)可以时链接器和优化器更加紧密地合作。
- 读取LLVM bitcode文件
链接器读取符号信息的文件包括LLVM的bitcode文件。为了减少开销,当读取的文件不是原生目标文件,链接器仅调用lto_module_create(),如果lto_module_create()返回指明该文件是LLVM的bitcode文件,链接器将使用lto_module_get_symbol_name()和lto_module_get_symbol_attribute()重新载入,获取符号信息并存入全局符号表。
- 符号处理
链接器用全局符号表处理符号,如果有符号未定义这时会报错,还会替换弱符号之类的。
- 优化bitcode文件
符号解析后,链接器告诉LTO共享库哪些符号将在原生的目标文件中用到。在2中的例子,链接器会通过函数lto_codegen_add_must_preserve_symbol()向LTO共享库提供信息:只有foo1()将用到。接下来链接器会使用lto_codegen_compile()调用LLVM优化器和代码生成器,合并bitcode文件并应用各种优化,最后返回原生目标文件。
- 优化后的符号处理
链接器将读取优化后的原生目标文件,更新内部全局符号表,响应所有更改。同时链接器还会收集LLVM位文件外部符号的改变信息,就像链接器会注意到foo4()不会用到,如果死代码剥离被启用了,链接器会刷新活跃的符号信息并执行死代码剥离。
之后,链接器继续进行正常的链接操作,与bitcode文件的结合并不会影响到后续的链接。
具体libLTO的函数。
ThinLTO
ThinLTO在编译阶段生成bc字节码文件(含有每个函数的函数汇总信息),链接时将所有summary合并,链接在一个大索引thin-link中,同时进行全局分析,确定importing函数。ThinLTO-backend进行函数importing,然后优化和代码生成,最后还是调用传统的linker产生可执行代码。
LTO进行link时,进行全面的IPA和IPO。占用内存大,时间长,无法并行。ThinLTO进行link时,仅仅进行有限的函数importing分析,它与传统编译链接方式所占用的内存和时间差不多,但又进行了全局函数importing分析。
ThinLTO分为3个阶段:
-
编译:像完整LTO模式那样生成IR,不过以模块汇总扩展
-
Thin链接:Thin链接器插件层来合并汇总信息以及执行全局分析
-
ThinLTO后端:使用基于汇总信息的导入与优化的并行后端
Reference: