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

我在处理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,因为这不是标准…仍然没有骰子.)

最佳答案 实际上不是一个答案,但是,因为我不想在评论中添加太多文本,这是我能用以下方法重现问题的最小代码:

#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
点赞