从地址位置加载XMM寄存器

时间:2016-08-18 13:38:53

标签: c++ assembly sse cpu-registers

我正在尝试使用32位操作系统上的XMM0 128位寄存器从/向char指针数组加载/存储内存。

我尝试的很简单:

int main() {
    char *data = new char[33];
    for (int i = 0; i < 32; i++)
        data[i] = 'a';
    data[32] = 0;
    ASM
    {
        movdqu xmm0,[data]
    }

    delete[] data;
}

问题是这似乎不起作用。我第一次调试Win32应用程序时得到了:

  

xmm0 = 0024F8380000000000F818E30055F158

我第二次调试它时得到了:

  

xmm0 = 0043FD6800000000002C18E3008CF158

所以行必须有一些东西:

movdqu xmm0,[data]

我尝试使用此代码:

movdqu xmm0,data

但我得到了相同的结果。

我认为问题在于我复制地址而不是地址处的数据。但是,xmm0寄存器中显示的值对于32位地址而言太大,因此必须从另一个地址复制内存。

我还尝试了一些我在互联网上找到的其他说明,但结果相同。

这是我传递指针的方式还是我误解了xmm基础知识?

我们将非常感谢有效的解决方案。

即使我找到了解决方案(最后三小时后),我还是想解释一下:

ASM
    {
        push eax
        mov eax,data
        movdqu xmm0,[eax]
        pop eax
    }

为什么要将指针传递给32位寄存器?

2 个答案:

答案 0 :(得分:3)

代码的问题是function getFragIdFromDbId(viewer, dbid) { var returnValue; var it = viewer.model.getData().instanceTree; it.enumNodeFragments(dbid, function (fragId) { console.log("dbId: " + dbid + " FragId : " + fragId); returnValue = fragId; }, false); return returnValue; } ... // only need the start vertex var floatArray = []; for (var i = 0; i < dbidArray.length; i++) { var fragId = getFragIdFromDbId(viewer, dbidArray[i]); var mesh = viewer.impl.getRenderProxy(viewer.model, fragId); var matrixWorld = mesh.matrixWorld; var lmvBufferGeometry = mesh.geometry; var lmvFloatArray = lmvBufferGeometry.vb; //this will have an array of 6 values 0,1,2 are start vertext , 3,4,5 are end vertex floatArray.push(lmvFloatArray[0]); floatArray.push(lmvFloatArray[1]); floatArray.push(lmvFloatArray[2]); } //use matrixWorld to convert array to worldSpace 是一个指针。汇编代码datamovdqu xmm0,[data]地址处的16个字节加载到寄存器data中。这意味着4或8个字节包含指针的值和内存中的任何字节。幸运的是指针地址在内存中正确对齐,否则会出现分段错误。没有什么能保证这种一致性。

使用自动数组xmm0的备选方案可以解决寻址问题(char data[33];将从数组加载数据)但不解决对齐问题,您仍然可能会遇到违规,具体取决于编译器如何对齐具有自动存储的阵列。同样,无法保证正确对齐。

您找到的解决方案可能是一种很好的方法,但与movqdu不同,我不确定malloc()返回的指针是否对任何对齐都有效。

这应该适用于所有情况:

new

正如Peter Cordes所评论的那样,将内在函数用于此类事情要好得多,即mm_loadu_si128。有两个主要原因:首先,64位版本不支持内联汇编,因此通过使用内在函数,您的代码变得更加轻松。其次,编译器在优化内联汇编方面做得相对较差,特别是,往往会做很多无意义的内存存储和加载。编译器在优化内在函数方面做得更好,这使得代码运行得更快(这是使用内联汇编的重点!)。

答案 1 :(得分:1)

#include <iostream>

int main()
{
    char *dataptr = new char[33];
    char datalocal[33];
    dataptr[0] = 'a';   dataptr[1] = 0;
    datalocal[0] = 'a'; datalocal[1] = 0;
    printf("%p %p %c\n", dataptr, &dataptr, dataptr[0]);
    printf("%p %p %c\n", datalocal, &datalocal, datalocal[0]);
    delete[] dataptr;
}

输出:

0xd38050 0x7635bd709448 a
0x7635bd709450 0x7635bd709450 a

正如我们所看到的,动态指针data实际上是指针变量(0x7635BD709448处的32位或64位),包含指向堆的指针0xD38050

局部变量直接是一个33个字符长的缓冲区,在地址0x7635BD709450分配。

datalocal也可以作为char *值。

我对这个正式的C ++解释有点困惑。在编写C ++代码时,这感觉非常自然,dataptr [0]是堆内存中的第一个元素(即两次取消引用dataptr),但在汇编程序中,您可以看到dataptr的真实性质,它是地址的指针变量。因此,您首先使用mov eax,[data]加载eax =加载0xD38050的堆指针,然后使用{{1}将0xD38050的内容加载到XMM0中}。

对于局部变量,没有变量的地址;符号[eax]已经是第一个元素的地址,因此datalocal将起作用。

在“错误”的情况下,你仍然可以movdqu xmm0,[data];从32位变量加载128位不是CPU的问题。它将继续读取超过32位并读取属于其他变量/代码的另外96位。如果你是一个内存边界,这是应用程序的最后一个内存页面,它将在无效访问时崩溃。

评论中几次提到了对齐。这是一个有效的观点;要通过movdqu xmm0,[data]访问内存,它应该对齐。检查您的C ++编译器内在函数。对于Visual Studio,这应该有效:

movdqu

关于我的C ++解释:也许我从一开始就弄错了。

__declspec(align(16)) char datalocal[33]; char *dataptr = _aligned_malloc(33, 16); _aligned_free(dataptr); 是dataptr符号的值,即堆地址。然后dataptr取消引用堆地址,访问分配的内存的第一个元素。 dataptr[0]&dataptr值的地址。这也适用于dataptr这样的语法,您将nullptr值存储到dataptr变量中,而不是覆盖dataptr符号地址。

对于dataptr = nullptr;,访问纯datalocal[]基本上没有意义,就像datalocal一样,因为它是一个数组变量,所以你应该总是提供datalocal = 'a';索引。而[]是这种数组的地址。然后,纯&datalocal是一个别名快捷方式,可以更容易地使用数组等进行数学计算,同时还有datalocal类型,但如果纯char *会抛出语法错误,它仍然会可以编写C ++代码(使用datalocal作为指针,&datalocal作为元素),它完全符合datalocal[..]逻辑。

结论:从一开始你的示例就出错了,因为汇编语言dataptr正在加载[data]的值,data是指向new返回的堆的指针。 / p>

这是我自己的解释,现在一些C ++专家会从正式的角度来解读它...... :))