解决 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_abi
和 sysv_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 也没关系。
在 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_abi
和 sysv_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 也没关系。