2017-03-14 22 views
4

我在处理SIMD颜色lerp函数时遇到了一些奇怪的行为,并将其修剪为最小程序。本示例中的SIMD代码不再执行lerp,但是它执行从32位颜色到XMM寄存器的解包,然后返回到32位。MSVC++ 2015 - SSE编译器错误或错误/未定义在我的程序中的行为?

在MSVC++ 2015(更新3)中,在发布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; 
} 

的版本的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 [[email protected] (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 64 g++ 4.8.4尝试这样做,它开启或关闭正常工作与-O3

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

(代码中使用使用类型双关通过工会得到uint32_t的值了Color4,我用的memcpy替代的,因为这不是标准......仍然没有骰子。)

+0

这可能会帮助:https://support.microsoft.com/en-us/help/3207317/visual-co ptimizer-fixes-for-visual-studio-2015-update-3 –

+0

在VS 2017中出乎意料的行为是相同的 – Timbo

+0

看起来像一个编译器错误。如果使用'tmp = color.red + 256 *(col.blue + 256 *(col.green + 256 * col.alpha)));'而不是'memcpy'会发生什么? – 1201ProgramAlarm

回答

0

这是由于到一个编译器错误。一种解决方法是代替memcpy或类型双关的使用

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

+0

谢谢 - - 我有点震惊已经遇到了一个真正的编译器错误,所以我打算把这个开放一段时间,以防止任何人可以证明,但如果他们不能,我会把这个标记为Accepted! :)(和MS文件。) –

+0

有没有已经提交过这个问题的bug,还是这是一个偶然观察的编译器bug? – MDV

+0

我不知道它是否被提交,但生成的代码是错误的将是一个错误。 – 1201ProgramAlarm

2

并不是一个真正的答案,但是,因为我不喜欢把太多的文本注释,这个最小的代码,我可以重现该问题有:

#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 

输出与发布64:

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

感谢您将这一切放在一起。 –

+0

稍微修改一下会导致发现登陆SSE寄存器的值实际上是数组的第4个字节。有趣的:-) – Timbo