assembly - 为什么不将功能参数存储在XMM向量寄存器中?

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

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

而且,第一个最多8个FP或矢量args传递给xmm0..7。

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

(据我所知)将数据存储在寄存器中比将其存储到内存然后从内存中读取数据要有效得多。

最佳答案

大多数函数没有超过6个整数参数,因此这确实是一个极端的情况。在xmm寄存器中传递一些多余的整数参数将使在哪里找到浮点args的规则变得更加复杂,几乎没有好处。除了它可能不会使代码更快的事实。

在内存中存储多余参数的另一个原因是,该函数可能不会立即使用它们。如果要调用另一个函数,则必须将这些参数从xmm寄存器保存到内存中,因为调用的函数将破坏所有传递参数的寄存器。 (而且所有xmm regs都已被调用者保存。)因此,您可能最终得到将参数填充到不能直接使用的向量寄存器中的代码,然后在调用另一个函数之前将其存储到内存中,并且仅然后将它们加载回整数寄存器。或者即使该函数不调用其他函数,也许它也需要向量寄存器供自己使用,并且必须将参数存储到内存中以释放它们以运行向量代码!只是将push设置到堆栈上会更容易,因为push出于明显的原因而非常优化,可以在单个uop中完成存储和RSP修改,价格与mov一样便宜。

有一个整数寄存器不用于参数传递,但也不在SysV Linux/Mac x86-64 ABI(r11)中保留调用。有一个暂存寄存器供懒惰的动态链接程序代码使用而无需保存(因为此类shim函数需要将其所有arg传递给动态加载的函数),以及类似的包装函数,这很有用。

因此,AMD64可以为功能参数使用更多的整数寄存器,但这只是以调用函数必须在使用前保存的寄存器数量为代价。 (或针对不使用“静态链”指针或其他语言的语言使用两用r10。)

无论如何,更多的参数传入寄存器并不总是更好。



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

L1缓存确实非常快,并且存储->负载转发使往返内存的总延迟大约为5个周期,例如英特尔Haswell。 (诸如inc dword [mem]之类的指令的等待时间为6个周期,包括一个ALU周期。)

如果您要做的只是将数据从xmm移到gp寄存器(没有其他事情可以使ALU执行单元保持繁忙),那么可以,在Intel CPU上,movd xmm0, eax / movd eax, xmm0的往返延迟(2个周期,Intel Haswell)小于mov [mem], eax / mov eax, [mem]的延迟(英特尔Haswell的5个周期),但是整数代码通常不会像FP代码那样经常受到延迟的困扰。

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

即使所有参数都在堆栈上传递并必须加载,32位代码也可以合理地运行。 CPU非常优化,可以将参数存储到堆栈上,然后再次加载它们,因为旧的32位ABI仍然用于很多代码,尤其是。在Windows上。 (大多数Linux系统大多数运行64位代码,而大多数Windows桌面系统运行很多32位代码,因为如此多的Windows程序只能作为预编译的32位二进制文​​件使用。)

有关CPU微体系结构指南,请参见http://agner.org/optimize/,以了解如何确定实际需要花费多少个周期。 Wiki中还有其他很好的链接,包括上面链接的x86-64 ABI文档。

关于assembly - 为什么不将功能参数存储在XMM向量寄存器中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33707228/

相关文章:

c - 让 gcc 使用条件移动

assembly - 组装cltq和movslq的区别

java - Java多维数组问题

C & 低级信号量实现

c++ - 如何在另一个正在运行的进程中找到自定义函数的地址以 Hook /绕行?

linux - 如何在 nasm 中包含调试信息?

go - 请解释 golang 类型是否按值传递

assembly - TASM 中的 "Near jump or call to different CS"错误

c - 为什么这两段简单的代码会给出不同的结果?

java - 对象的接口(interface)和参数存在问题