用于最大值的调用约定。 x86 系统之间的可移植性

Calling convention to use for max. portability between x86 systems

我正在研究一组独立的 x86 汇编例程,我想将其提供给以下系统上的 C 程序:

我已经在其他方面使用了 LLVM,几乎可以肯定我会使用 clang 而不是 gcc,尽管我可以设想有人想要使用 gcc 编译整个程序的情况。汇编程序将是 NASM.

我开发例程和将它们公开给用户的 C 库,即一切都在我的控制之下,我可以根据需要设计一切。

我预计有些用户会实际使用 C++,但他们仍会 link C 库 - 也就是说,不会直接使用汇编例程。

由于我是汇编的新手,我正在发现一个奇妙的迷宫,其中包含分布在系统、编译器、供应商、调用变体和语言中的各种调用约定。我不能说它有时不会带来有趣的阅读,但我也不能说它不会让初学者感到困惑。

我读完所有内容后的看法是,在一天结束时,我可以简单地从 cdecl 开始,以在初始版本中获得最大的可移植性,然后在需要时考虑使用特殊的外壳来覆盖其他约定——这取决于例程实际做什么我可以通过在特定情况下使用其他约定来加快速度。

但最初,因为我希望有一些东西可以正常工作,然后进一步优化它 - 说选择 cdecl 将提供最大的可移植性是否正确?我列出的系统?谢谢。

x86-64 Linux 和 MacOS 都使用 x86-64 System V ABI。 Windows 使用自己的调用约定。 None 这些 x86-64 平台称之为“cdecl”。

通常的做法是让您的库使用目标平台的标准调用约定,这意味着每个平台都使用不同的 asm。处理这个问题的一种方法是使用 asm 宏来调整函数的顶部以适应不同的调用约定。或者参数化寄存器名称,例如 ARG1 而不是 hard-coding RDI,但是如果您的函数不仅仅是微不足道的指针增量,或者如果您曾经将寄存器用于函数 arg 以外的其他东西,那么这会变得非常复杂非常快。

在 32 位上 Window 您可以选择多种约定; fastcall / vectorcall 是最糟糕的两个。在所有其他 x86 32 位和 64 位平台上,都有一个标准调用约定。如果您关注它,人们将更容易使用您的图书馆。

Agner Fog 的调用约定指南对于处理 hand-written asm 的可移植性有一些更详细的建议。 https://www.agner.org/optimize/


理论上您可以在任何地方使用 x86-64 System V,但是在 Windows 上 MSVC 将无法发出对您的代码的调用。 (GNU C 兼容编译器,如 gcc、clang 和 ICC 可以在 Windows 上的原型中使用 __attribute__((sysv_abi)),其中它们的默认调用约定是 MS 命名为 x64 fastcall)。

我猜你可以在任何地方使用x86-64 fastcall并在你的原型中为non-MSVC编译器使用__attribute__((ms_abi)) 但这可能会增加一些性能开销,特别是如果您想使用所有 XMM regs 时。 (xmm6..15 在 x64 fastcall 中是 call-preserved)。但要注意编译器错误;使用 non-default 调用约定几乎没有经过充分测试。

如果您的所有函数的总寄存器参数不超过 4 个,那么在大多数方面调用约定都还不错。否则,更多的寄存器参数通常效率更高。 Why does Windows64 use a different calling convention from all other OSes on x86-64?


32 位和 64 位明显不同; none 的标准调用约定在 32 位和 64 位代码之间兼容,并且您的代码通常需要完全不同。

唯一真正相似的是32位Windows fastcall和标准64位Windows调用约定(MS也叫fastcall),但是32位fastcall只通过第一个regs 中的 2 个参数,并且是 callee-pops 堆栈参数。 64 位 fastcall 在 regs 中传递前 4 个参数,从相同的 2 开始,然后使用仅在 64 位模式下存在的 r8 和 r9。