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



我在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-bituint 转换为128-bit然后将它们相乘)是 比 2 个 128 位 uint 的乘法(第 2 个函数)简单得多。基本上,只是 1 乘法。如果将 64 位 uint 的最大值相乘 2,则 肯定从 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...有符号乘法???为什么???)。谁能告诉我 海湾合作委员会在想什么?这是最佳的吗?
  3. 甚至无法理解师的组装...push stack ->调用名为__udivti3然后弹出堆栈...是__udivti3的东西 大?(喜欢表格查找?GCC在通话前试图推动什么东西?

神霹雳链接:https://godbolt.org/g/sIIaM3

您说得对,将两个无符号的 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是执行除法的函数。这并不简单,因此不会内联扩展。

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

最新更新