为什么不在XMM向量寄存器中存储函数参数?

时间:2015-11-14 10:04:05

标签: assembly x86 parameter-passing x86-64 calling-convention

我目前正在阅读这本书:"计算机系统 - 程序员视角"。我发现,在x86-64架构中,我们只限于6个积分参数,这些参数将被传递给寄存器中的函数。下一个参数将在堆栈上传递。

此外,第一个最多8个FP或矢量args在xmm0..7中传递。

为什么不使用浮点寄存器来存储下一个参数,即使参数不是单/双精度变量?

将数据存储在寄存器中,而不是将数据存储到存储器中,然后从内存中读取数据会更有效(据我所知)。

2 个答案:

答案 0 :(得分:19)

大多数功能都没有超过6个整数参数,所以这实际上是一个极端情况。在xmm寄存器中传递一些超出的整数参数将使得找到浮点算法的位置的规则更加复杂,几乎没有任何好处。除了事实上它可能不会更快地制作代码。

将多余参数存储在内存中的另一个原因是,该功能可能无法立即使用 它们。如果要调用另一个函数,则必须将这些参数从xmm寄存器保存到内存,因为您调用的函数将破坏任何参数传递寄存器。 (并且所有xmm regs都是调用者保存的。)所以你可能最终得到的代码将参数填充到向量寄存器中,它们不能直接使用,并从那里将它们存储到内存中,然后再调用另一个函数,只有然后将它们加载回整数寄存器。或者即使函数没有调用其他函数,也许它需要向量寄存器供自己使用,并且必须将params存储到内存中以释放它们以运行向量代码!将push params放到堆栈上会更容易,因为push非常优化,出于显而易见的原因,在一个uop中对RSP进行存储和修改,大约相同便宜mov

有一个整数寄存器不用于参数传递,但在SysV Linux/Mac x86-64 ABI(r11)中也没有调用保留。使用临时动态链接器代码的临时寄存器来保存(因为这样的shim函数需要将所有的args传递给动态加载的函数)和类似的包装函数。

因此,AMD64可能会使用更多整数寄存器作为函数参数,但这只会以调用函数在使用前必须保存的寄存器数量为代价。 (或者对于那些不使用"静态链"指针或其他东西的语言的双用途r10。)

无论如何,寄存器中传递的参数总是更好。

xmm寄存器不能用作指针或索引寄存器,将xmm寄存器中的数据移回整数寄存器可能会比加载刚才存储的数据更慢。 (如果任何执行资源将成为瓶颈,而不是缓存未命中或分支错误预测,它更可能是ALU执行单元,而不是加载/存储单元。将数据从xmm移动到gp寄存器需要一个ALU uop,在Intel和AMD的当前设计中。)

L1缓存非常快,并且存储 - >加载转发使得到内存的往返的总延迟大约等于例如5个周期。英特尔Haswell。 (像inc dword [mem]这样的指令的延迟是6个周期,包括一个ALU周期。)

如果将数据从xmm移动到gp寄存器是 all 你将要做的事情(没有其他任何东西可以保持ALU执行单元忙),那么是的,在Intel CPU上,{往返延迟为{ {1}} / movd xmm0, eax(2个周期Intel Haswell)小于movd eax, xmm0 / mov [mem], eax的延迟(5个周期的Intel Haswell),但整数代码通常不是完全的延迟就像FP代码经常出现的那样。

在AMD Bulldozer系列CPU上,两个整数内核共享一个向量/ FP单元,直接在GP regs和向量regs之间移动数据实际上非常慢(单向8或10个周期,或者是Steamroller的一半)。内存往返只有8个周期。

32位代码设法运行得相当好,即使所有参数都在堆栈上传递,并且必须加载。 CPU经过高度优化,可以将参数存储到堆栈中,然后再次加载它们,因为古老的32位ABI仍然用于 lot 代码,尤其是。在Windows上。 (大多数Linux系统主要运行64位代码,而大多数Windows桌面系统运行大量32位代码,因为许多Windows程序仅作为预编译的32位二进制文​​件提供。)

有关CPU微体系结构指南的信息,请参阅http://agner.org/optimize/,了解如何确定实际需要多少循环。 wiki中还有其他好的链接,包括上面链接的x86-64 ABI文档。

答案 1 :(得分:3)

我认为这不是一个好主意,因为:

  1. 您不能将FPU / SSE寄存器用作通用寄存器。我的意思是,这段代码不正确(NASM):

    mov byte[st0], 0xFF
    
  2. 如果比较向/从FPU / SSE发送数据与通用寄存器/内存,FPU / SSE非常慢。

  3. 编辑:请记住,我可能不对。