我应该使用什么调用约定来实现可移植性?

What calling convention should I use to make things portable?

我正在为 CPU 的 cpuid 指令编写 C 接口。我这样做只是作为一种练习:我不想使用 compiler-depended headers,例如 GCC 的 cpuid.h 或 MSVC 的 intrin.h。另外,我知道使用 C 内联汇编是更好的选择,因为它避免了考虑调用约定(参见 this implementation):我只需要考虑不同的编译器语法。不过,我想开始练习一下汇编和 C 语言的集成。

考虑到我现在必须为每个主要汇编器(我在考虑 GAS、MASM 和 NASM)以及 x86-64 和 x86 中的每一个编写不同的汇编实现,应该如何我处理不同机器和 C 编译器可能使用不同调用约定的事实?

如果你真的想编写一个“符合”x86_64 的所有常见调用约定的汇编函数(我只知道 Windows 一个和系统V 一),不依赖属性或编译器标志来强制调用约定,让我们看看有什么共同点。

Windows GPR 通过顺序为rcxrdxr8r9。 System V 的传递顺序是 rdirsirdxrcxr8r9。在这两种情况下,如果 rax 适合并且是 POD 的一部分,则 rax 保留 return 值。从技术上讲,如果它 (0) 保存每个 ABI 认为 non-volatile 的联合,并且 (1) returns 可以适合单个注册,并且 (2) 接受不超过 2 个 GPR 参数,因为重叠会发生超过那个。为了绝对通用,您可以使它采用指向某个结构的单个指针,该结构将保存您想要的任意 return 数据。

所以现在我们的争论将通过 rcxrdxrdirsi 来进行。你怎么知道哪个将包含参数?我实际上不确定一个好方法。也许你可以做的是有一个包装器将参数放在正确的位置,并让你的实际函数采用“填充”参数,这样你的参数总是落在 rcxrdx 中。您可以通过这种方式在技术上扩展到 r8r9

#ifdef _WIN32
#define CPUID(information) cpuid(information, NULL, NULL, NULL)
#else
#define CPUID(information) cpuid(NULL, NULL, NULL, information)
#endif

// d duplicates a
// c duplicates b
no_more_than_64_bits_t cpuid(void * a, void * b, void * c, void * d);

然后,在您的程序集中,保存每个 ABI 考虑的联合 non-volatile,做您的事,将您想要的任何信息放入 rcx 指向的结构中,然后恢复。