pusha/popa 在函数 prologue/epilogue 中的用法?

Usage of pusha/popa in function prologue/epilogue?

阅读汇编教程,我看到“函数”prologue/epilogue 包含在:

push bp
mov bp, sp  
---
pop bp

但我也看到一些其他教程使用 pusha 然后 popa 来保存寄存器。 那么为什么函数 prologue/epilogue 除了设置 bp 之外不执行 pusha/popa 来保存寄存器上下文?

Reading assembly tutorials, I saw that "function" prologue/epilogue...

对于真正的汇编语言(您不必遵守其他语言的调用约定),单词“函数 prologue/epilogue”没有意义。

对于“旨在遵守某些其他语言的调用约定的汇编语言”;你只需要 save/restore 一些寄存器(可能 none)。

举个例子;对于 CDECL,EAX、ECX 和 EDX 的内容可以被被调用者丢弃,并且永远不需要被被调用者 saved/restored(调用者需要保存它们);如果一个函数不使用任何其他寄存器,则被调用者也不需要保存或恢复任何其他寄存器。另请注意,“EBP 作为帧指针”是过时的垃圾(它存在是因为调试器不是很好并且在调试信息改进时变得毫无意义 - 例如 DWARF 调试信息等)。这些事情结合起来意味着像这样的事情有可接受的 CDECL 序言和结尾:

    myFunction:
            mov eax,12345      ;eax = returned value
            ret

如果确实需要保存和恢复“很多”寄存器; pusha 很慢(微编码),一系列多个 push 指令也很慢(存储地址取决于最近修改的 ESP 中的值)。典型的方法是自己做,比如:

                        ;Don't bother saving EAX, ECX, EDX.
    sub esp,16          ;Create space to save 4 registers (but maybe more for local variables)

    mov [esp],ebx
    mov [esp+4],esi
    mov [esp+8],edi
    mov [esp+12],ebp

但是;成本是 space。在代码大小极其有限(例如“512 字节的一部分”)的引导加载程序中,聪明的程序员将使用真正的汇编语言(其中“函数 prologue/epilogue”没有意义),而初学者可能会使用 pusha 来保存 space(没有意识到他们没有理由关心其他编程语言的调用约定)。

它们不会保存 所有 寄存器,因为您并不总是需要保存所有寄存器。保存和恢复它们很慢。是的,这是一个很小的单一指令,看起来很节省,但它需要时间和堆栈 space。要了解保存的内容,请查看调用约定。

https://en.wikipedia.org/wiki/X86_calling_conventions

PUSHA/PUSHAD—推送所有通用寄存器

这些是指令。在 Skylake 上,PUSHA 需要 19 微指令和 8 个周期的吞吐量。 POPA 需要 18 微指令和 8c 的吞吐量。

此外,PUSHA/PUSHAD 在 64 位中无效。当 x86 扩展到 64b 时,它们被 AMD 正确地清除,然后被英特尔清除。

现代编译器反其道而行之,尽可能避免保存寄存器。 LLVM 执行称为收缩包装的分析,其中序言被向前推进以允许快速提前退出。

https://llvm.org/doxygen/ShrinkWrap_8cpp_source.html

这些太糟糕了,太糟糕了,不好,非常糟糕的指示。