我玩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位uints(第二个函数)的乘法简单得多。基本上,只是
1乘法。如果你乘以2个最大值的64位uint,它
绝对溢出64位寄存器......它是如何产生的
只有1 64位64位乘法的128位结果??? hi
表示更高的4个字节
和lo
低4个字节),并组装结果,如
(hi1*hi2)<<64 + (hi1*lo2)<<32 + (hi2*lo1)<<32+(lo1*lo2)
。显然地
我错了......因为它只使用了3次乘法(其中2次)
甚至是imul
...有符号的乘法???为什么???)。有谁能告诉我
gcc在想什么?它是最佳的吗?__udivti3
的东西然后弹出堆栈...是__udivti3
的东西
很大?(比如查表?)以及gcc在通话前试图推送什么东西?godbolt链接:https://godbolt.org/g/sIIaM3
答案 0 :(得分:9)
你是对的,将两个无符号的64位值相乘可以产生128位的结果。有趣的是,硬件设计师也知道这一点。 &LT g取代;因此,将两个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位的结果部分将被忽略。
在第三个示例中,__udivti3
是执行除法的函数。它并不简单,因此它不会内联扩展。
答案 1 :(得分:3)
mul rsi
会在rdx
:rax
中生成128位结果,因为任何指令集引用都会告诉您。imul
用于获得64位结果。它甚至适用于未签名的。同样,指令集引用说:“两个和三个操作数形式也可以与无符号操作数一起使用,因为产品的下半部分
无论操作数是有符号还是无符号,都是一样的。“除此之外,是的,基本上它正在做与你描述的相当的双倍宽度。只有3倍,因为第4个的结果不适合无论如何输出128位。__udivti3
只是一个辅助函数,您可以查看它的反汇编,看看它在做什么。