Neo*_*ird 6 x86 assembly stack callstack stack-pointer
在x86程序集中,是否可以从堆栈中删除值而不存储它?有什么东西沿着pop word null?我显然可以使用add esp,4,但也许有一个很好的和干净的cisc助记符我不见了?
Pet*_*des 10
add esp,4/ add rsp,8 是正常/惯用/干净的方式.不需要特殊的方法,因为堆栈不是神奇的或特殊的(至少在这方面不是这样); 它只是一个寄存器中的指针,其中一些指令隐式使用它.(对于内核堆栈,中断异步使用它,因此软件无法实现内核红区,即使它想...)
除此之外,在函数结束时清理整个堆栈帧的神奇CISC方法是leave= mov esp, ebx/ pop ebp(或16或64位等效).不同的是enter,它在现代CPU上足够快,可以在实践中使用,但仍然是英特尔CPU上的3 uop指令.(http://agner.org/optimize/).但是leave,如果你花费额外的指令用ebp/ rbp首先制作堆栈框架,那么只能在第一时间工作.(通常你不会这么做,除非你需要保留的堆栈空间,比如一个变量量与push在一个循环中,使一个阵列,或C99 VLA相当于或alloca,或者初学者代码,以获得当地人更容易,或在16位模式下,SP不能用于寻址模式.)
清理stack-args的神奇CISC方法是让被调用者使用ret imm16(花费1个额外的uop)弹出args,创建一个调用约定,被调用者清理堆栈.在主叫用户啪啪调用约定,没有办法使用这种形式的ret,但你可以简单地离开堆栈偏移,并使用mov存储下一个函数调用,而不是ARGS push(如果该功能需要在所有的任何堆栈ARGS;注册-arg调用约定通常更有效.)
因此,神奇的CISC方式在现代CPU上没有性能优势,只有较小的代码大小.
您可以使用以下两个原因pop reg而不是add esp,4:
pop r32/r64是一个单字节指令,3个字节add esp,4或4个字节add rsp,8.性能:在堆栈指令(push/pop/call/ret)之后使用esp/ rspexplicit 时,Intel的堆栈引擎必须插入额外的堆栈同步uops .所以在a call(以a返回ret)之后,它会保存一个uop pop而不是在函数结束add esp,4之前使用ret.
AMD的堆栈引擎不需要额外的堆栈同步uops,但仍然可以生成推/弹单Uop指令.与旧的Intel/AMD CPU不同,其中push/pop的成本高于普通的mov加载/存储,需要单独的uop来进行堆栈指针修改.并在堆栈指针上创建数据依赖项.
请参阅为什么此功能将RAX作为第一个操作推送到堆栈?有关性能的更多详细信息.
如果您正在寻找美学,那么您可以很好地缩进,格式化和评论您的代码,但是当您选择x86 asm时,如果美学超过优化,则选择错误的语言.
当然,如果您需要将堆栈调整超过1个寄存器宽度,add那么如果您不需要pop加载的数据,请务必使用.或者,如果需要将其调整为+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)非堆栈寄存器都不可用作pop目的地,此优化不可用,您应该只使用传统的add. 不值得花费额外的指示来使它成为可能pop; 这将超过使用的微小好处pop.
注意pop Sreg(段寄存器)仍然消耗常规的"堆栈宽度"(32或64位,具体取决于模式),而不是16位寄存器的16. 但只是pop ds/es/ss单字节. pop fs/gs每个是2个字节.因此,如果您针对代码大小进行优化,pop gs则比1个字节小add esp,4,但速度要慢得多.(或小于2个字节add rsp,8).