x86 程序集:弹出一个值而不存储它
x86 assembly: Pop a value without storing it
在 x86 汇编中,是否可以从堆栈中删除一个值而不存储它?类似 pop word null
的东西?我显然可以使用 add esp,4
,但也许我缺少一个漂亮干净的 cisc 助记符?
add esp,4
/ add rsp,8
是 正常/惯用/干净的方式。不需要 special 方法,因为堆栈不是神奇的或特殊的(至少在这方面不是);它只是寄存器中的一个指针,其中包含一些隐式使用它的指令。 (对于内核堆栈,中断异步使用它,因此软件无法实现内核红区,即使它想要...)
除此之外,在函数末尾清理整个堆栈帧的神奇 CISC 方法是 leave
= mov esp, ebp
/ pop ebp
(or the 16 or 64-bit equivalent). Unlike enter
, it's fast enough on modern CPUs to be usable in practice, but still a 3 uop instruction on Intel CPUs. (http://agner.org/optimize/)。但是 leave
仅在您花费额外的指令首先使用 ebp
/ rbp
制作堆栈帧时才起作用。 (通常你不会这样做,除非你需要保留可变数量的堆栈 space,例如在循环中使用 push
来制作数组,或者相当于 C99 VLA 或 alloca
。或者对于初学者代码来说更容易访问本地,或者在 16 位模式下 SP
不能用于寻址模式。)
神奇的 CISC 清理堆栈参数的方法是让被调用者使用 ret imm16
(花费 1 个额外的 uop)来弹出 args,创建一个调用约定,让被调用者清理堆栈。在 caller-pops 调用约定中,无法使用这种形式的 ret
,但您可以简单地保留堆栈偏移量并使用 mov
来存储下一个函数调用的参数,而不是 [=19] =](如果函数根本不需要任何堆栈参数;寄存器参数调用约定通常更有效。)
所以神奇的 CISC 方式在现代 CPU 上没有性能优势,只有很小的代码大小。
您可能会使用 pop reg
而不是 add esp,4
的 2 个原因:
- 代码大小:
pop r32/r64
是一条单字节指令,而 add esp,4
为 3 个字节,add rsp,8
为 4 个字节。
性能:当您在堆栈指令 (push/pop/call/ret) 后显式使用 esp
/ rsp
时,英特尔的堆栈引擎必须插入额外的堆栈同步微指令。因此,在 call
(returns 和 ret
之后),它会保存一个 uop 以在 ret
之前使用 pop
而不是 add esp,4
函数结束。
AMD 的堆栈引擎不需要额外的堆栈同步微指令,但仍会生成 push/pop 单微指令。与旧的 Intel/AMD CPU 不同,其中 push/pop 比普通的 mov
loads/stores 成本更高,需要单独的 uop 来修改堆栈指针。并在堆栈指针上创建数据依赖性。
有关性能的更多详细信息,请参阅 。
如果您正在寻找美学,那么您可以很好地缩进、格式化和注释您的代码,但是 如果美学超过优化,您在选择 x86 asm 时选择了错误的语言.
当然,如果您需要将堆栈调整超过 1 个寄存器宽度,并且不需要 pop
加载的数据,则一定要使用 add
。或者,如果您需要将它调整 +128 字节,请使用 sub esp, -128
,因为 -128
可编码为符号扩展 imm8,但 +128 不是。
或者可以使用 lea esp, [esp+4]
,就像 gcc 使用 -mtune=atom
一样。 (对于有序原子,而不是 silvermont)。就像我说的,如果你想要干净,你不应该选择 x86 asm。
您几乎总能找到死寄存器 pop
到 。如果你需要在弹出一些你真正想要弹出的寄存器之前将 E/RSP 调整一个堆栈槽,你总是可以弹出同一个寄存器两次。
在极少数情况下,7 (x86-32) 或 15 (x86-64) 非堆栈寄存器中的 none 可用作 pop
目的地,此优化不可用,您应该简单地使用传统的 add
. 不值得花费额外的指令来实现 pop
;这将超过使用 pop
.
的次要好处
请注意,pop Sreg
(段寄存器)仍然使用常规的 "stack width"(32 位或 64 位,取决于模式),而不是 16 位寄存器仅使用 16 位。 But only pop ds/es/ss
are single-byte. pop fs/gs
are 2 bytes each。因此,如果您针对代码大小进行优化,pop gs
比 add esp,4
小 1 个字节,但速度要慢得多。 (或者比 add rsp,8
小 2 个字节)。
在 x86 汇编中,是否可以从堆栈中删除一个值而不存储它?类似 pop word null
的东西?我显然可以使用 add esp,4
,但也许我缺少一个漂亮干净的 cisc 助记符?
add esp,4
/ add rsp,8
是 正常/惯用/干净的方式。不需要 special 方法,因为堆栈不是神奇的或特殊的(至少在这方面不是);它只是寄存器中的一个指针,其中包含一些隐式使用它的指令。 (对于内核堆栈,中断异步使用它,因此软件无法实现内核红区,即使它想要...)
除此之外,在函数末尾清理整个堆栈帧的神奇 CISC 方法是 leave
= mov esp, ebp
/ pop ebp
(or the 16 or 64-bit equivalent). Unlike enter
, it's fast enough on modern CPUs to be usable in practice, but still a 3 uop instruction on Intel CPUs. (http://agner.org/optimize/)。但是 leave
仅在您花费额外的指令首先使用 ebp
/ rbp
制作堆栈帧时才起作用。 (通常你不会这样做,除非你需要保留可变数量的堆栈 space,例如在循环中使用 push
来制作数组,或者相当于 C99 VLA 或 alloca
。或者对于初学者代码来说更容易访问本地,或者在 16 位模式下 SP
不能用于寻址模式。)
神奇的 CISC 清理堆栈参数的方法是让被调用者使用 ret imm16
(花费 1 个额外的 uop)来弹出 args,创建一个调用约定,让被调用者清理堆栈。在 caller-pops 调用约定中,无法使用这种形式的 ret
,但您可以简单地保留堆栈偏移量并使用 mov
来存储下一个函数调用的参数,而不是 [=19] =](如果函数根本不需要任何堆栈参数;寄存器参数调用约定通常更有效。)
所以神奇的 CISC 方式在现代 CPU 上没有性能优势,只有很小的代码大小。
您可能会使用 pop reg
而不是 add esp,4
的 2 个原因:
- 代码大小:
pop r32/r64
是一条单字节指令,而add esp,4
为 3 个字节,add rsp,8
为 4 个字节。 性能:当您在堆栈指令 (push/pop/call/ret) 后显式使用
esp
/rsp
时,英特尔的堆栈引擎必须插入额外的堆栈同步微指令。因此,在call
(returns 和ret
之后),它会保存一个 uop 以在ret
之前使用pop
而不是add esp,4
函数结束。AMD 的堆栈引擎不需要额外的堆栈同步微指令,但仍会生成 push/pop 单微指令。与旧的 Intel/AMD CPU 不同,其中 push/pop 比普通的
mov
loads/stores 成本更高,需要单独的 uop 来修改堆栈指针。并在堆栈指针上创建数据依赖性。
有关性能的更多详细信息,请参阅
如果您正在寻找美学,那么您可以很好地缩进、格式化和注释您的代码,但是 如果美学超过优化,您在选择 x86 asm 时选择了错误的语言.
当然,如果您需要将堆栈调整超过 1 个寄存器宽度,并且不需要 pop
加载的数据,则一定要使用 add
。或者,如果您需要将它调整 +128 字节,请使用 sub esp, -128
,因为 -128
可编码为符号扩展 imm8,但 +128 不是。
或者可以使用 lea esp, [esp+4]
,就像 gcc 使用 -mtune=atom
一样。 (对于有序原子,而不是 silvermont)。就像我说的,如果你想要干净,你不应该选择 x86 asm。
您几乎总能找到死寄存器 pop
到 。如果你需要在弹出一些你真正想要弹出的寄存器之前将 E/RSP 调整一个堆栈槽,你总是可以弹出同一个寄存器两次。
在极少数情况下,7 (x86-32) 或 15 (x86-64) 非堆栈寄存器中的 none 可用作 pop
目的地,此优化不可用,您应该简单地使用传统的 add
. 不值得花费额外的指令来实现 pop
;这将超过使用 pop
.
请注意,pop Sreg
(段寄存器)仍然使用常规的 "stack width"(32 位或 64 位,取决于模式),而不是 16 位寄存器仅使用 16 位。 But only pop ds/es/ss
are single-byte. pop fs/gs
are 2 bytes each。因此,如果您针对代码大小进行优化,pop gs
比 add esp,4
小 1 个字节,但速度要慢得多。 (或者比 add rsp,8
小 2 个字节)。