“PUSH”指令的操作可以使用其他指令执行吗?
Can a “PUSH” instruction's operation be performed using other instructions?
目前在我看来,我们拥有像“Push”这样的指令的唯一原因是用一条指令代替多个 MOV 和算术指令。
有什么“PUSH”是更原始的指令无法完成的吗?
PUSH是不是就是一个助记符编译成多条机器码指令?
Push 是一个真正的机器指令(https://www.felixcloutier.com/x86/push) 不是只是一个汇编宏/伪指令。例如,push rax
的单字节编码为 0x50
.
但是是的,您可以使用 sub rsp, 8
和 mov
商店等其他指令来模拟它。 (这对于像 x86 这样的 CISC 机器来说是正常的!)例如见 What is the function of the push / pop instructions used on registers in x86 assembly?
为了准确模拟它(不修改标志),您使用 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 rsp
、push/pop [rsp+16]
以及任何其他行为相匹配的等效指令操作数(立即数、reg 或 mem)。
Is there anything “PUSH” does that cannot be accomplished by more primitive instructions?
除了效率和代码大小之外没有什么重要的。
单个指令是原子的。中断 - 它们要么发生要么不发生。这通常是完全无关紧要的;异步中断通常不会查看被中断代码的堆栈/寄存器内容。
PUSH 可以用一个字节的机器代码来完成压入单个寄存器的工作,或者 2 个字节的小立即数。多指令序列要大得多。 8086 的 ISA 的架构师非常专注于使小代码大小成为可能,所以是的 有一条指令用一个短的指令替换几个较长的指令是完全正常的。 例如我们有 not
而不是必须使用 xor reg, -1
,并且 inc
而不是 add reg, 1
。 (虽然它们都有不同的 FLAGS 语义,NOT 保持标志不变,INC/DEC 保持 CF 不变。)更不用说所有 x86 的其他特殊情况编码,比如 xchg-with-[[ 的 1 字节编码=63=]]斧头。参见 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code
还有效率:在 Pentium-M 和更高版本的 CPU 上,PUSH 解码为单个 uop(在融合域中),这要归功于堆栈引擎处理堆栈指针的隐式使用,如 push/pop 和call/ret。 2 条单独的指令当然解码为至少 2 微指令。 (test/cmp+JCC宏融合的特例除外)
在古老的 P5 Pentium 上,使用单独的 ALU 和 mov
指令模拟推送实际上是一个胜利 - 在 PPro CPU 不知道如何将复杂的 CISC 指令分解为单独的 uops 之前,复杂的指令不能' P5 的双问题有序流水线中的 t 对。 (参见 Agner Fog's microarch guide。)这里的主要好处是能够混合其他可以配对的指令,并且只做一个大的 sub
然后只做 mov
存储而不是多次更改到堆栈指针。
这也适用于堆栈引擎之前的早期 P6 系列。例如,带有 -march=pentium3
的 GCC 将倾向于避免 push
并且只对 ESP 做一个更大的调整。
目前在我看来,我们拥有像“Push”这样的指令的唯一原因是用一条指令代替多个 MOV 和算术指令。
有什么“PUSH”是更原始的指令无法完成的吗?
PUSH是不是就是一个助记符编译成多条机器码指令?
Push 是一个真正的机器指令(https://www.felixcloutier.com/x86/push) 不是只是一个汇编宏/伪指令。例如,push rax
的单字节编码为 0x50
.
但是是的,您可以使用 sub rsp, 8
和 mov
商店等其他指令来模拟它。 (这对于像 x86 这样的 CISC 机器来说是正常的!)例如见 What is the function of the push / pop instructions used on registers in x86 assembly?
为了准确模拟它(不修改标志),您使用 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 rsp
、push/pop [rsp+16]
以及任何其他行为相匹配的等效指令操作数(立即数、reg 或 mem)。
Is there anything “PUSH” does that cannot be accomplished by more primitive instructions?
除了效率和代码大小之外没有什么重要的。
单个指令是原子的。中断 - 它们要么发生要么不发生。这通常是完全无关紧要的;异步中断通常不会查看被中断代码的堆栈/寄存器内容。
PUSH 可以用一个字节的机器代码来完成压入单个寄存器的工作,或者 2 个字节的小立即数。多指令序列要大得多。 8086 的 ISA 的架构师非常专注于使小代码大小成为可能,所以是的 有一条指令用一个短的指令替换几个较长的指令是完全正常的。 例如我们有 not
而不是必须使用 xor reg, -1
,并且 inc
而不是 add reg, 1
。 (虽然它们都有不同的 FLAGS 语义,NOT 保持标志不变,INC/DEC 保持 CF 不变。)更不用说所有 x86 的其他特殊情况编码,比如 xchg-with-[[ 的 1 字节编码=63=]]斧头。参见 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code
还有效率:在 Pentium-M 和更高版本的 CPU 上,PUSH 解码为单个 uop(在融合域中),这要归功于堆栈引擎处理堆栈指针的隐式使用,如 push/pop 和call/ret。 2 条单独的指令当然解码为至少 2 微指令。 (test/cmp+JCC宏融合的特例除外)
在古老的 P5 Pentium 上,使用单独的 ALU 和 mov
指令模拟推送实际上是一个胜利 - 在 PPro CPU 不知道如何将复杂的 CISC 指令分解为单独的 uops 之前,复杂的指令不能' P5 的双问题有序流水线中的 t 对。 (参见 Agner Fog's microarch guide。)这里的主要好处是能够混合其他可以配对的指令,并且只做一个大的 sub
然后只做 mov
存储而不是多次更改到堆栈指针。
这也适用于堆栈引擎之前的早期 P6 系列。例如,带有 -march=pentium3
的 GCC 将倾向于避免 push
并且只对 ESP 做一个更大的调整。