_mm_max_ss在clang和gcc中的行为不同

6

我试图使用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 链接 在这里


3
这篇写得很好的帖子 https://dev59.com/cZvga4cB1Zd3GeqP8d1Q#40199125 解释了你遇到问题的原因。然而,尽管这个问题应该在GCC 7中被修复了,但它在最新版本的GCC中仍然存在。我认为在GCC漏洞跟踪器上开一个工单是明智的。 - Jérôme Richard
4
我已经提交了一个漏洞报告:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99497 ,针对主要问题。 - Biggy Smith
1个回答

3
我的理解是IEEE-754要求:(NaN cmp x)对于所有的cmp运算符{==、<、<=、>、>=},都返回false,除了{!=}返回true。基于任何不等式运算符的实现都可能定义max()函数。
所以问题是,_mm_max_ps如何实现?使用{<、<=、>、>=}还是位比较?
有趣的是,在禁用链接时,无论是gcc还是clang都使用相应的maxss指令。两者都产生:
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)总是为false,返回(val)
(val op NaN)总是为false,返回(NaN)

启用优化后,编译器可以在编译时自由预计算(c)(d)。看起来clang评估结果就像maxss指令一样——正确的“as-if”行为。GCC要么退回到另一个max()实现——它使用GMP和MPFR库进行编译时数值运算——要么只是在对_mm_max_ss语义上不加注意。
在godbolt上,GCC在10.2和trunk版本中仍然存在问题。所以我认为你发现了一个bug!我没有回答第二部分,因为我想不出一个通用的技巧来有效地解决这个问题。
从英特尔ISA参考手册中得知:

如果要比较的值都为0.0(任意符号),“第二个源操作数”中的值将返回到结果。如果“第二个源操作数”中的值是SNaN,则该SNaN将不变地返回到目标位置(即,不会返回SNaN版本的QNaN)。

如果这个指令的一个值是NaN(SNaN或QNaN),另一个源操作数——无论是NaN还是有效浮点数——将写入结果。如果需要返回来自任一源操作数的NaN,可以使用一系列指令来模拟MAXSS,例如,一个比较后面跟着AND、ANDN和OR。


2
如果我看到上面评论中链接的答案,我可能就不会麻烦了!无论如何,这仍然必须被视为gcc中的一个bug。另外值得注意的是,在gcc汇编中有两个不同的NaN编码:0x7fc000000x7ff80000 - 在那里发生了一些奇怪的事情... - Brett Hale
我很感激你这样做,因为它为情况提供了一些额外的背景。 - Biggy Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接