在我看来,目前看来,我们拥有“推”之类的指令的唯一原因是要用一条指令替换多个MOV和算术指令。
是否有任何“推”无法通过更原始的指令来完成?
“ PUSH”只是一个可编译为多个机器代码指令的助记符吗?
推式是真正的机器指令(https://www.felixcloutier.com/x86/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可以通过压入单个寄存器的机器代码的单个字节来完成工作,或者以2个字节的立即数完成操作。多指令序列要大得多。8086的ISA的架构师非常专注于使小代码成为可能,因此,有一条指令用一条短指令替换几条较长的指令是完全正常的。例如我们有not
不必使用xor reg, -1
和inc
代替add reg, 1
。(尽管它们都具有不同的FLAGS语义,没有使标志保持不变,而INC / DEC使CF保持不变。)更不用说所有x86的其他特殊情况的编码,例如xchg-with- [e / r的1字节编码。 ]斧头。参见https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code
还有效率:由于堆栈引擎可以通过push / pop和call / ret等指令处理堆栈指针的隐式使用,因此PUSH在Pentium-M和更高版本的CPU上解码为单个uop(在融合域中)。2条单独的指令当然会解码为至少2 oups。(除了特殊情况下的test / cmp + JCC的宏融合)。
在古老的P5 Pentium上,使用单独的ALU和mov
指令模拟推入实际上是一个胜利-在PPro CPU不知道如何将复杂的CISC指令分解为单独的uops之前,复杂的指令无法按P5的双重发行顺序进行配对管道。(请参阅Agner Fog的微体系结构指南。)这里的主要好处是能够混入可能配对的其他指令,并且只做一个大的sub
,然后再做mov
存储,而不是对堆栈指针进行多次更改。
这也适用于堆栈引擎之前的早期P6系列。-march=pentium3
例如,GCC倾向于避免使用,push
而只是对ESP进行了较大的调整。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句