Tyl*_*ler 3 x86 assembly instruction-set nasm opcode
目前在我看来,我们拥有像“Push”这样的指令的唯一原因是用一条指令替换多个 MOV 和算术指令。
有没有更原始的指令不能完成的“PUSH”?
“PUSH”只是一个编译成多个机器代码指令的助记符吗?
Push 是一个真正的机器指令 ( https://www.felixcloutier.com/x86/push ),而不仅仅是一个汇编宏/伪指令。例如,push raxis 的单字节编码为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
Run Code Online (Sandbox Code Playgroud)
有没有更原始的指令不能完成的“PUSH”?
除了效率和代码大小之外,没有什么重要的。
单个指令是原子的。中断 - 它们要么发生,要么不发生。这通常是完全无关的;异步中断通常不会查看被中断代码的堆栈/寄存器内容。
PUSH 可以在一个字节的机器代码中完成工作以推送单个寄存器,或者 2 个字节的小立即数。多指令序列要大得多。8086 ISA 的架构师非常专注于使小代码大小成为可能,所以是的,有一条指令用一条短指令替换几条较长指令是完全正常的。 例如,我们not不必使用xor reg, -1, 而inc不是add reg, 1。(尽管它们都有不同的 FLAGS 语义,NOT 保留标志不变,INC/DEC 保留 CF 不变。)更不用说 x86 的所有其他特殊情况编码,例如 xchg-with-[e/r 的 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 个单独的指令当然解码为至少 2 个 uops。(除了 test/cmp + JCC 宏融合的特殊情况)。
在古老的 P5 Pentium 上,用单独的 ALU 和mov指令模拟推送实际上是一个胜利——在 PPro CPU 不知道如何将复杂的 CISC 指令分解成单独的 uop 之前,复杂的指令无法在 P5 的双发出顺序中配对管道。(请参阅Agner Fog 的微架构指南。)这里的主要好处是能够混合其他可以配对的指令,并且只做一个大的sub,然后只做mov存储,而不是对堆栈指针进行多次更改。
这也适用于堆栈引擎之前的早期 P6 系列。-march=pentium3例如,GCC将倾向于避免push并仅对 ESP 进行更大的调整。