C++ 叮当声:x86 FPU调用约定

C++ 叮当声:x86 FPU调用约定,c++,x86,clang,sse,calling-convention,C++,X86,Clang,Sse,Calling Convention,我需要支持32位平台(x86)的对象文件的动态库和静态链接:Win32、Linux32和MacOS32。传递FPU参数(float和double)时会出现问题。默认情况下,它们在SSE寄存器中传递,而不是在堆栈中传递。我并不反对SSE,但我需要通过堆栈和FPU标准地传递参数和结果 设置-mno sse选项,这将产生所需的结果。但我不想完全放弃SSE,有时我想使用内部函数和/或使用MMX/SSE优化 \uuuuuuuuuuuuuuuuuuuu属性((stdcall)) 长双精度测试(int*num

我需要支持32位平台(x86)的对象文件的动态库和静态链接:Win32、Linux32和MacOS32。传递FPU参数(float和double)时会出现问题。默认情况下,它们在SSE寄存器中传递,而不是在堆栈中传递。我并不反对SSE,但我需要通过堆栈和FPU标准地传递参数和结果

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

\uuuuuuuuuuuuuuuuuuuu属性((stdcall))
长双精度测试(int*num,浮点f,双精度d)
{
*num=sizeof(长双精度);
返回f*d;
}
函数的两个版本使用相同的调用约定 默认情况下,它们在SSE寄存器中传递,而不是在堆栈中传递

这不是asm输出所显示的,也不是所发生的。请注意,您的第一个函数将其dword
float
arg从堆栈加载到xmm0中,然后使用
mulsd
和qword
double
arg也从堆栈中加载。
movss xmm0,dword ptr[ebp+12]
是一个销毁xmm0旧内容的加载;XMM0不是此函数的输入

然后,为了按照您正在使用的陈旧的32位调用约定返回x87
st0
中的retval,它使用
movsd
存储到堆栈和
fld
x87加载

*
运算符将
浮点
提升为
双精度
,以匹配另一个操作数,结果是
双精度
相乘,而不是
长双精度
。只有返回临时的
double
结果,才会从
double
升级到
long double

它似乎默认为gcc所称的
-mfpmath=sse
(如果可用)
。这通常是好的,除了x87返回值调用约定会妨碍的小函数之外。(还请注意,x87具有从float和double到long double的“免费”升级,这是
fld dword
qword
工作原理的一部分。)Clang没有检查在一个小函数中使用SSE math会花费多少开销;在这里,使用x87进行一次乘法显然会更有效

但无论如何,
-mno sse
不会改变ABI;更仔细地阅读你的asm。
如果是,生成的asm就不会那么糟糕了


在Windows上,如果您一直无法编写32位代码,
vectorcall
应该是一种更好的传递/返回FP变量的方法,如果可能的话:它可以使用XMM寄存器传递/返回FP变量。显然,任何一个固定设置的ABI(如现有库)都需要正确声明,以便编译器正确地调用它们/接收它们的返回值

您当前拥有的是
stdcall
,堆栈上有FP args,并在
st0
中返回


顺便说一句,第一个函数中的很多代码都是从对齐堆栈的叮当声到溢出/重新加载临时
double
;Windows ABI仅保证4字节堆栈对齐。这是避免缓存线拆分风险的工作量,几乎肯定不值得。尤其是当它本可以销毁其
双d
堆栈arg作为暂存空间时,并且希望调用方已将其对齐。优化已启用,只需为其设置一个帧指针即可
和esp
,而不会丢失旧的esp


您可以使用
返回f*(长双精度)d
编译为与
-mno sse
版本相同的asm

SSE2不支持80位x87类型,因此clang被迫使用
fmul
。它最终完全不会与SSE发生冲突,结果就是它需要它作为返回值。

两个版本的函数都使用相同的调用约定 默认情况下,它们在SSE寄存器中传递,而不是在堆栈中传递

这不是asm输出所显示的,也不是所发生的。请注意,您的第一个函数将其dword
float
arg从堆栈加载到xmm0中,然后使用
mulsd
和qword
double
arg也从堆栈中加载。
movss xmm0,dword ptr[ebp+12]
是一个销毁xmm0旧内容的加载;XMM0不是此函数的输入

然后,为了按照您正在使用的陈旧的32位调用约定返回x87
st0
中的retval,它使用
movsd
存储到堆栈和
fld
x87加载

*
运算符将
浮点
提升为
双精度
,以匹配另一个操作数,结果是
双精度
相乘,而不是
长双精度
。只有返回临时的
double
结果,才会从
double
升级到
long double

它似乎默认为gcc所称的
-mfpmath=sse
(如果可用)
。这通常是好的,除了x87返回值调用约定会妨碍的小函数之外。(还请注意,x87具有从float和double到long double的“免费”升级,这是
fld dword
qword
工作原理的一部分。)Clang没有检查在一个小函数中使用SSE math会花费多少开销;在这里,使用x87进行一次乘法显然会更有效

但无论如何,
-mno sse
不会改变ABI;更仔细地阅读你的asm。
如果是,生成的asm就不会那么糟糕了


在Windows上,如果您一直无法编写32位代码,
vectorcall
应该是一种更好的传递/返回FP变量的方法,如果可能的话:它可以使用XMM寄存器传递/返回FP变量。显然,任何设置为
/*-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