VC++ SSE 代码生成 - 这是编译器错误吗?
VC++ SSE code generation - is this a compiler bug?
VC++ 中的一个非常特殊的代码序列生成了以下指令(对于 Win32):
unpcklpd xmm0,xmmword ptr [ebp-40h]
出现2个问题:
(1) 据我了解英特尔手册,unpcklpd 接受 128 对齐的内存地址作为第二个参数。如果地址是相对于栈帧对齐的,则不能强制。这真的是编译器错误吗?
(2) 仅当 运行 来自调试器 时,才会在执行此指令 时抛出异常,即便如此也并非总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样??
抛出的特定异常是 0xFFFFFFFF 处的访问冲突,但据我所知,这只是未对齐的代码。
[编辑:]
这是一些演示错误代码生成的来源 - 但通常不会导致崩溃。 (这主要是我想知道的)
[编辑 2:]
代码示例现在重现了实际的崩溃。这个也会在调试器之外崩溃 - 我怀疑会出现差异,因为调试器会在不同的典型基地址启动程序。
// mock.cpp
#include <stdio.h>
struct mockVect2d
{
double x, y;
mockVect2d() {}
mockVect2d(double a, double b) : x(a), y(b) {}
mockVect2d operator + (const mockVect2d& u) {
return mockVect2d(x + u.x, y + u.y);
}
};
struct MockPoly
{
MockPoly() {}
mockVect2d* m_Vrts;
double m_Area;
int m_Convex;
bool m_ParClear;
void ClearPar() { m_Area = -1.; m_Convex = 0; m_ParClear = true; }
MockPoly(int len) { m_Vrts = new mockVect2d[len]; }
mockVect2d& Vrt(int i) {
if (!m_ParClear) ClearPar();
return m_Vrts[i];
}
const mockVect2d& GetCenter() { return m_Vrts[0]; }
};
struct MockItem
{
MockItem() : Contour(1) {}
MockPoly Contour;
};
struct Mock
{
Mock() {}
MockItem m_item;
virtual int GetCount() { return 2; }
virtual mockVect2d GetCenter() { return mockVect2d(1.0, 2.0); }
virtual MockItem GetItem(int i) { return m_item; }
};
void testInner(int a)
{
int c = 8;
printf("%d", c);
Mock* pMock = new Mock;
int Flag = true;
int nlr = pMock->GetCount();
if (nlr == 0)
return;
int flr = 1;
if (flr == nlr)
return;
if (Flag)
{
if (flr < nlr && flr>0) {
int c = 8;
printf("%d", c);
MockPoly pol(2);
mockVect2d ctr = pMock->GetItem(0).Contour.GetCenter();
// The mess happens here:
// ; 74 : pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
//
// call ? Vrt@MockPoly@@QAEAAUmockVect2d@@H@Z; MockPoly::Vrt
// movdqa xmm0, XMMWORD PTR $T4[ebp]
// unpcklpd xmm0, QWORD PTR tv190[ebp] **** crash!
// movdqu XMMWORD PTR[eax], xmm0
pol.Vrt(0) = ctr + mockVect2d(1.0, 0.);
pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
}
}
}
void main()
{
testInner(2);
return;
}
如果您愿意,请下载一个现成的 vcxproj,其中包含 here 中设置的所有开关。这也包括完整的 ASM。
既然没有人站出来,那我就来一炮。
1) 如果地址是相对于栈帧对齐的则不能强制。这真的是编译器错误吗?
我不确定您是否不能强制堆栈变量对齐。考虑这段代码:
struct foo
{
char a;
int b;
unsigned long long c;
};
int wmain(int argc, wchar_t* argv[])
{
foo moo;
moo.a = 1;
moo.b = 2;
moo.c = 3;
}
查看 main 的启动代码,我们看到:
00E31AB0 push ebp
00E31AB1 mov ebp,esp
00E31AB3 sub esp,0DCh
00E31AB9 push ebx
00E31ABA push esi
00E31ABB push edi
00E31ABC lea edi,[ebp-0DCh]
00E31AC2 mov ecx,37h
00E31AC7 mov eax,0CCCCCCCCh
00E31ACC rep stos dword ptr es:[edi]
00E31ACE mov eax,dword ptr [___security_cookie (0E440CCh)]
00E31AD3 xor eax,ebp
00E31AD5 mov dword ptr [ebp-4],eax
将 __declspec(align(16)) 添加到 moo 得到
01291AB0 push ebx
01291AB1 mov ebx,esp
01291AB3 sub esp,8
01291AB6 and esp,0FFFFFFF0h <------------------------
01291AB9 add esp,4
01291ABC push ebp
01291ABD mov ebp,dword ptr [ebx+4]
01291AC0 mov dword ptr [esp+4],ebp
01291AC4 mov ebp,esp
01291AC6 sub esp,0E8h
01291ACC push esi
01291ACD push edi
01291ACE lea edi,[ebp-0E8h]
01291AD4 mov ecx,3Ah
01291AD9 mov eax,0CCCCCCCCh
01291ADE rep stos dword ptr es:[edi]
01291AE0 mov eax,dword ptr [___security_cookie (12A40CCh)]
01291AE5 xor eax,ebp
01291AE7 mov dword ptr [ebp-4],eax
显然,编译器(VS2010 为 Win32 编译调试)认识到我们需要对代码进行特定对齐,因此采取措施确保它可以提供。
2) 仅当 运行 来自调试器时,才会在执行此指令时抛出异常,即使如此也并非总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样??
所以,有几点想法:
"and even then not always" - 当你 运行 这个的时候不站在你的肩膀上,我不能肯定地说。然而,看起来似乎有道理的是,只是偶然的机会,可以根据您需要的对齐方式创建堆栈。默认情况下,x86 使用 4 字节堆栈对齐。如果你需要 16 字节对齐,你有 1 in 4 shot。
至于其余的(来自https://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx#ia64alignment_topic4):
在x86架构上,操作系统不会让对齐错误对应用程序可见。 ...您也会因对齐错误而遭受性能下降,但它会比 Itanium 严重得多,因为硬件将多次访问内存以检索未对齐的数据。
TLDR:使用 __declspec(align(16)) 应该可以为您提供所需的对齐方式,即使对于堆栈变量也是如此。对于未对齐的访问,OS 将捕获异常并为您处理(以性能为代价)。
Edit1:回复下面的前 2 条评论:
根据MS的docs,你对堆栈参数的对齐是正确的,但他们也提出了解决方案:
You cannot specify alignment for function parameters. When data that
has an alignment attribute is passed by value on the stack, its
alignment is controlled by the calling convention. If data alignment
is important in the called function, copy the parameter into correctly
aligned memory before use.
您在 Microsoft connect 上的示例和有关的代码都没有为我生成相同的代码(我只使用 vs2010),所以我无法对此进行测试。但是鉴于您的样本中的这段代码:
struct mockVect2d
{
double x, y;
mockVect2d(double a, double b) : x(a), y(b) {}
似乎对齐 mockVect2d 或 2 个双打可能会有帮助。
更新:现在是 confirmed VC++ compiler bug,希望在 VS2015 RTM 中得到解决。
编辑:与许多其他报告一样,连接报告现在是垃圾。然而,编译器错误似乎在 VS2017 中得到解决 - not in 2015 update 3.
VC++ 中的一个非常特殊的代码序列生成了以下指令(对于 Win32):
unpcklpd xmm0,xmmword ptr [ebp-40h]
出现2个问题:
(1) 据我了解英特尔手册,unpcklpd 接受 128 对齐的内存地址作为第二个参数。如果地址是相对于栈帧对齐的,则不能强制。这真的是编译器错误吗?
(2) 仅当 运行 来自调试器 时,才会在执行此指令 时抛出异常,即便如此也并非总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样??
抛出的特定异常是 0xFFFFFFFF 处的访问冲突,但据我所知,这只是未对齐的代码。
[编辑:] 这是一些演示错误代码生成的来源 - 但通常不会导致崩溃。 (这主要是我想知道的)
[编辑 2:] 代码示例现在重现了实际的崩溃。这个也会在调试器之外崩溃 - 我怀疑会出现差异,因为调试器会在不同的典型基地址启动程序。
// mock.cpp
#include <stdio.h>
struct mockVect2d
{
double x, y;
mockVect2d() {}
mockVect2d(double a, double b) : x(a), y(b) {}
mockVect2d operator + (const mockVect2d& u) {
return mockVect2d(x + u.x, y + u.y);
}
};
struct MockPoly
{
MockPoly() {}
mockVect2d* m_Vrts;
double m_Area;
int m_Convex;
bool m_ParClear;
void ClearPar() { m_Area = -1.; m_Convex = 0; m_ParClear = true; }
MockPoly(int len) { m_Vrts = new mockVect2d[len]; }
mockVect2d& Vrt(int i) {
if (!m_ParClear) ClearPar();
return m_Vrts[i];
}
const mockVect2d& GetCenter() { return m_Vrts[0]; }
};
struct MockItem
{
MockItem() : Contour(1) {}
MockPoly Contour;
};
struct Mock
{
Mock() {}
MockItem m_item;
virtual int GetCount() { return 2; }
virtual mockVect2d GetCenter() { return mockVect2d(1.0, 2.0); }
virtual MockItem GetItem(int i) { return m_item; }
};
void testInner(int a)
{
int c = 8;
printf("%d", c);
Mock* pMock = new Mock;
int Flag = true;
int nlr = pMock->GetCount();
if (nlr == 0)
return;
int flr = 1;
if (flr == nlr)
return;
if (Flag)
{
if (flr < nlr && flr>0) {
int c = 8;
printf("%d", c);
MockPoly pol(2);
mockVect2d ctr = pMock->GetItem(0).Contour.GetCenter();
// The mess happens here:
// ; 74 : pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
//
// call ? Vrt@MockPoly@@QAEAAUmockVect2d@@H@Z; MockPoly::Vrt
// movdqa xmm0, XMMWORD PTR $T4[ebp]
// unpcklpd xmm0, QWORD PTR tv190[ebp] **** crash!
// movdqu XMMWORD PTR[eax], xmm0
pol.Vrt(0) = ctr + mockVect2d(1.0, 0.);
pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
}
}
}
void main()
{
testInner(2);
return;
}
如果您愿意,请下载一个现成的 vcxproj,其中包含 here 中设置的所有开关。这也包括完整的 ASM。
既然没有人站出来,那我就来一炮。
1) 如果地址是相对于栈帧对齐的则不能强制。这真的是编译器错误吗?
我不确定您是否不能强制堆栈变量对齐。考虑这段代码:
struct foo
{
char a;
int b;
unsigned long long c;
};
int wmain(int argc, wchar_t* argv[])
{
foo moo;
moo.a = 1;
moo.b = 2;
moo.c = 3;
}
查看 main 的启动代码,我们看到:
00E31AB0 push ebp
00E31AB1 mov ebp,esp
00E31AB3 sub esp,0DCh
00E31AB9 push ebx
00E31ABA push esi
00E31ABB push edi
00E31ABC lea edi,[ebp-0DCh]
00E31AC2 mov ecx,37h
00E31AC7 mov eax,0CCCCCCCCh
00E31ACC rep stos dword ptr es:[edi]
00E31ACE mov eax,dword ptr [___security_cookie (0E440CCh)]
00E31AD3 xor eax,ebp
00E31AD5 mov dword ptr [ebp-4],eax
将 __declspec(align(16)) 添加到 moo 得到
01291AB0 push ebx
01291AB1 mov ebx,esp
01291AB3 sub esp,8
01291AB6 and esp,0FFFFFFF0h <------------------------
01291AB9 add esp,4
01291ABC push ebp
01291ABD mov ebp,dword ptr [ebx+4]
01291AC0 mov dword ptr [esp+4],ebp
01291AC4 mov ebp,esp
01291AC6 sub esp,0E8h
01291ACC push esi
01291ACD push edi
01291ACE lea edi,[ebp-0E8h]
01291AD4 mov ecx,3Ah
01291AD9 mov eax,0CCCCCCCCh
01291ADE rep stos dword ptr es:[edi]
01291AE0 mov eax,dword ptr [___security_cookie (12A40CCh)]
01291AE5 xor eax,ebp
01291AE7 mov dword ptr [ebp-4],eax
显然,编译器(VS2010 为 Win32 编译调试)认识到我们需要对代码进行特定对齐,因此采取措施确保它可以提供。
2) 仅当 运行 来自调试器时,才会在执行此指令时抛出异常,即使如此也并非总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样??
所以,有几点想法:
"and even then not always" - 当你 运行 这个的时候不站在你的肩膀上,我不能肯定地说。然而,看起来似乎有道理的是,只是偶然的机会,可以根据您需要的对齐方式创建堆栈。默认情况下,x86 使用 4 字节堆栈对齐。如果你需要 16 字节对齐,你有 1 in 4 shot。
至于其余的(来自https://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx#ia64alignment_topic4):
在x86架构上,操作系统不会让对齐错误对应用程序可见。 ...您也会因对齐错误而遭受性能下降,但它会比 Itanium 严重得多,因为硬件将多次访问内存以检索未对齐的数据。
TLDR:使用 __declspec(align(16)) 应该可以为您提供所需的对齐方式,即使对于堆栈变量也是如此。对于未对齐的访问,OS 将捕获异常并为您处理(以性能为代价)。
Edit1:回复下面的前 2 条评论:
根据MS的docs,你对堆栈参数的对齐是正确的,但他们也提出了解决方案:
You cannot specify alignment for function parameters. When data that has an alignment attribute is passed by value on the stack, its alignment is controlled by the calling convention. If data alignment is important in the called function, copy the parameter into correctly aligned memory before use.
您在 Microsoft connect 上的示例和有关的代码都没有为我生成相同的代码(我只使用 vs2010),所以我无法对此进行测试。但是鉴于您的样本中的这段代码:
struct mockVect2d
{
double x, y;
mockVect2d(double a, double b) : x(a), y(b) {}
似乎对齐 mockVect2d 或 2 个双打可能会有帮助。
更新:现在是 confirmed VC++ compiler bug,希望在 VS2015 RTM 中得到解决。
编辑:与许多其他报告一样,连接报告现在是垃圾。然而,编译器错误似乎在 VS2017 中得到解决 - not in 2015 update 3.