如何复制一个寄存器并用最少的指令执行 `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 机器代码的代码大小(以字节为单位);不同的指令具有不同的长度。 )
我是 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 不是普通程序会做的事情。
通常你可以
我认为我们最好的选择仍然是将 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 机器代码的代码大小(以字节为单位);不同的指令具有不同的长度。 )