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),但似乎无济于事。
答案 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_abi
和sysv_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。