我需要为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
答案 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=sse
和fld 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混为一谈,然后结果就是需要它作为返回值的地方。