C++ 编译器用于编译128位整数的基本算术运算的技巧

C++ 编译器用于编译128位整数的基本算术运算的技巧,c++,gcc,assembly,x86-64,compiler-optimization,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

我在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个问题:

  • 第一个函数(将64位uint转换为128位,然后将它们相乘)是 比2个128位单元的乘法(第二个函数)简单得多。基本上,只是 1乘法。如果将64位uint的两个最大值相乘,则 肯定是64位寄存器溢出…它是如何产生的 128位的结果只需要1个64位的64位乘法
  • 我无法很好地读取第二个结果…我的猜测是将64位数字分解为2个32位数字(表示,
    hi
    为更高的4字节) 和
    lo
    作为较低的4个字节),并按如下方式组合结果
    (hi1*hi2)两个无符号64位值相乘可以生成128位结果,这是对的。有趣的是,硬件设计师也知道这一点。因此,将两个64位值相乘,将结果的下半部分存储在一个64位寄存器中,将结果的上半部分存储在另一个64位寄存器中,从而生成128位结果。编译器编写器知道使用了哪些寄存器,当您调用
    mul_to_128
    时,它将在适当的寄存器中查找结果

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

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

  • 任何指令集参考都会告诉您,
    mul-rsi
    将在
    rdx
    rax
    中产生128位结果
  • imul
    用于获得64位结果。它甚至适用于未签名的用户。同样,指令集引用说:“两个和三个操作数形式也可以用于无符号操作数,因为乘积的下半部分 不管操作数是有符号的还是无符号的,都是一样的。”除此之外,是的,基本上它做的是你所描述的双倍宽度。只有3次乘法,因为第4次乘法的结果无论如何都不适合输出128位
  • \uuUdivti3
    只是一个辅助函数,您可以查看它的反汇编以了解它在做什么

  • 64位x86乘法是一种64位、64位->128位的操作。如果您要查看编译器的汇编语言输出,您需要找到它的参考,因为有太多的内容是不明显的,只需读取输出即可。我建议您在指令集参考中查找
    mul
    ,那么一切都清楚了。
    IMUL
    :在nxn->2N位乘法中,结果的低阶N位在有符号和无符号变量之间是相同的,只有高阶N位不同。在这里,我们只需要两个部分积的低阶N位,因此
    IMUL
    可以很好地用于这些部分积。