为什么Mac ABI要求x86-32的16字节栈对齐?

34

我理解对于旧的PPC RISC系统和x86-64,这个要求是可以理解的,但对于老牌的x86系统呢?在这种情况下,堆栈只需要按4字节边界对齐即可。是的,一些MMX/SSE指令需要16字节对齐,但如果调用方有此要求,则应确保对齐正确。为什么要给每个调用者加上这个额外的要求呢?这实际上可能会导致一些性能下降,因为每个调用点都必须管理此要求。我有什么遗漏的吗?

更新:在进一步调查和与内部同事的咨询后,我有一些关于这个问题的理论:

  1. PPC、x86和x64版本的操作系统之间的一致性
  2. 现在看来,GCC代码生成器现在一致地进行sub esp,xxx然后将数据“mov”到堆栈上,而不是简单地执行“push”指令。这实际上在某些硬件上可能更快。
  3. 虽然这确实让调用点变得有点复杂,但在使用默认的“cdecl”约定时,几乎没有额外开销,因为调用方清理堆栈。

我对最后一项的问题是,对于依赖于被调用方清理堆栈的调用约定,上述要求会使代码生成变得非常丑陋。例如,如果某个编译器决定为其自己的内部使用(即任何不打算从其他语言或来源调用的代码)实现更快速的基于寄存器的调用风格,那么这个堆栈对齐问题可能会抵消通过寄存器传递某些参数所实现的性能增益。

更新: 到目前为止,唯一真正的答案是一致性,但对我来说,那是一个过于简单的答案。我在x86架构方面拥有超过20年的经验,如果真正的原因是一致性而不是性能或其他具体的原因,那么我尊敬地建议开发人员可能有点天真。他们忽略了近30年的工具和支持。特别是如果他们期望工具供应商在不必跳过几个看似不必要的障碍的情况下迅速轻松地为他们的平台适应其工具(也许不会……这是苹果……)。

我再给这个话题一天左右的时间,然后关闭它……

相关


1
ABI(应用程序二进制接口)。 - Allen Bauer
相关:为什么System V / AMD64 ABI要求16字节的堆栈对齐? - 现代版本的i386 System V ABI也需要相同的东西。 - Peter Cordes
嘿@AllenBauer,你说你理解这个“即使对于x86-64” - 你能否(或任何人)可能解释一下其中的原理?我个人不理解任何平台(正如你所说,这是我的堆栈帧)。 - Tom
10个回答

31

来自《Intel®64和IA-32体系结构优化参考手册》4.4.2节:

“为了获得最佳性能,流式SIMD扩展和流式SIMD扩展2要求它们的内存操作数必须对齐到16字节边界。与对齐数据相比,非对齐数据可能会导致显着的性能惩罚。”

来自附录D:

“重要的是确保在函数进入时将堆栈帧对齐到16字节边界,以保持本地__m128数据、参数和XMM寄存器溢出位置在整个函数调用期间对齐。”

http://www.intel.com/Assets/PDF/manual/248966.pdf


6

我不确定,因为没有第一手证据,但我认为原因是SSE。如果您的缓冲区已经对齐在16字节边界上(movps vs movups),则SSE速度要快得多,并且任何x86都至少有mac os x的sse2。这可以由应用程序用户处理,但成本相当显著。如果将其作为ABI中强制性的总体成本不太显著,则可能值得。在mac os X中广泛使用SSE:加速框架等...


1
这也是我能想到的最好的理由...但要求在调用之前对齐堆栈。一旦被调用者控制,堆栈就不再对齐了!(返回地址现在是堆栈的顶部)。 - Allen Bauer
4
在这一点上,堆栈指针未对齐并不太重要,因为您希望参数在内存中对齐。因此,在典型的堆栈框架中,您保证在8(%ebp)处具有16字节对齐,也就是您的参数开始的地方。 - Lara Dougan

5

我认为这是为了与x86-64 ABI保持一致。


1
有点道理,但这真的有价值吗?只有工具创建者才会真正关心这些东西,因为大多数开发人员只是依赖工具“做正确的事情”。 - Allen Bauer
也许是因为x86-32在Mac上的寿命相对较短? - Andrew Grant

3

首先,需要注意16字节对齐是由苹果公司引入到System V IA-32 ABI的一个特例。

栈对齐只有在调用系统函数时才需要,因为很多系统库使用SSE或Altivec扩展,这些扩展要求16字节对齐。我在libgmalloc MAN页面中找到了明确的参考资料。

您完全可以按照自己想要的方式处理堆栈帧,但如果您尝试使用不对齐的堆栈调用系统函数,将出现misaligned_stack_error消息。

编辑:值得一提的是,在使用GCC编译时,您可以使用mstack-realign选项来摆脱对齐问题。


1
问题在于编译器不知道给定的调用是系统函数还是其他函数。这意味着唯一“安全”的做法是确保堆栈在整个调用链中保持对齐。当处理手写的低级汇编函数时,我们已经利用了这一事实,因为它们被认为永远不会调用系统函数。 - Allen Bauer
哦,还有一件事,由于我们正在修改现有的Delphi编译器以针对Mac进行目标设置,因此“使用GCC重新编译”有点困难...由于我们拥有自己的前端和代码生成器/后端,所以GCC并没有参与其中,这就是问题所在。 - Allen Bauer

2
这是一个效率问题。
在每个使用新SSE指令的函数中确保堆栈16字节对齐会增加很多开销,从而有效降低性能。
另一方面,始终保持堆栈16字节对齐可确保您可以自由使用SSE指令而无需付出性能代价。这没有任何成本(至少以指令计算的成本)。它只涉及在函数序言中更改常量。
浪费堆栈空间是廉价的,它可能是高速缓存中最热门的部分。

2
我认为这是一个非常肤浅的解释。为什么调用链中的每个函数都必须做这个工作,即使可能使用SSE指令时?如果这种“开销”并不重要,那么在使用SSE指令的点上执行它就“没问题”!我不需要我的邻居来保持我的房子干净。 - Allen Bauer
1
你的结论是不正确的。注意制造和保持之间的区别。保持栈16字节对齐没有涉及到任何工作。这只需要在序言中更改一个常量以确保栈对齐即可。我已经更新了我的原始答案来强调这一点。另一方面,使栈16字节对齐需要工作,并且有以指令为单位的成本。 - user239558
这仅仅是假设你的编译器代码生成器像GCC一样工作。世界远不止GCC。如果编译器为当前函数调用保留了所有本地变量和所有参数的堆栈空间,那是有效的。然而,许多编译器可能不会以这种方式工作,事实上,试图让它们以这种方式工作可能代价太高。另一件事是,并非所有SSE指令都需要对齐,只有MOVxxA指令需要。因此,即使在这种情况下,系统调整的潜在指令子集也相对较小。一个应用程序可能永远不会直接或间接使用SSE。 - Allen Bauer
2
成本分析无论是在序言中保留所有本地变量的堆栈空间还是不保留,都是相同的。每当分配堆栈空间时,使用“sub $xx,%esp”方法即可。保持堆栈16字节对齐意味着xx是16的倍数。编译器所需做的就是向上取整。也许您可以举个例子说明这会有什么影响? - user239558

2
我的猜测是,苹果公司认为所有人都只使用XCode(gcc),它会为您对齐堆栈。因此,要求对齐堆栈以使内核不必这样做只是一种微小的优化。

1

嗯,难道 OS X ABI 也像传递小结构体一样,在寄存器中执行有趣的 RISC 操作吗?

这表明了其与其他平台理论的一致性。

想起来了,FreeBSD 系统调用 API 也会对齐 64 位值。(例如 lseek 和 mmap)


1

0

不确定为什么没有人考虑过从传统的基于PowerPC平台轻松移植的可能性?

阅读此内容:

http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/LowLevelABI/100-32-bit_PowerPC_Function_Calling_Conventions/32bitPowerPC.html#//apple_ref/doc/uid/TP40002438-SW20

然后放大到“32位PowerPC函数调用约定”,最终得到如下内容:

这些是32位PowerPC环境中可用的嵌入式对齐模式:

Power对齐模式源于IBM XLC编译器用于AIX操作系统的对齐规则。它是在AIX和Mac OS X上使用的PowerPC架构版本的GCC的默认对齐模式。因为这种模式最有可能在来自不同供应商的PowerPC架构编译器之间兼容,所以通常与在不同程序之间共享的数据结构一起使用。

考虑到OSX基于遗留的PowerPC背景,可移植性是一个重要的考虑因素-它决定了遵循从AIX的XLC编译器开始的惯例。当您考虑需要确保所有工具和应用程序能够在最小的重新工作量下协同工作时,我认为坚持尽可能遵循相同的遗留ABI非常重要。

这就是哲学,进一步阅读的是明确提到的规则(“Prolog和Epilog”):

被调用的函数负责分配自己的堆栈帧,并确保在堆栈中保留16字节对齐。这个操作是通过一个称为前导代码的代码段完成的,编译器将其放置在子例程体之前。在子例程体之后,编译器会放置一个尾声来恢复处理器到调用子例程之前的状态。

0
为了保持内核的一致性。这使得同一个内核可以在多个架构上启动而无需修改。

这似乎是人们所说的唯一问题,但对于高级语言来说,这是一个(应该)隐藏的细节。任何编译的x86-32 ObjC、C或C++应用程序都不会关心这个不透明的细节。 - Allen Bauer
1
一个内核需要兼容用户进程的调用栈,因为有时它需要使用它作为工作空间来处理某些系统调用或中断。 - SingleNegationElimination
似乎不对齐并不会影响Windows和Linux内核。那么x86架构的MacOS有什么特别之处呢? - Allen Bauer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接