如何在 x86 中仅使用 2 条连续的 leal 指令将寄存器乘以 37?

How to multiply a register by 37 using only 2 consecutive leal instructions in x86?

假设 %edi 包含 x,我想只使用 2 个连续的 leal 指令以 37*x 结束,我该怎么做?

例如,要获得 45 倍,您会这样做

leal (%edi, %edi, 8), %edi   
leal (%edi, %edi, 4), %eax (to be returned)

我这辈子都想不出用什么数字来代替 8 和 4,这样结果 (%eax) 就是 37x

At -O3, gcc will emit (Godbolt compiler explorer):

int mul37(int a)  { return a*37; }

    leal    (%rdi,%rdi,8), %eax      # eax = a * 9
    leal    (%rdi,%rax,4), %eax      # eax = a + 4*(a*9)
    ret

这是在使用 37 = 9*4 + 1不会破坏第一个 lea 的原始 a 值,因此它可以在第二个中使用两者。

虽然你没有发现这个,但你是个好伙伴:最近的 clang(3.8 和更新版本)通常会使用 2 lea 指令而不是 imul(例如 *15), 但它错过了这个并使用:

    imull   , %edi, %eax
    ret

它确实 *21 使用与 gcc 使用相同的模式,如 5*4 + 1。 (clang3.6 和更早版本总是使用 imul 除非有单指令替代 shllea

ICC 和 MSVC 也使用 imul,但他们似乎不喜欢使用 2 lea 指令,所以 imul 是 "on purpose"。

请参阅 godbolt link 了解 gcc7.2 与 clang5.0 的各种乘法器。尝试 gcc -m32 -mtune=pentium 甚至 pentium3 看看 gcc 当时愿意使用多少指令是很有趣的。尽管 P2/P3 对于 imul r, r, i 有 4 个周期的延迟,所以这有点疯狂。 Pentium 有 9 个周期 imul 并且没有 OOO 来隐藏延迟,因此努力避免它是有意义的。

mtune=silvermont 可能只愿意用一条指令替换 32 位 imul,因为它有 3 周期延迟/1c 吞吐量乘法,但解码通常是瓶颈(根据阿格纳雾,http://agner.org/optimize/)。您甚至可以考虑 imul , %edi, %eax(或 2 的其他幂)而不是 mov/shl,因为 imul-immediate 是复制和乘法。


具有讽刺意味的是,gcc 错过了 * 45 的情况,并使用了 imul,而 clang 使用了 2 个 lea。猜猜是时候提交一些未优化的错误报告了。 如果2 个 LEA 优于 1 个 IMUL,则应尽可能使用它们。

较旧的 clang(3.7 及更早版本)使用 imul,除非单个 lea 可以解决问题。我没有查看变更日志,看看他们是否做了基准测试来决定优先考虑延迟而不是吞吐量。


相关:关于为什么 LEA 使用内存操作数语法和机器编码的规范回答,即使它是一个 shift+add 指令(并且在大多数现代微体系结构中运行在 ALU 而不是 AGU 上。)