我一直在阅读有关汇编函数的内容,但对于是使用进入和退出还是仅使用调用/返回指令来快速执行感到困惑。一种方式快而另一种方式更小吗?例如,在不内联函数的情况下,在汇编中执行此操作的最快(stdcall)方法是什么:
static Int32 Add(Int32 a, Int32 b) {
return a + b;
}
int main() {
Int32 i = Add(1, 3);
}
使用call
/ ,而不使用/或ret
制作堆栈帧。gcc(使用默认值)仅在对堆栈进行可变大小分配的函数中创建堆栈帧。 这可能会使调试更加困难,因为 gcc 通常在使用 编译时会发出堆栈展开信息,但您的手写 asm 不会有。通常只在 asm 中编写叶函数才有意义,或者至少是那些不调用许多其他函数的函数。enter
leave
push&pop rbp / mov rbp, rsp
-fomit-frame-pointer
-fomit-frame-pointer
堆栈帧意味着您不必跟踪自从函数进入后堆栈指针发生了多少变化以访问堆栈上的内容(例如,函数参数和局部变量的溢出槽)。Windows 和 Linux/Unix 64 位 ABI 都在寄存器中传递前几个 args,并且通常有足够的 regs,您不必将任何变量溢出到堆栈中。在大多数情况下,堆栈帧是对指令的浪费。在 32 位代码中,具有ebp
可用(从 6 到 7 个 GP regs,不计算堆栈指针)比从 14 到 15 产生更大的差异。当然,如果你确实push/pop
使用它,你仍然必须使用rbp ,因为在两个 ABI 都是一个被调用者保存的寄存器,不允许函数破坏。
如果您正在优化 x86-64 asm,您应该阅读Agner Fog 的指南,并查看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
这实际上是 gcc 为 发出的Add()
,但它仍然将 main 变成一个空函数,或者变成一个return 4
if you return i
。 即使结果是编译时常量,clang 3.7也会尊重。-fno-inline-functions
它通过进行尾调用优化和jmp
ing to击败了我的 asm Add
。
请注意,Windows 64 位 ABI 对函数 args 使用不同的寄存器。请参阅 x86 标签 wiki 或 Agner Fog 的 ABI 指南中的链接。 汇编器宏可能有助于在 asm 中编写函数,这些函数使用正确的寄存器作为参数,具体取决于您的目标平台。