【问题标题】:Why does eax contain the number of vector parameters?为什么eax包含向量参数的数量?
【发布时间】:2018-12-31 11:42:54
【问题描述】:

为什么汇编中al包含向量参数的个数?

为什么向量参数与被调用者的正常参数有任何不同?

【问题讨论】:

  • 注意,这仅适用于可变参数函数,而不是 eax 只是 al。我想这是允许通用 thunk 处理适当数量的向量寄存器,例如以节省空间。规范说它确实“不需要完全匹配寄存器的数量,但必须是使用的向量寄存器数量的上限”
  • 请注意,这不是向量参数的数量,而是在 XMM/YMM/ZMM regs 中传递的数量。您可以拥有无​​限数量的 __m128 args,但超过前 8 个它们会在堆栈中传递。我认为它应该可以在堆栈上传递 FP args 并设置al=0,但我还没有测试过。 gcc 的可变参数函数的代码生成只是检查al!=0 并将所有 xmm0..7 存储到堆栈上它可以索引的数组中,所以也许前 8 个 FP / 向量 args 确实需要进入向量 regs。跨度>
  • 如果我没记错的话,参数只是在寄存器中传递,额外的参数在堆栈上传递。如果是这种情况,为什么我需要将 FP/向量 args 的数量传递给它?被调用者是否应该能够访问它知道需要访问的内容,或者这对于可变参数 args 是否更重要?
  • 有时该信息不可用,例如在通用的 thunk 或 hooks 中。
  • 这是加载寄存器(在调用者中)的编译器,所以它当然知道。它遵循 ABI 中的规则。

标签: assembly x86-64 calling-convention abi sysv


【解决方案1】:

该值用于优化,如ABI document中所述

序言应使用%al 以避免不必要地保存XMM 寄存器。这对于仅整数程序以防止 XMM 单元的初始化尤其重要。

3.5.7 变量参数列表 - 寄存器保存区。 System V Application Binary Interface version 1.0

当你调用va_start时,它会将所有传入寄存器的参数保存到寄存器保存区

首先,任何已知使用va_start的函数都需要在函数开始时将所有可能用于将参数传递到堆栈上的寄存器保存到“寄存器保存区”中,供va_startva_arg 将来访问。这是一个明显的步骤,我相信在任何具有寄存器调用约定的平台上都是相当标准的。寄存器保存为整数寄存器,后跟浮点寄存器...

https://blog.nelhage.com/2010/10/amd64-and-va_arg/

但保存所有 8 个向量寄存器可能会很慢,因此编译器可能会选择使用 al 中传递的值对其进行优化

... 作为优化,在函数调用期间,%rax 需要保存用于保存参数的 SSE 寄存器的数量,以允许 varargs 调用者在没有浮点的情况下完全避免接触 FPU论据。

https://blog.nelhage.com/2010/10/amd64-and-va_arg/

由于您想保存至少使用的寄存器,因此该值可以大于实际使用的寄存器数。这就是为什么 ABI 中有这一行

%al 的内容不需要完全匹配寄存器的数量,但必须是使用的向量寄存器数量的上限,并且在 0 到 8 的范围内。

你可以从prolog of ICC看到效果

    sub       rsp, 216                                      #5.1
    mov       QWORD PTR [8+rsp], rsi                        #5.1
    mov       QWORD PTR [16+rsp], rdx                       #5.1
    mov       QWORD PTR [24+rsp], rcx                       #5.1
    mov       QWORD PTR [32+rsp], r8                        #5.1
    mov       QWORD PTR [40+rsp], r9                        #5.1
    movzx     r11d, al                                      #5.1
    lea       rax, QWORD PTR [r11*4]                        #5.1
    lea       r11, QWORD PTR ..___tag_value_varstrings(int, ...).6[rip] #5.1
    sub       r11, rax                                      #5.1
    lea       rax, QWORD PTR [175+rsp]                      #5.1
    jmp       r11                                           #5.1
    movaps    XMMWORD PTR [-15+rax], xmm7                   #5.1
    movaps    XMMWORD PTR [-31+rax], xmm6                   #5.1
    movaps    XMMWORD PTR [-47+rax], xmm5                   #5.1
    movaps    XMMWORD PTR [-63+rax], xmm4                   #5.1
    movaps    XMMWORD PTR [-79+rax], xmm3                   #5.1
    movaps    XMMWORD PTR [-95+rax], xmm2                   #5.1
    movaps    XMMWORD PTR [-111+rax], xmm1                  #5.1
    movaps    XMMWORD PTR [-127+rax], xmm0                  #5.1
..___tag_value_varstrings(int, ...).6: 

本质上是Duff's devicer11寄存器加载xmm保存指令后的地址,然后从结果中减去al*4(因为movaps XMMWORD PTR [rax-X], xmmX是4字节长)跳转到我们应该运行的movaps指令

正如我所见,其他编译器总是保存所有向量寄存器,或者根本不保存它们,所以他们不关心 al 的值,只检查它是否为零

通用寄存器总是被保存,可能是因为将 6 个寄存器移动到内存而不是花时间进行条件检查、地址计算和跳转更便宜。因此,您不需要参数来指示在寄存器中传递了多少整数

这是similar question to yours。您可以在以下链接中找到更多信息

【讨论】:

  • 还有一件事:我们为什么不保存整数寄存器?是不是因为调用者对此负责,或者浮点寄存器的保存成本特别高?
  • @Riolku 整数寄存器被保存,你可以在序言中看到。正如我在上一段中所说,保存它们的 6 条 mov 指令的成本可能比计算要保存的寄存器要小,所以只需将它们全部保存
  • 抱歉。很好的答案,这很有意义,谢谢!
  • 您引用了旧版本的 ABI,错误地说 RAX 需要计数。它实际上只是 AL,您不能假设它是零扩展至 RAX(尽管许多编译器执行异或零 RAX 或使用 mov eax,4 或其他任何东西,在优化大小时,他们可能会使用 2 字节 mov al, 4零个数字。)
  • 有趣的事实:旧的 GCC 使用 AL 来保存正确数量的 XMM regs,并带有计算的跳转 IIRC,就像您在 ICC 中展示的那样。当前的 GCC 只是执行 test al,al / jz 可能会跳过 8x movaps 指令。
猜你喜欢
  • 2017-02-25
  • 2011-11-23
  • 1970-01-01
  • 2013-11-22
  • 2010-09-18
  • 2017-05-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多