如何复制寄存器并用最少的指令数执行"x*4+常量"



我是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+大常量是而不是,这是普通程序永远不会做的事情。


通常您可以在一条LEA指令中执行此操作,但ESP是x86地址模式中唯一不能作为索引的寄存器是否看到rbp不被允许作为SIB基础?(索引是寻址模式的一部分,可以应用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个uops,并且在AMD推土机系列CPU上效率更高(GP整数mov没有mov消除,并且缩放索引会为LEA花费额外的延迟周期(。Zen有mov消除,但我认为LEA延迟仍然相同,所以两个版本都是2个周期的延迟。即使是"复杂"的LEA在Zen上也有2/时钟的吞吐量,对于简单的LEA(任何ALU端口(也有4/时钟的吞吐量。

但是在Intel IvyBridge和更高版本的CPU上效率较低,其中mov可以以零延迟(mov消除(运行,并且[edi*4 + disp32]寻址模式仍然是快速的双组件LEA。因此,在消除mov的英特尔CPU上,第一个版本是2个前端uop,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个周期的延迟,这很好,但在像Pentium 3这样的旧CPU上较慢。mov+LEA仍然不如1或2个周期的延迟,并且imul在更少的端口上运行。


(指令的数量通常不是需要优化的;uop的数量通常更重要,以及延迟/后端吞吐量。还有x86机器代码的字节代码大小;不同的指令有不同的长度。(

最新更新