编写代码或构建脚本以便使用LTO进行编译时,需要记住哪些注意事项和陷阱?
这个问题背后的动机是更好地理解为什么有些项目在启用LTO时不能干净地编译。特别是,在MSVC和GCC中都无法在启用LTO的情况下构建ICU。在其他情况下,我可以使用给定的工具链版本启用LTO,但不能使用其他(较新的)版本启用LTO;例如,libiconv发生了这种情况。
在我遇到的所有失败案例中,都涉及由于未解决的符号导致的链接失败。
为什么会发生这种情况?这是工具链,构建脚本还是源代码的问题?
答案 0 :(得分:5)
这个答案总结了我在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
时,配置脚本仍会使用libtool
而不是ar
配置内部gcc-ar
,即使告诉AR=gcc-ar
1}}。
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有关。“ / p>
所有这一切都意味着由于工具链中存在一些不完善之处,使用LTO构建随机项目仍然是非常重要的。有些项目会成功建立,有些则不会。
要在MSVC(称为LTCG)中使用LTO进行编译,编译时必须使用/GL
,并且在链接时必须使用/LTCG
,这就是它。
尽管如此,当在MSVC中启用LTCG时,编译器会不发出传统的COFF对象。它会发出包含IR的special kind of object file,其标头(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构建失败。