考虑下面的C++代码:
#include <cstdint>
// returns a if less than b or if b is INT32_MIN
int32_t special_min(int32_t a, int32_t b)
{
return a < b || b == INT32_MIN ? a : b;
}
带-fwrapv
的GCC正确地认识到b
减1可以消除特殊情况,它generates this code for x86-64:
lea edx, [rsi-1]
mov eax, edi
cmp edi, edx
cmovg eax, esi
ret
但如果没有-fwrapv
,它会生成更糟糕的代码:
mov eax, esi
cmp edi, esi
jl .L4
cmp esi, -2147483648
je .L4
ret
.L4:
mov eax, edi
ret
我知道如果我编写依赖于有符号溢出的C代码,就需要-fwrapv
。但是:
1.上面的C代码不依赖于有符号溢出(它是有效的标准C++)。
1.我们都知道有符号溢出在x86-64上有特定的行为。
1.编译器知道它正在为x86-64编译。
如果我写了“手工优化”的C++代码来实现这种优化,我知道-fwrapv
是必需的,否则编译器可能会决定有符号溢出是UB,并在b == INT32_MIN
的情况下做任何它想做的事情。而且我看不出是什么阻止它使用没有-fwrapv
的优化。是否有什么原因不允许它这样做?
1条答案
按热度按时间b4lqfgs41#
这种优化缺失以前在GCC中也发生过,比如没有完全把有符号的int add当作关联的,尽管它是用 Package 加法编译2的补码目标的。所以它对无符号的优化更好。IIRC,原因是像GCC丢失了一些关于操作的信息,因此是保守的?我忘了这个问题是否得到了修复。
我找不到我以前在SO上看到过的关于内部结构的GCC开发人员的回复;也许是在GCC的bug报告中?我想是像
a+b+c+d+e
(not)这样的东西重新关联到依赖树中以缩短关键路径。但不幸的是,它仍然存在于当前的GCC中:Godbolt适用于x86-64 System V(
-O3
),clang和gcc -fwrapv
为所有4个函数生成相同的asm,如您所料。GCC(没有
-fwrapv
)为sumu
和sumuv2
生成相同的asm(求和到r8d
,保存e
的reg)。但是GCC为sum
和sumv2
生成不同的asm,因为它们使用带符号的int
因此,具有讽刺意味的是,GCC在不重新关联源代码时使 * asm更好,这是假设所有6个输入同时就绪,如果前面代码的乱序执行每个周期只产生1个输入寄存器,那么这里的最终结果将在最终输入就绪后仅1个周期就绪,假设最终输入为
f
。但是,如果最后一个输入是
a
或b
,则结果将直到5个周期之后才准备好,其中在可能的情况下使用像GCC和clang这样的单链,而树约简的最坏情况是3个周期,最好情况是2个周期(如果e
或f
最后准备好)。(更新:
-mtune=znver2
使GCC重新关联到一个树中,谢谢@amonakov。所以这是一个默认的调优选择,我觉得很奇怪,至少对于这个特定的问题大小来说是这样。参见GCC源代码,搜索reassoc
以查看其他调优设置的成本;大多数都是1,1,1,1
,这很疯狂,尤其是对于浮点数。这可能是GCC在展开FP循环时无法使用多个向量累加器的原因,违背了目的。)**但无论如何,这是GCC仅将带符号的
int
与-fwrapv
重新关联的情况。**因此,很明显,如果没有-fwrapv
,它会限制自己。相关:编译器优化可能会导致整数溢出。这样可以吗?-这当然是法律的的,如果不这样做就是错过了优化。
GCC并没有完全被带符号的
int
所束缚;它会自动向量化int sum += arr[i]
,并且它会设法优化为什么GCC不把a* a * a * aa优化为(aaa)(a* a * a)?对于有符号的int a
。