我需要为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
默认情况下,它们通过SSE寄存器而不是堆栈传递。
这不是您的asm输出显示的内容,也不是发生的情况。请注意,您的第一个函数将从堆栈中将其双字float
arg加载到xmm0中,然后还将mulsd
与qworddouble
arg也从堆栈中使用。 movss xmm0, dword ptr [ebp + 12]
是会破坏旧内容的加载XMM0; XMM0不是此函数的输入。
然后,按照您使用的老旧的32位调用约定,以x87 st0
的形式返回retval,它将使用movsd
存储到堆栈中,并使用fld
x87加载。
*
运算符将float
提升为double
以匹配另一个操作数,从而导致double
乘而不是long double
。从double
升级到long double
直到返回临时double
结果才发生。
似乎clang缺省为gcc称为-mfpmath=sse
(如果可用)。这通常是很好的,除了小函数会妨碍x87返回值调用约定。 (还请注意,x87具有从“ float”和“ double”到“ long double”的“免费”升级,这是fld dword
和qword
的工作方式。)Clang并未检查使用SSE数学将花费多少开销小功能在这里使用x87进行一次乘法显然会更有效。
但是无论如何,-mno-sse
不会更改ABI;请更仔细地阅读您的asm。如果是,则生成的asm会少一些!
[在Windows上,如果您根本无法编写32位代码,则vectorcall
应该是传递/返回FP变量的更好方法:它可以使用XMM寄存器传递/返回。显然,任何一成不变的ABI(例如现有库)都需要正确声明,以便编译器对其进行调用/从它们正确接收返回值。
您当前拥有的是[[is stdcall
,在堆栈上带有FP args,并在st0
中返回。
double
; Windows ABI仅保证4字节堆栈对齐。要避免高速缓存行拆分的风险,这几乎是绝对不值得的工作。尤其是当它可能刚刚破坏了其double d
堆栈arg作为临时空间,并希望调用者对此进行对齐时。启用了优化,它只是设置了一个框架指针,使其可以and esp
而不会丢失旧的ESP。return f * (long double)d;
-mno-sse
版本相同的asm。 https://godbolt.org/z/LK0s_5SSE2不支持80位x87类型,因此clang被迫使用fmul
。最终它完全不会与SSE混为一谈,然后结果是需要它作为返回值的地方。