C++ 编译器用于编译128位整数的基本算术运算的技巧
我在GodBolt上玩过,看到x86-64 gcc(6.3)编译了以下代码: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
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个问题:
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
可以很好地用于这些部分积。