SSE 移位指令在后续指令中导致奇怪的输出 (-1.#IND00)?

Posted

技术标签:

【中文标题】SSE 移位指令在后续指令中导致奇怪的输出 (-1.#IND00)?【英文标题】:SSE shifting instruction causes weird output (-1.#IND00) in subsequent instruction(s)? 【发布时间】:2013-07-04 03:29:52 【问题描述】:

这个错误实际上可能不会出现在所有机器上,但在我的机器上我运行了下面的代码并得到了输出(注意值-1.#IND00)?

values int:: 4 2
shifts:: 4 2
result: : 64 32
input 1 HADDPD:: 10.000000 -1.#IND00
input 2 HADDPD:: 13.000000 10.000000
result of HADDPD:: -1.#IND00 23.000000

如果我注释掉

__m64 PSLLDm64_IN = _mm_set_pi32(2,4);
    __m64 PSLLDm64_C = _mm_set_pi32(2,4);//could this be the culprit?
    __m64 PSLLDm64_r  =  PSLLD(PSLLDm64_IN, PSLLDm64_C);

    print_2_32_bit_int("values int:" , PSLLDm64_IN);
    print_2_32_bit_int("shifts:", PSLLDm64_C);
    print_2_32_bit_int("result: ", PSLLDm64_r);

我明白了……

input 1 HADDPD:: 10.000000 100.000000
input 2 HADDPD:: 13.000000 10.000000
result of HADDPD:: 110.000000 23.000000

我想知道__m64 PSLLDm64_C = _mm_set_pi32(2,4); 所在的第 32 行是否可以搞砸?

这里是完整的代码(它使用 g++ 与 -msse3 -mmmx 一起运行)虽然并不是所有的标头都是必需的。

#include <xmmintrin.h>
#include <emmintrin.h>
#include <pmmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <iostream>

void print_2_64_bit_doubles(const char * label, __m128d m64_r)

    double *val = (double *) &m64_r;
    printf("%s: %f %f\n",
       label, val[0], val[1]);

void print_2_32_bit_int(const char * label, __m64 m32_r)

    int *val = (int *) &m32_r;
    printf("%s: %d %d\n",
       label, val[0], val[1]);

__m128d HADDPD(__m128d __X, __m128d __Y)

    return _mm_hadd_pd ( __X, __Y);

__m64 PSLLD(__m64 __m, __m64 __count)

    return _mm_sll_pi32 ( __m,  __count);

int main()

    //PSLLD-------------------------------------------------------------------
    __m64 PSLLDm64_IN = _mm_set_pi32(2,4);
    __m64 PSLLDm64_C = _mm_set_pi32(2,4);
    __m64 PSLLDm64_r  =  PSLLD(PSLLDm64_IN, PSLLDm64_C);

    print_2_32_bit_int("values int:" , PSLLDm64_IN);
    print_2_32_bit_int("shifts:", PSLLDm64_C);
    print_2_32_bit_int("result: ", PSLLDm64_r);
    //HADDPD------------------------------------------------------------------
    double C1 = 10;
    double D = C1*C1;
    double x = 10;
    double y = 13;

    __m128d HADDPDm64_1 = _mm_set_pd(D,C1);
    __m128d HADDPDm64_2 = _mm_set_pd(x,y);
    __m128d HADDPDm64_r = HADDPD( HADDPDm64_1, HADDPDm64_2);

    print_2_64_bit_doubles("input 1 HADDPD:", HADDPDm64_1);
    print_2_64_bit_doubles("input 2 HADDPD:", HADDPDm64_2);
    print_2_64_bit_doubles("result of HADDPD:", HADDPDm64_r);

    return 0;

编辑:这是使用 g++ 4.4.1 -msse -msse2 -msse3 -msse4987654328@编译的新移位指令的更新代码

#include <xmmintrin.h>
#include <emmintrin.h>
#include <pmmintrin.h>
#include <mmintrin.h>
#include <stdio.h>
#include <stdint.h>


void print_2_64_bit_doubles(const char * label, __m128d m64_r)

    double *val = (double *) &m64_r;
    printf("%s: %f %f\n",
       label, val[0], val[1]);

void print_2_32_bit_int(const char * label, __m64 m32_r)

    int *val = (int *) &m32_r;
    printf("%s: %d %d\n",
       label, val[0], val[1]);

void print_1_32_bit_int(const char * label, __m64 m32_r)

    int *val = (int *) &m32_r;
    printf("%s: %d \n",
       label, val[0]);

__m128d HADDPD(__m128d __X, __m128d __Y)

    return _mm_hadd_pd ( __X, __Y);

__m64 PSLLD(__m64 __m, __m64 __count)

    return _mm_sll_pi32 ( __m,  __count);

int main()

    //PSLLD-------------------------------------------------------------------
    __m64 PSLLDm64_IN = _mm_set_pi32(2,4);
    long long __i = 2;
    __m64 PSLLDm64_C = (__m64)(__i);
    __m64 PSLLDm64_r  =  PSLLD(PSLLDm64_IN, PSLLDm64_C);
    _mm_empty();

    print_2_32_bit_int("values int:" , PSLLDm64_IN);
    print_1_32_bit_int("shifts:", PSLLDm64_C);
    print_2_32_bit_int("result: ", PSLLDm64_r);
    //HADDPD------------------------------------------------------------------
    double C1 = 10;
    double D = C1*C1;
    double x = 10;
    double y = 13;

    __m128d HADDPDm64_1 = _mm_set_pd(D,C1);
    __m128d HADDPDm64_2 = _mm_set_pd(x,y);
    __m128d HADDPDm64_r = HADDPD( HADDPDm64_1, HADDPDm64_2);

    print_2_64_bit_doubles("input 1 HADDPD:", HADDPDm64_1);
    print_2_64_bit_doubles("input 2 HADDPD:", HADDPDm64_2);
    print_2_64_bit_doubles("result of HADDPD:", HADDPDm64_r);

    return 0;

还有输出

values int:: 4 2
shifts:: 2
result: : 16 8
input 1 HADDPD:: 10.000000 -1.#IND00
input 2 HADDPD:: 13.000000 10.000000
result of HADDPD:: -1.#IND00 23.000000

【问题讨论】:

使用 gcc 4.2.1 对我来说很好 - 你使用什么编译器? 我想知道你是否需要在 64 位 SIMD 内容之后使用 _mm_empty() 当我在命令行中输入g++ -v 时,我得到gcc 4.4.1 tdm-2 mingw 32。我将_mm_empty(); 放在__m64 PSLLDm64_r = PSLLD(PSLLDm64_IN, PSLLDm64_C); 之后,与输出结果相同? 可能是我在使用 cygwin mmintrin 标头和 mingw 吗? 然后打败我 - 可能是编译器问题。我看到的唯一代码错误是__m64 PSLLDm64_C = _mm_set_pi32(2,4);,它应该是例如__m64 PSLLDm64_C = _mm_set_pi64x(2); 但这不会导致您的特定问题。 【参考方案1】:

使用来自http://www.drangon.org/mingw/ 的 gcc 和 g++ 4.8.1 的 Windows x64 端口进行测试都给出了预期的结果。只需解压缩存档并将路径设置为 mingw64\bin。使用 -msse4 等编译器选项告诉编译器您的硬件支持这些指令。

2013 年 7 月 5 日:对于最初的评论不完整,我们深表歉意。此外,上述答案旨在作为评论而不是答案。

Microsoft VS2010 与您从 cygwin 报告的错误结果相同,使用 Microsoft 调试器很容易找到原因。其实一个编译警告也指出了问题所在:

warning C4730: 'main' : mixing _m64 and floating point expressions may result in incorrect code

当编译器生成 MMX 和 x87 FPU 指令的混合时,就会出现您报告的问题。编译器将 MMX 寄存器用于 _m64 数据,编译器使用 x87 FPU 寄存器或更新的 XMM 或 YMM 寄存器用于浮点数据类型 double。英特尔在设计 MMX 时,决定将 x87 寄存器重用于 MMX 寄存器数据。这样做是为了使操作系统不需要任何更新即可支持 MMX 的使用。这个决定的缺点是不能混合 MMX 和 x87 FPU 指令。为了帮助防止意外混合 FPU 和 MMX 指令,Intel 使 MMX 寄存器加载将相应 FPU 寄存器的标记字位标记为 SNAN(信令 NAN)。这就是导致您看到的意外输出的原因。某些编译器和构建选项组合可能允许此代码正常运行。此代码在某些情况下可能起作用的可能原因: 1) 编译器对双精度数据使用 XMM 或 YMM 寄存器。 2) 编译器将所有 x87 FPU 值保存在内存中,并且跨 MMX 指令不依赖 FPU 寄存器状态。 底线是由编码器来避免允许编译器生成混合 MMX 和 x87 FPU 指令的代码的情况。认真对待“函数'print_2_32_bit_int'没有EMMS指令”或“混合_m64和浮点表达式可能导致错误代码”等警告。一种可行的方法是完全避免使用 _m64 数据类型。

Paul R 关于使用 _mm_empty() 的建议解决了 Microsoft VS2010 的问题。我在'double C1 = 10'之前添加了它,问题就消失了。 _mm_empty 在这里解释http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/intref_cls/common/intref_mmx_emms_usage.htm。

对于您的其他问题,我仅对 gcc 使用命令行,没有 IDE。如果您添加 _mm_empty() 或避免混合 MMX 和 x87 FPU 代码,旧版本的 gcc 应该可以正常工作。

【讨论】:

我只是想知道您是否正在使用诸如代码块之类的 IDE(这就是我正在使用的)。我可以将我的工具链更改为 mingw64/bin,但我无法让它编译任何东西(例如,它停止说编译失败)。顺便问一下,您使用的是什么 IDE?

以上是关于SSE 移位指令在后续指令中导致奇怪的输出 (-1.#IND00)?的主要内容,如果未能解决你的问题,请参考以下文章

西门子plc循环移位指令的用法

使用 SSE 指令的图像最大值

奇怪的 SIMD 指令行为

转载移位指令

SSE、SSE2、SSE3指令集的区别?

在编制乘除法程序时,为啥常用移位指令来代替乘除法指令?