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

Posted

技术标签:

【中文标题】将 __m128d 从 MASM 过程返回给 C 调用者【英文标题】:Returning a __m128d from MASM procedure to a C caller 【发布时间】:2014-12-09 19:18:57 【问题描述】:

我正在将一个函数从内联汇编移植到 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]) 会导致访问冲突(访问冲突读取位置 0x00000000)。我已经在调试器中验证了结果的地址,它看起来很好。

我做错了什么?

【问题讨论】:

地址对齐是否正确? 是否可以修改调用者以返回指向 __m128d 的指针? @Mehrdad。是的。 __m128d 用 __declspec 定义以正确对齐它,我在调试器中仔细检查了地址。 @mbomb007 在这种特殊情况下,我可以通过 xmm0 返回值。但是,我还有一些其他函数需要返回多个值,所以我真的需要弄清楚如何让 out 参数正常工作。这就是你的意思吗? @jaket 是的,这就是我要问的。 【参考方案1】:

出于教育目的,我编写了一个使用内在函数的函数版本:

#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 进行编译以生成程序集列表(编辑以删除行 cmets):

; 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约定,并在堆栈上隐藏参数,我看到out参数实际上是通过r8间接写入的,这是x64代码的第三个整数参数,每个MSDN。我认为如果您的汇编代码采用此参数约定,它将起作用。

阴影堆栈空间没有用实际参数值初始化;它适用于被调用者,如果他们在使用寄存器时需要一个地方来存储值。这就是您的代码中出现零值取消引用错误的原因。调用约定不匹配。调试器知道调用约定,因此它可以向您显示参数的注册值。

【讨论】:

在所有情况下都无法使用内在函数。 VC++ 发出的代码至少在某些情况下可能非常可怕,而我试图移植的内联程序集是用于信号处理的高度优化的内部循环。我确实喜欢使用 intrisincs 至少为我的函数原型建模的想法。谢谢。

以上是关于将 __m128d 从 MASM 过程返回给 C 调用者的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MSVC 中高效地将两个 __m128d 转换为一个 __m128i?

如何将两个_pd 转换为一个_ps?

如何将 __m128 反转为整数

用内在函数初始化 __m128i 常量的最快方法?

如何在 C 中打印 __m128i 变量的位?

*(__m128*)(&A) 和 (__m128)A 有啥区别