0

我正在研究一组独立的 x86 汇编例程,我希望这些例程可用于以下系统上的 C 程序:

  • 仅限 Linux 64 位
  • Windows 32 位和 64 位
  • (很高兴最终拥有 64 位 Mac,但目前尚不清楚,因为 Apple 似乎正在放弃 x86 以支持 ARM)

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

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

我希望有些用户会真正使用 C++,但他们仍会链接到 C 库——也就是说,不会直接使用汇编程序。

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

在阅读完所有内容后,我的看法是,在一天结束时,我可以简单地从 cdecl 开始,以在初始版本中获得最大的可移植性,然后在需要时考虑使用特殊的外壳来涵盖其他约定 - 取决于例程实际执行的操作在特定情况下,我可以通过使用其他约定来使事情变得更快。

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

4

1 回答 1

2

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

通常的方法是让您的库使用目标平台的标准调用约定,这意味着每个平台都有不同的 asm。处理此问题的一种方法是使用 asm 宏来调整函数的顶部以适应不同的调用约定。或者参数化像 ARG1 这样的寄存器名称,而不是硬编码 RDI,但是如果你的函数不仅仅是简单的指针增量,或者你曾经将寄存器用于函数 arg 以外的东西,那么这会变得非常复杂。

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

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


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

我想你可以在任何地方使用 x86-64 fastcall 并__attribute__((ms_abi))在你的原型中使用非 MSVC 编译器。 但这可能会花费一些性能开销,特别是如果您想使用所有 XMM regs。(xmm6..15 在 x64 fastcall 中保留调用)。但要注意编译器错误;使用非默认调用约定几乎没有经过很好的测试。

如果你所有的函数有 4 个或更少的总寄存器参数,那么在大多数方面它的调用约定还不错。否则更多的寄存器参数通常更有效。 为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定?


32 位和 64 位显然有很大不同;没有任何标准调用约定在 32 位和 64 位代码之间兼容,而且您的代码通常需要完全不同。

唯一真正的相似之处在于 32 位 Windows fastcall 和标准 64 位 Windows 调用约定(MS 也称为 fastcall),但 32 位 fastcall 仅传递 regs 中的前 2 个 args,并且是 callee-pops stack args。64 位 fastcall 传递 regs 中的前 4 个 args,从相同的 2 开始,但随后使用 r8 和 r9,它们仅存在于 64 位模式中。

于 2020-09-08T10:18:32.093 回答