如何解决何时使用某种呼叫约定
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。
例如,应使用它:
关于何时何时“按我的意愿”使用寄存器,然后根据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,%reg
或jz
对于某些寄存器将完全变得疯狂和模糊,并且变得更大和更慢。或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>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。