x86-64 System V abi - 参数传递的参数分类

Jan*_*šil 5 c linux x86-64 abi calling-convention

第 3.2.3 节中的x86_64 System V ABI指定函数调用的哪些参数进入哪些寄存器以及哪些被压入堆栈。我很难理解聚合分类的算法,它说(突出显示的是我的):

\n
\n

聚合(结构体和数组)和联合类型的分类工作原理如下:

\n
    \n
  1. 如果对象的大小大于八个字节,或者包含未对齐的字段,则它具有 MEMORY 类。
  2. \n
  3. 如果 C++ 对象对于调用而言非常重要(如 C++ ABI13 中所指定),则它通过不可见引用传递(该对象在参数列表中被具有类 INTEGER 的指针替换)。
  4. \n
  5. 如果聚合的大小超过一个八字节,则每个字节将被单独分类。每个八字节都被初始化为 NO_CLASS 类。
  6. \n
  7. 对象的每个字段都被递归分类,以便始终考虑 两个字段。由此产生的类是根据八字节中字段的类来计算的: (a) 如果两个类相等,则这就是结果类。(b) 如果其中一个类是 NO_CLASS,则结果类是另一个类。(c) 如果其中一个类是 MEMORY,则结果是 MEMORY 类。(d) 如果其中一个类是 INTEGER,则结果是 MEMORY 类。是整数。(e) 如果类之一是 X87、X87UP、COMPLEX_X87 类,则使用 MEMORY 作为类。 (f) 否则使用类 SSE。
  8. \n
  9. 然后进行合并后清理: (a) 如果其中一个类是 MEMORY,则整个参数将在内存中传递。(b) 如果 X87UP 前面没有 X87,则整个参数将在内存中传递。(c) 如果聚合的大小超过两个八字节,并且第一个八字节是\xe2\x80\x99t SSE 或任何其他八字节是\xe2\x80\x99t SSEUP,则整个参数将在内存中传递。(d) 如果 SSEUP 前面没有 SSE 或 SSEUP,则转换为 SSE
  10. \n
\n
\n

我不明白第(3)、(4)和(5)点。具体来说,我有以下问题:

\n

Q1. 在第(3)点中,“每个单独分类”,作者的意思是“每个八字节”吗?如果是这样,那么我希望接下来是八字节分类的解释。

\n

Q2。在第 (4) 点中,“对象的每个字段”是否表示“作为第 (3) 点(分隔)结果的八字节的每个字段?

\n

Q3。在第(4)点中,“总是考虑两个字段”中的“两个字段”是否意味着两个连续的字段?

\n

Q4。在第(4)点中,“结果类”是指对象的类,还是八字节的类,还是第二个考虑的字段的类,还是其他东西的类?在最后一种情况下,生成的类在哪里使用?这是否意味着算法保持第一个字段的字段不变,然后迭代计算下一个字段的类,直到我们获得八字节中所有字段的类?或者这是否意味着我们的算法同时处理两个字段?

\n

Q5. 第(4)点中,如果只有一个字段怎么办?或者偶数个字段?

\n

Q6. 在第 (5) 点中,字段或八字节的“类之一”?

\n

如果有人可以提供更正式/更精确的东西 - 例如伪代码或流程图 - 那将是理想的。

\n

mka*_*alp 3

查看实现gcc

对第 1 点的澄清(回应“八是一个拼写错误,应该是二”的评论):

  1. 如果对象的大小大于八个字节,或者包含未对齐的字段,则它具有 MEMORY 类。
      /* On x86-64 we pass structures larger than 64 bytes on the stack.  */
      if (bytes > 64)
        return 0;
Run Code Online (Sandbox Code Playgroud)

该函数返回用于参数的寄存器数量,零表示应使用内存。

(后来经过分析,如果超过两个八字节,只有第一个是SSE,其余都是SSEUP时才使用寄存器,如5.(c)中指出的:

(c) 如果聚合的大小超过两个八字节,并且第一个八字节不是 SSE 或任何其他八字节不是 SSEUP,则整个参数将在内存中传递。)


Q1. 在第(3)点中,“每个单独分类”,作者的意思是“每个八字节”吗?

是的。在代码中,每个八字节称为一个word

每个八字节都被初始化为 NO_CLASS 类。

  int words = CEIL (bytes + (bit_offset % 64) / 8, UNITS_PER_WORD);
  // ...
      for (i = 0; i < words; i++)
        classes[i] = X86_64_NO_CLASS;
Run Code Online (Sandbox Code Playgroud)

Q2。在第 (4) 点中,“对象的每个字段”是否表示“作为第 (3) 点(分隔)结果的八字节的每个字段?

不,它们指的是结构/类、联合或数组元素的每个字段。这些是在代码中的几个地方处理的,但您会看到for如下循环:

          for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
Run Code Online (Sandbox Code Playgroud)

这就是为什么它是递归的。字段本身可以是聚合类型。整个逻辑从每个字段开始应用,递归函数:

  • 要么返回 0,这意味着整个内容都在内存中传递,
  • 或者它返回将使用的寄存器的数量(八字节)以及每个寄存器的类(通过嵌套字段的递归将在具有非聚合类型的字段处终止)。
                      num = classify_argument (TYPE_MODE (type), type,
                                               subclasses,
                                               (int_bit_position (field)
                                               + bit_offset) % 512);
                      if (!num)
                        return 0;
Run Code Online (Sandbox Code Playgroud)

Q3。在第(4)点中,“总是考虑两个字段”中的“两个字段”是否意味着两个连续的字段?

我认为“字段”在这里不准确。而且不是连续的。它所做的是将迄今为止为每个 确定的类word与为对应于相同 s 的字段递归确定的类进行合并word。见下文:

                      pos = (int_bit_position (field)
                            + (bit_offset % 64)) / 8 / 8;
                      for (i = 0; i < num && (i + pos) < words; i++)
                        classes[i + pos]
                          = merge_classes (subclasses[i], classes[i + pos]);
Run Code Online (Sandbox Code Playgroud)

pos从(该字段所在的八字节)开始,每个类都会与由该字段的递归调用确定的子类合并。


Q4。在第(4)点中,“结果类”是指对象的类,还是八字节的类,还是第二个考虑的字段的类,还是其他东西的类?

现在正在描述该merge_classes函数,该函数采用两个类并返回八字节的合并类。我们正在迭代字段,但类是八字节的。

在最后一种情况下,生成的类在哪里使用?

每个类都会确定对应寄存器的类型(GPR/SSE/X87等)。


Q5. 第(4)点中,如果只有一个字段怎么办?或者偶数个字段?

我希望“两个领域”在这一点上得到解答。例如,如果一个结构体有一个字段,则该类将针对该八字节初始化为NO_CLASS,然后对于该字段,它将被确定为INTEGER。然后在合并时,类将变成INTEGER.


Q6. 在第 (5) 点中,字段或八字节的“类之一”?

八字节的。类总是指八字节。