从MASM过程返回__m128d给C调用者

9

我将一个内联汇编函数从Visual Studio 2013中转换为MASM汇编,但是在获取返回值时遇到了问题。

以下是C调用者和汇编函数原型:

extern "C" void AbsMax(__m128d* samples, int len, __m128d* pResult);

__m128d AbsMax(__m128d* samples, int len)
{
    __m128d absMax = { 0, 0 };
    AbsMax(samples, len, &absMax);
    return absMax;
}

还有汇编函数:

.686              ;Target processor.  Use instructions for Pentium class machines
.xmm

.model flat, c    ;Use the flat memory model. Use C calling conventions
.code             ;Indicates the start of a code segment.

AbsMax proc samples:PTR DWORD, len:DWORD, result:PTR XMMWORD
    ;; Load up registers. xmm0 is min, xmm1 is max. L is Ch0, H is Ch1.
    mov     ecx,  [len]
    shl     ecx,  4
    mov     esi,  [samples]
    lea     esi,  [esi+ecx]
    neg     ecx
    pxor    xmm0, xmm0
    pxor    xmm1, xmm1

ALIGN 16
_loop:
    movaps  xmm2, [esi+ecx]
    add     ecx,  16
    minpd   xmm0, xmm2
    maxpd   xmm1, xmm2
    jne     _loop

    ;; Store larger of -min and max for each channel. xmm2 is -min.
    pxor    xmm2, xmm2
    subpd   xmm2, xmm0
    maxpd   xmm1, xmm2
    movaps  [result], xmm1  ; <=== access violation here

    xor eax, eax
    xor ebx, ebx
    ret
AbsMax ENDP 
END 

据我了解,MASM的约定是,通常通过EAX寄存器返回返回值。然而,由于我试图返回一个128位的值,所以我认为使用out参数是正确的方法。正如您在汇编清单中看到的,分配out参数(movaps [result])导致访问冲突(Access violation reading location 0x00000000)。我已经在调试器中验证了result的地址,它看起来很好。
我做错了什么?

你能否将调用者修改一下,以便它返回一个指向__m128d的指针? - mbomb007
@Mehrdad。是的,没错。__m128d被定义为具有适当对齐方式的__declspec,并且我在调试器中仔细检查了地址。 - jaket
@mbomb007 在这种情况下,我可以通过xmm0返回值。然而,我有一些其他函数需要返回多个值,所以我真的需要弄清楚如何让一个输出参数起作用。这是你的意思吗? - jaket
@mbomb007 谢谢。我也在使用cdecl。此外,我进行这个练习的整个原因是VC++在64位模式下没有内联汇编器。所以我不知道如何从调用者中访问寄存器。谢谢。 - jaket
@jaket 我尝试在Google上搜索访问C++寄存器的方法。这里有一个可能会有所帮助的链接:http://accu.org/index.php/journals/281 - mbomb007
显示剩余7条评论
1个回答

3

为了教育目的,我编写了一个使用内置函数的版本来实现您的功能:

#include <immintrin.h>

extern "C" void AbsMax(__m128d* samples, int len, __m128d* pResult)
{
    __m128d min = _mm_setzero_pd();
    __m128d max = _mm_setzero_pd();
    while (len--)
    {
        min = _mm_min_pd(min, *samples);
        max = _mm_max_pd(max, *samples);
        ++samples;
    }
    *pResult = _mm_max_pd(max, _mm_sub_pd(_mm_setzero_pd(), min));
}

然后我使用VC++ x64编译器进行编译,使用cl /c /O2 /FA absmax.cpp生成汇编清单(编辑后删除了行注释):

; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.31101.0 
include listing.inc

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  AbsMax
_TEXT   SEGMENT
samples$ = 8
len$ = 16
pResult$ = 24
AbsMax PROC                     ; COMDAT
    xorps   xmm3, xmm3
    movaps  xmm2, xmm3
    movaps  xmm1, xmm3
    test    edx, edx
    je  SHORT $LN6@AbsMax
    npad   3
$LL2@AbsMax:
    minpd   xmm2, XMMWORD PTR [rcx]
    maxpd   xmm1, XMMWORD PTR [rcx]
    lea rcx, QWORD PTR [rcx+16]
    dec edx
    jne SHORT $LL2@AbsMax
$LN6@AbsMax:
    subpd   xmm3, xmm2
    maxpd   xmm1, xmm3
    movaps  XMMWORD PTR [r8], xmm1
    ret 0
AbsMax  ENDP
_TEXT   ENDS
END

注意x64默认使用__fastcall传参方式,参数被放在栈上的阴影位置。据MSDN所述,在x64代码中,输出参数实际上是通过第三个整数参数r8间接写入的。如果您的汇编代码采用这种参数传递方式,它应该可以正常工作。
被隐藏的栈空间没有用实际参数值初始化;这是为被调用者准备的,如果他们需要一个存储寄存器中值的地方。这就是你的代码为什么出现零值解引用错误的原因。这里有一个调用约定不匹配的问题。调试器知道此调用约定,所以可以向您显示参数的寄存器值。

在某些情况下,我无法使用内置函数。VC++生成的代码在某些情况下可能非常可怕,而我正在尝试移植的内联汇编是用于信号处理的高度优化的内部循环。不过,我确实喜欢使用内置函数来模拟我的函数原型的想法。谢谢。 - jaket

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接