【问题标题】:Vararg x86-64 ABI. Number of floating point parameters in registers可变参数 x86-64 ABI。寄存器中浮点参数的数量
【发布时间】:2019-11-13 05:09:52
【问题描述】:

我正在查看x86-64 ABI 并对Figure 3.31Figure 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 个浮点值:ldm。所以我实现了下面的例子:

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

【问题讨论】:

  • 你为什么不像普通人一样使用调试器,而不是希望 GCC 不会用你的 inline-asm hack 破坏传入的 RAX?或者用纯asm写函数。
  • @RaymondChen n 应该在堆栈上。
  • 我认为图 3.32 完全是错误的。例如,没有提到z,它说“只有命名的 __m256 和 __m512 参数可以在寄存器中传递”,然后它显示一个可变参数 __m256 正在寄存器中传递......
  • 是的,当然可以。它可以在检查传入的 AL 和内联 asm 语句的代码之间生成它想要的任何代码。例如在禁用优化的情况下,它将设置一个堆栈帧并将固定的参数溢出到堆栈中。它可能不会修改 RAX,但通常不是一个安全的假设。使用调试器设置断点会容易得多,asm("func: ret"); 在全局范围内也是如此。
  • 糟糕,上面的链接错误,应该是github.com/hjl-tools/x86-psABI/blame/hjl/master/… line 2076

标签: c assembly x86-64 abi


【解决方案1】:

ABI 文档有一个错误:对于该示例,它应该是 al=4。此图仅在添加 AVX512 __m512 时部分更新; al=3 之前是正确的,例如in the 0.99.7 revision of the ABI 没有固定的__m512 arg。

@AnttiHaapala 是正确的,他们也未能更新 3.32 以在堆栈上显示 z 64:

al 应该是向量 regs 中 args 的 total 数量(包括固定的 args)。 这包括 XMM 寄存器中的 任何 arg,无论是标量还是 __m128 可以作为可变参数传递)。 或者对于固定参数,还包括__m256__m512。 (宽向量在可变参数函数的堆栈上传递;(可能)在 __m256 上使用 va_arg 的可变参数函数不需要转储所有 YMM regs,仍然只有 XMM。传递的用例很少可变 SIMD 向量。)

但请注意,80 位 long double ld 不是在 XMM 寄存器中传递。 SSE/AVX 指令无法对 80 位 x87 扩展精度数据执行任何操作,因此强制函数将其复制到 XMM reg 或从 XMM reg 复制然后返回到 x87 堆栈 reg 是没有意义的。


注意,RAX 的高字节必须被被调用者忽略。对于调用者来说,mov eax, 3 而不仅仅是mov al,3 通常很方便,以避免出现错误依赖的可能性; ABI 文档的图表基于 GCC 的正常行为,实际上应该说 %al,而不是 %rax


为什么 GCC 对旧 ABI 使用值 2?

因为您在编译时忘记实际启用 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 编译会给出警告,并在 asm 中包含 mov eax,2 以获取包含来自 ABI 文档的 func() 调用的 caller()

gcc -O3 -march=skylake-avx512(或-mavx512f)编译得到4。或者3,如果您要省略进入向量寄存器的参数之一。

GCC 正确实现 ABI 通常是一个安全的选择,因此您只需查看其代码即可了解发生了什么。构建一种复杂的方式来实际打印 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 个 args,AL = 4。

【讨论】:

    猜你喜欢
    • 2011-11-04
    • 1970-01-01
    • 2019-02-12
    • 1970-01-01
    • 1970-01-01
    • 2017-03-21
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多