【问题标题】:Is SSE2 signed integer overflow undefined?SSE2 有符号整数溢出是否未定义?
【发布时间】:2014-10-22 21:02:49
【问题描述】:

有符号整数溢出在 C 和 C++ 中未定义。但是__m128i 的各个字段中的有符号整数溢出呢?换句话说,这种行为是否在英特尔标准中定义?

#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <emmintrin.h>

union SSE2
{
    __m128i m_vector;
    uint32_t m_dwords[sizeof(__m128i) / sizeof(uint32_t)];
};

int main()
{
    union SSE2 reg = {_mm_set_epi32(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX)};
    reg.m_vector = _mm_add_epi32(reg.m_vector, _mm_set_epi32(1, 1, 1, 1));

    printf("%08" PRIX32 "\n", (uint32_t) reg.m_dwords[0]);
    return 0;
}
[myria@polaris tests]$ gcc -m64 -msse2 -std=c11 -O3 sse2defined.c -o sse2defined
[myria@polaris tests]$ ./sse2defined
80000000

请注意,SSE2 __m128i 的 4 字节大小的字段被视为已签名。

【问题讨论】:

  • 这是一个很好的问题!
  • 在实践中,这些元素的行为符合预期,即正常的 2s 补码环绕等。我不知道您是否会在英特尔文档中找到任何东西来保证这一点。
  • SSE2 __mi128i (显然)是一个特定于体系结构的概念,因此 C 和 C++ 标准没有提及类型或其行为或内在。您需要查看“供应商”文档以了解超出标准所提供的任何保证。
  • Intel® 64 and IA-32 Architectures Software Developer’s Manual 表 9-2 将其列为 Wrap-around,但我找不到任何明确的保证 _mm_add_epi32 永远不会被简单的 C 包装器模拟。
  • @thatotherguy:我不知道你认为_mm_add_epi32() 许可证的软件模拟会给出与PADDD 不同的结果。

标签: c language-lawyer undefined-behavior sse2


【解决方案1】:

这个问题大约有三点错误(不是以“你缺乏理解”的方式投票,而是以“你缺乏理解”的方式......这就是我猜你来这里的原因)。

1) 您询问的是特定的实施问题(使用 SSE2)而不是标准。您已经回答了自己的问题“有符号整数溢出在 C 中未定义”。

2) 当您处理 c 内在函数时,您甚至都不是在用 C 编程!这些是在行中插入汇编指令。它以某种可移植的方式进行,但您的数据不再是有符号整数。它是传递给 SSE 内在函数的向量类型。然后,您将其转换为整数并告诉 C 您希望查看该操作的结果。 转换时发生的任何字节都是您将看到的,与 C 标准中的有符号算术无关。

3) 只有两个错误的假设。我对错误的数量做了一个假设,结果是错误的。

如果编译器插入 SSE 指令(比如在循环中),情况会有所不同。现在编译器保证结果与带符号的 32 位操作相同......除非存在未定义的行为(例如溢出),在这种情况下它可以做任何它喜欢的事情。

还请注意,未定义并不意味着意外......无论您观察到的自动矢量化行为可能是一致且可重复的(也许它确实总是包裹在您的机器上......这可能并非适用于所有情况代码或所有编译器。或者,如果编译器根据 SSSE3、SSE4 或 AVX* 的可用性选择不同的指令,如果它为不同的指令集选择不同的代码生成选择,则可能甚至不是所有处理器。签名溢出的优势是 UB)。

编辑:

好的,现在我们正在询问“英特尔标准”(不存在,我认为您的意思是 x86 标准),我可以在答案中添加一些内容。事情有点复杂。

首先,内部 _mm_add_epi32 由 Microsoft 定义,以匹配 Intel 的内部 API 定义(https://software.intel.com/sites/landingpage/IntrinsicsGuide/ 和 Intel x86 汇编手册中的内部注释)。他们巧妙地将其定义为对 __m128i 执行与 x86 PADDD 指令对 XMM 寄存器执行相同的操作,没有更多讨论(例如,它是 ARM 上的编译错误还是应该被模拟?)。

其次,PADDD 不仅仅是一个签名添加!它是一个 32 位二进制加法。 x86 对有符号整数使用二进制补码,添加它们与无符号基数 2 的二进制运算相同。所以是的,paddd 保证可以换行。所有x86指令都有一个很好的参考here

那是什么意思:同样,您问题中的假设是有缺陷的,因为甚至没有任何溢出。所以你看到的输出应该是定义的行为。请注意,它是由 Microsoft 和 x86(不是 C 标准)定义的。

其他 x86 编译器也以相同的方式实现 Intel 的内部 API,因此 _mm_add_epi32 可移植地保证只是包装。

【讨论】:

  • 嗯,是的;问题是英特尔如何定义标准,ICC、GCC、Clang、MSVC等是否遵循英特尔标准。这不是一个真正的 C 标准问题。
  • 在这种情况下,您可能需要编辑您的问题:您询问的是 x86 汇编指令集以及是否定义了 SSE 溢出。
  • @Myria Intel 没有定义任何标准。他们只是定义了他们的 CPU 的行为方式
  • 来自 Intel 的 SSE4 手册:“Intel C/C++ Compiler Intrinsic Equivalent PBLENDVB __m128i _mm_blendv_epi8 (__m12 8i v1, __m128i v2, __m128i mask);
【解决方案2】:

这不是“__m128i 的字段中的有符号整数溢出”。这是一个函数调用。 (作为编译器内在函数只是一种优化,很像内联,只要遵守 as-if 规则,它就不会与 C 标准交互)

它的行为必须遵循函数开发者记录的契约(前置条件、后置条件)。通常内部函数由编译器供应商记录,尽管它们倾向于协调内部函数的命名和合同以帮助移植代码。

【讨论】:

  • 可移植性:在_mm...内在函数的情况下,它们由英特尔为ICC(software.intel.com/sites/landingpage/IntrinsicsGuide)定义,并由MSVC、GCC、clang等以兼容的方式实现其他不太主流的 x86 编译器,因此英特尔的文档适用。 (一些编译器有时会丢失某些版本的 _mm256_setr_m128 或其他东西,或者像 _mm_bslli_si128 用于字节移位 pslldq 的替代名称,但映射到单个指令的内在函数非常可移植。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-13
  • 2019-08-23
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多