我是x86汇编的新手。例如,以下指令:将ESP的内容乘以4,然后添加0x11233344,并将结果存储在EDI中。
如何用最少的指令数在x86汇编中表示此指令?
push esp
mov edi, 4
mul edi
add edi, 0x11233344
您的asm没有任何意义(push esp
复制到内存,而不是其他寄存器),并且mul edi
写入EDX:EAX而不是edi
。它会EDX:EAX = EAX * src_operand
。阅读手册:https://www.felixcloutier.com/x86/MUL.html。更好的方法是改用imul
,除非您实际上需要完整的32x32 => 64位乘法的上半部分输出。
[此外,除非您确切地知道自己在做什么,否则不要使用堆栈指针寄存器ESP来保存临时值(例如,您在用户空间中,并且确保没有信号处理程序可以异步使用堆栈” 。)堆栈指针* 4 +大常数是not普通程序会做的事情。
[通常,您可以在一条LEA指令中执行此操作,但是ESP是唯一在x86地址模式下不能作为索引的寄存器。请参见rbp not allowed as SIB base?(索引是寻址模式的一部分,可以应用2位移位计数,即比例因子)。
我思考我们最好的选择仍然是将ESP复制到EDI,然后使用LEA:
mov edi, esp
lea edi, [edi * 4 + 0x11223344]
或者您可以使用LEA复制并添加,然后然后左移,因为我们添加的值的低位具有两个零(即,是4的倍数)。因此,我们可以将其右移2而不丢失任何位。
SHIFTED_ADD_CONSTANT equ 0x11223344 >> 2
lea edi, [esp + SHIFTED_ADD_CONSTANT]
shl edi, 2
左移之前的加法会产生进位到前2位,但是我们将把这些位移出,所以这里没有关系。
这也是2 oups,并且更多在AMD Bulldozer系列CPU上有效(GP整数mov
不需要消除运动,并且缩放索引会花费LEA额外的延迟周期)。 Zen具有消除运动的功能,但我认为LEA延迟仍然相同,因此两个版本的延迟均为2个周期。甚至“复杂” LEA在Zen上的吞吐量为2 /时钟,对于简单LEA(任何ALU端口)的吞吐量为4 /时钟。
但是less在Intel IvyBridge和更高版本的CPU上效率很高,在这些CPU上mov
可以以零延迟(消除移动)运行,并且[edi*4 + disp32]
寻址模式仍然是快速的2分量LEA。因此,在具有消除运动功能的Intel CPU上,第一个版本是2个前端uops,1个用于执行单元的未融合域uop,并且只有1个延迟周期。
另一个2指令选项是使用较慢的imul
而不是快速移位。 (寻址模式使用移位:即使将其写为* 1 / 2 / 4 / 8
,也被编码在机器代码的2位移位计数字段中。)>
imul edi, esp, 4 ; this is dumb, don't use mul/imul for powers of 2. add edi, 0x11223344
imul
在现代x86 CPU上具有3个周期的延迟,这是相当不错的,但在像奔腾3这样的旧CPU上却较慢。对于mov + LEA,它仍然不如1或2个周期的延迟,并且imul
在端口更少。