可变参数 x86-64 ABI。寄存器中的浮点参数个数
Vararg x86-64 ABI. Number of floating point parameters in registers
我正在查看 x86-64
ABI,对 Figure 3.31
和 Figure 3.32
中的示例有疑问:
int a, b;
long double ld;
double m, n;
__m256 u, y;
__m512 v, z;
extern void func (int a, double m, __m256 u, __m512 v, ...);
func (a, m, u, v, b, ld, y, z, n);
据说当向 func
函数传递参数时 %rax
包含 3 个,但我只能看到寄存器中传递了 2 个浮点值:ld
和 m
.所以我实现了下面的例子:
impl.c
:
#include <immintrin.h>
unsigned long func(int a, double m, __m256 u, __m512 v, ...){
unsigned long rax;
__asm__ __volatile__(
"" :
"=a" (rax) : :
);
return rax;
}
main.c
#include <immintrin.h>
#include <stdio.h>
unsigned long func(int a, double m, __m256 u, __m512 v, ...);
int main(void){
int a = 10,
b = 20;
long double ld = 30.0;
double m = 40.0,
n = 50.0;
__m256 u, y;
__m512 v, z;
printf("%lu\n", func(a, m, u, v, b, ld, y, z, n)); //prints 2
}
是打错了吗?所以寄存器 %rax
的正确内容应该是 2
而不是 3
?
ABI 文档有一个错误:对于该示例,它应该是 al=4
。此图在添加 AVX512 __m512
时仅得到部分更新;以前 al=3
是正确的,例如in the 0.99.7 revision of the ABI 没有固定的 __m512
参数。
@AnttiHaapala 是正确的,他们也未能更新 3.32 以在堆栈上显示 z
,位于 64:
。
al
应该是向量 regs 中的 总 个参数(包括固定参数)。 这包括 XMM 寄存器中的 any arg,无论是标量还是 __m128
( 可以 作为可变参数传递) . 或固定参数,也包括 __m256
和 __m512
。 (宽向量在可变参数函数的堆栈上传递;(可能)在 __m256
上使用 va_arg
的可变参数函数不需要转储所有 YMM regs,仍然只需要 XMM。用处很小-传递可变 SIMD 向量的情况。)
但请注意,80 位 long double ld
是 而不是 在 XMM 寄存器中传递的。 SSE/AVX 指令无法对 80 位 x87 扩展精度数据执行任何操作,因此强制函数将其复制 to/from XMM reg 然后返回到 x87 堆栈 reg 是没有意义的。
注意RAX的高位字节必须被被调用者忽略。调用者通常使用 mov eax, 3
而不是 mov al,3
来避免错误依赖的可能性; ABI 文档的图表基于 GCC 的正常行为,实际上应该说 %al
,而不是 %rax
Why does GCC use the value 2 for older ABI?
因为你在编译的时候忘了实际启用AVX(和AVX512)
ABI 文档假定 __m256
变量将仅在具有 YMM 寄存器(AVX 支持)的机器上使用,因此它们可以在寄存器中传递。
如果你弄错了,GCC 会警告你:
<source>: In function 'void caller()':
<source>:11:9: warning: AVX vector argument without AVX enabled changes the ABI [-Wpsabi]
11 | func (a, m, u, v, b, ld, y, z, n);
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:11:9: note: the ABI for passing parameters with 32-byte alignment has changed in GCC 4.6
<source>:11:9: warning: AVX512F vector argument without AVX512F enabled changes the ABI [-Wpsabi]
使用 gcc -O3
编译会给出该警告,并在包含来自 ABI 文档的 func()
调用的 caller()
的 asm 中包含 mov eax,2
。
用 gcc -O3 -march=skylake-avx512
(或 -mavx512f
)编译得到 4
。或者 3
如果您要遗漏进入向量寄存器的参数之一。
通常可以肯定的是,GCC 正确地实现了 ABI,所以您只需查看它的代码,看看会发生什么。构建一个复杂的方法来实际 print RAX 更复杂,并且阻止你注意到你的代码没有使用 AVX512。
void caller() {
func (a, m, u, v, b, ld, y, z, n);
}
正确编译为这个 asm (Godbolt, gcc9.2 -O3 -march=skylake-avx512
):
caller():
lea r10, [rsp+8]
and rsp, -64 # align the stack by 64
push QWORD PTR [r10-8]
mov eax, 4 # AL = 4 args in vector regs
push rbp
mov rbp, rsp # frame pointer for some reason?
push r10
sub rsp, 152 # reserve space for args
vmovaps zmm4, ZMMWORD PTR z[rip]
vmovaps ymm5, YMMWORD PTR y[rip]
vmovaps ZMMWORD PTR [rsp+48], zmm4
vmovaps YMMWORD PTR [rsp+16], ymm5 # copy the variadic wide vectors to their slots
push QWORD PTR ld[rip+8]
vmovsd xmm3, QWORD PTR n[rip] # n passed in xmm3
mov esi, DWORD PTR b[rip] # b passed in ESI
push QWORD PTR ld[rip] # low half of 16-byte ld
vmovaps zmm2, ZMMWORD PTR v[rip]
vmovaps ymm1, YMMWORD PTR u[rip] # fixed args passed in x/y/zmm0..2
vmovsd xmm0, QWORD PTR m[rip]
mov edi, DWORD PTR a[rip] # a passed in EDI
call func(int, double, float __vector(8), float __vector(16), ...)
mov r10, QWORD PTR [rbp-8]
sub rsp, -128
leave
lea rsp, [r10-8] # stack-alignment cleanup
ret
向量 regs 中的 4 个参数,AL = 4。
我正在查看 x86-64
ABI,对 Figure 3.31
和 Figure 3.32
中的示例有疑问:
int a, b;
long double ld;
double m, n;
__m256 u, y;
__m512 v, z;
extern void func (int a, double m, __m256 u, __m512 v, ...);
func (a, m, u, v, b, ld, y, z, n);
据说当向 func
函数传递参数时 %rax
包含 3 个,但我只能看到寄存器中传递了 2 个浮点值:ld
和 m
.所以我实现了下面的例子:
impl.c
:
#include <immintrin.h>
unsigned long func(int a, double m, __m256 u, __m512 v, ...){
unsigned long rax;
__asm__ __volatile__(
"" :
"=a" (rax) : :
);
return rax;
}
main.c
#include <immintrin.h>
#include <stdio.h>
unsigned long func(int a, double m, __m256 u, __m512 v, ...);
int main(void){
int a = 10,
b = 20;
long double ld = 30.0;
double m = 40.0,
n = 50.0;
__m256 u, y;
__m512 v, z;
printf("%lu\n", func(a, m, u, v, b, ld, y, z, n)); //prints 2
}
是打错了吗?所以寄存器 %rax
的正确内容应该是 2
而不是 3
?
ABI 文档有一个错误:对于该示例,它应该是 al=4
。此图在添加 AVX512 __m512
时仅得到部分更新;以前 al=3
是正确的,例如in the 0.99.7 revision of the ABI 没有固定的 __m512
参数。
@AnttiHaapala 是正确的,他们也未能更新 3.32 以在堆栈上显示 z
,位于 64:
。
al
应该是向量 regs 中的 总 个参数(包括固定参数)。 这包括 XMM 寄存器中的 any arg,无论是标量还是 __m128
( 可以 作为可变参数传递) . 或固定参数,也包括 __m256
和 __m512
。 (宽向量在可变参数函数的堆栈上传递;(可能)在 __m256
上使用 va_arg
的可变参数函数不需要转储所有 YMM regs,仍然只需要 XMM。用处很小-传递可变 SIMD 向量的情况。)
但请注意,80 位 long double ld
是 而不是 在 XMM 寄存器中传递的。 SSE/AVX 指令无法对 80 位 x87 扩展精度数据执行任何操作,因此强制函数将其复制 to/from XMM reg 然后返回到 x87 堆栈 reg 是没有意义的。
注意RAX的高位字节必须被被调用者忽略。调用者通常使用 mov eax, 3
而不是 mov al,3
来避免错误依赖的可能性; ABI 文档的图表基于 GCC 的正常行为,实际上应该说 %al
,而不是 %rax
Why does GCC use the value 2 for older ABI?
因为你在编译的时候忘了实际启用AVX(和AVX512)
ABI 文档假定 __m256
变量将仅在具有 YMM 寄存器(AVX 支持)的机器上使用,因此它们可以在寄存器中传递。
如果你弄错了,GCC 会警告你:
<source>: In function 'void caller()':
<source>:11:9: warning: AVX vector argument without AVX enabled changes the ABI [-Wpsabi]
11 | func (a, m, u, v, b, ld, y, z, n);
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:11:9: note: the ABI for passing parameters with 32-byte alignment has changed in GCC 4.6
<source>:11:9: warning: AVX512F vector argument without AVX512F enabled changes the ABI [-Wpsabi]
使用 gcc -O3
编译会给出该警告,并在包含来自 ABI 文档的 func()
调用的 caller()
的 asm 中包含 mov eax,2
。
用 gcc -O3 -march=skylake-avx512
(或 -mavx512f
)编译得到 4
。或者 3
如果您要遗漏进入向量寄存器的参数之一。
通常可以肯定的是,GCC 正确地实现了 ABI,所以您只需查看它的代码,看看会发生什么。构建一个复杂的方法来实际 print RAX 更复杂,并且阻止你注意到你的代码没有使用 AVX512。
void caller() {
func (a, m, u, v, b, ld, y, z, n);
}
正确编译为这个 asm (Godbolt, gcc9.2 -O3 -march=skylake-avx512
):
caller():
lea r10, [rsp+8]
and rsp, -64 # align the stack by 64
push QWORD PTR [r10-8]
mov eax, 4 # AL = 4 args in vector regs
push rbp
mov rbp, rsp # frame pointer for some reason?
push r10
sub rsp, 152 # reserve space for args
vmovaps zmm4, ZMMWORD PTR z[rip]
vmovaps ymm5, YMMWORD PTR y[rip]
vmovaps ZMMWORD PTR [rsp+48], zmm4
vmovaps YMMWORD PTR [rsp+16], ymm5 # copy the variadic wide vectors to their slots
push QWORD PTR ld[rip+8]
vmovsd xmm3, QWORD PTR n[rip] # n passed in xmm3
mov esi, DWORD PTR b[rip] # b passed in ESI
push QWORD PTR ld[rip] # low half of 16-byte ld
vmovaps zmm2, ZMMWORD PTR v[rip]
vmovaps ymm1, YMMWORD PTR u[rip] # fixed args passed in x/y/zmm0..2
vmovsd xmm0, QWORD PTR m[rip]
mov edi, DWORD PTR a[rip] # a passed in EDI
call func(int, double, float __vector(8), float __vector(16), ...)
mov r10, QWORD PTR [rbp-8]
sub rsp, -128
leave
lea rsp, [r10-8] # stack-alignment cleanup
ret
向量 regs 中的 4 个参数,AL = 4。