铛:x86 FPU调用约定

时间:2019-10-26 11:00:56

标签: c++ x86 clang sse calling-convention

我需要为32位平台(x86):Win32,Linux32和MacOS32支持动态库和目标文件的静态链接。传递FPU参数(浮点型和双精度型)时会发生此问题。默认情况下,它们在SSE寄存器而不是堆栈中传递。我并不反对SSE,但我需要将参数和结果通过堆栈和FPU进行标准传递。

I tried (godbolt)设置 -mno-sse 选项,这将产生所需的结果。但是我不想完全放弃SSE,有时我想使用内部函数和/或使用MMX / SSE优化。

__attribute__((stdcall))
long double test(int* num, float f, double d) 
{
    *num = sizeof(long double);
    return f * d;
}
/*-target i386-windows-gnu -c -O3*/
        push    ebp
        mov     ebp, esp
        and     esp, -8
        sub     esp, 8
        movss   xmm0, dword ptr [ebp + 12] # xmm0 = mem[0],zero,zero,zero
        mov     eax, dword ptr [ebp + 8]
        cvtss2sd        xmm0, xmm0
        mov     dword ptr [eax], 12
        mulsd   xmm0, qword ptr [ebp + 16]
        movsd   qword ptr [esp], xmm0
        fld     qword ptr [esp]
        mov     esp, ebp
        pop     ebp
        ret     16
/*-target i386-windows-gnu -mno-sse -c -O3*/
        mov     eax, dword ptr [esp + 4]
        mov     dword ptr [eax], 12
        fld     dword ptr [esp + 8]
        fmul    qword ptr [esp + 12]
        ret     16

1 个答案:

答案 0 :(得分:4)

函数的两个版本都使用相同的调用约定

  

默认情况下,它们是通过SSE寄存器而不是堆栈传递的。

那不是您的asm输出显示的内容,也不是发生的内容。请注意,您的第一个函数将从堆栈中将其双字float arg加载到xmm0中,然后还将mulsd与qword double arg一起从堆栈中使用。 {{ 1}}是破坏XMM0旧内容的负载; XMM0不是此函数的输入。

然后,按照您使用的旧的32位调用约定,以x87 movss xmm0, dword ptr [ebp + 12]的形式返回retval,它使用st0存储到堆栈中,并使用movsd x87加载。

fld运算符将*提升为float以匹配另一个操作数,从而导致double乘而不是double。在返回临时long double结果之前,不会从double升级到long double

似乎clang默认为gcc称为double(如果有)。这通常是很好的,除了小函数会妨碍x87返回值调用约定。 (还请注意,作为-mfpmath=ssefld dword工作方式的一部分,x87具有从浮动和双精度到长双精度的“免费”提升。)Clang并未检查是否要花费多少开销在一个小函数中使用SSE数学;在这里使用x87进行一次乘法显然会更有效。

但是无论如何,qword并未更改ABI; 仔细阅读您的asm。


在Windows上,如果您根本无法编写32位代码,则-mno-sse应该是传递/返回FP变量的更好方法:它可以使用XMM寄存器传递/返回。显然,任何一成不变的ABI(例如现有库)都需要正确声明,以便编译器对其进行调用/正确接收它们的返回值。

您当前拥有的 vectorcall,堆栈中有FP args,并在stdcall中返回。


顺便说一句,第一个函数中的很多代码都是通过clang对齐堆栈来溢出/重新加载临时st0; Windows ABI仅保证4字节堆栈对齐。要避免高速缓存行拆分的风险,这几乎是绝对不值得的工作。尤其是当它可能刚刚破坏了其double堆栈arg作为临时空间,并希望调用者将其对齐时。启用了优化功能,只需设置一个框架指针即可double d,而不会丢失旧的ESP。


您可以使用and esp

这将编译为与return f * (long double)d;版本相同的asm。 https://godbolt.org/z/LK0s_5

SSE2不支持80位x87类型,因此clang被迫使用-mno-sse。最终它完全不会与SSE混为一谈,然后结果就是需要它作为返回值的地方。