_mm_comieq_ss Clang和GCC之间的差异

jjjwad0x  于 2023-03-23  发布在  其他
关注(0)|答案(1)|浏览(108)

我有一些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

xmjla07d

xmjla07d1#

在Clang 3.9.0. Related Link中,比较NaN值时对返回值的不同处理进行了 * 特别 * 更改。
尽管人们期望 intrinsic 函数仅仅是CPU的内部函数,* 不依赖于 * 编译器,但the comiss instruction在多位FLAGS中产生结果。在ASM中,将由程序员来使用像jejb和/或jpsetcc/cmovcc的指令的组合来使用比较结果。
这里发生的是GCC只检查ZF(零标志)值,而Clang也(正确地)检查PF('parity'标志:如果比较是无序的,即其中一个输入是NaN,则设置。此matches the way integer FLAGS were由P6 x87 fcomi设置,反过来匹配旧的x87 fcom/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

相关问题