Clang:x86 FPU调用约定

问题描述 投票:1回答:1

我需要为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
c++ x86 clang sse calling-convention
1个回答
4
投票

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

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

这不是您的asm输出显示的内容,也不是发生的情况。请注意,您的第一个函数将从堆栈中将其双字float arg加载到xmm0中,然后还将mulsd与qworddoublearg也从堆栈中使用。 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 dwordqword的工作方式。)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_5

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

© www.soinside.com 2019 - 2024. All rights reserved.