【问题标题】:Tricks compiler uses to compile basic arithmetic operations of 128-bit integerTricks 编译器用于编译 128 位整数的基本算术运算
【发布时间】:2017-09-03 02:13:57
【问题描述】:

我在GodBolt上玩过,看到x86-64 gcc(6.3)编译了以下代码:

typedef __int128_t int128_t;
typedef __uint128_t uint128_t;

uint128_t mul_to_128(uint64_t x, uint64_t y) {
  return uint128_t(x)*uint128_t(y);
}
uint128_t mul(uint128_t x, uint128_t y) {
  return x*y;
}
uint128_t div(uint128_t x, uint128_t y) {
  return x/y;
}

我得到了:

mul_to_128(unsigned long, unsigned long):
        mov     rax, rdi
        mul     rsi
        ret
mul(unsigned __int128, unsigned __int128):
        imul    rsi, rdx
        mov     rax, rdi
        imul    rcx, rdi
        mul     rdx
        add     rcx, rsi
        add     rdx, rcx
        ret
div(unsigned __int128, unsigned __int128):
        sub     rsp, 8
        call    __udivti3 //what is this???
        add     rsp, 8
        ret

3 个问题:

  1. 第一个函数(将64-bit uint 转换为128-bit 然后将它们相乘)是 比 2 128 位 uints(第二个函数)的乘法简单得多。基本上,只要 1 乘法。如果将 2 个最大值乘以 64 位 uint,它 肯定会溢出 64 位寄存器...它是如何产生的 128 位结果只需 1 次 64 位 - 64 位乘法???
  2. 我无法很好地读取第二个结果...我的猜测是将 64 位数字分解为 2 个 32 位数字(例如,hi 更高的 4 个字节 和 lo 作为低 4 个字节),并像这样组装结果 (hi1*hi2)<<64 + (hi1*lo2)<<32 + (hi2*lo1)<<32+(lo1*lo2)。显然 我错了......因为它只使用 3 个乘法(其中 2 个 甚至是imul...有符号乘法???为什么???)。谁能告诉我 gcc 在想什么?它是最优的吗?
  3. 甚至无法理解除法的组装...推栈->调用名为__udivti3的东西然后弹出栈...是__udivti3的东西 大?(比如查表?) gcc 在调用之前会尝试推送什么内容?

神螺栓链接:https://godbolt.org/g/sIIaM3

【问题讨论】:

  • 64 位 x86 乘法是 64 位、64 位 -> 128 位操作。
  • 如果您要查看编译器的汇编语言输出,您需要找到它的参考,因为其中有太多内容,仅阅读输出并不明显。
  • 我建议你在指令集参考中查找mul,然后一切就清楚了。

标签: c++ gcc assembly x86-64 compiler-optimization


【解决方案1】:

你说得对,两个无符号 64 位值相乘可以产生 128 位结果。有趣的是,硬件设计师也知道这一点。 因此,通过将结果的下半部分存储在一个 64 位寄存器中并将结果的上半部分存储在另一个 64 位寄存器中,将两个 64 位值相乘产生一个 128 位结果。编译器编写器知道使用了哪些寄存器,当您调用 mul_to_128 时,它会在适当的寄存器中查找结果。

在第二个示例中,将值视为a1*2^64 + a0b1*2^64 + b0(即将每个 128 位值分成两部分,高 64 位和低 64 位)。当你将它们相乘时,你会得到a1*b1*2^64*2^64 + a1*b0*2^64 + a0*b1*2^64 + a0*b0。这基本上就是汇编代码正在做的事情。结果中溢出 128 位的部分被忽略。

在第三个示例中,__udivti3 是一个执行除法的函数。它并不简单,所以它不会被内联扩展。

【讨论】:

  • 至于IMUL的使用:在N x N -> 2N位乘法中,有符号和无符号变体的结果的低位N位相同,只有高位N位不同。在这里,我们只需要两个部分乘积的低 N 位,所以IMUL 可以很好地处理这些。
【解决方案2】:
  1. mul rsi 将在 rdx:rax 中生成 128 位结果,任何指令集参考都会告诉您。
  2. imul 用于获得 64 位结果。它甚至适用于未签名的。同样,指令集参考说:“二操作数和三操作数形式也可以与无符号操作数一起使用,因为产品的下半部分 无论操作数是有符号还是无符号,都是一样的。”除此之外,是的,基本上它是在做你描述的双倍宽度。只有 3 个乘法,因为第 4 个的结果不适合无论如何输出 128 位。
  3. __udivti3 只是一个辅助函数,你可以看看它的反汇编,看看它在做什么。

【讨论】:

    猜你喜欢
    • 2012-10-05
    • 2018-08-19
    • 1970-01-01
    • 1970-01-01
    • 2020-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多