如何复制一个寄存器并用最少的指令执行 `x*4 + constant`

How to copy a register and do `x*4 + constant` with the minimum number of instructions

我是 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 来保存临时值,除非你确切地知道你在做什么(例如,你在 user-space,并且你确定没有信号处理程序可以异步使用堆栈。)堆栈指针 * 4 + large-constant 不是普通程序会做的事情。


通常你可以 but ESP is the only register that can't be an index in an x86 address mode. See (索引是寻址模式的一部分,可以应用 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 微指令,并且 在 AMD 推土机系列 CPU 上更有效(GP 整数 mov 没有移动消除,并且缩放索引为 LEA 花费一个额外的延迟周期)。 Zen 有 mov-elimination,但我认为 LEA 延迟仍然相同,所以两个版本都是 2 周期延迟。即使 "complex" LEA 在 Zen 上也有 2/clock 的吞吐量,或者对于简单的 LEA(任何 ALU 端口)有 4/clock 的吞吐量。

但是 less 在英特尔 IvyBridge 和更高版本的 CPU 上效率更高,其中 mov 可以 运行 零延迟(mov 消除),并且 [edi*4 + disp32] 寻址模式仍然是一个快速的 2-component LEA。所以在带有 mov-elimination 的 Intel CPU 上,第一个版本是 2 个前端微指令,1 个未融合域微指令用于一个执行单元,并且只有 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 运行s 在更少的端口上。


(指令数通常不是要优化的对象;微指令数通常更重要,延迟/后端吞吐量也更重要。还有 x86 机器代码的代码大小(以字节为单位);不同的指令具有不同的长度。 )