_mm_max_ss在clang和gcc之间有不同的行为



我正在尝试使用clang和gcc交叉编译一个项目,但我在使用_mm_max_ss时看到一些奇怪的差异,例如

__m128 a = _mm_set_ss(std::numeric_limits<float>::quiet_NaN());
__m128 b = _mm_set_ss(2.0f);
__m128 c = _mm_max_ss(a,b);
__m128 d = _mm_max_ss(b,a);

现在我期望在涉及nan时std::max类型的行为,但clang和gcc给出不同的结果:

Clang: (what I expected)
c: 2.000000 0.000000 0.000000 0.000000 
d: nan 0.000000 0.000000 0.000000 
Gcc: (Seems to ignore order)
c: nan 0.000000 0.000000 0.000000 
d: nan 0.000000 0.000000 0.000000 

当我使用_mm_max_ps时,它做了预期的事情。我试过使用-ffast-math,-fno-fast-math,但似乎没有效果。有什么办法使不同编译器的行为相似吗?

Godbolt链接这里

我的理解是IEEE-754要求:(NaN cmp x)对于所有cmp操作符{==, <, <=, >, >=}返回false,除了{!=}返回truemax()的实现函数可以用任意不等式操作符定义。

所以,问题是,_mm_max_ps是如何实现的?与{<, <=, >, >=},或一点比较?

有趣的是,当禁用优化在你的链接,相应的maxss指令被GCC和clang使用。两个收益率:
2.000000 0.000000 0.000000 0.000000 
nan 0.000000 0.000000 0.000000

这表明,给定:max(NaN, 2.0f) -> 2.0f,即:max(a, b) = (a op b) ? a : b,其中op是:{<, <=, >, >=}之一。对于IEEE-754规则,此比较的结果总是false,因此:

(NaN op val)alwaysfalse,返回(val)
(val op NaN)alwaysfalse,返回(NaN)

打开优化后,编译器可以在编译时自由地预计算(c)(d)。clang将结果计算为maxss指令会纠正"假装"的行为。GCC要么依赖于max()的另一种实现-它使用GMP和MPFR库编译时的数字-或者只是粗心的_mm_max_ss语义。

GCC在godbolt上的10.2和主干版本仍然出错。所以我认为你发现了一个bug!我还没有回答第二部分,因为我想不出一个通用的hack可以有效地解决这个问题。


来自Intel的ISA参考:

如果所比较的值都是0(任意一个符号),则值为0在第二个源操作数返回。如果第二个中的值源操作数是一个SNaN,该SNaN将不加更改地返回给目的地(即不返回SNaN的QNaN版本)。

如果只有一个值是该指令的NaN (SNaN或QNaN),则第二个源操作数,NaN或有效的浮点数,写入结果。如果不是这种行为,它是必需的从任意一个源操作数返回NaN,即MAXSS可以使用一系列指令进行模拟,例如,a

最新更新