MSVC 2015-我程序中的SSE编译器错误或错误/未定义的行为

MSVC++ 2015 - SSE compiler bug or bug/undefined behavior in my program?

本文关键字:错误 未定义 SSE 2015- 程序 MSVC 编译器      更新时间:2023-10-16

我在处理SIMD颜色LERP函数时遇到了一些怪异的行为,然后将其缩小到最小程序中。此示例中的SIMD代码不再执行LERP,但它执行从32位颜色到XMM寄存器的打开包装,然后返回32位。

在MSVC 2015(更新3)中,在Release X64模式中,以下代码不会产生正确的结果,但是在调试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("DEBUGn");
#else
    printf("RELEASEn");
#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 %dn",  c.red, c.green, c.blue, c.alpha);
    return 0;
}

版本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 "RELEASEn" (07FF674392200h)]  
00007FF67439107B  call        printf (07FF674391010h)  
00007FF674391080  movdqa      xmm0,xmmword ptr [__xmm@000000ff000000ff000000ff000000ff (07FF674392220h)]  
00007FF674391088  lea         rcx,[string "%d %d %d %dn" (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上使用g++ 4.8.4尝试了此操作,在14.04 x64上,它可以与-O3打开或关闭。

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

(用于使用工会使用类型的代码将UINT32_T值从颜色中获取uint32_t值,我用memcpy代替,因为那不是标准...仍然没有骰子。)

实际上不是答案,但是,由于我不想在评论中将文本提交太多,所以我可以用以下方式复制该问题的最小代码:

#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%08xn", tmp );
    printf( "a.m128i_i32[0] = 0x%08xn", a.m128i_i32[0] );  
    return 0;
}

预期输出:

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

用版本x64输出:

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

这是由于编译器错误。解决方法是使用

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

代替memcpy或键入双关。