如何编写启用 LTO 的代码?

Posted

技术标签:

【中文标题】如何编写启用 LTO 的代码?【英文标题】:How to write LTO-enabled code? 【发布时间】:2017-02-14 15:33:11 【问题描述】:

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

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

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

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

【问题讨论】:

我从未遇到过在 MSVC 中启用 LTCG 时无法编译的项目。当您尝试在 MSVC 中构建 ICU 时,您收到了哪些错误消息?当您翻转 LTCG 开关时,您用于编译优化构建的相同设置也应该起作用。事实上,LTCG 是默认开启的,用于内置项目模板中的优化构建。 封装。减少全局变量的数量。减少函数中的外部依赖。酌情使用const @CodyGray:我在链接时得到未定义的符号(_uconvmsg_dat 在某些情况下,_icudt58_dat 在其他情况下)。我为此使用了几个构建标志 (/MT /Oxy- /fp:fast /Zi),但是构建总是在没有 LTCG 的情况下通过 (/GL/LTCG)。 @CodyGray:我添加了一个答案,解释了为什么 ICU 在启用 LTCG 的情况下无法在 MSVC(32 位)中正确构建,您可能有兴趣了解原因。在这种情况下,这确实是包的缺陷,而不是工具链的缺陷。 【参考方案1】:

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

海合会

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

    确保使用gcc-ar 而不是binutils ar; 确保使用gcc-ranlib 而不是binutils ranlib; 确保使用gcc-nm 而不是binutils nm; 用-flto编译链接。

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

现在讨论问题:

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

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

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

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

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

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

MSVC

要在MSVC中使用LTO(也就是LTCG)进行编译,编译时必须使用/GL,链接时必须使用/LTCG,就是这样。

尽管如此,当在 MSVC 中启用 LTCG 时,编译器确实发出传统的 COFF 对象。相反,它发出一个 special kind of object file 包含 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 构建失败。

【讨论】:

感谢您的回答并分享您的经验。 LTO 和 Clang 以及 LLVM 工具链呢? @GeoObserver:LLVM 机制在这方面的问题较少(尽管我一直在 Linux 上专门使用 LLVM)。到此为止,用-flto编译链接就够了。

以上是关于如何编写启用 LTO 的代码?的主要内容,如果未能解决你的问题,请参考以下文章

如何编写我的 C++ 函数以便我可以从 C# 调用它?

如何编写我的 HTML 登录表单以显式启用 LastPass?

如何将 GCC LTO 与不同优化的目标文件一起使用?

如何编写 pep8 配置(pep8.rc)文件?

gcc有薄lto吗?

如何在 ReactJS 中启用悬停