GCC vs Clang:将功能与-FPIC一起插入
gcc vs clang: inlining a function with -fPIC
考虑此代码:
// foo.cxx
int last;
int next() {
return ++last;
}
int index(int scale) {
return next() << scale;
}
用GCC 7.2:
编译时$ g++ -std=c++11 -O3 -fPIC
这发出:
next():
movq last@GOTPCREL(%rip), %rdx
movl (%rdx), %eax
addl $1, %eax
movl %eax, (%rdx)
ret
index(int):
pushq %rbx
movl %edi, %ebx
call next()@PLT ## next() not inlined, call through PLT
movl %ebx, %ecx
sall %cl, %eax
popq %rbx
ret
但是,使用clang 3.9改用相同标志的相同代码时:
next(): # @next()
movq last@GOTPCREL(%rip), %rcx
movl (%rcx), %eax
incl %eax
movl %eax, (%rcx)
retq
index(int): # @index(int)
movq last@GOTPCREL(%rip), %rcx
movl (%rcx), %eax
incl %eax ## next() was inlined!
movl %eax, (%rcx)
movl %edi, %ecx
shll %cl, %eax
retq
gcc通过plt调用 next()
,clang键。两者仍然从GOT查找last
。对于Linux上的编译,Clang是否有权使该优化和GCC在轻松的内部丢失,或者Clang进行优化是错误的,还是纯粹是QOI问题?
我认为标准不符合那么多细节。它仅说,如果符号在不同的翻译单元中具有外部链接,则是相同的符号。这使Clang的版本正确。
从那时起,到我所知,我们都不是标准了。编译器的选择在他们认为有用的-fPIC
输出方面有所不同。
请注意,g++ -c -std=c++11 -O3 -fPIE
输出:
0000000000000000 <_Z4nextv>:
0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 6 <_Z4nextv+0x6>
6: 83 c0 01 add $0x1,%eax
9: 89 05 00 00 00 00 mov %eax,0x0(%rip) # f <_Z4nextv+0xf>
f: c3 retq
0000000000000010 <_Z5indexi>:
10: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 16 <_Z5indexi+0x6>
16: 89 f9 mov %edi,%ecx
18: 83 c0 01 add $0x1,%eax
1b: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 21 <_Z5indexi+0x11>
21: d3 e0 shl %cl,%eax
23: c3 retq
SO GCC do 知道如何优化它。它只是选择使用-fPIC
时不选择。但为什么?我只能看到一个解释:使在动态链接过程中覆盖符号并始终如一地看到效果成为可能。该技术称为符号插入。
在共享库中,如果index
调用next
,因为next
在全球范围内可见,GCC必须考虑可以插入next
的可能性。因此,它使用了PLT。但是,当使用-fPIE
时,不允许您插入符号,因此GCC可以启用优化。
那么clang错误吗?否。但是GCC似乎为符号插入提供了更好的支持,这对于仪器代码很方便。如果有人使用-fPIC
而不是-fPIE
来构建他的可执行文件,则以某些开销为代价。
其他注释:
在此帖子结尾处的GCC开发人员之一的此博客条目中:
在比较一些基准与Clang的基准时,我注意到Clang实际上忽略了精灵的插入规则。虽然是错误,但我决定将
-fno-semantic-interposition
标志添加到GCC中以获得类似的行为。如果插入不是可取的,精灵的官方答案是使用隐藏的可见性,如果需要导出符号,则定义一个别名。这并不总是可以用手做的。
之后,导致我将我降落在X86-64 ABI规格上。在第3.5.5节中,它确实要求所有调用全球可见符号的函数必须通过PLT(它可以根据内存模型定义要使用的确切指令序列)。
因此,尽管它不违反C 标准,但忽略语义插入似乎违反了ABI。
最后一句话:不知道该放在哪里,但您可能会感兴趣。我会为您提供转储,但是我的测试和编译器选项显示:
在GCC方面:
-
gcc -fPIC
:访问last
的访问通过GOT,呼叫next()
通过PLT。 -
gcc -fPIC -fno-semantic-interposition
:last
通过GOT,next()
被镶嵌。 -
gcc -fPIE
:last
是IP相关性,next()
被镶嵌。 -
-fPIE
含义-fno-semantic-interposition
在事物的叮当声中:
-
clang -fPIC
:last
通过got,next()
被镶嵌。 -
clang -fPIE
:last
通过got,next()
被镶嵌。
和一个修改后的版本,将两个编译器上的编译为IP层次:
// foo.cxx
int last_ __attribute__((visibility("hidden")));
extern int last __attribute__((alias("last_")));
int __attribute__((visibility("hidden"))) next_()
{
return ++last_;
}
// This one is ugly, because alias needs the mangled name. Could extern "C" next_ instead.
extern int next() __attribute__((alias("_Z5next_v")));
int index(int scale) {
return next_() << scale;
}
基本上,这明确标志着尽管它们在全球范围内可用,但我们使用了这些符号的隐藏版本,这些版本将忽略任何类型的插入。然后,这两个编译器都会完全优化访问权限,而不论经过的选项如何。
- 如何将enable-if与模板参数和参数包一起使用
- 如何将PERF_AMPLE_READ与mmap一起使用
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 如何将C++中的库和头与MinGW一起使用
- 将静态库链接到不带-fPIC的共享库中
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 为什么我不能将 rand() 与数组的大小一起使用?
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 将fold表达式与std::一起用于两个元组
- spdlog标头仅与外部fmt一起使用.spdlog错误:'内部':不是'fmt'
- 将 std::allocate_shared 与多态资源分配器一起使用
- 为什么常量词在重载运算符中不与 ostream 对象一起使用<<?
- 将 OpenCV 与 CMAKE 中的项目一起构建为第三方库的正确方法
- 将 exprtk 与自定义类的对象一起使用
- 将 std::set 与基于键的比较器一起使用
- 将 C++ 类与 Rcpp 一起使用,从 C 或 R 修改它
- 如何将 Eigen::Ref 与 pybind11 一起使用?
- 如何将AERT_Allocate与 std:vector 一起使用
- GCC vs Clang:将功能与-FPIC一起插入