Tricks编译器用于编译128位整数的基本算术运算

时间:2017-04-07 15:55:32

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

我玩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位寄存器......它是如何产生的 只有1 64位64位乘法的128位结果???
  2. 我无法很好地阅读第二个结果......我的猜测是将64位数字分解为2个32位数字(hi表示更高的4个字节 和lo低4个字节),并组装结果,如 (hi1*hi2)<<64 + (hi1*lo2)<<32 + (hi2*lo1)<<32+(lo1*lo2)。显然地 我错了......因为它只使用了3次乘法(其中2次) 甚至是imul ...有符号的乘法???为什么???)。有谁能告诉我 gcc在想什么?它是最佳的吗?
  3. 甚至无法理解师的组装...推叠 - &gt;调用名为__udivti3的东西然后弹出堆栈...是__udivti3的东西 很大?(比如查表?)以及gcc在通话前试图推送什么东西?
  4. godbolt链接:https://godbolt.org/g/sIIaM3

2 个答案:

答案 0 :(得分:9)

你是对的,将两个无符号的64位值相乘可以产生128位的结果。有趣的是,硬件设计师也知道这一点。 &LT g取代;因此,将两个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是执行除法的函数。它并不简单,因此它不会内联扩展。

答案 1 :(得分:3)

  1. mul rsi会在rdxrax中生成128位结果,因为任何指令集引用都会告诉您。
  2. imul用于获得64位结果。它甚至适用于未签名的。同样,指令集引用说:“两个和三个操作数形式也可以与无符号操作数一起使用,因为产品的下半部分 无论操作数是有符号还是无符号,都是一样的。“除此之外,是的,基本上它正在做与你描述的相当的双倍宽度。只有3倍,因为第4个的结果不适合无论如何输出128位。
  3. __udivti3只是一个辅助函数,您可以查看它的反汇编,看看它在做什么。