x86程序集:弹出一个值而不存储它

时间:2018-02-09 12:04:42

标签: assembly x86 stack cpu-registers

在x86程序集中,是否可以从堆栈中删除值而不存储它? add esp,4的某些内容?我显然可以使用{{1}},但也许有一个很好的,干净的cisc助记符,我错过了?

1 个答案:

答案 0 :(得分:8)

add esp,4 / add rsp,8 正常/惯用/清洁方式。不需要特殊的方式,因为堆栈不是神奇的或特殊的(至少在这方面不是这样);它只是一个寄存器中的指针,其中一些指令隐含地使用它。 (对于内核堆栈,中断使用它是异步的,因此软件即使想要实现内核红区也不会......)

除此之外,在函数末尾清理整个堆栈帧的神奇CISC方法是leave = mov esp, ebx / pop ebp(或16位或64位)当量)。与enter不同,它在现代CPU上足够快,可以在实践中使用,但仍然可以在Intel CPU上使用3 uop指令。 (http://agner.org/optimize/)。但是leave只在第一时间起作用,如果你花费额外的指令制作一个首先使用ebp / rbp的堆栈框架。 (通常你不会这样做,除非你需要保留一定数量的堆栈空间,例如在循环中使用push来制作数组,或者相当于C99 VLA或{{1}或者,对于初学者代码,可以更容易地访问本地,或者在16位模式下alloca不能用于寻址模式。)

清理stack-args的神奇CISC方法是让被调用者使用ret imm16(花费1个额外的uop)来弹出args,创建一个调用约定,其中被调用者清理堆栈。在调用者 - 弹出式调用约定中,没有办法使用这种形式的SP,但是您可以简单地保留堆栈偏移并使用ret来存储下一个函数调用的args mov的函数(如果函数需要任何stack-args; register-arg调用约定通常更有效。)

因此,神奇的CISC方式在现代CPU上没有性能优势,只有较小的代码大小。

您可以使用push代替pop reg有两个原因:

  • 代码大小:add esp,4是一个单字节指令,pop r32/r64为3个字节,add esp,4为4个字节。
  • 性能:在堆栈指令(push / pop / call / ret)后显式使用add rsp,8 / esp时,Intel的堆栈引擎必须插入额外的堆栈同步uops )。因此,在rsp(使用call返回)之后,它会保存一个uop,以便在ret之前使用pop代替add esp,4功能

    AMD的堆栈引擎不需要额外的堆栈同步uops,但仍然可以制作推/弹单Uop指令。与较旧的Intel / AMD CPU不同,其中push / pop的成本高于普通ret加载/存储,需要单独的uop来进行堆栈指针修改。并在堆栈指针上创建数据依赖。

有关效果的详情,请参阅Why does this function push RAX to the stack as the first operation?

如果您正在寻找美学,那么您可以很好地缩进,格式化和评论您的代码,但除了,如果美学超过优化,当您选择x86 asm时,您选择了错误的语言。

当然,如果您需要将堆栈调整超过1个寄存器宽度,如果您不需要mov将加载的数据,请务必使用add。或者,如果您需要将其调整为+128字节,请使用pop,因为sub esp, -128可编码为符号扩展的imm8,但+128不是

或者也许使用-128,就像gcc与lea esp, [esp+4]一样。 (对于有序原子,而不是silvermont)。就像我说的,如果你想干净,你就不应该选择x86 asm。

您几乎总能找到-mtune=atom进入的无效注册。如果您需要在弹出一些实际想要弹出的寄存器之前将E / RSP调整一个堆栈插槽,您可以随时弹出相同的寄存器两次。

在非常罕见的情况下,7(x86-32)或15(x86-64)非堆栈寄存器都不可用作pop目的地,此优化不可用,您应该只需使用传统的pop不值得花费额外的指示来add;这将超过使用pop的微小好处。

请注意pop(段寄存器)仍然使用常规"堆栈宽度" (32或64位,取决于模式),而不是16位寄存器只有16位。 But only pop ds/es/ss are single-byte. pop fs/gs are 2 bytes each。因此,如果您针对代码大小进行优化,pop Sregpop gs小1个字节,但速度要慢得多。 (或者比add esp,4小2个字节。)