这个128位整数乘法如何在汇编(x86-64)中工作?

时间:2015-11-18 19:59:11

标签: c assembly x86-64 128-bit

我正在阅读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

3 个答案:

答案 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的一部分,就像cqtorax扩展为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,然后函数返回0hi += 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位整数-mx=2**32-n, y=2**32-m表示为无符号数x*y = 2**64 - 2**32*n - 2**32*m + n*m。如果你乘以{{1}}。中间术语表示对产品上半部分的必要更正。使用-1 * -1来完成一个简单的例子应该非常有启发性。