C 中浮点值的调用约定是什么,用于系统 V 中的x86_64



我目前正在深入研究汇编领域,主要从x86_64、C 和 System V AMD64 的角度,通常针对 Linux。

整数(以及暗示指针)值的调用约定非常简单,按顺序使用以下寄存器:

  • RDI
  • 相对强弱指数
  • RDX
  • RCX
  • R8
  • R9
  • XMM0–7

较长的参数计数是通过将值推送到子例程的堆栈帧来处理的。我从维基百科页面上获得了这些寄存器名称x86_64调用约定。

对于较大的值(如结构和数组),约定似乎也是推送到被调用方的堆栈帧中。

但是,函数浮点参数的调用约定是什么?是否使用浮点寄存器?

另一个相关问题:如果我有混合的参数类型怎么办?

void mixed(int a, float b, mystruct c) { /* ... */ }

如果我的函数采用这样的参数列表,如何从程序集调用这样的函数?像这样的交错参数列表中使用了哪些寄存器?

参数传递的调用约定在第 3.2.3 节的 AMD 64 PDF 文档的 System V 应用程序二进制接口中指定。

我不确定文档是否可以在这里合法引用,但我至少可以解释一下。

分类类型

首先,文档为参数值定义了八种不同的分类:

  • INTEGER:使用通用寄存器的整数类型和指针
  • SSE:使用向量寄存器的类型。
  • SSEUP:类似于SSE,但主要用于存储大(>=128 位)值的较高字节
  • X87:浮点类型。
  • X87UP:大浮点类型的上字节。
  • COMPLEX_X87complex浮点类型的寄存器。
  • NO_CLASS:填充区域以及空结构和联合,通常在堆栈的内存中。
  • 内存
  • :在主内存中的堆栈上独占传递的类型。

分类规则

接下来,它定义了 C 类型如何适应这些分类:

  • _Boolcharshortintlonglong long和指针被归类为整数,并将使用这些寄存器。
  • floatdouble_Decimal32_Decimal64__m64被归类为SSE,并将使用这些寄存器。
  • __float128_Decimal128__m128被分成两半,将最低有效字节/位存储在SSE中,将最高有效字节/位存储在SSEUP中。
  • __m256被拆分为四个 64 位(8 字节)值,其中最低有效字节存储为SSE,其余存储为SSEUP
  • __m512同样被分成 64 位(8 字节)块,其中最低有效字节存储为SSE,其他所有字节存储为SSEUP
  • long double值将其 64 位尾数存储为 X87,16 位指数填充为 64 位(8 字节)并存储在X87UP中。
  • __int128基本上存储在INTEGER中的两个long值,前半部分是低位/字节,后半部分是高位/字节。它们可以理解为好像它们被定义为结构体:

    typedef struct {
    long low_bits, high_bits;
    } __int128;
    
  • complex doublecomplex float类型被分成两半,前半部分是实部,后半部分是虚部,并存储在SSE中。它们可以理解为好像它们被定义为这样的结构:

    typedef struct {
    double real, imaginary;
    } complex_double;
    
  • complex long double值分类为COMPLEX_X87
  • structs、unions和数组的逻辑相当复杂,有关详细信息,请参阅上面链接的文档。简而言之,有一个递归算法定义了如何传递聚合类型,该算法决定如何传递值。

参数传递

现在我们有一个分类系统和一个递归算法来处理structs、unions和数组,我们将这个系统和算法应用于函数的参数,该函数包括每个参数的以下步骤:

  • 如果它是MEMORY对象,请将其写入堆栈。
  • 如果是整数,请使用%rdi%rsi%rdx%rcx%r8%r9中的下一个可用寄存器。
  • 如果是SSE,请使用%xmm0%xmm7范围内的下一个可用寄存器。
  • 如果是SSEUP,请使用上次使用的%xmm的下一个可用 64 位区块来寄存SSE类型。
  • 如果是 X87
  • X87UPCOMPLEX_X87,则会在内存中传递。

冲洗并对所有参数值重复此操作。如果给定类型的寄存器用完,请写入堆栈。


TL;DRSystem V ABI 定义了一种非平凡但相当简单的算法,用于传递不同类型的数据。

最新更新