LLVM LTO(Link Time Optimizer) 链接时优化

Link Time Optimizer

翻译自:
https://llvm.org/docs/LinkTimeOptimization.html
https://llvm.org/docs/GoldPlugin.html

LTO Design and Implementation

1. Description

LLVM具有强大的模块间优化功能,可以在链接时使用。链接时优化(LTO)是在链接阶段执行的模块间优化的另一个名称。本文档描述了LTO优化器和链接器之间的接口和设计。

2. Design Philosophy

LLVM Link Time Optimizer在编译器工具链中进行模块间优化的同时,提供了完全的透明性。它的主要目标是让开发人员利用模块间优化,而无需对开发人员的 makefile 或构建系统进行任何重大更改。这是通过与连接器紧密集成实现的。在这个模型中,链接器将LLVM bitccode 文件视为本地目标文件,并允许它们之间混合和匹配。链接器使用共享对象libLTO来处理LLVM bitcode文件。链接器和LLVM优化器之间的紧密集成有助于实现在其他模型中不可能实现的优化。链接器输入允许优化器避免依赖保守的转义分析。

3. Example of link time optimization

以下示例说明了 LTO 的集成方法和干净界面的优势。 此示例需要一个本文档中描述的通过接口支持 LTO 的系统链接器。 在这里,clang 透明地调用系统链接器。

  • 输入源文件 a.c 被编译成 LLVM bitcode形式。
  • 输入源文件 main.c 被编译为本机目标代码。
--- 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
  • 在此示例中,链接器识别出 foo2() 是在 LLVM bitcode 文件中定义的外部可见符号。 链接器完成其通常的符号解析过程并发现 foo2() 没有在任何地方使用。 LLVM 优化器使用此信息并删除 foo2()。
  • 一旦 foo2() 被删除,优化器就会识别出条件 i < 0 始终为假,这意味着永远不会使用 foo3()。 因此,优化器也会删除 foo3()。
    这反过来又使链接器能够删除 foo4()。

此示例说明了与链接器紧密集成的优势。 在这里,优化器无法在没有链接器输入的情况下删除 foo3()。

4. Alternative Approaches

编译器驱动程序单独调用链接时间优化器。

在此模型中,链接时优化器无法利用在链接器的正常符号解析阶段收集的信息。在上面的示例中,优化器无法在没有链接器输入的情况下删除 foo2(),因为它是外部可见的。这反过来又禁止优化器删除 foo3()。

使用单独的工具从所有目标文件中收集符号信息

在这个模型中,一个新的、独立的工具或库复制了链接器收集信息以进行链接时间优化的能力。这种代码重复不仅难以证明其合理性,而且还有其他几个缺点。例如,链接器在各种平台上提供的链接语义和特性并不是唯一的。这意味着,这个新工具需要在一个超级工具中支持所有这些功能和平台,或者每个平台需要一个单独的工具。这显着增加了链接时间优化器的维护成本,这是不必要的。这种方法还需要与各种平台上的链接器开发保持同步,这不是链接时优化器的主要关注点。最后,由于这个单独的工具和链接器本身所做的工作重复,这种方法增加了最终用户的构建时间。

5. Multi-phase communication between libLTO and linker

链接器收集有关符号定义和在各种链接对象中使用的信息,这比其他工具在典型构建周期中收集的任何信息都更准确。 链接器通过查看本地 .o 文件中符号的定义和使用以及使用符号可见性信息来收集此信息。 链接器还使用用户提供的信息,例如导出符号列表。

LLVM 优化器收集控制流信息、数据流信息,并从优化器的角度了解更多关于程序结构的信息。

我们的目标是通过在各个链接阶段共享这些信息来利用链接器和优化器之间的紧密集成。

Phase 1 : Read LLVM Bitcode Files

链接器首先按自然顺序读取所有目标文件并收集符号信息。 这包括本机目标文件以及 LLVM bitcode 文件。 为了在所有 .o 文件都是本机目标文件的情况下最小化链接器的成本,链接器仅在发现提供的目标文件不是本机目标文件时才调用 lto_module_create()。 如果 lto_module_create() 返回文件是 LLVM bitcode文件,则链接器然后使用 lto_module_get_symbol_name()lto_module_get_symbol_attribute() 迭代模块以获取定义和引用的所有符号。 该信息被添加到链接器的全局符号表中。

lto* 函数都在共享对象 libLTO 中实现。 这允许独立于链接器工具更新 LLVM LTO 代码。 在支持它的平台上,共享对象是延迟加载的。

Phase 2 : Symbol Resolution

在这一阶段,链接器使用全局符号表解析符号。它可能报告未定义的符号错误,读取存档成员,替换弱符号等。即使链接器不知道输入LLVM bitcode文件的确切内容,它也能够无缝地做到这一点。如果启用了死代码剥离,那么链接器只收集活符号的列表。

Phase 3 : Optimize Bitcode Files

在解析符号之后,链接器告诉LTO共享对象本地对象文件需要哪些符号。在上面的例子中,链接器报告本地对象文件使用lto_codegen_add_must_preserve_symbol() 只使用了foo1()。接下来,链接器使用 lto_codegen_compile() 调用LLVM优化器和代码生成器,lto_codegen_compile() 返回一个通过合并LLVM bitcode 文件和应用各种优化 passes 创建的本地对象文件。

Phase 4 : Symbol Resolution after optimization

在此阶段,链接器读取优化的本机对象文件并更新内部全局符号表以反映任何更改。链接器还通过LLVM bitcode文件收集关于使用外部符号的任何更改的信息。在上面的例子中,链接器注意到foo4()不再被使用。如果激活了死代码剥离,那么连接器将适当地刷新活动符号信息并执行死代码剥离。

在这个阶段之后,链接器继续链接,就好像它从来没有看到LLVM位码文件一样。

6. libLTO

libLTO是一个共享对象,是LLVM工具的一部分,旨在供链接器使用。libLTO提供了一个抽象的C接口来使用LLVM的过程间优化器,而不暴露LLVM的内部细节。目的是保持接口尽可能稳定,即使在LLVM优化器继续发展的情况下。甚至可以使用完全不同的编译技术来提供不同的libLTO来处理它们的目标文件和标准链接器工具。

7. lto_module_t

非本机对象文件是通过 lto_module_t处理的。以下函数允许链接器检查一个文件(在磁盘上或内存缓冲区中)是否是libLTO可以处理的文件:

lto_module_is_object_file(const char*)
lto_module_is_object_file_for_target(const char*, const char*)
lto_module_is_object_file_in_memory(const void*, size_t)
lto_module_is_object_file_in_memory_for_target(const void*, size_t, const char*)

如果对象文件可以被libLTO处理,链接器通过使用以下方法之一创建 lto_module_t:

lto_module_create(const char*)
lto_module_create_from_memory(const void*, size_t)

完成后, 通过以下方法释放

lto_module_dispose(lto_module_t)

链接器可以通过获取符号的数量和每个符号的名称和属性来审查非本机对象文件:

lto_module_get_num_symbols(lto_module_t)
lto_module_get_symbol_name(lto_module_t, unsigned int)
lto_module_get_symbol_attribute(lto_module_t, unsigned int)

符号的属性包括 alignment, visibility, kind

在Darwin上使用目标文件的工具(例如lipo)需要知道CPU类型等属性:

lto_module_get_macho_cputype(lto_module_t mod, unsigned int *out_cputype, unsigned int *out_cpusubtype)

8. lto_code_gen_t

一旦链接器将每个非本机对象文件加载到lto_module_t中,它就可以请求libLTO处理所有这些文件并生成本机对象文件。这需要几个步骤来完成。首先,创建一个代码生成器:

lto_codegen_create()

然后,将每个非本机对象文件添加到代码生成器中:

lto_codegen_add_module(lto_code_gen_t, lto_module_t)

然后,链接器可以选择设置一些代码生成选项。是否生成DWARF debug信息设置为:

lto_codegen_set_debug_model(lto_code_gen_t)

设置哪种位置独立性:

lto_codegen_set_pic_model(lto_code_gen_t)

每个被本地对象文件引用或不能被优化掉的符号都设置为:

lto_codegen_add_must_preserve_symbol(lto_code_gen_t, const char*)

所有这些设置完成后,链接器请求从模块中创建一个本地对象文件,使用以下设置:

lto_codegen_compile(lto_code_gen_t, size*)

它返回一个指向包含生成的本机对象文件的缓冲区的指针。然后链接器对其进行解析,并将其与其余的本机对象文件链接起来。

LLVM gold plugin 插件模式实现 LTO

1. Introduction

使用链接时间优化进行构建需要系统链接器的合作。 Linux 系统上的 LTO 可通过 gold linker 实现,该链接器通过插件支持 LTO。 这与 GCC LTO 项目使用的机制相同。

LLVM gold 插件在 libLTO 之上实现了gold 插件接口。 其他工具也可以使用相同的插件,例如 ar 和 nm。 请注意,来自 binutils 版本 2.21.51.0.2 及更高版本的 ld.bfd 也通过插件支持 LTO。 但是,LLVM gold 插件与 ld.bfd 的使用未经测试,因此不被官方支持或推荐。

从 LLVM 15 开始,gold 插件将忽略 ELF 对象文件中 .llvmbc 部分的 bitcode。 但是,仍然支持带有 bitcode 文件的 LTO。

2. How to build it

你需要有插件支持的 gold 并构建 LLVMgold 插件。 gold linker 安装为 ld.gold。 要查看gold是否是您系统上的默认设置,请运行 /usr/bin/ld -v,如果是,它将报告“GNU gold”,如果不是“GNU ld”。

如果 gold 已经安装在 /usr/bin/ld.gold,一个选择是通过备份现有的 /usr/bin/ld 并使用 ln -s /usr/bin/ld.gold /usr/bin/ld创建一个符号链接来简单地将其设置为默认值 。

或者,您可以使用 clang 的 -fuse-ld=gold 构建或将 -fuse-ld=gold 添加到 LDFLAGS,这将导致 clang 驱动程序直接调用 /usr/bin/ld.gold。

如果您安装了 gold,请通过运行 /usr/bin/ld.gold -plugin 检查插件支持。 如果它报 “missing argument” ,那么你有插件支持。 如果没有,并且您收到诸如“unknown option”之类的错误,那么您将需要构建 gold 或安装具有插件支持的版本。

  • 下载、配置以及构建有插件支持的 gold
$ git clone --depth 1 git://sourceware.org/git/binutils-gdb.git binutils
$ mkdir build
$ cd build
$ ../binutils/configure --enable-gold --enable-plugins --disable-werror
$ make all-gold

这应该让您拥有支持 -plugin 选项的 build/gold/ld-new。 运行 make 将另外构建支持插件的 build/binutils/ar 和 nm-new 二进制文件。

一旦你准备好切换到使用 gold,备份你现有的 /usr/bin/ld 然后用 ld-new 替换它。 或者,安装在 /usr/bin/ld.gold 中并使用 -fuse-ld=gold,如前所述。

或者,将 --enable-gold=default 添加到上述condigure 中,以使用 make install 自动安装新建的gold作为默认链接器。

构建 LLVMgold 插件。 使用 -DLLVM_BINUTILS_INCDIR=/path/to/binutils/include 运行 CMake。 正确的 include 路径将包含文件 plugin-api.h。

3. Usage

您应该使用-flto选项从clang生成 bitcode 文件。这个标志还会导致clang在它的 prefix 下的 lib目录中查找gold插件,并将 -plugin 选项传递给ld。如果没有 -fuse-ld=gold,它就不会查找替代链接器,这就是为什么你需要在路径中安装gold作为系统链接器的原因。

ar和nm也接受-plugin选项,并且可以安装LLVMgold。所以到 /usr/lib/bfd-plugins 进行无缝设置。如果您构建了自己的gold,请确保将您构建的ar和nm-new安装到/usr/bin中。

4. Example of link time optimization

下面的示例展示了 gold 插件链接LLVM bitcode 和本机代码的工作示例。

--- a.c ---
#include <stdio.h>

extern void foo1(void);
extern void foo4(void);

void foo2(void) {
  printf("Foo2\n");
}

void foo3(void) {
  foo4();
}

int main(void) {
  foo1();
}


--- b.c ---
#include <stdio.h>

extern void foo2(void);

void foo1(void) {
  foo2();
}

void foo4(void) {
  printf("Foo4");
}
--- command lines ---
$ clang -flto a.c -c -o a.o      # <-- a.o is LLVM bitcode file
$ ar q a.a a.o                   # <-- a.a is an archive with LLVM bitcode
$ clang b.c -c -o b.o            # <-- b.o is native object file
$ clang -flto a.a b.o -o main    # <-- link with LLVMgold plugin

Gold通知插件foo3从未在IR之外被引用,导致LLVM删除该函数。然而,与libLTO例子不同的是,gold目前并不排除foo4。

5. Quickstart for using LTO with autotooled projects

一旦你的系统ld, ar和nm都支持LLVM位码,一切都准备好了,可以轻松使用LTO构建自动化工具项目:

  • 根据如何构建LLVMgold.so的说明 https://llvm.org/docs/GoldPlugin.html#lto-how-to-build
  • 将新构建的binutils安装到 $PREFIX
  • 复制 Release/ lib / LLVMgold 到 $PREFIX/ lib / bfd-plugins /
  • 设置环境变量($PREFIX是安装clang和binutils的地方):
$ export CC="$PREFIX/bin/clang -flto"
$ export CXX="$PREFIX/bin/clang++ -flto"
$ export AR="$PREFIX/bin/ar"
$ export NM="$PREFIX/bin/nm"
$ export RANLIB=/bin/true #ranlib is not needed, and doesn't support .bc files in .a

或者你可以设置你的路径:

$ export PATH="$PREFIX/bin:$PATH"
$ export CC="clang -flto"
$ export CXX="clang++ -flto"
$ export RANLIB=/bin/true

像往常一样配置和构建项目:

$ ./configure && make && make check

环境变量设置可能也适用于非自动工具项目,但您可能还需要设置LD环境变量。

6. Licensing

gold 是根据GPLv3授权的。LLVMgold使用来自gold的接口文件plugin-api.h,这意味着生成的LLVMgold。所以二进制也是GPLv3。这仍然可以用来链接非gplv3程序,就像没有插件的gold一样。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值