【问题标题】:MSVC++ 2015 - SSE compiler bug or bug/undefined behavior in my program?MSVC++ 2015 - 我的程序中的 SSE 编译器错误或错误/未定义行为?
【发布时间】:2017-03-14 16:25:45
【问题描述】:

我在处理 SIMD 颜色 lerp 函数时遇到了一些奇怪的行为,我将其精简为一个最小程序。此示例中的 SIMD 代码不再执行 lerp,而是执行从 32 位颜色解包到 XMM 寄存器,然后返回到 32 位。

在 MSVC++ 2015 (Update 3) 中,在 Release x64 模式下,以下代码不会产生正确的结果,但在 Debug x64 或 Release/Debug x86 中它可以正常工作。这是原本为空的 Win32 C++ 控制台应用程序项目中的唯一代码:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "emmintrin.h"

struct Color4
{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;

    Color4(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255)
        : red(red), green(green), blue(blue), alpha(alpha) {}

    explicit Color4(uint32_t rgba)
    {
        red = (uint8_t)(rgba & 0xFF);
        green = (uint8_t)((rgba >> 8)&0xFF);
        blue = (uint8_t)((rgba >> 16) & 0xFF);
        alpha = (uint8_t)((rgba >> 24) & 0xFF);
    }
};

Color4 PackUnpack(Color4 col)
{
    uint32_t tmp;

    memcpy(&tmp, &col, sizeof(tmp));

    __m128 aFloat = _mm_cvtepi32_ps(
        _mm_unpacklo_epi16(
            _mm_unpacklo_epi8(
                _mm_set1_epi32(tmp),
                _mm_setzero_si128()
            ),
            _mm_setzero_si128()
        )
    );

    __m128i ret = _mm_packus_epi16(
        _mm_packs_epi32(
            _mm_cvtps_epi32(aFloat),
            _mm_setzero_si128()
        ),
        _mm_setzero_si128()
    );

    return Color4((uint32_t)_mm_cvtsi128_si32(ret));
}

int main()
{
#ifdef _DEBUG
    printf("DEBUG\n");
#else
    printf("RELEASE\n");
#endif

    Color4 c = PackUnpack(Color4(32, 64, 128, 255));

    // Debug x64 or Debug/Release x86: Prints "32 64 128 255"
    // Release x64: Prints "255 0 0 0"
    printf("%d %d %d %d\n",  c.red, c.green, c.blue, c.alpha);

    return 0;
}

Release x64 输出是:

RELEASE
255 0 0 0

调试 x64 和所有 x86 输出是:

DEBUG
32 64 128 255

反汇编看起来像是在预先计算一个常量值以加载到 XMM 寄存器以跳过 _mm_set1_epi32 (参见第一条 movdqa 指令。)

main:
00007FF674391070  sub         rsp,38h  
00007FF674391074  lea         rcx,[string "RELEASE\n" (07FF674392200h)]  
00007FF67439107B  call        printf (07FF674391010h)  
00007FF674391080  movdqa      xmm0,xmmword ptr [__xmm@000000ff000000ff000000ff000000ff (07FF674392220h)]  
00007FF674391088  lea         rcx,[string "%d %d %d %d\n" (07FF674392210h)]  
00007FF67439108F  xorps       xmm2,xmm2  
00007FF674391092  mov         dword ptr [rsp+40h],0FF804020h  
00007FF67439109A  punpcklbw   xmm0,xmm2  
00007FF67439109E  punpcklwd   xmm0,xmm2  
00007FF6743910A2  cvtdq2ps    xmm0,xmm0  
00007FF6743910A5  cvtps2dq    xmm1,xmm0  
00007FF6743910A9  packssdw    xmm1,xmm2  
00007FF6743910AD  packuswb    xmm1,xmm2  
00007FF6743910B1  movd        r10d,xmm1  
00007FF6743910B6  mov         edx,r10d  
00007FF6743910B9  mov         r8d,r10d  
00007FF6743910BC  shr         edx,10h  
00007FF6743910BF  mov         eax,r10d  
00007FF6743910C2  shr         r8d,8  
00007FF6743910C6  movzx       r9d,dl  
00007FF6743910CA  shr         eax,18h  
00007FF6743910CD  movzx       edx,r10b  
00007FF6743910D1  movzx       r8d,r8b  
00007FF6743910D5  mov         dword ptr [rsp+20h],eax  
00007FF6743910D9  call        printf (07FF674391010h)  
00007FF6743910DE  xor         eax,eax  
00007FF6743910E0  add         rsp,38h  
00007FF6743910E4  ret  

我已经在 Ubuntu 14.04 x64 上使用 g++ 4.8.4 进行了尝试,并且无论打开或关闭 -O3 都可以正常工作。

所以我的问题是,这是一个编译器错误、使用未定义/实现定义的行为的结果,还是我的代码中更普通的错误?

(用于通过联合使用类型双关语从 Color4 中获取 uint32_t 值的代码,我用 memcpy 替换了它,因为这不是标准的......仍然没有骰子。)

【问题讨论】:

  • VS 2017 中的意外行为是一样的
  • 看起来像一个编译器错误。如果您使用tmp = color.red + 256 * (col.blue + 256 * (col.green + 256 * col.alpha))); 而不是memcpy,会发生什么?
  • @SeverinPappadeux 感谢您的提示,我下载并安装了它,但它似乎没有修复它。 :( @1201ProgramAlarm 当我用 tmp = col.red + 256 * (col.blue + 256 * (col.green + 256 * col.alpha)); 替换该行时,它工作得很好。

标签: c++ visual-c++ visual-studio-2015 sse


【解决方案1】:

实际上不是一个答案,但是,由于我不喜欢在评论中添加太多文本,这是我可以重现问题的最小代码:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "emmintrin.h"

int main()
{
    uint8_t src[4] = { 32, 64, 128, 255 };

    uint32_t tmp = 0;
    memcpy( &tmp, &src, sizeof( tmp ) );    

    auto a = _mm_set1_epi32( tmp );

    printf( "tmp = 0x%08x\n", tmp );
    printf( "a.m128i_i32[0] = 0x%08x\n", a.m128i_i32[0] );  

    return 0;
}

预期输出:

tmp = 0xff804020
a.m128i_i32[0] = 0xff804020

x64 版本的输出:

tmp = 0xff804020
a.m128i_i32[0] = 0x000000ff

【讨论】:

  • 感谢您将这些放在一起。
  • 再修改一点会导致发现 SSE 寄存器中的值实际上是数组的第 4 个字节。有趣:-)
【解决方案2】:

这是由于编译器错误造成的。一种解决方法是使用

tmp = color.red + 256 * (col.blue + 256 * (col.green + 256 * col.alpha)));

代替memcpy 或键入双关语。

【讨论】:

  • 谢谢——我对遇到一个真正的编译器错误感到有点震惊,所以我将把这个打开一段时间,以防万一有人能证明不是这样,但如果他们可以的话t 我会将此标记为已接受! :)(并与 MS 归档。)
  • 是否已经提交了一个关于此的错误,或者这是一个编译器错误,只是偶然观察到的?
  • 不知道有没有归档,但是生成的代码错了就是bug了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-18
  • 2020-05-21
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 2021-05-28
  • 2016-01-05
相关资源
最近更新 更多