2

在编写代码或构建脚本以使用 LTO 进行编译时,需要牢记哪些注意事项和陷阱?

这个问题背后的动机是更好地理解为什么某些项目在启用 LTO 时编译不干净。特别是,我无法在启用 LTO 的情况下构建ICU,无论是在 MSVC 还是在 GCC 中。在其他情况下,我可以使用给定的工具链版本启用 LTO,但不能使用另一个(更新的)版本;例如,这种情况发生在libiconv中。

在我遇到的所有失败案例中,都涉及由于未解析的符号导致的链接失败。

为什么会发生这种情况?这是工具链、构建脚本还是源代码的问题?

4

1 回答 1

8

这个答案总结了我在 GCC 和 MSVC 中构建启用 LTO 的项目时所涉及的一些复杂性的发现。

海合会

首先,根据GCC Wiki,为了正确构建启用 LTO 的项目,您必须:

  1. 确保gcc-ar使用而不是 binutils ar
  2. 确保gcc-ranlib使用而不是 binutils ranlib
  3. 确保gcc-nm使用而不是 binutils nm
  4. 编译链接-flto

这意味着在传统./configure && make循环中,必须注意设置 的值AR=RANLIB=NM=在相关时设置。就是这样。但是,这些步骤很容易被忽略,因为需要更改例如的值。AR比较少见。

现在到问题:

在 GCC 4.8 和更早的版本中,编译器默认发出胖目标文件。这意味着即使编译后工具(链接器、归档器等)无法识别 LTO 对象,它们也会正常工作(但不会实际执行 LTO)。

在 GCC 4.9 及更高版本中,编译器默认发出 slim 对象文件,这意味着编译后工具必须识别 LTO 对象,否则工具将失败。这解释了为什么有时 LTO 构建在使用 GCC 4.8 时会通过,但在使用 GCC 4.9 及更高版本时会失败。

我还注意到构建脚本并不总是在需要时将某些配置指令的值正确传递给子脚本。例如,当libiconv在 MinGW-w64 中使用 LTO 构建静态时,配置脚本仍然libtool使用ar而不是配置内部gcc-ar,即使被告知AR=gcc-ar.

LTO 构建倾向于发现隐藏的错误,特别是由静态 init order fiasco引起的错误。它们还可能妨碍其他优化,例如 ICF(由 Gold 执行)。

最后,显然 LTO 机器中仍然存在许多错误。在尝试使用启用了 LTO 和其他优化的 MinGW-w64 编译 ICU 时,我遇到了这个错误和内部编译器错误(internal compiler error: in splice_child_die, at dwarf2out.c可能与使用-gLTO 有关)。

所有这一切都意味着,由于工具链中的一些缺陷,使用 LTO 构建随机项目仍然不是一件容易的事。有些项目会成功构建,有些则不会。

MSVC

在MSVC中用LTO编译(称为LTCG),/GL编译时/LTCG必须使用,链接时必须使用,原来如此。

尽管如此,当在 MSVC 中启用 LTCG 时,编译器不会发出传统的 COFF 对象。相反,它发出一种特殊类型的目标文件,其中包含 IR,其标头 ( ANON_OBJECT_HEADER_BIGOBJ) 与 COFF 标头 ( IMAGE_FILE_HEADER) 不同。这在构建项目时显然应该没有什么区别,因为这些细节留给工具链处理。

现在,为什么在 MSVC 中启用 LTCG 时 ICU 不能正确构建?

ICU 有一个称为pkgdata为给定架构生成目标代码的工具。在构建过程中,该工具用于构建包中的其他实用程序。但是,pkgdata尝试通过检查给定的参考对象文件来猜测目标架构。在 Windows 中,该工具采用 COFF 标头,在 32 位构建中,它错误地确定目标是 64 位架构(由于内部的草率逻辑pkg_genc.c:getArchitecture())。因此,MSVC 32 位 LTCG 构建失败。

于 2017-02-21T19:50:20.207 回答