何时使用某种呼叫约定

问题描述

x86-64中是否有关于何时应遵守System V准则以及何时无关紧要的准则?这是对答案here的回应,该答案提到使用其他调用约定来简化内部/本地函数。

# gcc 32-bit regparm calling convention
is_even:          # input in RAX,bool return value in AL
    not   %eax             # 2 bytes
    and   $1,%al          # 2 bytes
    ret
# custom calling convention:
is_even:   # input in RDI
           # returns in ZF.  ZF=1 means even
    test  $1,%dil         # 4 bytes.  Would be 2 for AL,3 for DL or CL (or BL)
    ret

有关上下文,请参见that answer

例如,应使用它:

  • 仅在由外部高级C函数调用时需要。
  • 仅当标签/功能为globl时才需要。

关于何时何时“按我的意愿”使用寄存器,然后根据System V约定何时使用寄存器的最佳指南是什么?

解决方法

这取决于您在asm中编写哪种类型的东西。如果您编写的是一个完全独立编写的小型asm程序,例如用16位Bootloader完全用emm编写的em,则一定要继续为所有内容建立自定义调用约定(如果您执行任何功能,根本不只是内联)。例如看看@ecm's legacy BIOS bootloader中的disp_ax_hex函数作为一个有趣的示例,并查看有关让disp_al破坏更多寄存器的注释的讨论。

我要说的是,大多数其他代码(包含编译器生成的代码的较大程序的一部分)通常都遵循标准的调用约定; x86-64 System V的设计很好。仅考虑将自定义约定用于“私有”辅助函数,尤其是仅从另一个函数的不同部分调用的约定。通常,这些文件将呼叫者全部集中在一个文件中,而不是global

可以有益地返回2个单独值的函数肯定可以从自定义调用约定中受益,从而使asm调用者受益。
例如C memcmp不返回第一个差的位置,仅返回-/ 0 / +。这就是really stupid and useless,这使我们无法利用现有的手动优化组件找到不匹配位置的好方法。在asm中,我们可以轻松地返回两者,例如指向RDI中位置的指针和cmp中FLAGS的结果。

在这种情况下,您可以编写与x86-64 System V调用约定100%兼容的memcmp函数(因此,您需要将两个字节都零扩展并执行双字{{1 }},而不只是做一个字节sub),而将RDI输出作为对asm调用者的奖励。


您链接的我的答案部分是我决定提及的一种随机想法。这通常不是您要做的事情(尽管一开始也不是手工编写asm),并且您根本不希望将cmp本身单独放在函数中,除非是解决代码高尔夫练习的方法。那是其背后的真实想法:该功能的大部分“成本”只是因为您将其制成了一个功能,而在现实生活中,您总是会内联这么简单的内容。

通常,您一开始就不会编写微小的函数。您只需在较大的函数中间用几条指令实现逻辑,就像编译器会内嵌一个小的辅助函数一样。然后,使用平台ABI(在本例中为x86-64 System V)来实现所有功能并不昂贵。

优化逻辑以返回0/1 test(而不仅仅是8位int),遵循标准的调用约定,可能很有趣练习,但通常没有用,除非事实证明您的实际用例想做类似bool的事情。但是在这种情况下,您应该执行even_count += is_even(x);并在需要时最后一次计算一次偶数,即odds += x&1;。除了消除调用/返回开销外,内联还可以考虑将小函数的逻辑作为实际用例的一部分进行优化。


有一个私人助手功能的用例:

有时候,您想将一条多条指令的块作为一个较大的功能的私有“帮助”功能重复使用,例如even = total-odd / mov eax,1 /做其他事情/ call func / mov eax 123。然后,您可以将“函数”更像是循环体或更大函数中的某些东西,而调用方则更像是自定义迭代。

有时候,使用宏重复一段代码是有意义的,但是如果序列太长,则会使代码膨胀。 (每次使用时宏都会扩展;不像5字节的call func。)

需要明确的是,call rel32非常简单,以至于把它放在自己的函数中是毫无意义的。调用函数而不是仅仅运行is_even / test $1,%regjz对于某些寄存器将完全变得疯狂和模糊,并且变得更大和更慢。或jnz从正整数的reg中获得一个0/1整数,您可以将其与and $1,%eax一起使用以计算奇数。 (末尾的总数为奇数)。大多数程序员也不会将其包装在宏中。理解二进制是汇编语言的标准,并且只需要对add或jcc指令进行简单描述以描述语义(test)。


从理论上讲,对于纯手写程序,您可以根据具体情况为每个函数使用最方便的调用约定,并附带注释。但是通常而言,与遵循标准的调用约定相比,这样做的好处很小,并且跟踪哪些函数阻塞器注册并希望其args迅速成为通用函数的维护噩梦,这些函数具有与多个高度不同的调用者彼此之间,而不是被调用的函数。

当然,出于同样的原因,我们用高级语言编写应用程序,而实际上很少手工编写 any asm。您打算在asm中手动编写函数的事实意味着,有必要考虑“像编译器一样思考”是否过于严格。这就是我的codegolf answer的意义:如果值得从函数中抽取每个最后一个字节或循环出一个字节,则整个程序(或至少其调用者)的编写方式可能类似。

这几天在asm中编写整个程序的唯一很好的理由是优化其机器代码大小的废话,例如演示场景。 https://en.wikipedia.org/wiki/Demoscene。 (或者,如果“程序”实际上是在没有/操作系统的情况下运行的引导加载程序。)

那时,不要让ABI和调用约定约束您的优化。而且您的程序通常足够小,可以跟踪不同的函数及其调用约定,尤其是在它们具有一定逻辑意义(或与它们的调用者无论如何恰好保持正确的变量的寄存器最匹配)的情况下。 / p>

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...