目前,我认为“Push”等指令存在的唯一原因是将多条MOV和算术指令替换为单个指令。
是否有任何“PUSH”能够完成而使用更原始的指令无法完成的功能?
“PUSH”只是一个单一助记符,它被编译成多个机器码指令吗?
目前,我认为“Push”等指令存在的唯一原因是将多条MOV和算术指令替换为单个指令。
是否有任何“PUSH”能够完成而使用更原始的指令无法完成的功能?
“PUSH”只是一个单一助记符,它被编译成多个机器码指令吗?
推送(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
push rsp
/pop rsp
,push/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进行一次更大的调整。