“PUSH”指令的操作是否可以使用其他指令执行?

4

目前,我认为“Push”等指令存在的唯一原因是将多条MOV和算术指令替换为单个指令。

是否有任何“PUSH”能够完成而使用更原始的指令无法完成的功能?

“PUSH”只是一个单一助记符,它被编译成多个机器码指令吗?

1个回答

10

推送(Push)是一条真正的机器指令(不只是汇编宏/伪指令)。例如,push rax的单字节编码为0x50(参见此处)

但是,可以使用其他指令来模拟它,比如sub rsp, 8和一个mov存储。这在像x86这样的CISC机器上很常见!例如,请参见x86汇编中用于寄存器的push / pop指令的功能是什么?

要完全模拟(而无需修改标志),请使用LEA代替ADD/SUB。

  lea   rsp, [rsp-8]
  mov   qword [rsp], 123      ; push 123 in 64-bit mode

请参见What is an assembly-level representation of pushl/popl %esp?,其中有与push rsp/pop rsppush/pop [rsp+16]以及任何其他操作数(即立即数、寄存器或内存)相匹配的等效指令。

是否有任何“PUSH”操作无法通过更基本的指令完成?

除了效率和代码大小之外,没有重要的东西。

单个指令在中断方面是原子性的-它们要么发生,要么不发生。这通常完全不相关;异步中断通常不会查看被中断代码的堆栈/寄存器内容。

PUSH可以用一个机器码字节推送一个寄存器,或者用两个字节推送一个小的立即数。多指令序列要大得多。8086 ISA的设计者非常注重使小的代码大小成为可能,因此是的使用一条短指令替换几个较长的指令是很正常的。例如,我们有not而不是必须使用xor reg,-1,和inc而不是add reg,1。(虽然这两个都具有不同的FLAGS语义,NOT保留标志未更改,INC/DEC保留CF未更改。)更不用说x86的其他特殊情况编码,例如用于xchg-with-[e/r]ax的1字节编码。请参见https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code

此外是效率:PUSH在Pentium-M和更高版本的CPU上解码为单个uop(在融合域中),这要归功于堆栈引擎处理push/pop和call/ret等指令对堆栈指针的隐式使用。 两个单独的指令当然至少解码为2个uop。(除了test/cmp + JCC的宏融合的特殊情况。)

在古老的P5 Pentium上,使用独立的ALU和mov指令来模拟push实际上是一项胜利——在PPro CPU之前,CPU不知道如何将复杂CISC指令分解成单独的uops,并且复杂指令无法配对P5的双发射顺序流水线。(请参见Agner Fog's microarch guide。)这里的主要好处是能够混合其他可以配对的指令,并且只需进行一次大的sub,然后只需要进行mov存储而不是多次更改堆栈指针。

在没有堆栈引擎的早期P6家族中,这也适用。例如,使用-march=pentium3的GCC将倾向于避免push,而只是对ESP进行一次更大的调整。


没有比这个答案更好的了!非常有帮助。 - Tyler
1
@Tyler:我本来想将其关闭为在x86汇编中用于寄存器的push / pop指令的功能是什么?的重复,但后来我注意到你问是否有任何不能在没有它的情况下完成的事情。这证明是足够有趣的,让我想要回答。除了效率/代码大小之外,我所能想到的唯一一件事就是关于中断的原子性。但我喜欢谈论效率,所以这也可以。:P - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接