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

Big*_*ith 6 c++ x86 gcc sse clang

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

__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);
Run Code Online (Sandbox Code Playgroud)

现在我期望std::max涉及 NaN 时的类型行为,但 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 
Run Code Online (Sandbox Code Playgroud)

_mm_max_ps 在我使用它时会做预期的事情。我试过使用-ffast-math-fno-fast-math但它似乎没有效果。有什么想法可以使编译器之间的行为相似吗?

Godbolt 链接在这里

Bre*_*ale 3

我的理解是 IEEE-754 要求:(NaN cmp x)返回所有运算false符,除了返回。函数的实现可以根据任何不等式运算符来定义。 cmp{==, <, <=, >, >=}{!=}truemax()

那么,问题来了,如何_mm_max_ps实施呢?与{<, <=, >, >=},还是有点比较?

有趣的是,当禁用链接优化时,maxssgcc 和 clang 都会使用相应的指令。两者产量:

2.000000 0.000000 0.000000 0.000000 
nan 0.000000 0.000000 0.000000
Run Code Online (Sandbox Code Playgroud)

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

(NaN op val)始终为假,返回(val),
(val op NaN)始终为,返回(NaN)

启用优化后,编译器可以在编译时自由进行预计(c)(d)。看来 clangmaxss会按照指令评估结果 - 纠正“假设”行为。GCC 要么依赖于另一种实现max()- 它使用 GMP 和 MPFR 库来进行编译时数字 - 或者只是对语义不小心_mm_max_ss

GCC 在 godbolt 上的 10.2 和 trunk 版本上仍然出现错误。所以我认为你发现了一个错误!我还没有回答第二部分,因为我想不出可以有效解决这个问题的万能黑客。


来自 Intel 的 ISA 参考:

如果比较的值都是 0.0s(任一符号),则返回第二个源操作数中的值。如果第二个源操作数中的值是 SNaN,则该 SNaN 会原封不动地返回到目标(即,不返回 SNaN 的 QNaN 版本)。

如果该指令只有一个值为 NaN(SNaN 或 QNaN),则将第二个源操作数(NaN 或有效浮点值)写入结果。如果需要返回来自任一源操作数的 NaN,而不是此行为,则可以使用一系列指令来模拟 MAXSS 的操作,例如,比较后跟 AND、ANDN 和 OR。

  • 如果我看到上面评论中链接的答案,我可能就不会打扰了!无论如何,它仍然必须被视为 gcc 中的一个错误。gcc asm 中有两种不同的 NaN 编码也可能很重要:`0x7fc00000`和`0x7ff80000` - 那里发生了一些奇怪的事情...... (2认同)