我正在阅读Computer Systems: A Programmer's Perspective,家庭作业是描述这种算法是如何运作的。
C函数:
void store_prod(__int128 *dest, int64_t x, int64_t y) {
*dest = x * (__int128)y;
}
大会:
movq %rdx, %rax
cqto
movq %rsi, %rcx
sarq $63, %rcx
imulq %rax, %rcx
imulq %rsi, %rdx
addq %rdx, %rcx
mulq %rsi
addq %rcx, %rdx
movq %rax, (%rdi)
movq %rdx, 8(%rdi)
ret
我不知道它的表现原因:xh * yl + yh * xl = value which we add after unsigned multiplication
答案 0 :(得分:4)
与往常一样,编译器选项很重要。该源代码带有gcc -Og
(针对调试进行优化)produces very similar asm to your listing(转换符号 - 在执行完整的128x128-> 128乘法之前将两个操作数扩展到128位)。这正是C标准所说的应该发生的事情(整数提升)。如果你要讨论编译器输出,你应该总是说哪个版本的编译器有哪些选项。或者只是在godbolt上发布一个链接,就像上面那样。
(编辑:oops,source和asm来自一本没有提供该信息的书。)
使用gcc -O3
,gcc利用两个操作数仍然只有64位so a single imul
is enough的事实。
sar $63, %rcx
是将rsi
扩展为rcx:rsi
的一部分,就像cqto
将rax
扩展为rdx:rax
一样。< / p>
其他人在评论中已经提供了大部分答案,但我认为没有其他人注意到gcc -Og
/ -O1
几乎完全给出了asm输出。
答案 1 :(得分:2)
为了理解我们为什么要执行此操作,请尝试将int128_t解释为:2 ^ 64 * xh + xl
所以如果我们想要将两个int128_t整数相乘,我们将执行以下操作:
x = 2 ^ 64 * xh + xl
y = 2 ^ 64 * yh + yl
所以x * y =(2 ^ 128 * xh * yh)+(2 ^ 64 * xh * yl)+(2 ^ 64 * yh * xl)+(yl * xl)
这就是汇编代码的作用:
yh =%rdx yl =%rax
xh =%rcx xl =%rsi
2 ^ 64 * xh * yl:是imulq %rax, %rcx
2 ^ 64表示,我们需要将其添加到高阶位
2 ^ 64 * yh * xl:是imulq %rsi, %rdx
2 ^ 64表示,我们需要将其添加到高阶位
2 ^ 128 * xh * yh:不需要此操作,因为2^128 * xh * yh
不符合128位整数。它仅代表符号位信息,可以忽略。
xl * yl:是mulq %rsi
我希望这能解决问题!
答案 2 :(得分:2)
GCC正在做的是使用带符号乘法的属性可以使用the following formula来完成。
imul
尽管事实上没有必要这样做,因为在这种情况下,x86-64指令集具有带符号的64位* 64位到128位指令(sign_ext
带有一个操作数)公式在其他情况下很有用。例如,用于实施signed 128-bit multiplication with SSE2/AVX2/AVX512或实施256-bit multiplication when the instruction set only does 128-bit multiplication(例如使用x86-64)。
GCC实施这个公式有点不同。如果我们取符号位并将其扩展到整个单词,请调用此函数-1
,然后函数返回0
或hi += sign_ext(x)*y + sign_ext(y)*x
。然后GCC做的是:
sign_ext(x)*y
例如{64}字的伪指令中的sarq $63, x ; sign_ext(x)
imulq y, x ; sign_ext(x)*y
是
-n
所以现在你问(或打算问):
为什么这个公式是真的?
这是一个很好的qeustion。我也问了同样的问题和njuffa wrote
@Zboson:它直接来自二进制补码表示。例如。 32位整数
-m
和x=2**32-n, y=2**32-m
表示为无符号数x*y = 2**64 - 2**32*n - 2**32*m + n*m
。如果你乘以{{1}}。中间术语表示对产品上半部分的必要更正。使用-1 * -1来完成一个简单的例子应该非常有启发性。