Clang:x86 FPU调用约定

Clang: x86 FPU calling convention

本文关键字:调用 约定 FPU x86 Clang      更新时间:2023-10-16

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

我尝试(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输出显示的内容,也不是发生的内容。请注意,您的第一个函数将其dwordfloatarg从堆栈加载到xmm0中,然后将mulsd与qworddoublearg一起使用movss xmm0, dword ptr [ebp + 12]是一个破坏XMM0旧内容的负载;XMM0不是此函数的输入。

然后,为了按照您正在使用的顽固的旧32位调用约定在x87st0中返回retval,它使用movsd存储到堆栈和fldx87加载。

*运算符将float提升为double以匹配其他操作数,从而产生double乘法,而不是long double。在返回临时double结果之前,不会发生从doublelong double的升级。

看起来clang默认为gcc调用的-mfpmath=sse(如果可用(。这通常是好的,除了x87返回值调用约定会阻碍的小函数。(还要注意,x87从浮点和双精度"免费"升级为长双精度,这是fld dwordqword工作方式的一部分。(Clang并没有检查在一个小函数中使用SSE数学会花费多少开销;在这里,使用x87进行一次乘法显然会更有效。

但无论如何,-mno-sse并没有改变ABI;仔细阅读你的asm如果是的话,生成的asm会吸得更少!


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

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


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


您可以使用return f * (long double)d;

它编译为与-mno-sse版本相同的asm。https://godbolt.org/z/LK0s_5

SSE2不支持80位x87类型,因此clang被迫使用fmul。它最终根本没有扰乱SSE,结果是它需要它作为返回值。