使用SIMD指令时的堆栈对齐

3
在我正在阅读的关于汇编的书中,我们被告知对于我们编写的任何函数,如果它是一个分支函数并且会调用其他函数,它必须保持堆栈对齐。这样做是为了使我们自己的函数调用的函数能够使用SIMD指令。
到目前为止,我被告知对于x86架构,我们必须保持16字节的堆栈对齐以便使用SIMD指令。对于使用SIMD的所有x86程序,无论是32位还是64位,它总是16字节吗?它是否会根据我们构建程序的x86操作系统而改变?
1个回答

3
函数无法知道其他函数在内部会做什么,所以对于能够将库链接在一起并执行的真正重要的是它们在调用约定/ABI上达成一致,并且该ABI为调用者设置了对被调用者关于堆栈对齐的保证要求(以及其他方面)。所以这并不是“在使用SIMD指令时”,除非你的意思是“如果任何被调用者实际上依赖于ABI保证,例如通过在其堆栈空间上使用SIMD加载或存储”。如在glibc scanf Segmentation faults when called from a function that doesn't align RSP中所述。
有关我在这个答案中提到的一些事情的更多详细信息,请参阅Why does the x86-64 / AMD64 System V ABI mandate a 16 byte stack alignment?
64位模式:始终按16字节对齐:x86-64 System V和Windows x64 ABIs在调用之前要求RSP%16 == 0,并且在函数进入时保证RSP % 16 == 8。这对于16字节向量已经足够了,但是对于希望使用alignas(32)或更高对齐方式的函数,仍然需要自行处理。
32位模式:非Linux系统要求4字节对齐。只有在Linux上使用的i386 System V ABI版本要求16字节对齐(在调用之前要求ESP % 16 == 0,在函数进入时要求ESP % 16 == 12)。其他使用SysV ABI的操作系统仍然保持旧的4字节对齐要求,没有采用这个更改(例如*BSD,以及在它变为64位系统之前的Mac OS X)。而且在Windows上的32位代码也只要求/保证4字节对齐。
如果您(或编译器)想要16字节对齐的本地变量(例如溢出/重新加载__m128),函数需要额外的指令。(通常设置EBP作为帧指针和and esp,-16,类似于为VLA分配空间时。)
在GNU/Linux的32位模式下,保持所有函数的16字节堆栈对齐的ABI要求是GCC的一个意外。当他们注意到-bpreferred-stack-boundary=4的错误时,GCC会假设对齐并生成在没有该对齐的情况下调用时会出错的代码,此时已经有一些依赖它的二进制文件在使用,包括像RedHat Enterprise Linux(RHEL)这样变化缓慢的主要发行版。从这种情况中找到的最好的解决方法是改变ABI要求,所以-bpreferred-stack-boundary=4成为ABI的一部分,而不仅仅是一个乐观的性能调整,就像我认为它在成为默认设置时所想象的那样。
这个改变确实破坏了使用先前允许的小于16的ESP对齐调用C函数的手写汇编代码,但是这样的二进制文件很可能是由在注意到这个问题时广泛使用的GCC版本的默认设置创建的。因此,将ABI更改为与已发布的GCC版本实际执行的操作相匹配并不是很好,但可能会减少一些问题。对于具有新可执行文件的旧库来说,在实践中的破坏将仅限于回调函数或其他旧代码调用新代码的方式。(新代码调用旧代码是可以的,因为给出16字节对齐的调用者满足了较宽松的对齐要求。)
其他操作系统避免了这个破坏旧二进制文件和手写汇编代码的ABI更改的困境。
请参阅https://sourceforge.net/p/fbc/bugs/659/了解一些历史,以及我在https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838#c91上的评论,尝试对i386 GNU/Linux + GCC如何意外陷入一种情况进行总结,其中对i386 System V ABI的不兼容的更改是两害相权衡中较小的那个。

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