我在处理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,因为它不是标准的......仍然没有骰子。)
答案 0 :(得分: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
使用版本x64输出:
tmp = 0xff804020
a.m128i_i32[0] = 0x000000ff
答案 1 :(得分:0)
这是由编译器错误造成的。解决方法是使用
tmp = color.red + 256 * (col.blue + 256 * (col.green + 256 * col.alpha)));
代替memcpy
或打字。