要解决Windows调用保留xmm寄存器约定的问题吗?

时间:2019-05-17 15:06:22

标签: windows assembly sse calling-convention abi

Windows上是否有任何方法可以解决将XMM寄存器保留在函数调用中的要求?(除了全部以汇编形式编写)

不幸的是,我有许多AVX2内在功能使之blo肿。

作为示例,它将被编译器(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),但似乎无济于事。

1 个答案:

答案 0 :(得分:2)

对要通过函数指针组合在一起的助手功能使用x86-64 System V调用约定。在该调用约定中,所有xmm / ymm0..15和zmm0..31都被调用,因此即使需要多个5个向量寄存器的帮助器函数也不必保存/恢复任何向量。

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

这会将XMM6..15的所有保存/恢复提升到该调用者中,而不是每个助手功能。这样可以减少静态代码的大小,并通过函数指针多次调用来分摊运行时成本。


AFAIK,MSVC不支持使用x86-64 System V调用约定的标记功能,仅支持快速调用与矢量调用,因此您将不得不使用clang

(ICC有错误,无法在调用System V ABI函数的周围保存/恢复XMM6..15)。

Windows GCC is buggy with 32-byte stack alignment是为了溢出__m256,因此将-march=的GCC与包括AVX在内的任何东西一起使用通常是不安全的。


在函数和函数指针声明上使用__attribute__((sysv_abi))__attribute__((ms_abi))

我认为ms_abi__fastcall,而不是__vectorcall。 Clang也可能支持__attribute__((vectorcall)),但是我还没有尝试过。 Google的搜索结果主要是功能请求/讨论。

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。 (在Godbolt目标Linux上为gcc / clang,但是我在函数指针和函数指针上都使用了显式ms_abisysv_abi,因此代码生成不依赖于默认值为{{1 }}。显然,您希望使用Windows gcc或clang来构建函数,因此对其他函数的调用将使用正确的调用约定。以及有用的目标文件格式等)

请注意,gcc / clang发出了sysv_abi的代码,该代码期望RCX(Windows x64)中的传入指针arg,但将其传递给RDI和RSI(x86-64 System V)中的被调用者。

outer()

GCC编写基本相同的代码。但是Windows GCC对于AVX来说是错误的。

ICC19进行类似的代码,但没有保存/恢复xmm6..15。这是一个严重的错误。如果有任何被调用者破坏那些允许的规则,则从此函数返回将违反其调用约定。

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


如果您的被调用方不需要YMM寄存器中的所有,则将它们全部保存/恢复到外部函数中就太过分了。但是现有的工具链没有中间立场。您必须在asm中手写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 才能利用这一优势,例如,知道您所有可能的被叫者都不会破坏XMM15。


请注意,从outer内部调用其他MS-ABI函数完全可以。 GCC / clang也会(排除错误)为此发出正确的代码,如果被调用的函数选择不破坏xmm6..15。