assembly 从地址位置加载XMM寄存器

2nc8po8w  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(163)

我尝试在32位操作系统上使用XMM 0 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 = 0024F838000000000F818E30055F158
第二次调试时,我得到了:
xmm0 = 0043FD6800000000002C18E3008CF158
所以这句话一定有什么含义:

movdqu xmm0,[data]

我试着用这个代替:

movdqu xmm0,data

但我得到了同样的结果
我认为的问题是我复制了地址而不是地址处的数据。但是,xmm0寄存器显示的值对于32位地址来说太大,因此它必须从另一个地址复制内存。
我也试了一些我在网上找到的其他说明,但结果是一样的。
是我传递指针的方式还是我误解了xmm的一些基础知识?
将理解具有解释的有效解决方案。
即使我找到了解决方案(终于在三个小时后),我仍然想要一个解释:

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

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

rwqw0loc

rwqw0loc1#

你的代码的问题是data是一个指针。汇编代码movdqu xmm0,[data]data地址处的16个字节加载到寄存器xmm0中。这意味着4或8个字节包括指针的值和内存中跟随的任何字节。你很幸运,指针地址在内存中正确对齐,否则你会得到一个分段错误。没有什么能保证这种一致性。
使用自动数组char data[33];的替代方案可以解决寻址问题(movqdu将从数组加载数据),但不能解决对齐问题,您仍然可能会遇到冲突,具体取决于编译器如何将数组与自动存储对齐。同样,不能保证正确对齐。
您找到的解决方案可能是一个很好的方法,但与malloc()不同,我不确定new返回的指针是否对大于指定类型的对齐有效。此外,newmalloc()都不能保证您打算使用的SIMD指令所需的16字节对齐。大多数系统都有内存分配API,以确保16或更宽的对齐:
POSIX系统有posix_memalign

int posix_memalign(void **memptr, size_t alignment, size_t size);

Linux系统也支持memalign

void *memalign(size_t alignment, size_t size);

首选的解决方案是在<stdlib.h>中定义的C标准函数aligned_alloc,在C11中添加,但可能无法在所有系统上使用:

void *aligned_alloc(size_t alignment, size_t size);

如果您的系统上有此功能,您可以这样写:

#include <stdlib.h>

int main(void) {
    char *data = aligned_alloc(16, 32);
    for (int i = 0; i < 32; i++) {
        data[i] = 'a';
    }
    __asm {
        mov    eax, data
        movdqu xmm0, [eax]
    }
    free(data);
    return 0;
}

正如Peter Cordes所评论的那样,使用intrinsic来处理这类事情要好得多,即mm_loadu_si128。有两个主要原因:首先,内联汇编语法不是标准的,并且在不同的编译器之间以及在32位和64位版本之间都有所不同,因此通过使用intrinsic,代码的可移植性会稍微提高一些。第二,编译器在优化内联汇编方面做得相对较差,特别是倾向于做大量无意义的内存存储和加载。编译器在优化intrinsic方面做得更好,这使您的代码运行得更快(这是使用内联汇编的全部要点!).

a0x5cqrl

a0x5cqrl2#

#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实际上是一个指针变量(32位或64位在0x7635BD709448),包含一个指向堆的指针0xD38050
局部变量直接是一个33个字符长的缓冲区,分配在地址0x7635BD709450
但是datalocal也可以作为char *值使用。
我有点搞不清楚C对这个问题的正式解释是什么。在编写C代码时,这感觉很自然,dataptr[0]是堆内存中的第一个元素(也就是说,解引用dataptr两次),但在汇编程序中,您可以看到dataptr的真实本质,它是指针变量的地址。因此,您必须首先通过mov eax,[data] = loads eax0xD38050加载堆指针,然后您可以通过使用[eax]0xD38050的内容加载到XMM 0中。
有了局部变量,就没有带有它的地址的变量;符号datalocal已经是第一个元素的地址,所以movdqu xmm0,[data]将工作。
在“错误”的情况下,您仍然可以执行movdqu xmm0,[data];从一个32位变量加载128位并不是CPU的问题。它将简单地继续读取32位以外的数据,并读取属于其他变量/代码的另外96位。如果您在内存边界附近,并且这是应用程序的最后一个内存页,则它将在无效访问时崩溃。
在评论中多次提到对齐。这是一个有道理的观点为了通过movdqu访问存储器,它应该被对准。检查你的C++编译器内部。对于Visual Studio,这应该可以工作:

__declspec(align(16)) char datalocal[33];
char *dataptr = _aligned_malloc(33, 16);
_aligned_free(dataptr);

关于我的C解释:也许我从一开始就错了。
dataptr是dataptr符号的值,即堆地址。然后dataptr[0]解引用堆地址,访问已分配内存的第一个元素。&dataptrdataptr值的地址。这对于像dataptr = nullptr;这样的语法也是有意义的,在这种语法中,您将nullptr值存储到dataptr变量中,而不是覆盖dataptr符号地址。
对于datalocal[]来说,访问纯datalocal基本上没有意义,就像在datalocal = 'a';中一样,因为它是一个数组变量,所以你应该总是提供[]索引。而&datalocal是这样一个数组的地址。纯datalocal是一个别名快捷方式,可以更容易地使用数组等进行点数学运算,也有char *类型,但如果纯datalocal会抛出语法错误,仍然可以编写C
代码(使用&datalocal作为指针,datalocal[..]作为元素),并且它完全适合dataptr逻辑。
结论:从一开始你的例子就错了,因为在汇编语言中,[data]正在加载data的值,这是new返回的堆指针。
这是我自己的解释,现在一些C++Maven会来从形式的Angular 把它撕成碎片...:)))

相关问题