如何在汇编中乘以两个十六进制128位数

时间:2016-11-24 22:55:10

标签: algorithm assembly byte x86-64 multiplication

我在内存中有两个128位的十六进制数字,例如(小端):

x:0x12 0x45 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
y:0x36 0xa1 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

我要在这两个数字之间执行无符号乘法,所以我的新数字将是:

z:0xcc 0xe3 0x7e 0x2b 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

现在,我知道我可以将半个x和y数字移动到raxrbx寄存器中,例如,执行mul操作,并执行相同操作与另一半。问题是,通过这样做,我失去了结转,我不知道如何避免这种情况。大约4个小时我遇到了这个问题,我能看到的唯一解决方案是二进制转换(and< - > shl,1)。

你能就这个问题给我一些意见吗? 我认为最好的解决方案是占用一个字节的时间。

2 个答案:

答案 0 :(得分:8)

设μ= 2 64 ,然后我们可以将您的128位数 a b 分解为 a = a 1 μ+ a 2 b = b < / em> 1 μ+ b 2 。然后我们可以通过首先计算部分乘积来计算 c = ab ,64·64→128位乘法:

  

q 1 μ+ q 2 = a 2 b'/ EM> <子> 2
   r 1 μ+ r 2 = a 1 b'/ EM> <子> 2
   s 1 μ+ s 2 = a 2 b'/ EM> <子> 1
   t 1 μ+ t 2 = a 1 b'/ EM> <子> 1

然后将它们累加到256位结果中(在添加时观察溢出!):

  

c = t 1 μ 3 +( t 2 + s 1 + r 1 )μ 2 + ( s 2 + r 2 + q 1 )μ+ q 2

答案 1 :(得分:6)

像往常一样,询问编译器如何有效地执行某些操作:64位平台上的GNU C支持__int128_t__uint128_t

__uint128_t mul128(__uint128_t a, __uint128_t b) { return a*b; }

编译为(gcc6.2 -O3 on Godbolt

    imul    rsi, rdx        # tmp94, b
    mov     rax, rdi  # tmp93, a
    imul    rcx, rdi        # tmp95, a
    mul     rdx       # b
    add     rcx, rsi  # tmp96, tmp94
    add     rdx, rcx  #, tmp96
    ret

由于这是针对x86-64 System V调用约定,a位于RSI:RDI中,而b位于RCX:RDX中。 结果以RDX:RAX 返回。

非常漂亮,只需要一条MOV指令,因为gcc不需要a_upper * b_lower的高半结果,反之亦然。它可以用更快的2操作数形式的IMUL来破坏输入的高半部分,因为它们只使用一次。

使用-march=haswell启用BMI2,gcc使用MULX来避免一个MOV。

有时编译器输出并不完美,但通常一般策略是手动优化的良好起点。

当然,如果你真正想要的东西在C中是128位乘法,那么只需使用编译器内置的支持。这可以让优化器完成它的工作,通常会提供比你在inline-asm中编写几个部分更好的结果。 (https://gcc.gnu.org/wiki/DontUseInlineAsm)。