在x86-64中,当我们要推送堆栈上的东西时,是否总是要做pushq?

问题描述 投票:0回答:1

因为在x86-64中,16个寄存器都可以是8个字节,在函数调用开始时,当函数(callee)要推送它想使用的callee保存的寄存器(%rbx、%rbp和%r12-15)时,它无法知道调用者在这些寄存器中存储的是64位还是32位或16位或8位的值,所以是否总是要调用 pushq 将这些寄存器的全部8个字节推送到堆栈上,而不是用 pushl? 换句话说,是 pushlpushw 在x86-64中用过吗?

assembly x86-64 calling-convention att
1个回答
2
投票

整个寄存器都是调用保存的而不仅仅是低位的dword或word。 普通函数总是保存整个qword寄存器 因为这是唯一安全的做法,而且它的效率也足够高,没有理由创建一个机制让函数知道它们什么时候可以做其他事情。

在写完32位的低半部分后再读一个完整的寄存器总是很有效率的,因为 32位寄存器写入隐式零扩展到64位. 在调用者写下低8位或16位后读取一个64位寄存器,如果调用者在调用前不小心使用了寄存器,可能会在英特尔P6系列微架构上造成部分寄存器停顿。call. 在现代的Uarches上(不是Intel P6),816位操作数大小的寄存器写已经支付了。罚则. (我忽略了一些细节,比如部分AH重命名仍然是现代英特尔的事情,包括Skylake)


当你 可以 移动堆栈指针 sub $24, %rsp 并使用 movlmovb 来存储一些寄存器的32位或8位低位部分,只有当你对你的调用者如何使用寄存器有所了解,并想进行相应的优化时,这才是安全的。 使你的函数依赖于调用者的内部结构,而不仅仅是ABI)。 即使那是一些辅助函数的选项,通常也不值得为了减少你的堆栈帧的几个字节的占用。

(函数使用16位数据的情况很少,但8位数据并不罕见。 boolchar 是常见的。 编译器通常使用 movzx 又名 movzbl 从内存中加载到零扩展到完整的寄存器,并且可以经常使用32位操作数大小来避免实际处理部分寄存器的诡计。 但是他们不会在意你是否用mov store movzbl重载只保存了低8位,对于编译要保留一个零扩展的bool或char的寄存器)。)

pushl 和pushw曾经在x86-64中使用?

pushl 在64位模式下根本不存在。32位操作数大小,用于 push无法编码 REX.W=0 前缀.

pushw 可编码,但从未被32或64位模式的编译器使用。 (一般来说,除了奇怪的角落情况或黑客,比如可能是shellcode,对人类没有用处,也不推荐使用。 我确实在合并代码时使用过一次(优化代码大小)。将两个16位的值放入一个寄存器中,用于adler-32。).

如果编译器确实想做字或词存储,(例如在未优化的构建中,以溢出传入的寄存器args),它将只使用 movwmovl.

一般来说,你要保持堆栈的排列方式为16位。 这样你就可以进行另一个函数调用了;这就是为什么我建议使用 sub $24, %rsp 以上。 (在函数输入时,RSP指向你的调用者所推送的返回地址,RSP+8和RSP-8是16字节对齐的)。 RSP+8和RSP-8是16字节对齐的)。)


pushq %reg 在现代的CPU上是非常高效的:在CPU上解码到一个uop,而在CPU上,RSP+8和RSP-8是16字节对齐的。堆栈引擎(处理RSP更新) OoO exec后台外。 它的效率很高,以至于 clang使用 push %rax 寄存器 而不是 sub $8, %rsp 当它只需要将堆栈指针移动8个字节时,例如在另一个调用前重新调整堆栈。

pushq %reg 是一个1字节的指令(或2字节的r8...r15包括REX前缀)

© www.soinside.com 2019 - 2024. All rights reserved.