如果ESP上面还有空间,你可以将mov放到那个空间,而不是推送args。 例如,对于两个背靠背的call,你可以使用mov来写入新的参数,而不是使用add esp, 8/ push / push来清理堆栈并压入新的参数。GCC does this with -maccumulate-outgoing-args),这是一些-mtune=设置启用的。 (实际上,gcc的-maccumulate-outgoing-args稍有不同。它在使用sub的第一次调用之前为args预留了空间,因此即使第一次调用也不使用push。这增加了代码大小,但最大限度地减少了对esp的更改,因此它缩小了将指令地址Map到堆栈帧大小的CFI元数据,用于-fomit-frame-pointer的堆栈展开目的。在推入速度较慢的旧CPU上,避免使用它。)
;; Delayed arg popping. Push is cheap since Pentium-M stack engine so we use it for the first call
push 10
push 20
call foo
; add esp,8 ; don't do this yet, using mov instead of push
; then like gcc -maccumulate-outgoing-args
mov dword [esp+4], 15
mov [esp], eax
call bar
add esp,8 ; NOW pop the args.
3条答案
按热度按时间bqjvbblv1#
这与您的代码执行完全相同的操作,但没有使用
push
;假定推入被定义为“将堆栈指针递减操作数大小,然后将操作数移动到堆栈指针所指向的位置”,那么“转译”是直接的。ao218c7q2#
在这个问题的原始版本中,OP提到了AMD 64。x86-64上的常见调用约定在寄存器中传递前几个参数(像一些32位约定,如MS的vectorcall),而不是在堆栈上传递,所以当然在那里使用
mov
。如果您正在编写只从asm调用的函数,则可以在每个函数的基础上制定自定义调用约定,这样即使是32位也可以使用类似上面的代码。(但是我建议选择在常规ABI中不保留调用的寄存器。例如,在函数上使用
__attribute__((regparm(3)))
的gcc -m32
会使它接受EAX、EDX、ECX请参阅x86标签wiki以获取ABI文档的链接。取代
push
以将参数放在堆栈上如果
ESP
上面还有空间,你可以将mov
放到那个空间,而不是推送args。例如,对于两个背靠背的
call
,你可以使用mov
来写入新的参数,而不是使用add esp, 8
/ push / push来清理堆栈并压入新的参数。GCC does this with-maccumulate-outgoing-args
),这是一些-mtune=
设置启用的。(实际上,gcc的
-maccumulate-outgoing-args
稍有不同。它在使用sub
的第一次调用之前为args预留了空间,因此即使第一次调用也不使用push
。这增加了代码大小,但最大限度地减少了对esp
的更改,因此它缩小了将指令地址Map到堆栈帧大小的CFI元数据,用于-fomit-frame-pointer
的堆栈展开目的。在推入速度较慢的旧CPU上,避免使用它。)这会执行
bar( foo(20,10), 15)
,并使堆栈指向与开始之前相同的位置。弹出堆栈(使用add
),然后使用push
输入新的参数,在现代CPU上可能会做得更好。这节省了指令,但不节省代码大小。它可能会节省1个uop。在Intel的堆栈引擎上,在
mov [esp+4]
或add esp, 4
之前需要一个额外的堆栈同步微操作,因此前端微操作的开销是相同的。避免这种情况的唯一方法是推送新参数。最后再做一次大清理(add esp, 16
)。但是这会因为接触新的堆栈空间而导致更多的缓存未命中。(参见x86标签wiki以获得更多优化文档)。这确实最小化了微操作高速缓存的微操作计数;堆栈同步微操作是动态生成的。但是
mov
往往比push
大,特别是对于小的立即数(2字节push 1
与8字节mov dword [esp+4], 1
),因此使用add esp,8
/ 2xpush
时,I缓存的占用空间可能更好。nhhxz33t3#
就像这样:
mov eax [value]
祝你在汇编编程方面好运,这很难。