VC ++ SSE代码生成 - 这是一个编译器错误吗?

时间:2015-03-11 08:14:41

标签: visual-c++ assembly x86 sse visual-studio-debugging

VC ++中一个非常特殊的代码序列生成了以下指令(对于Win32):

unpcklpd    xmm0,xmmword ptr [ebp-40h]

出现2个问题:

(1)就我理解的intel手册而言,unpcklpd接受128对齐的内存地址作为第二个参数。如果地址相对于堆栈帧,则不能强制对齐。这真的是一个编译器错误吗?

(2)只有在从调试器运行时才会执行此指令时抛出异常,即使这样也不总是如此。即使附加到进程并执行此代码也不会抛出。这怎么可能?

抛出的特殊异常是0xFFFFFFFF处的访问冲突,但AFAIK只是未对齐的代码。


[编辑:] 这里有一些来源可以证明代码生成错误 - 但通常不会导致崩溃。 (这主要是我想知道的)

[编辑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。

2 个答案:

答案 0 :(得分:2)

更新:现在是confirmed VC++ compiler bug,希望在VS2015 RTM中解决。


编辑:与许多其他人一样,连接报告现在是垃圾。但是,编译器错误似乎在2015年更新3中的VS2017 - 而不是中得到了解决。

答案 1 :(得分:1)

由于没有其他人加强,我打算开枪。

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)仅在从调试器运行时才会执行此指令时抛出异常,即使这样也不总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样?

所以,有几点想法:

在x86架构上,操作系统不会使对齐故障对应用程序可见。 ...你也会在对齐故障上遭受性能下降,但是它会比Itanium严重得多,因为硬件会对内存进行多次访问以检索未对齐的数据。

TLDR:使用__declspec(align(16))可以为您提供所需的对齐方式,即使对于堆栈变量也是如此。对于未对齐的访问,操作系统将捕获异常并为您处理(以性能为代价)。

Edit1:回应下面的前两条评论:

基于MS的docs,您对堆栈参数的对齐是正确的,但他们也提出了一个解决方案:

  

您无法指定功能参数的对齐方式。当数据那个   对象属性是通过堆栈上的值传递的   对齐由调用约定控制。 如果数据对齐   在被调用函数中很重要,将参数正确复制   使用前对齐记忆。

你的微软连接样本和代码都没有为我生成相同的代码(我只在vs2010上),所以我无法测试。但是从您的样本中获取此代码:

struct mockVect2d
{
    double x, y;
    mockVect2d(double a, double b) : x(a), y(b) {}

似乎对齐mockVect2d或2个双打可能有所帮助。