我一直在阅读有关汇编功能的内容,我对是否使用输入和退出或仅使用调用/返回指令进行快速执行感到困惑。一路快,另一路小吗?例如,在没有内联函数的情况下,在汇编中执行此操作的最快(stdcall)方法是什么:
static Int32 Add(Int32 a, Int32 b) {
return a + b;
}
int main() {
Int32 i = Add(1, 3);
}
答案 0 :(得分:5)
使用call
/ ret
,而无需使用enter
/ leave
或push&pop rbp / mov rbp, rsp
制作堆叠框架。 gcc(使用默认的-fomit-frame-pointer
)仅在堆栈中执行可变大小分配的函数中生成堆栈帧。 This may make debugging slightly more difficult,因为gcc在使用-fomit-frame-pointer
进行编译时通常会发出堆栈展开信息,但是您手写的asm将不会有这种情况。通常只在asm中编写leaf函数才有意义,或者至少在不调用许多其他函数的函数中编写函数。
堆栈帧意味着您不必跟踪堆栈指针自从函数入口访问堆栈中的内容(例如本地的函数args和溢出槽)以来已经改变了多少。 Windows和Linux / Unix 64位ABI都通过寄存器中的前几个args,并且通常有足够的regs,您不必将任何变量溢出到堆栈中。在大多数情况下,堆栈帧是浪费指令。在32位代码中,ebp
可用(从6到7个GP,从不计算堆栈指针)产生的差异比从14到15更大。当然,你还需要push/pop
rbp但是,如果你做使用它,因为在两个ABI中它都是被调用者保存的寄存器,不允许函数被破坏。
如果您正在优化x86-64 asm,则应阅读Agner Fog's guides,并查看x86标记wiki中的其他一些链接。
您的功能的最佳实现可能是:
align 16
global Add
Add:
lea eax, [rdi + rsi]
ret
; the high 32 of either reg doesn't affect the low32 of the result
; so we don't need to zero-extend or use a 32bit address-size prefix
; like lea eax, [edi, esi]
; even if we're called with non-zeroed upper32 in rdi/rsi.
align 16
global main
main:
mov edi, 1 ; 1st arg in SysV ABI
mov esi, 3 ; 2nd arg in SysV ABI
call Add
; return value in eax in all ABIs
ret
align 16
OPmain: ; This is what you get if you don't return anything from main to use the result of Add
xor eax, eax
ret
Add()
对于return 4
实际上是what gcc emits,但如果你return i
,它仍会将主要变为空函数,或变为-fno-inline-functions
。 clang 3.7即使结果是编译时常量,也会尊重jmp
。它通过尾部调用优化来胜过我的主题,Add
到{{1}}。
请注意,Windows 64位ABI对函数args使用不同的寄存器。请参阅x86标记wiki中的链接或Agner Fog的ABI指南。 Assembler macros可能有助于在asm中编写函数,这些函数使用正确的寄存器作为其args,具体取决于您所针对的平台。