x86 的替代调用约定

Alternative calling convention for x86

是否可以在不修改编译器本身的情况下创建替代调用约定?例如,这个假设的调用约定可以用于优化

gcc / clang 了解将调用约定编译到编译器可执行文件中,而不是由配置文件定义,您可以在不重新编译编译器的情况下覆盖它。

所以是的,您需要修改编译器,但对于 open-source 编译器来说可能并不难。 gcc-internals 文档有 a whole chapter on stack layout and calling conventions,记录了如何向编译器描述调用约定。看起来这是用 C 源代码完成的,而不是用 machine-definition 文件向编译器描述 instruction-set 的方式用特殊语言完成的。

我没有检查 clang / LLVM 是如何定义调用约定的。


但在某些情况下,GNU C (gcc/clang/ICC) 的灵活性有限。不是很好,但也不是完全固定的。

在 x86 上,编译器知道多个现有的调用约定。例如x86-64 System V 和 Windows 具有不同的调用约定,您可以使用 __attribute__((ms_abi)) 来声明使用 Windows 约定的函数,即使是为 non-Windows 目标编译时也是如此平台。在编译对函数的调用时,gcc 当然会根据为函数声明的调用约定生成代码来传递 args 和设置堆栈。

对于 32 位 x86,有很多约定。见 gcc docs for function attributes.

例如您可以使用 __attribute__((fastcall)) void foo(int a, int b) { ... } 来声明一个函数,该函数期望它在 ECX 和 EDX 中而不是在堆栈中作为参数。

甚至支持(在 32 位 x86 上)为调用约定设置寄存器参数的最大数量,__attribute((regparm(3))) 声明一个函数在寄存器中最多接受 3 个整数参数而不是堆。但是寄存器的顺序是不可配置的,它只使用EAX、ECX和EDX(大多数约定中call-clobbered的寄存器)。

您也可以使用 -mregparm=2 进行编译以全局设置它。

但是不支持使用 shadow space(如 x64 Windows)或 caller-pops(某些特定的 caller-pops 约定除外)来调用函数),或者使用 FLAGS 而不是整数寄存器返回的布尔结果,或者您可以使用 hand-written 程序集执行的任何其他操作。


MSVC 同样支持 vectorcall 与常规的一些 __declspec 选项。