我有一些SIMD代码可以检查变量之间的相等性,但是当涉及NaN时,我在GCC和clang之间得到了不同的结果:
bool equal(__m128 a, __m128 b){
return _mm_comieq_ss(a,b) == 1;
}
int main()
{
__m128 a, b, c;
a = _mm_set_ss(std::numeric_limits<float>::quiet_NaN());
b = _mm_set_ss(1.0f);
c = _mm_set_ss(1.0f);
std::cout << "comieq(a,b):" << equal(a,b) << std::endl;
std::cout << "comieq(b,a):" << equal(b,a) << std::endl;
std::cout << "comieq(b,c):" << equal(b,c) << std::endl;
std::cout << "comieq(a,a):" << equal(a,a) << std::endl;
return 0;
}
Clang和GCC返回不同的值:
gcc:
comieq(a,b):1
comieq(b,a):1
comieq(b,c):1
comieq(a,a):1
clang:
comieq(a,b):0
comieq(b,a):0
comieq(b,c):1
comieq(a,a):0
有没有人知道为什么会发生这种情况?我只是想检查两个regs是否相等;有没有一个替代的方法来做到这一点,这是一致的?
神箭:https://godbolt.org/z/ETKenE45f
1条答案
按热度按时间xmjla07d1#
在Clang 3.9.0. Related Link中,比较
NaN
值时对返回值的不同处理进行了 * 特别 * 更改。尽管人们期望 intrinsic 函数仅仅是CPU的内部函数,* 不依赖于 * 编译器,但the
comiss
instruction在多位FLAGS中产生结果。在ASM中,将由程序员来使用像je
、jb
和/或jp
或setcc
/cmovcc
的指令的组合来使用比较结果。这里发生的是GCC只检查
ZF
(零标志)值,而Clang也(正确地)检查PF
('parity'标志:如果比较是无序的,即其中一个输入是NaN
,则设置。此matches the way integer FLAGS were由P6 x87fcomi
设置,反过来匹配旧的x87fcom
/fstsw ax
/sahf
)。我将从上面链接的讨论中提供一个简短的引用,这可能会对LLVM(clang)团队做出的决定背后的原因有所启发:
在Clang 3.8.0及之前版本中,比较两个标量,其中至少一个是NaN,将返回1。这也是GCC,Visual Studio和我们当前的Emscripten代码实现的行为。这种行为是不直观的,因为比较浮点数中的NaN具有IEEE-754中相反的传统,即“没有什么等于NaN”。
Intel是这些intrinsic的原作者,必须承认,这些函数长期以来一直遭受着糟糕的文档的困扰。Intel没有详细说明这些intrinsic应该如何与NaN一起工作(https://software.intel.com/en-us/node/514308),但大概是他们自己的编译器中的参考实现被认为是基本事实。VS和Clang〈= 3.8都遵循了英特尔编译器中实现的原始代码,其中_mm_comieq_ss被实现以执行COMISS指令并返回结果零标志(ZF)寄存器状态作为内在函数的输出int值。COMISS指令本身虽然有很好的文档记录,因为它是伊萨的一部分,这示出了意外NaN行为的起源,因为如果比较相等,或者如果比较结果是无序的,即至少一个寄存器是NaN,则设置零标志。
根据Peter Cordes的评论,现在很明显,(修改后的)clang行为是正确的,并且上面引用中提到的英特尔“糟糕的文档”已经得到纠正。英特尔
_mm_comieq_ss
的文档现在明确表示,任何存在的NaN
值都应该返回零值:操作
RETURN ( a[31:0] != NaN AND b[31:0] != NaN AND a[31:0] == b[31:0] ) ? 1 : 0