我在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-bit
uint 转换为128-bit
然后将它们相乘)是 比 2 个 128 位 uint 的乘法(第 2 个函数)简单得多。基本上,只是 1 乘法。如果将 64 位 uint 的最大值相乘 2,则 肯定从 64 位寄存器溢出...它是如何产生的 只需 1 个 64 位-64 位乘法即可获得 128 位结果??? - 我不能很好地阅读第二个结果...我的猜测是将 64 位数字分解为 2 个 32 位数字(例如,
hi
为更高的 4 字节 并lo
为较低的 4 个字节),并将结果组装成(hi1*hi2)<<64 + (hi1*lo2)<<32 + (hi2*lo1)<<32+(lo1*lo2)
.显然地 我错了。。。因为它只使用 3 个乘法(其中 2 个 甚至imul
...有符号乘法???为什么???)。谁能告诉我 海湾合作委员会在想什么?这是最佳的吗? - 甚至无法理解师的组装...push stack ->调用名为
__udivti3
然后弹出堆栈...是__udivti3
的东西 大?(喜欢表格查找?GCC在通话前试图推动什么东西?
神霹雳链接:https://godbolt.org/g/sIIaM3
您说得对,将两个无符号的 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 位的部分将被忽略。
在第三个示例中,__udivti3
是执行除法的函数。这并不简单,因此不会内联扩展。
mul rsi
将在rdx
:rax
中产生 128 位结果,正如任何指令集引用都会告诉您的那样。imul
用于获得 64 位结果。它甚至适用于未签名。同样,指令集参考说:"二操作数和三操作数形式也可以与无符号操作数一起使用,因为乘积的下半部分 无论操作数是有符号还是无符号,都是相同的。除此之外,是的,基本上它正在做你描述的双倍宽度等效。只有 3 次乘法,因为第 4 次的结果无论如何都不适合输出 128 位。__udivti3
只是一个辅助函数,你可以看看它的反汇编,看看它在做什么。