解决 windows 保留 xmm 寄存器的调用约定?

Work around windows calling convention preserving xmm registers?

在 Windows 上有什么方法可以解决 XMM 寄存器在函数调用中保留的要求吗?(除了在汇编中全部编写外)

不幸的是,我有许多 AVX2 内部函数因此变得臃肿。

例如,编译器 (MSVC) 会将其放置在函数的顶部:

00007FF9D0EBC602 vmovaps xmmword ptr [rsp+1490h],xmm6
00007FF9D0EBC60B vmovaps xmmword ptr [rsp+1480h],xmm7
00007FF9D0EBC614 vmovaps xmmword ptr [rsp+1470h],xmm8
00007FF9D0EBC61D vmovaps xmmword ptr [rsp+1460h],xmm9
00007FF9D0EBC626 vmovaps xmmword ptr [rsp+1450h],xmm10
00007FF9D0EBC62F vmovaps xmmword ptr [rsp+1440h],xmm11
00007FF9D0EBC638 vmovaps xmmword ptr [rsp+1430h],xmm12
00007FF9D0EBC641 vmovaps xmmword ptr [rsp+1420h],xmm13
00007FF9D0EBC64A vmovaps xmmword ptr [rsp+1410h],xmm14
00007FF9D0EBC653 vmovaps xmmword ptr [rsp+1400h],xmm15

然后在函数的末尾..

00007FF9D0EBD6E6 vmovaps xmm6,xmmword ptr [r11-10h]
00007FF9D0EBD6EC vmovaps xmm7,xmmword ptr [r11-20h]
00007FF9D0EBD6F2 vmovaps xmm8,xmmword ptr [r11-30h]
00007FF9D0EBD6F8 vmovaps xmm9,xmmword ptr [r11-40h]
00007FF9D0EBD6FE vmovaps xmm10,xmmword ptr [r11-50h]
00007FF9D0EBD704 vmovaps xmm11,xmmword ptr [r11-60h]
00007FF9D0EBD70A vmovaps xmm12,xmmword ptr [r11-70h]
00007FF9D0EBD710 vmovaps xmm13,xmmword ptr [r11-80h]
00007FF9D0EBD716 vmovaps xmm14,xmmword ptr [r11-90h]
00007FF9D0EBD71F vmovaps xmm15,xmmword ptr [r11-0A0h]

那是 20 条什么都不做的指令,因为我不需要保留 XMM 的状态。我有 100 个这样的函数,编译器正在像这样膨胀。它们都是通过函数指针从同一个调用点调用的。

我尝试更改调用约定(__vectorcall/cdecl/fastcall),但这似乎没有任何作用。

将 x86-64 System V 调用约定用于您希望通过函数指针组合在一起的辅助函数。在该调用约定中,所有 xmm/ymm0..15 和 zmm0..31 都是 call-clobbered,因此即使需要 5 个以上向量寄存器的辅助函数也不必 save/restore。

调用它们的外部解释器函数仍应使用 Windows x64 fastcall 或 vectorcall,因此从外部来看它完全尊重调用约定。

这会将 XMM6..15 的所有 save/restore 提升到那个调用者 ,而不是每个辅助函数。这减少了静态代码大小,并通过函数指针分摊了多次调用的运行时成本。


AFAIK,MSVC 不支持将函数标记为使用 x86-64 System V 调用约定,仅支持 fastcall 与 vectorcall,因此您必须使用 clang

(ICC 有问题,无法 save/restore XMM6..15 调用 System V ABI 函数)。

溢出 __m256,因此将 GCC 与 -march= 与包含 AVX 的任何内容一起使用通常不安全。


在函数和 function-pointer 声明中使用 __attribute__((sysv_abi))__attribute__((ms_abi))

我认为ms_abi__fastcall,而不是__vectorcall。 Clang 可能也支持 __attribute__((vectorcall)),但我没试过。 Google 结果主要是特征 requests/discussion。

void (*helpers[10])(float *, float*) __attribute__((sysv_abi));

__attribute__((ms_abi))
void outer(float *p) {
    helpers[0](p, p+10);
    helpers[1](p, p+10);
    helpers[2](p+20, p+30);
}

编译如下on Godbolt with clang 8.0-O3 -march=skylake。 (gcc/clang 在 Godbolt 目标 Linux 上,但我在函数和 function-pointer 上都使用了显式 ms_abisysv_abi,因此代码生成不依赖于默认值是 sysv_abi 的事实。显然你想用 Windows gcc 或 clang 构建你的函数,所以调用其他函数将使用正确的调用约定。有用的 object-file格式等)

注意 gcc/clang 为 outer() 发出代码,期望 RCX (Windows x64) 中的传入指针 arg,但将其传递给 RDI 和 RSI (x86-) 中的被调用者64 系统 V).

outer:                                  # @outer
        push    r14
        push    rsi
        push    rdi
        push    rbx
        sub     rsp, 168
        vmovaps xmmword ptr [rsp + 144], xmm15 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 128], xmm14 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 112], xmm13 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 96], xmm12 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 80], xmm11 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 64], xmm10 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 48], xmm9 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 32], xmm8 # 16-byte Spill
        vmovaps xmmword ptr [rsp + 16], xmm7 # 16-byte Spill
        vmovaps xmmword ptr [rsp], xmm6 # 16-byte Spill
        mov     rbx, rcx                            # save p 
        lea     r14, [rcx + 40]
        mov     rdi, rcx
        mov     rsi, r14
        call    qword ptr [rip + helpers]
        mov     rdi, rbx
        mov     rsi, r14
        call    qword ptr [rip + helpers+8]
        lea     rdi, [rbx + 80]
        lea     rsi, [rbx + 120]
        call    qword ptr [rip + helpers+16]
        vmovaps xmm6, xmmword ptr [rsp] # 16-byte Reload
        vmovaps xmm7, xmmword ptr [rsp + 16] # 16-byte Reload
        vmovaps xmm8, xmmword ptr [rsp + 32] # 16-byte Reload
        vmovaps xmm9, xmmword ptr [rsp + 48] # 16-byte Reload
        vmovaps xmm10, xmmword ptr [rsp + 64] # 16-byte Reload
        vmovaps xmm11, xmmword ptr [rsp + 80] # 16-byte Reload
        vmovaps xmm12, xmmword ptr [rsp + 96] # 16-byte Reload
        vmovaps xmm13, xmmword ptr [rsp + 112] # 16-byte Reload
        vmovaps xmm14, xmmword ptr [rsp + 128] # 16-byte Reload
        vmovaps xmm15, xmmword ptr [rsp + 144] # 16-byte Reload
        add     rsp, 168
        pop     rbx
        pop     rdi
        pop     rsi
        pop     r14
        ret

GCC 生成的代码基本相同。但是 Windows GCC 与 AVX 有问题。

ICC19 生成类似的代码,但没有 xmm6..15 的 save/restore。这是一个 showstopper 错误;如果任何被调用者 像他们允许的那样破坏这些 regs,那么从此函数返回将违反其调用约定。

这使得 clang 成为您可以使用的唯一编译器。没关系; clang 非常好


如果你的被调用者不需要 all YMM 寄存器,saving/restoring 全部在外部函数中是多余的。但是现有的工具链没有中间立场;例如,您必须 hand-write outer 在 asm 中利用知道 none 可能的被调用者曾经破坏 XMM15。


请注意,从 outer() 内部调用其他 MS-ABI 函数完全没问题。 GCC / clang 也会(除了错误)为此发出正确的代码,如果被调用的函数选择不销毁 xmm6..15 也没关系。